mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
71 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
386e23b042 | ||
![]() |
fc76bafc7c | ||
![]() |
de53f4df4f | ||
![]() |
9fedf124db | ||
![]() |
2c68bb4bdd | ||
![]() |
2360cf899f | ||
![]() |
3fc57c2b06 | ||
![]() |
b4f41e96ae | ||
![]() |
b20108ddcb | ||
![]() |
a58b039994 | ||
![]() |
a7f218622e | ||
![]() |
8d95eb4269 | ||
![]() |
4e46690418 | ||
![]() |
db1737281e | ||
![]() |
d5eecda570 | ||
![]() |
80cb6d697c | ||
![]() |
6b08fec747 | ||
![]() |
2c92ce74ce | ||
![]() |
30643ced07 | ||
![]() |
bf27761cff | ||
![]() |
58df3aeca1 | ||
![]() |
69f8961e9e | ||
![]() |
1abb39ec17 | ||
![]() |
c5226c1e45 | ||
![]() |
d40789005b | ||
![]() |
fcbe5c364e | ||
![]() |
df3433c55f | ||
![]() |
7d892fd976 | ||
![]() |
56320d7253 | ||
![]() |
05acd061bd | ||
![]() |
7991ceb351 | ||
![]() |
bf6d18ccbe | ||
![]() |
536b7dcc62 | ||
![]() |
a157f32d4a | ||
![]() |
49f579f32d | ||
![]() |
8f4ecd373f | ||
![]() |
a3a53415b1 | ||
![]() |
c278c0a432 | ||
![]() |
862f270b83 | ||
![]() |
7232dbc2fa | ||
![]() |
78cbe48718 | ||
![]() |
e153d295de | ||
![]() |
1b8510655e | ||
![]() |
bd88127563 | ||
![]() |
01b9f09604 | ||
![]() |
0db30f02c9 | ||
![]() |
a852e91690 | ||
![]() |
79d6281061 | ||
![]() |
eba6b2540c | ||
![]() |
0706a54305 | ||
![]() |
d929fd6e48 | ||
![]() |
4dbabab052 | ||
![]() |
827c5d6442 | ||
![]() |
46be0e0ffc | ||
![]() |
72e3cf5b99 | ||
![]() |
54f32a2542 | ||
![]() |
b826f66455 | ||
![]() |
6fc03cccc6 | ||
![]() |
ed7313a330 | ||
![]() |
7390f7cd6a | ||
![]() |
4619ae824b | ||
![]() |
0f69ee435f | ||
![]() |
f46241d5a2 | ||
![]() |
4c8ec48004 | ||
![]() |
3ac121be4c | ||
![]() |
3aa636861c | ||
![]() |
6d4b422be6 | ||
![]() |
b8ee27f62c | ||
![]() |
6996ff452d | ||
![]() |
da7934c6ac | ||
![]() |
90bebcb404 |
@@ -886,7 +886,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"res/conditions/timer24.png",
|
||||
"res/conditions/timer.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("identifier", _("Timer's name"), "objectTimer")
|
||||
.AddParameter("expression", _("Time in seconds"))
|
||||
.SetHidden();
|
||||
|
||||
@@ -900,7 +900,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"res/conditions/timer24.png",
|
||||
"res/conditions/timer.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("identifier", _("Timer's name"), "objectTimer")
|
||||
.AddParameter("relationalOperator", _("Sign of the test"), "time")
|
||||
.AddParameter("expression", _("Time in seconds"))
|
||||
.SetManipulatedType("number");
|
||||
@@ -913,7 +913,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"res/conditions/timerPaused24.png",
|
||||
"res/conditions/timerPaused.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("identifier", _("Timer's name"), "objectTimer")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
obj.AddAction(
|
||||
@@ -926,7 +926,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"res/actions/timer24.png",
|
||||
"res/actions/timer.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("string", _("Timer's name"));
|
||||
.AddParameter("identifier", _("Timer's name"), "objectTimer");
|
||||
|
||||
obj.AddAction("PauseObjectTimer",
|
||||
_("Pause an object timer"),
|
||||
@@ -936,7 +936,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"res/actions/pauseTimer24.png",
|
||||
"res/actions/pauseTimer.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("identifier", _("Timer's name"), "objectTimer")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
obj.AddAction("UnPauseObjectTimer",
|
||||
@@ -947,7 +947,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"res/actions/unPauseTimer24.png",
|
||||
"res/actions/unPauseTimer.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("identifier", _("Timer's name"), "objectTimer")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
obj.AddAction("RemoveObjectTimer",
|
||||
@@ -958,7 +958,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"res/actions/timer24.png",
|
||||
"res/actions/timer.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("identifier", _("Timer's name"), "objectTimer")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
obj.AddExpression("X",
|
||||
@@ -1127,7 +1127,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
_("Object timers"),
|
||||
"res/actions/time.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("string", _("Timer's name"));
|
||||
.AddParameter("identifier", _("Timer's name"), "objectTimer");
|
||||
|
||||
obj.AddExpression("AngleToObject",
|
||||
_("Angle between two objects"),
|
||||
|
@@ -22,6 +22,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
"object or a position.",
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("Camera")
|
||||
.SetExtensionHelpPath("/interface/scene-editor/layers-and-cameras");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Layers and cameras"))
|
||||
.SetIcon("res/conditions/camera24.png");
|
||||
|
@@ -21,7 +21,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFileExtension(
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/all-features/storage")
|
||||
.SetCategory("Device");
|
||||
.SetCategory("Advanced");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Storage"))
|
||||
.SetIcon("res/conditions/fichier24.png");
|
||||
|
||||
|
@@ -22,6 +22,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/objects/sprite");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Sprite"))
|
||||
.SetIcon("CppPlatform/Extensions/spriteicon.png");
|
||||
|
||||
gd::ObjectMetadata& obj =
|
||||
extension
|
||||
|
@@ -36,7 +36,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
"res/conditions/timer.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("expression", _("Time in seconds"))
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("identifier", _("Timer's name"), "sceneTimer")
|
||||
.SetHidden();
|
||||
|
||||
extension
|
||||
@@ -50,7 +50,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
"res/conditions/timer24.png",
|
||||
"res/conditions/timer.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("identifier", _("Timer's name"), "sceneTimer")
|
||||
.AddParameter("relationalOperator", _("Sign of the test"), "time")
|
||||
.AddParameter("expression", _("Time in seconds"))
|
||||
.SetManipulatedType("number");
|
||||
@@ -78,7 +78,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
"res/conditions/timerPaused24.png",
|
||||
"res/conditions/timerPaused.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("identifier", _("Timer's name"), "sceneTimer")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
@@ -93,7 +93,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
"res/actions/timer24.png",
|
||||
"res/actions/timer.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("string", _("Timer's name"));
|
||||
.AddParameter("identifier", _("Timer's name"), "sceneTimer");
|
||||
|
||||
extension
|
||||
.AddAction("PauseTimer",
|
||||
@@ -105,7 +105,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
"res/actions/pauseTimer24.png",
|
||||
"res/actions/pauseTimer.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("identifier", _("Timer's name"), "sceneTimer")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
@@ -118,7 +118,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
"res/actions/unPauseTimer24.png",
|
||||
"res/actions/unPauseTimer.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("identifier", _("Timer's name"), "sceneTimer")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
@@ -131,7 +131,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
"res/actions/timer24.png",
|
||||
"res/actions/timer.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("identifier", _("Timer's name"), "sceneTimer")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
@@ -191,7 +191,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
"",
|
||||
"res/actions/time.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("string", _("Timer's name"));
|
||||
.AddParameter("identifier", _("Timer's name"), "sceneTimer");
|
||||
|
||||
extension
|
||||
.AddExpression("TimeFromStart",
|
||||
|
@@ -20,6 +20,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsWindowExtension(
|
||||
"these features can be applied.",
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("User interface")
|
||||
.SetExtensionHelpPath("/all-features/window");
|
||||
extension
|
||||
.AddInstructionOrExpressionGroupMetadata(
|
||||
|
@@ -199,7 +199,8 @@ class GD_CORE_API ParameterMetadata {
|
||||
parameterType == "objectAnimationName" ||
|
||||
parameterType == "functionParameterName" ||
|
||||
parameterType == "externalLayoutName" ||
|
||||
parameterType == "leaderboardId";
|
||||
parameterType == "leaderboardId" ||
|
||||
parameterType == "identifier";
|
||||
} else if (type == "variable") {
|
||||
return parameterType == "objectvar" || parameterType == "globalvar" ||
|
||||
parameterType == "scenevar";
|
||||
|
@@ -73,4 +73,54 @@ bool ArbitraryEventsWorker::VisitInstruction(gd::Instruction& instruction,
|
||||
|
||||
ArbitraryEventsWorkerWithContext::~ArbitraryEventsWorkerWithContext() {}
|
||||
|
||||
|
||||
ReadOnlyArbitraryEventsWorker::~ReadOnlyArbitraryEventsWorker() {}
|
||||
|
||||
void ReadOnlyArbitraryEventsWorker::VisitEventList(const gd::EventsList& events) {
|
||||
DoVisitEventList(events);
|
||||
|
||||
for (std::size_t i = 0; i < events.size(); ++i) {
|
||||
VisitEvent(events[i]);
|
||||
|
||||
if (events[i].CanHaveSubEvents()) {
|
||||
VisitEventList(events[i].GetSubEvents());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReadOnlyArbitraryEventsWorker::VisitEvent(const gd::BaseEvent& event) {
|
||||
DoVisitEvent(event);
|
||||
|
||||
const vector<const gd::InstructionsList*> conditionsVectors =
|
||||
event.GetAllConditionsVectors();
|
||||
for (std::size_t j = 0; j < conditionsVectors.size(); ++j) {
|
||||
VisitInstructionList(*conditionsVectors[j], true);
|
||||
}
|
||||
|
||||
const vector<const gd::InstructionsList*> actionsVectors = event.GetAllActionsVectors();
|
||||
for (std::size_t j = 0; j < actionsVectors.size(); ++j) {
|
||||
VisitInstructionList(*actionsVectors[j], false);
|
||||
}
|
||||
}
|
||||
|
||||
void ReadOnlyArbitraryEventsWorker::VisitInstructionList(
|
||||
const gd::InstructionsList& instructions, bool areConditions) {
|
||||
DoVisitInstructionList(instructions, areConditions);
|
||||
|
||||
for (std::size_t i = 0; i < instructions.size(); ++i) {
|
||||
VisitInstruction(instructions[i], areConditions);
|
||||
if (!instructions[i].GetSubInstructions().empty()) {
|
||||
VisitInstructionList(instructions[i].GetSubInstructions(),
|
||||
areConditions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReadOnlyArbitraryEventsWorker::VisitInstruction(const gd::Instruction& instruction,
|
||||
bool isCondition) {
|
||||
DoVisitInstruction(instruction, isCondition);
|
||||
}
|
||||
|
||||
ReadOnlyArbitraryEventsWorkerWithContext::~ReadOnlyArbitraryEventsWorkerWithContext() {}
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -121,6 +121,101 @@ class GD_CORE_API ArbitraryEventsWorkerWithContext
|
||||
const gd::ObjectsContainer* currentObjectsContainer;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief ReadOnlyArbitraryEventsWorker is an abstract class used to browse events (and
|
||||
* instructions). It can be used to implement autocompletion for example.
|
||||
*
|
||||
* \see gd::ReadOnlyArbitraryEventsWorkerWithContext
|
||||
*
|
||||
* \ingroup IDE
|
||||
*/
|
||||
class GD_CORE_API ReadOnlyArbitraryEventsWorker {
|
||||
public:
|
||||
ReadOnlyArbitraryEventsWorker(){};
|
||||
virtual ~ReadOnlyArbitraryEventsWorker();
|
||||
|
||||
/**
|
||||
* \brief Launch the worker on the specified events list.
|
||||
*/
|
||||
void Launch(const gd::EventsList& events) { VisitEventList(events); };
|
||||
|
||||
private:
|
||||
void VisitEventList(const gd::EventsList& events);
|
||||
void VisitEvent(const gd::BaseEvent& event);
|
||||
void VisitInstructionList(const gd::InstructionsList& instructions,
|
||||
bool areConditions);
|
||||
void VisitInstruction(const gd::Instruction& instruction, bool isCondition);
|
||||
|
||||
/**
|
||||
* Called to do some work on an event list.
|
||||
*/
|
||||
virtual void DoVisitEventList(const gd::EventsList& events){};
|
||||
|
||||
/**
|
||||
* Called to do some work on an event
|
||||
*/
|
||||
virtual void DoVisitEvent(const gd::BaseEvent& event) {};
|
||||
|
||||
/**
|
||||
* Called to do some work on an instruction list
|
||||
*/
|
||||
virtual void DoVisitInstructionList(const gd::InstructionsList& instructions,
|
||||
bool areConditions){};
|
||||
|
||||
/**
|
||||
* Called to do some work on an instruction.
|
||||
*/
|
||||
virtual void DoVisitInstruction(const gd::Instruction& instruction,
|
||||
bool isCondition) {};
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief An events worker that will know about the context (the objects
|
||||
* container). Useful for workers working on expressions notably.
|
||||
*
|
||||
* \see gd::ReadOnlyArbitraryEventsWorker
|
||||
*
|
||||
* \ingroup IDE
|
||||
*/
|
||||
class GD_CORE_API ReadOnlyArbitraryEventsWorkerWithContext
|
||||
: public ReadOnlyArbitraryEventsWorker {
|
||||
public:
|
||||
ReadOnlyArbitraryEventsWorkerWithContext()
|
||||
: currentGlobalObjectsContainer(nullptr),
|
||||
currentObjectsContainer(nullptr){};
|
||||
virtual ~ReadOnlyArbitraryEventsWorkerWithContext();
|
||||
|
||||
/**
|
||||
* \brief Launch the worker on the specified events list,
|
||||
* giving the objects container on which the events are applying to.
|
||||
*/
|
||||
void Launch(const gd::EventsList& events,
|
||||
const gd::ObjectsContainer& globalObjectsContainer_,
|
||||
const gd::ObjectsContainer& objectsContainer_) {
|
||||
currentGlobalObjectsContainer = &globalObjectsContainer_;
|
||||
currentObjectsContainer = &objectsContainer_;
|
||||
ReadOnlyArbitraryEventsWorker::Launch(events);
|
||||
};
|
||||
|
||||
void Launch(gd::EventsList& events) = delete;
|
||||
|
||||
protected:
|
||||
const gd::ObjectsContainer& GetGlobalObjectsContainer() {
|
||||
// Pointers are guaranteed to be not nullptr after
|
||||
// Launch was called.
|
||||
return *currentGlobalObjectsContainer;
|
||||
};
|
||||
const gd::ObjectsContainer& GetObjectsContainer() {
|
||||
// Pointers are guaranteed to be not nullptr after
|
||||
// Launch was called.
|
||||
return *currentObjectsContainer;
|
||||
};
|
||||
|
||||
private:
|
||||
const gd::ObjectsContainer* currentGlobalObjectsContainer;
|
||||
const gd::ObjectsContainer* currentObjectsContainer;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
#endif // GDCORE_ARBITRARYEVENTSWORKER_H
|
||||
|
254
Core/GDCore/IDE/Events/EventsIdentifiersFinder.cpp
Normal file
254
Core/GDCore/IDE/Events/EventsIdentifiersFinder.cpp
Normal file
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "EventsIdentifiersFinder.h"
|
||||
#include "GDCore/Events/Event.h"
|
||||
#include "GDCore/Events/Instruction.h"
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
|
||||
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/ExternalEvents.h"
|
||||
#include "GDCore/IDE/DependenciesAnalyzer.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace gd {
|
||||
namespace {
|
||||
/**
|
||||
* \brief Go through the nodes to search for identifier occurrences.
|
||||
*
|
||||
* \see gd::ExpressionParser2
|
||||
*/
|
||||
class GD_CORE_API IdentifierFinderExpressionNodeWorker
|
||||
: public ExpressionParser2NodeWorker {
|
||||
public:
|
||||
IdentifierFinderExpressionNodeWorker(std::set<gd::String>& results_,
|
||||
const gd::Platform &platform_,
|
||||
const gd::ObjectsContainer &globalObjectsContainer_,
|
||||
const gd::ObjectsContainer &objectsContainer_,
|
||||
const gd::String& identifierType_,
|
||||
const gd::String& objectName_ = "")
|
||||
: results(results_),
|
||||
platform(platform_),
|
||||
globalObjectsContainer(globalObjectsContainer_),
|
||||
objectsContainer(objectsContainer_),
|
||||
identifierType(identifierType_),
|
||||
objectName(objectName_){};
|
||||
virtual ~IdentifierFinderExpressionNodeWorker(){};
|
||||
|
||||
protected:
|
||||
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
|
||||
node.expression->Visit(*this);
|
||||
}
|
||||
void OnVisitOperatorNode(OperatorNode& node) override {
|
||||
node.leftHandSide->Visit(*this);
|
||||
node.rightHandSide->Visit(*this);
|
||||
}
|
||||
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
|
||||
node.factor->Visit(*this);
|
||||
}
|
||||
void OnVisitNumberNode(NumberNode& node) override {}
|
||||
void OnVisitTextNode(TextNode& node) override {}
|
||||
void OnVisitVariableNode(VariableNode& node) override {
|
||||
if (node.child) node.child->Visit(*this);
|
||||
}
|
||||
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
|
||||
if (node.child) node.child->Visit(*this);
|
||||
}
|
||||
void OnVisitVariableBracketAccessorNode(
|
||||
VariableBracketAccessorNode& node) override {
|
||||
node.expression->Visit(*this);
|
||||
if (node.child) node.child->Visit(*this);
|
||||
}
|
||||
void OnVisitIdentifierNode(IdentifierNode& node) override {}
|
||||
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {}
|
||||
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
|
||||
bool considerFunction = objectName.empty() || node.objectName == objectName;
|
||||
|
||||
const bool isObjectFunction = !node.objectName.empty();
|
||||
const gd::ExpressionMetadata &metadata = isObjectFunction ?
|
||||
MetadataProvider::GetObjectAnyExpressionMetadata(
|
||||
platform,
|
||||
GetTypeOfObject(globalObjectsContainer, objectsContainer, objectName),
|
||||
node.functionName):
|
||||
MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName);
|
||||
|
||||
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t parameterIndex = 0;
|
||||
for (size_t metadataIndex = (isObjectFunction ? 1 : 0); metadataIndex < metadata.parameters.size()
|
||||
&& parameterIndex < node.parameters.size(); ++metadataIndex) {
|
||||
auto& parameterMetadata = metadata.parameters[metadataIndex];
|
||||
if (parameterMetadata.IsCodeOnly()) {
|
||||
continue;
|
||||
}
|
||||
auto& parameterNode = node.parameters[parameterIndex];
|
||||
++parameterIndex;
|
||||
|
||||
if (considerFunction && parameterMetadata.GetType() == "identifier"
|
||||
&& parameterMetadata.GetExtraInfo() == identifierType) {
|
||||
// Store the value of the parameter
|
||||
results.insert(
|
||||
gd::ExpressionParser2NodePrinter::PrintNode(*parameterNode));
|
||||
} else {
|
||||
parameterNode->Visit(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
void OnVisitEmptyNode(EmptyNode& node) override {}
|
||||
|
||||
private:
|
||||
const gd::Platform &platform;
|
||||
const gd::ObjectsContainer &globalObjectsContainer;
|
||||
const gd::ObjectsContainer &objectsContainer;
|
||||
|
||||
std::set<gd::String>& results; ///< Reference to the std::set where argument
|
||||
///< values must be stored.
|
||||
gd::String identifierType; ///< The type of the parameters to be searched for.
|
||||
gd::String objectName; ///< If not empty, parameters will be taken into
|
||||
///< account only if related to this object.
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Go through the events to search for identifier occurrences.
|
||||
*/
|
||||
class GD_CORE_API IdentifierFinderEventWorker
|
||||
: public ReadOnlyArbitraryEventsWorkerWithContext {
|
||||
public:
|
||||
IdentifierFinderEventWorker(std::set<gd::String>& results_,
|
||||
const gd::Platform &platform_,
|
||||
const gd::String& identifierType_,
|
||||
const gd::String& objectName_ = "")
|
||||
: results(results_),
|
||||
platform(platform_),
|
||||
identifierType(identifierType_),
|
||||
objectName(objectName_){};
|
||||
virtual ~IdentifierFinderEventWorker(){};
|
||||
|
||||
void DoVisitInstructionList(const gd::InstructionsList& instructions,
|
||||
bool areConditions) override {
|
||||
for (std::size_t aId = 0; aId < instructions.size(); ++aId) {
|
||||
auto& instruction = instructions[aId];
|
||||
gd::String lastObjectParameter = "";
|
||||
const gd::InstructionMetadata& instrInfos =
|
||||
areConditions ? MetadataProvider::GetConditionMetadata(
|
||||
platform, instruction.GetType())
|
||||
: MetadataProvider::GetActionMetadata(
|
||||
platform, instruction.GetType());
|
||||
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
|
||||
// The parameter has the searched type...
|
||||
if (instrInfos.parameters[pNb].type == "identifier"
|
||||
&& instrInfos.parameters[pNb].supplementaryInformation == identifierType) {
|
||||
//...remember the value of the parameter.
|
||||
if (objectName.empty() || lastObjectParameter == objectName) {
|
||||
results.insert(instruction.GetParameter(pNb).GetPlainString());
|
||||
}
|
||||
}
|
||||
// Search in expressions
|
||||
else if (ParameterMetadata::IsExpression(
|
||||
"number", instrInfos.parameters[pNb].type) ||
|
||||
ParameterMetadata::IsExpression(
|
||||
"string", instrInfos.parameters[pNb].type)) {
|
||||
auto node = instruction.GetParameter(pNb).GetRootNode();
|
||||
|
||||
IdentifierFinderExpressionNodeWorker searcher(
|
||||
results,
|
||||
platform,
|
||||
GetGlobalObjectsContainer(),
|
||||
GetObjectsContainer(),
|
||||
identifierType,
|
||||
objectName);
|
||||
node->Visit(searcher);
|
||||
}
|
||||
// Remember the value of the last "object" parameter.
|
||||
else if (gd::ParameterMetadata::IsObject(
|
||||
instrInfos.parameters[pNb].type)) {
|
||||
lastObjectParameter =
|
||||
instruction.GetParameter(pNb).GetPlainString();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
const gd::Platform &platform;
|
||||
|
||||
std::set<gd::String>& results; ///< Reference to the std::set where argument
|
||||
///< values must be stored.
|
||||
gd::String identifierType; ///< The type of the parameters to be searched for.
|
||||
gd::String objectName; ///< If not empty, parameters will be taken into
|
||||
///< account only if related to this object.
|
||||
};
|
||||
} // namespace
|
||||
|
||||
std::set<gd::String> EventsIdentifiersFinder::FindAllIdentifierExpressions(
|
||||
const gd::Platform& platform,
|
||||
const gd::Project& project,
|
||||
const gd::Layout& layout,
|
||||
const gd::String& identifierType,
|
||||
const gd::String& contextObjectName) {
|
||||
std::set<gd::String> results;
|
||||
|
||||
const bool isObjectIdentifier = identifierType.find("object") == 0;
|
||||
// The object from the context is only relevent for object identifiers.
|
||||
auto& actualObjectName = isObjectIdentifier ? contextObjectName : "";
|
||||
|
||||
FindArgumentsInEventsAndDependencies(
|
||||
results,
|
||||
platform,
|
||||
project,
|
||||
layout,
|
||||
identifierType,
|
||||
actualObjectName);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
void EventsIdentifiersFinder::FindArgumentsInEventsAndDependencies(
|
||||
std::set<gd::String>& results,
|
||||
const gd::Platform& platform,
|
||||
const gd::Project& project,
|
||||
const gd::Layout& layout,
|
||||
const gd::String& identifierType,
|
||||
const gd::String& objectName) {
|
||||
IdentifierFinderEventWorker eventWorker(results,
|
||||
platform,
|
||||
identifierType,
|
||||
objectName);
|
||||
eventWorker.Launch(layout.GetEvents(), project, layout);
|
||||
|
||||
DependenciesAnalyzer dependenciesAnalyzer = DependenciesAnalyzer(project, layout);
|
||||
dependenciesAnalyzer.Analyze();
|
||||
for (const gd::String& externalEventName : dependenciesAnalyzer.GetExternalEventsDependencies()) {
|
||||
const gd::ExternalEvents& externalEvents = project.GetExternalEvents(externalEventName);
|
||||
|
||||
IdentifierFinderEventWorker eventWorker(results,
|
||||
platform,
|
||||
identifierType,
|
||||
objectName);
|
||||
eventWorker.Launch(externalEvents.GetEvents(), project, layout);
|
||||
}
|
||||
for (const gd::String& sceneName : dependenciesAnalyzer.GetScenesDependencies()) {
|
||||
const gd::Layout& dependencyLayout = project.GetLayout(sceneName);
|
||||
|
||||
IdentifierFinderEventWorker eventWorker(results,
|
||||
platform,
|
||||
identifierType,
|
||||
objectName);
|
||||
eventWorker.Launch(dependencyLayout.GetEvents(), project, dependencyLayout);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gd
|
81
Core/GDCore/IDE/Events/EventsIdentifiersFinder.h
Normal file
81
Core/GDCore/IDE/Events/EventsIdentifiersFinder.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#ifndef EVENTSIDENTIFIERSFINDER_H
|
||||
#define EVENTSIDENTIFIERSFINDER_H
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include "GDCore/Events/Event.h"
|
||||
#include "GDCore/String.h"
|
||||
namespace gd {
|
||||
class Instruction;
|
||||
class Platform;
|
||||
class Object;
|
||||
class Project;
|
||||
class Layout;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
|
||||
/**
|
||||
* \brief Perform a search over a layout, searching for layout or object custom
|
||||
* identifiers.
|
||||
*
|
||||
* \todo Refactor this class using ArbitraryEventsWorker
|
||||
*
|
||||
* \ingroup IDE
|
||||
*/
|
||||
class EventsIdentifiersFinder {
|
||||
public:
|
||||
EventsIdentifiersFinder(){};
|
||||
virtual ~EventsIdentifiersFinder(){};
|
||||
|
||||
/**
|
||||
* Construct a list containing all the expressions for a given identifier used
|
||||
* in the layout.
|
||||
*
|
||||
* \param project The project to use.
|
||||
* \param layout The layout to use.
|
||||
* \param identifierType The identifier type to be analyzed.
|
||||
* \param objectName If not empty, parameters will be taken into account
|
||||
* only if the last object parameter is filled with
|
||||
* this value.
|
||||
* \return A std::set containing the names of all identifiers used.
|
||||
*/
|
||||
static std::set<gd::String> FindAllIdentifierExpressions(
|
||||
const gd::Platform& platform,
|
||||
const gd::Project& project,
|
||||
const gd::Layout& layout,
|
||||
const gd::String& identifierType,
|
||||
const gd::String& objectName = "");
|
||||
|
||||
private:
|
||||
/**
|
||||
* Construct a list containing all the expressions for a given identifier used
|
||||
* in the layout. It searches in events dependencies.
|
||||
*
|
||||
* \param results A std::set to fill with the expressions used for all parameters of the
|
||||
* specified identifier type
|
||||
* \param platform The platform of the project
|
||||
* \param project The project to use.
|
||||
* \param layout The layout to use.
|
||||
* \param events The events to be analyzed
|
||||
* \param identifierType The identifier type to be analyzed
|
||||
* \param objectName If not empty, parameters will be taken into account
|
||||
* only if the last object parameter is filled with
|
||||
* this value.
|
||||
*/
|
||||
static void FindArgumentsInEventsAndDependencies(
|
||||
std::set<gd::String>& results,
|
||||
const gd::Platform& platform,
|
||||
const gd::Project& project,
|
||||
const gd::Layout& layout,
|
||||
const gd::String& identifierType,
|
||||
const gd::String& objectName = "");
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
#endif // EVENTSIDENTIFIERSFINDER_H
|
@@ -13,6 +13,7 @@
|
||||
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
@@ -22,28 +23,28 @@
|
||||
using namespace std;
|
||||
|
||||
namespace gd {
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* \brief Go through the nodes and change the given object name to a new one.
|
||||
* \brief Go through the nodes to search for variable occurrences.
|
||||
*
|
||||
* \see gd::ExpressionParser2
|
||||
*/
|
||||
class GD_CORE_API ExpressionParameterSearcher
|
||||
class GD_CORE_API VariableFinderExpressionNodeWorker
|
||||
: public ExpressionParser2NodeWorker {
|
||||
public:
|
||||
ExpressionParameterSearcher(const gd::Platform &platform_,
|
||||
VariableFinderExpressionNodeWorker(std::set<gd::String>& results_,
|
||||
const gd::Platform &platform_,
|
||||
const gd::ObjectsContainer &globalObjectsContainer_,
|
||||
const gd::ObjectsContainer &objectsContainer_,
|
||||
std::set<gd::String>& results_,
|
||||
const gd::String& parameterType_,
|
||||
const gd::String& objectName_ = "")
|
||||
: platform(platform_),
|
||||
: results(results_),
|
||||
platform(platform_),
|
||||
globalObjectsContainer(globalObjectsContainer_),
|
||||
objectsContainer(objectsContainer_),
|
||||
results(results_),
|
||||
parameterType(parameterType_),
|
||||
objectName(objectName_){};
|
||||
virtual ~ExpressionParameterSearcher(){};
|
||||
virtual ~VariableFinderExpressionNodeWorker(){};
|
||||
|
||||
protected:
|
||||
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
|
||||
@@ -74,27 +75,34 @@ class GD_CORE_API ExpressionParameterSearcher
|
||||
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
|
||||
bool considerFunction = objectName.empty() || node.objectName == objectName;
|
||||
|
||||
const gd::ExpressionMetadata &metadata = node.objectName.empty() ?
|
||||
MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName) :
|
||||
const bool isObjectFunction = !node.objectName.empty();
|
||||
const gd::ExpressionMetadata &metadata = isObjectFunction ?
|
||||
MetadataProvider::GetObjectAnyExpressionMetadata(
|
||||
platform,
|
||||
GetTypeOfObject(globalObjectsContainer, objectsContainer, objectName),
|
||||
node.functionName);
|
||||
node.functionName):
|
||||
MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName);
|
||||
|
||||
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < node.parameters.size() &&
|
||||
i < metadata.parameters.size();
|
||||
++i) {
|
||||
auto& parameterMetadata = metadata.parameters[i];
|
||||
|
||||
size_t parameterIndex = 0;
|
||||
for (size_t metadataIndex = (isObjectFunction ? 1 : 0); metadataIndex < metadata.parameters.size()
|
||||
&& parameterIndex < node.parameters.size(); ++metadataIndex) {
|
||||
auto& parameterMetadata = metadata.parameters[metadataIndex];
|
||||
if (parameterMetadata.IsCodeOnly()) {
|
||||
continue;
|
||||
}
|
||||
auto& parameterNode = node.parameters[parameterIndex];
|
||||
++parameterIndex;
|
||||
|
||||
if (considerFunction && parameterMetadata.GetType() == parameterType) {
|
||||
// Store the value of the parameter
|
||||
results.insert(
|
||||
gd::ExpressionParser2NodePrinter::PrintNode(*node.parameters[i]));
|
||||
gd::ExpressionParser2NodePrinter::PrintNode(*parameterNode));
|
||||
} else {
|
||||
node.parameters[i]->Visit(*this);
|
||||
parameterNode->Visit(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,18 +120,87 @@ class GD_CORE_API ExpressionParameterSearcher
|
||||
///< account only if related to this object.
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Go through the events to search for variable occurrences.
|
||||
*/
|
||||
class GD_CORE_API VariableFinderEventWorker
|
||||
: public ReadOnlyArbitraryEventsWorkerWithContext {
|
||||
public:
|
||||
VariableFinderEventWorker(std::set<gd::String>& results_,
|
||||
const gd::Platform &platform_,
|
||||
const gd::String& parameterType_,
|
||||
const gd::String& objectName_ = "")
|
||||
: results(results_),
|
||||
platform(platform_),
|
||||
parameterType(parameterType_),
|
||||
objectName(objectName_){};
|
||||
virtual ~VariableFinderEventWorker(){};
|
||||
|
||||
void DoVisitInstructionList(const gd::InstructionsList& instructions,
|
||||
bool areConditions) override {
|
||||
for (std::size_t aId = 0; aId < instructions.size(); ++aId) {
|
||||
auto& instruction = instructions[aId];
|
||||
gd::String lastObjectParameter = "";
|
||||
const gd::InstructionMetadata& instrInfos =
|
||||
areConditions ? MetadataProvider::GetConditionMetadata(
|
||||
platform, instruction.GetType())
|
||||
: MetadataProvider::GetActionMetadata(
|
||||
platform, instruction.GetType());
|
||||
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
|
||||
// The parameter has the searched type...
|
||||
if (instrInfos.parameters[pNb].type == parameterType) {
|
||||
//...remember the value of the parameter.
|
||||
if (objectName.empty() || lastObjectParameter == objectName)
|
||||
results.insert(instruction.GetParameter(pNb).GetPlainString());
|
||||
}
|
||||
// Search in expressions
|
||||
else if (ParameterMetadata::IsExpression(
|
||||
"number", instrInfos.parameters[pNb].type) ||
|
||||
ParameterMetadata::IsExpression(
|
||||
"string", instrInfos.parameters[pNb].type)) {
|
||||
auto node = instruction.GetParameter(pNb).GetRootNode();
|
||||
|
||||
VariableFinderExpressionNodeWorker searcher(
|
||||
results,
|
||||
platform,
|
||||
GetGlobalObjectsContainer(),
|
||||
GetObjectsContainer(),
|
||||
parameterType,
|
||||
objectName);
|
||||
node->Visit(searcher);
|
||||
}
|
||||
// Remember the value of the last "object" parameter.
|
||||
else if (gd::ParameterMetadata::IsObject(
|
||||
instrInfos.parameters[pNb].type)) {
|
||||
lastObjectParameter =
|
||||
instruction.GetParameter(pNb).GetPlainString();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
const gd::Platform &platform;
|
||||
|
||||
std::set<gd::String>& results; ///< Reference to the std::set where argument
|
||||
///< values must be stored.
|
||||
gd::String parameterType; ///< The type of the parameters to be searched for.
|
||||
gd::String objectName; ///< If not empty, parameters will be taken into
|
||||
///< account only if related to this object.
|
||||
};
|
||||
} // namespace
|
||||
|
||||
std::set<gd::String> EventsVariablesFinder::FindAllGlobalVariables(
|
||||
const gd::Platform& platform, const gd::Project& project) {
|
||||
std::set<gd::String> results;
|
||||
|
||||
for (std::size_t i = 0; i < project.GetLayoutsCount(); ++i) {
|
||||
std::set<gd::String> results2 =
|
||||
FindArgumentsInEventsAndDependencies(
|
||||
FindArgumentsInEventsAndDependencies(
|
||||
results,
|
||||
platform,
|
||||
project,
|
||||
project.GetLayout(i),
|
||||
"globalvar");
|
||||
results.insert(results2.begin(), results2.end());
|
||||
}
|
||||
|
||||
return results;
|
||||
@@ -135,9 +212,12 @@ std::set<gd::String> EventsVariablesFinder::FindAllLayoutVariables(
|
||||
const gd::Layout& layout) {
|
||||
std::set<gd::String> results;
|
||||
|
||||
std::set<gd::String> results2 = FindArgumentsInEventsAndDependencies(
|
||||
platform, project, layout, "scenevar");
|
||||
results.insert(results2.begin(), results2.end());
|
||||
FindArgumentsInEventsAndDependencies(
|
||||
results,
|
||||
platform,
|
||||
project,
|
||||
layout,
|
||||
"scenevar");
|
||||
|
||||
return results;
|
||||
}
|
||||
@@ -149,159 +229,51 @@ std::set<gd::String> EventsVariablesFinder::FindAllObjectVariables(
|
||||
const gd::Object& object) {
|
||||
std::set<gd::String> results;
|
||||
|
||||
std::set<gd::String> results2 = FindArgumentsInEventsAndDependencies(
|
||||
FindArgumentsInEventsAndDependencies(
|
||||
results,
|
||||
platform,
|
||||
project,
|
||||
layout,
|
||||
"objectvar",
|
||||
object.GetName());
|
||||
results.insert(results2.begin(), results2.end());
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
std::set<gd::String> EventsVariablesFinder::FindArgumentsInInstructions(
|
||||
const gd::Platform& platform,
|
||||
const gd::Project& project,
|
||||
const gd::Layout& layout,
|
||||
const gd::InstructionsList& instructions,
|
||||
bool instructionsAreConditions,
|
||||
const gd::String& parameterType,
|
||||
const gd::String& objectName) {
|
||||
std::set<gd::String> results;
|
||||
|
||||
for (std::size_t aId = 0; aId < instructions.size(); ++aId) {
|
||||
gd::String lastObjectParameter = "";
|
||||
const gd::InstructionMetadata& instrInfos =
|
||||
instructionsAreConditions ? MetadataProvider::GetConditionMetadata(
|
||||
platform, instructions[aId].GetType())
|
||||
: MetadataProvider::GetActionMetadata(
|
||||
platform, instructions[aId].GetType());
|
||||
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
|
||||
// The parameter has the searched type...
|
||||
if (instrInfos.parameters[pNb].type == parameterType) {
|
||||
//...remember the value of the parameter.
|
||||
if (objectName.empty() || lastObjectParameter == objectName)
|
||||
results.insert(instructions[aId].GetParameter(pNb).GetPlainString());
|
||||
}
|
||||
// Search in expressions
|
||||
else if (ParameterMetadata::IsExpression(
|
||||
"number", instrInfos.parameters[pNb].type) ||
|
||||
ParameterMetadata::IsExpression(
|
||||
"string", instrInfos.parameters[pNb].type)) {
|
||||
auto node = instructions[aId].GetParameter(pNb).GetRootNode();
|
||||
|
||||
ExpressionParameterSearcher searcher(
|
||||
platform,
|
||||
project,
|
||||
layout,
|
||||
results,
|
||||
parameterType,
|
||||
objectName);
|
||||
node->Visit(searcher);
|
||||
}
|
||||
// Remember the value of the last "object" parameter.
|
||||
else if (gd::ParameterMetadata::IsObject(
|
||||
instrInfos.parameters[pNb].type)) {
|
||||
lastObjectParameter =
|
||||
instructions[aId].GetParameter(pNb).GetPlainString();
|
||||
}
|
||||
}
|
||||
|
||||
if (!instructions[aId].GetSubInstructions().empty())
|
||||
FindArgumentsInInstructions(platform,
|
||||
project,
|
||||
layout,
|
||||
instructions[aId].GetSubInstructions(),
|
||||
instructionsAreConditions,
|
||||
parameterType);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
std::set<gd::String> EventsVariablesFinder::FindArgumentsInEventsAndDependencies(
|
||||
void EventsVariablesFinder::FindArgumentsInEventsAndDependencies(
|
||||
std::set<gd::String>& results,
|
||||
const gd::Platform& platform,
|
||||
const gd::Project& project,
|
||||
const gd::Layout& layout,
|
||||
const gd::String& parameterType,
|
||||
const gd::String& objectName) {
|
||||
std::set<gd::String> results;
|
||||
|
||||
std::set<gd::String> results2 = FindArgumentsInEvents(
|
||||
platform, project, layout, layout.GetEvents(), parameterType, objectName);
|
||||
results.insert(results2.begin(), results2.end());
|
||||
VariableFinderEventWorker eventWorker(results,
|
||||
platform,
|
||||
parameterType,
|
||||
objectName);
|
||||
eventWorker.Launch(layout.GetEvents(), project, layout);
|
||||
|
||||
DependenciesAnalyzer dependenciesAnalyzer = DependenciesAnalyzer(project, layout);
|
||||
dependenciesAnalyzer.Analyze();
|
||||
for (const gd::String& externalEventName : dependenciesAnalyzer.GetExternalEventsDependencies()) {
|
||||
const gd::ExternalEvents& externalEvents = project.GetExternalEvents(externalEventName);
|
||||
|
||||
std::set<gd::String> results3 = FindArgumentsInEvents(
|
||||
platform, project, layout, externalEvents.GetEvents(), parameterType, objectName);
|
||||
results.insert(results3.begin(), results3.end());
|
||||
VariableFinderEventWorker eventWorker(results,
|
||||
platform,
|
||||
parameterType,
|
||||
objectName);
|
||||
eventWorker.Launch(externalEvents.GetEvents(), project, layout);
|
||||
}
|
||||
for (const gd::String& sceneName : dependenciesAnalyzer.GetScenesDependencies()) {
|
||||
const gd::Layout& dependencyLayout = project.GetLayout(sceneName);
|
||||
|
||||
std::set<gd::String> results3 = FindArgumentsInEvents(
|
||||
platform, project, dependencyLayout, dependencyLayout.GetEvents(), parameterType, objectName);
|
||||
results.insert(results3.begin(), results3.end());
|
||||
VariableFinderEventWorker eventWorker(results,
|
||||
platform,
|
||||
parameterType,
|
||||
objectName);
|
||||
eventWorker.Launch(dependencyLayout.GetEvents(), project, dependencyLayout);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
std::set<gd::String> EventsVariablesFinder::FindArgumentsInEvents(
|
||||
const gd::Platform& platform,
|
||||
const gd::Project& project,
|
||||
const gd::Layout& layout,
|
||||
const gd::EventsList& events,
|
||||
const gd::String& parameterType,
|
||||
const gd::String& objectName) {
|
||||
std::set<gd::String> results;
|
||||
for (std::size_t i = 0; i < events.size(); ++i) {
|
||||
vector<const gd::InstructionsList*> conditionsVectors =
|
||||
events[i].GetAllConditionsVectors();
|
||||
for (std::size_t j = 0; j < conditionsVectors.size(); ++j) {
|
||||
std::set<gd::String> results2 =
|
||||
FindArgumentsInInstructions(platform,
|
||||
project,
|
||||
layout,
|
||||
*conditionsVectors[j],
|
||||
/*conditions=*/true,
|
||||
parameterType,
|
||||
objectName);
|
||||
results.insert(results2.begin(), results2.end());
|
||||
}
|
||||
|
||||
vector<const gd::InstructionsList*> actionsVectors =
|
||||
events[i].GetAllActionsVectors();
|
||||
for (std::size_t j = 0; j < actionsVectors.size(); ++j) {
|
||||
std::set<gd::String> results2 =
|
||||
FindArgumentsInInstructions(platform,
|
||||
project,
|
||||
layout,
|
||||
*actionsVectors[j],
|
||||
/*conditions=*/false,
|
||||
parameterType,
|
||||
objectName);
|
||||
results.insert(results2.begin(), results2.end());
|
||||
}
|
||||
|
||||
if (events[i].CanHaveSubEvents()) {
|
||||
std::set<gd::String> results2 =
|
||||
FindArgumentsInEvents(platform,
|
||||
project,
|
||||
layout,
|
||||
events[i].GetSubEvents(),
|
||||
parameterType,
|
||||
objectName);
|
||||
results.insert(results2.begin(), results2.end());
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -23,7 +23,6 @@ namespace gd {
|
||||
* \brief Perform a search over a project or a layout, searching for layout,
|
||||
* global or object variables.
|
||||
*
|
||||
* \todo Refactor this class using ArbitraryEventsWorker
|
||||
* \todo Rework this class to return the shapes (maybe even types?) of the
|
||||
* variables (in particular for structures and arrays), so we can use this
|
||||
* for better autocompletions in the variables dialogs in the IDE.
|
||||
@@ -74,34 +73,13 @@ class EventsVariablesFinder {
|
||||
const gd::Object& object);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Construct a list of the value of the arguments for parameters of type @
|
||||
* parameterType
|
||||
*
|
||||
* \param project The project used
|
||||
* \param project The layout used
|
||||
* \param instructions The instructions to be analyzed
|
||||
* \param instructionsAreConditions True if the instructions are conditions.
|
||||
* \param parameterType The parameters type to be analyzed
|
||||
* \param objectName If not empty, parameters will be taken into account only
|
||||
* if the last object parameter is filled with this value.
|
||||
*
|
||||
* \return A std::set filled with the values used for all parameters of the
|
||||
* specified type
|
||||
*/
|
||||
static std::set<gd::String> FindArgumentsInInstructions(
|
||||
const gd::Platform& platform,
|
||||
const gd::Project& project,
|
||||
const gd::Layout& layout,
|
||||
const gd::InstructionsList& instructions,
|
||||
bool instructionsAreConditions,
|
||||
const gd::String& parameterType,
|
||||
const gd::String& objectName = "");
|
||||
|
||||
/**
|
||||
* Construct a list of the value of the arguments for parameters of type @
|
||||
* parameterType. It searchs in events dependencies.
|
||||
* parameterType. It searches in events dependencies.
|
||||
*
|
||||
* \param results A std::set to fill with the values used for all parameters of the
|
||||
* specified type
|
||||
* \param platform The platform of the project
|
||||
* \param project The project used
|
||||
* \param layout The layout used
|
||||
@@ -110,40 +88,14 @@ class EventsVariablesFinder {
|
||||
* \param objectName If not empty, parameters will be taken into account
|
||||
* only if the last object parameter is filled with
|
||||
* this value.
|
||||
*
|
||||
* \return A std::set filled with the values used for all parameters of the
|
||||
* specified type
|
||||
*/
|
||||
static std::set<gd::String> FindArgumentsInEventsAndDependencies(
|
||||
static void FindArgumentsInEventsAndDependencies(
|
||||
std::set<gd::String>& results,
|
||||
const gd::Platform& platform,
|
||||
const gd::Project& project,
|
||||
const gd::Layout& layout,
|
||||
const gd::String& parameterType,
|
||||
const gd::String& objectName = "");
|
||||
|
||||
/**
|
||||
* Construct a list of the value of the arguments for parameters of type @
|
||||
* parameterType. It doesn't search in events dependencies.
|
||||
*
|
||||
* \param platform The platform of the project
|
||||
* \param project The project used
|
||||
* \param layout The layout used
|
||||
* \param events The events to be analyzed
|
||||
* \param parameterType The parameters type to be analyzed
|
||||
* \param objectName If not empty, parameters will be taken into account
|
||||
* only if the last object parameter is filled with
|
||||
* this value.
|
||||
*
|
||||
* \return A std::set filled with the values used for all parameters of the
|
||||
* specified type
|
||||
*/
|
||||
static std::set<gd::String> FindArgumentsInEvents(
|
||||
const gd::Platform& platform,
|
||||
const gd::Project& project,
|
||||
const gd::Layout& layout,
|
||||
const gd::EventsList& events,
|
||||
const gd::String& parameterType,
|
||||
const gd::String& objectName);
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -5,6 +5,8 @@
|
||||
*/
|
||||
#include "ProjectStripper.h"
|
||||
|
||||
#include "GDCore/Project/EventsFunctionsContainer.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/ExternalEvents.h"
|
||||
#include "GDCore/Project/ExternalLayout.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
@@ -12,7 +14,7 @@
|
||||
|
||||
namespace gd {
|
||||
|
||||
void GD_CORE_API ProjectStripper::StripProjectForExport(gd::Project& project) {
|
||||
void GD_CORE_API ProjectStripper::StripProjectForExport(gd::Project &project) {
|
||||
project.GetObjectGroups().Clear();
|
||||
while (project.GetExternalEventsCount() > 0)
|
||||
project.RemoveExternalEvents(project.GetExternalEvents(0).GetName());
|
||||
@@ -22,7 +24,28 @@ void GD_CORE_API ProjectStripper::StripProjectForExport(gd::Project& project) {
|
||||
project.GetLayout(i).GetEvents().Clear();
|
||||
}
|
||||
|
||||
project.ClearEventsFunctionsExtensions();
|
||||
// Keep the EventsBasedObject object list because it's useful for the Runtime
|
||||
// to create the child-object.
|
||||
for (unsigned int extensionIndex = 0;
|
||||
extensionIndex < project.GetEventsFunctionsExtensionsCount();
|
||||
++extensionIndex) {
|
||||
auto &extension = project.GetEventsFunctionsExtension(extensionIndex);
|
||||
auto &eventsBasedObjects = extension.GetEventsBasedObjects();
|
||||
if (eventsBasedObjects.size() == 0) {
|
||||
project.RemoveEventsFunctionsExtension(extension.GetName());
|
||||
extensionIndex--;
|
||||
continue;
|
||||
}
|
||||
for (unsigned int objectIndex = 0; objectIndex < eventsBasedObjects.size();
|
||||
++objectIndex) {
|
||||
auto &eventsBasedObject = eventsBasedObjects.at(objectIndex);
|
||||
eventsBasedObject.SetFullName("");
|
||||
eventsBasedObject.SetDescription("");
|
||||
eventsBasedObject.GetEventsFunctions().GetInternalVector().clear();
|
||||
eventsBasedObject.GetPropertyDescriptors().GetInternalVector().clear();
|
||||
}
|
||||
extension.GetEventsBasedBehaviors().Clear();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
} // namespace gd
|
||||
|
@@ -22,16 +22,14 @@ void CustomObjectConfiguration::Init(const gd::CustomObjectConfiguration& object
|
||||
// There is no default copy for a map of unique_ptr like childObjectConfigurations.
|
||||
childObjectConfigurations.clear();
|
||||
for (auto& it : objectConfiguration.childObjectConfigurations) {
|
||||
childObjectConfigurations[it.first] =
|
||||
gd::make_unique<gd::ObjectConfiguration>(*it.second);
|
||||
childObjectConfigurations[it.first] = it.second->Clone();
|
||||
}
|
||||
}
|
||||
|
||||
gd::ObjectConfiguration CustomObjectConfiguration::badObjectConfiguration;
|
||||
|
||||
std::unique_ptr<gd::ObjectConfiguration> CustomObjectConfiguration::Clone() const {
|
||||
CustomObjectConfiguration* clone = new CustomObjectConfiguration(*this);
|
||||
return std::unique_ptr<gd::ObjectConfiguration>(clone);
|
||||
return gd::make_unique<gd::CustomObjectConfiguration>(*this);
|
||||
}
|
||||
|
||||
gd::ObjectConfiguration &CustomObjectConfiguration::GetChildObjectConfiguration(const gd::String &objectName) {
|
||||
@@ -51,7 +49,7 @@ gd::ObjectConfiguration &CustomObjectConfiguration::GetChildObjectConfiguration(
|
||||
if (configurationPosition == childObjectConfigurations.end()) {
|
||||
childObjectConfigurations.insert(std::make_pair(
|
||||
objectName,
|
||||
project->CreateObjectConfiguration(childObject.GetType())));
|
||||
childObject.GetConfiguration().Clone()));
|
||||
return *(childObjectConfigurations[objectName]);
|
||||
}
|
||||
else {
|
||||
|
@@ -110,6 +110,11 @@ class GD_CORE_API EffectsContainer {
|
||||
*/
|
||||
void UnserializeFrom(const SerializerElement& element);
|
||||
|
||||
/**
|
||||
* \brief Clear all effects of the container.
|
||||
*/
|
||||
inline void Clear() { effects.clear(); }
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<gd::Effect>> effects;
|
||||
static Effect badEffect;
|
||||
|
@@ -23,12 +23,16 @@ EventsBasedObject::EventsBasedObject(const gd::EventsBasedObject &_eventBasedObj
|
||||
}
|
||||
|
||||
void EventsBasedObject::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("defaultName", defaultName);
|
||||
|
||||
AbstractEventsBasedEntity::SerializeTo(element);
|
||||
SerializeObjectsTo(element.AddChild("objects"));
|
||||
}
|
||||
|
||||
void EventsBasedObject::UnserializeFrom(gd::Project& project,
|
||||
const SerializerElement& element) {
|
||||
defaultName = element.GetStringAttribute("defaultName");
|
||||
|
||||
AbstractEventsBasedEntity::UnserializeFrom(project, element);
|
||||
UnserializeObjectsFrom(project, element.GetChild("objects"));
|
||||
}
|
||||
|
@@ -38,6 +38,19 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity, public Ob
|
||||
*/
|
||||
EventsBasedObject* Clone() const { return new EventsBasedObject(*this); };
|
||||
|
||||
/**
|
||||
* \brief Get the default name for created objects.
|
||||
*/
|
||||
const gd::String& GetDefaultName() const { return defaultName; };
|
||||
|
||||
/**
|
||||
* \brief Set the default name for created objects.
|
||||
*/
|
||||
EventsBasedObject& SetDefaultName(const gd::String& defaultName_) {
|
||||
defaultName = defaultName_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
EventsBasedObject& SetDescription(const gd::String& description_) override {
|
||||
AbstractEventsBasedEntity::SetDescription(description_);
|
||||
return *this;
|
||||
@@ -65,6 +78,7 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity, public Ob
|
||||
const SerializerElement& element) override;
|
||||
|
||||
private:
|
||||
gd::String defaultName;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
289
Core/tests/EventsIdentifiersFinder.cpp
Normal file
289
Core/tests/EventsIdentifiersFinder.cpp
Normal file
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
/**
|
||||
* @file Tests covering common features of GDevelop Core.
|
||||
*/
|
||||
#include "GDCore/IDE/Events/EventsIdentifiersFinder.h"
|
||||
#include "GDCore/Events/Builtin/LinkEvent.h"
|
||||
#include "GDCore/Events/Builtin/StandardEvent.h"
|
||||
#include "GDCore/Events/Event.h"
|
||||
#include "GDCore/Extensions/Builtin/AllBuiltinExtensions.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Project/ExternalEvents.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
namespace {
|
||||
const void DeclareTimerExtension(gd::Project &project, gd::Platform &platform) {
|
||||
std::shared_ptr<gd::PlatformExtension> extension =
|
||||
std::shared_ptr<gd::PlatformExtension>(new gd::PlatformExtension);
|
||||
gd::BuiltinExtensionsImplementer::ImplementsTimeExtension(*(extension.get()));
|
||||
gd::BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
*(extension.get()));
|
||||
// Add an instruction to test expressions.
|
||||
extension
|
||||
->AddAction("DoSomething", "Do something", "This does something",
|
||||
"Do something please", "", "", "")
|
||||
.AddParameter("expression", "Parameter 1 (a number)");
|
||||
platform.AddExtension(extension);
|
||||
project.AddPlatform(platform);
|
||||
}
|
||||
|
||||
const gd::StandardEvent UseSceneTimer(const gd::String &name) {
|
||||
gd::StandardEvent event;
|
||||
gd::Instruction instruction;
|
||||
instruction.SetType("ResetTimer");
|
||||
instruction.SetParametersCount(2);
|
||||
instruction.SetParameter(0, gd::Expression("scene"));
|
||||
instruction.SetParameter(1, gd::Expression("\"" + name + "\""));
|
||||
event.GetActions().Insert(instruction);
|
||||
return event;
|
||||
}
|
||||
|
||||
const gd::StandardEvent UseObjectTimer(const gd::String &objectName,
|
||||
const gd::String &timerName) {
|
||||
gd::StandardEvent event;
|
||||
gd::Instruction instruction;
|
||||
instruction.SetType("ResetObjectTimer");
|
||||
instruction.SetParametersCount(2);
|
||||
instruction.SetParameter(0, gd::Expression(objectName));
|
||||
instruction.SetParameter(1, gd::Expression("\"" + timerName + "\""));
|
||||
event.GetActions().Insert(instruction);
|
||||
return event;
|
||||
}
|
||||
|
||||
const gd::StandardEvent UseSceneTimerInExpression(const gd::String &name) {
|
||||
gd::StandardEvent event;
|
||||
gd::Instruction instruction;
|
||||
instruction.SetType("DoSomething");
|
||||
instruction.SetParametersCount(1);
|
||||
instruction.SetParameter(
|
||||
0, gd::Expression("1 + TimerElapsedTime(\"" + name + "\")"));
|
||||
event.GetActions().Insert(instruction);
|
||||
return event;
|
||||
}
|
||||
|
||||
const gd::StandardEvent
|
||||
UseObjectTimerInExpression(const gd::String &objectName,
|
||||
const gd::String &timerName) {
|
||||
gd::StandardEvent event;
|
||||
gd::Instruction instruction;
|
||||
instruction.SetType("DoSomething");
|
||||
instruction.SetParametersCount(1);
|
||||
instruction.SetParameter(0, gd::Expression("1 + " + objectName +
|
||||
".ObjectTimerElapsedTime(\"" + timerName +
|
||||
"\")"));
|
||||
event.GetActions().Insert(instruction);
|
||||
return event;
|
||||
}
|
||||
|
||||
const void UseExternalEvents(gd::Layout &layout,
|
||||
gd::ExternalEvents &externalEvents) {
|
||||
gd::LinkEvent linkEvent;
|
||||
linkEvent.SetTarget(externalEvents.GetName());
|
||||
layout.GetEvents().InsertEvent(linkEvent);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("EventsIdentifiersFinder (scene timers)", "[common]") {
|
||||
SECTION("Can find scene timers in scenes") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareTimerExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
layout.GetEvents().InsertEvent(UseSceneTimer("MySceneTimer"));
|
||||
|
||||
auto identifierExpressions =
|
||||
gd::EventsIdentifiersFinder::FindAllIdentifierExpressions(
|
||||
platform, project, layout, "sceneTimer");
|
||||
|
||||
REQUIRE(identifierExpressions.size() == 1);
|
||||
REQUIRE(*(identifierExpressions.begin()) == "\"MySceneTimer\"");
|
||||
}
|
||||
|
||||
SECTION("Can find scene timers in scene expressions") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareTimerExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
layout.GetEvents().InsertEvent(UseSceneTimerInExpression("MySceneTimer"));
|
||||
|
||||
auto identifierExpressions =
|
||||
gd::EventsIdentifiersFinder::FindAllIdentifierExpressions(
|
||||
platform, project, layout, "sceneTimer");
|
||||
|
||||
REQUIRE(identifierExpressions.size() == 1);
|
||||
REQUIRE(*(identifierExpressions.begin()) == "\"MySceneTimer\"");
|
||||
}
|
||||
|
||||
SECTION("Can find scene timers in external layouts") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareTimerExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0);
|
||||
externalEvents.GetEvents().InsertEvent(UseSceneTimer("MySceneTimer"));
|
||||
UseExternalEvents(layout, externalEvents);
|
||||
|
||||
auto identifierExpressions =
|
||||
gd::EventsIdentifiersFinder::FindAllIdentifierExpressions(
|
||||
platform, project, layout, "sceneTimer");
|
||||
|
||||
REQUIRE(identifierExpressions.size() == 1);
|
||||
REQUIRE(*(identifierExpressions.begin()) == "\"MySceneTimer\"");
|
||||
}
|
||||
|
||||
SECTION("Can find scene timers the right scene") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareTimerExtension(project, platform);
|
||||
|
||||
auto &layout1 = project.InsertNewLayout("Layout1", 0);
|
||||
layout1.GetEvents().InsertEvent(UseSceneTimer("MySceneTimerInLayout1"));
|
||||
|
||||
auto &layout2 = project.InsertNewLayout("Layout2", 0);
|
||||
layout2.GetEvents().InsertEvent(UseSceneTimer("MySceneTimerInLayout2"));
|
||||
|
||||
auto identifierExpressions =
|
||||
gd::EventsIdentifiersFinder::FindAllIdentifierExpressions(
|
||||
platform, project, layout1, "sceneTimer");
|
||||
|
||||
REQUIRE(identifierExpressions.size() == 1);
|
||||
REQUIRE(*(identifierExpressions.begin()) == "\"MySceneTimerInLayout1\"");
|
||||
}
|
||||
|
||||
SECTION("Can find scene timers in the right external layouts") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareTimerExtension(project, platform);
|
||||
|
||||
auto &layout1 = project.InsertNewLayout("Layout1", 0);
|
||||
auto &externalEvents1 =
|
||||
project.InsertNewExternalEvents("ExternalEvents1", 0);
|
||||
externalEvents1.GetEvents().InsertEvent(
|
||||
UseSceneTimer("MySceneTimerInExternalEvents1"));
|
||||
UseExternalEvents(layout1, externalEvents1);
|
||||
|
||||
auto &layout2 = project.InsertNewLayout("Layout2", 0);
|
||||
auto &externalEvents2 =
|
||||
project.InsertNewExternalEvents("ExternalEvents2", 0);
|
||||
externalEvents2.GetEvents().InsertEvent(
|
||||
UseSceneTimer("MySceneTimerInExternalEvents2"));
|
||||
UseExternalEvents(layout2, externalEvents2);
|
||||
|
||||
auto identifierExpressions =
|
||||
gd::EventsIdentifiersFinder::FindAllIdentifierExpressions(
|
||||
platform, project, layout1, "sceneTimer");
|
||||
|
||||
REQUIRE(identifierExpressions.size() == 1);
|
||||
REQUIRE(*(identifierExpressions.begin()) ==
|
||||
"\"MySceneTimerInExternalEvents1\"");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("EventsIdentifiersFinder (object timers)", "[common]") {
|
||||
SECTION("Can find object timers in scenes") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareTimerExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
auto &object = layout.InsertNewObject(project, "", "MyObject", 0);
|
||||
layout.GetEvents().InsertEvent(UseObjectTimer("MyObject", "MyObjectTimer"));
|
||||
|
||||
auto identifierExpressions =
|
||||
gd::EventsIdentifiersFinder::FindAllIdentifierExpressions(
|
||||
platform, project, layout, "objectTimer", object.GetName());
|
||||
|
||||
REQUIRE(identifierExpressions.size() == 1);
|
||||
REQUIRE(*(identifierExpressions.begin()) == "\"MyObjectTimer\"");
|
||||
}
|
||||
|
||||
SECTION("Can find object timers in scene expression") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareTimerExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
auto &object = layout.InsertNewObject(project, "", "MyObject", 0);
|
||||
layout.GetEvents().InsertEvent(UseObjectTimerInExpression("MyObject", "MyObjectTimer"));
|
||||
|
||||
auto identifierExpressions =
|
||||
gd::EventsIdentifiersFinder::FindAllIdentifierExpressions(
|
||||
platform, project, layout, "objectTimer", object.GetName());
|
||||
|
||||
REQUIRE(identifierExpressions.size() == 1);
|
||||
REQUIRE(*(identifierExpressions.begin()) == "\"MyObjectTimer\"");
|
||||
}
|
||||
|
||||
SECTION("Can find object timers in external layouts") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareTimerExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
auto &object = layout.InsertNewObject(project, "", "MyObject", 0);
|
||||
auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0);
|
||||
externalEvents.GetEvents().InsertEvent(
|
||||
UseObjectTimer("MyObject", "MyObjectTimer"));
|
||||
UseExternalEvents(layout, externalEvents);
|
||||
|
||||
auto identifierExpressions =
|
||||
gd::EventsIdentifiersFinder::FindAllIdentifierExpressions(
|
||||
platform, project, layout, "objectTimer", object.GetName());
|
||||
|
||||
REQUIRE(identifierExpressions.size() == 1);
|
||||
REQUIRE(*(identifierExpressions.begin()) == "\"MyObjectTimer\"");
|
||||
}
|
||||
|
||||
SECTION("Can find object timers in scenes for the right object") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareTimerExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
auto &object1 = layout.InsertNewObject(project, "", "MyObject1", 0);
|
||||
auto &object2 = layout.InsertNewObject(project, "", "MyObject2", 0);
|
||||
layout.GetEvents().InsertEvent(
|
||||
UseObjectTimer("MyObject1", "MyObjectTimer1"));
|
||||
layout.GetEvents().InsertEvent(
|
||||
UseObjectTimer("MyObject2", "MyObjectTimer2"));
|
||||
|
||||
auto identifierExpressions =
|
||||
gd::EventsIdentifiersFinder::FindAllIdentifierExpressions(
|
||||
platform, project, layout, "objectTimer", object1.GetName());
|
||||
|
||||
REQUIRE(identifierExpressions.size() == 1);
|
||||
REQUIRE(*(identifierExpressions.begin()) == "\"MyObjectTimer1\"");
|
||||
}
|
||||
|
||||
SECTION("Can find object timers in external layouts for the right object") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareTimerExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
auto &object1 = layout.InsertNewObject(project, "", "MyObject1", 0);
|
||||
auto &object2 = layout.InsertNewObject(project, "", "MyObject2", 0);
|
||||
auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0);
|
||||
externalEvents.GetEvents().InsertEvent(
|
||||
UseObjectTimer("MyObject1", "MyObjectTimer1"));
|
||||
externalEvents.GetEvents().InsertEvent(
|
||||
UseObjectTimer("MyObject2", "MyObjectTimer2"));
|
||||
UseExternalEvents(layout, externalEvents);
|
||||
|
||||
auto identifierExpressions =
|
||||
gd::EventsIdentifiersFinder::FindAllIdentifierExpressions(
|
||||
platform, project, layout, "objectTimer", object1.GetName());
|
||||
|
||||
REQUIRE(identifierExpressions.size() == 1);
|
||||
REQUIRE(*(identifierExpressions.begin()) == "\"MyObjectTimer1\"");
|
||||
}
|
||||
}
|
360
Core/tests/EventsVariablesFinder.cpp
Normal file
360
Core/tests/EventsVariablesFinder.cpp
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
/**
|
||||
* @file Tests covering common features of GDevelop Core.
|
||||
*/
|
||||
#include "GDCore/IDE/Events/EventsVariablesFinder.h"
|
||||
#include "GDCore/Events/Builtin/LinkEvent.h"
|
||||
#include "GDCore/Events/Builtin/StandardEvent.h"
|
||||
#include "GDCore/Events/Event.h"
|
||||
#include "GDCore/Extensions/Builtin/AllBuiltinExtensions.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Project/ExternalEvents.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/Variable.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
namespace {
|
||||
const void DeclareVariableExtension(gd::Project &project,
|
||||
gd::Platform &platform) {
|
||||
std::shared_ptr<gd::PlatformExtension> extension =
|
||||
std::shared_ptr<gd::PlatformExtension>(new gd::PlatformExtension);
|
||||
gd::BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
*(extension.get()));
|
||||
gd::BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
*(extension.get()));
|
||||
// Add an instruction to test expressions.
|
||||
extension
|
||||
->AddAction("DoSomething", "Do something", "This does something",
|
||||
"Do something please", "", "", "")
|
||||
.AddParameter("expression", "Parameter 1 (a number)");
|
||||
platform.AddExtension(extension);
|
||||
project.AddPlatform(platform);
|
||||
}
|
||||
|
||||
const gd::StandardEvent UseGlobalVariable(const gd::String &name) {
|
||||
gd::StandardEvent event;
|
||||
gd::Instruction instruction;
|
||||
instruction.SetType("ModVarGlobal");
|
||||
instruction.SetParametersCount(2);
|
||||
instruction.SetParameter(0, gd::Expression(name));
|
||||
instruction.SetParameter(1, gd::Expression("0"));
|
||||
event.GetActions().Insert(instruction);
|
||||
return event;
|
||||
}
|
||||
|
||||
const gd::StandardEvent UseSceneVariable(const gd::String &name) {
|
||||
gd::StandardEvent event;
|
||||
gd::Instruction instruction;
|
||||
instruction.SetType("ModVarScene");
|
||||
instruction.SetParametersCount(2);
|
||||
instruction.SetParameter(0, gd::Expression(name));
|
||||
instruction.SetParameter(1, gd::Expression("0"));
|
||||
event.GetActions().Insert(instruction);
|
||||
return event;
|
||||
}
|
||||
|
||||
const gd::StandardEvent UseObjectVariable(const gd::String &objectName,
|
||||
const gd::String &variableName) {
|
||||
gd::StandardEvent event;
|
||||
gd::Instruction instruction;
|
||||
instruction.SetType("ModVarObjet");
|
||||
instruction.SetParametersCount(3);
|
||||
instruction.SetParameter(0, gd::Expression(objectName));
|
||||
instruction.SetParameter(1, gd::Expression(variableName));
|
||||
instruction.SetParameter(2, gd::Expression("0"));
|
||||
event.GetActions().Insert(instruction);
|
||||
return event;
|
||||
}
|
||||
|
||||
const gd::StandardEvent UseGlobalVariableInExpression(const gd::String &name) {
|
||||
gd::StandardEvent event;
|
||||
gd::Instruction instruction;
|
||||
instruction.SetType("DoSomething");
|
||||
instruction.SetParametersCount(1);
|
||||
instruction.SetParameter(0,
|
||||
gd::Expression("1 + GlobalVariable(" + name + ")"));
|
||||
event.GetActions().Insert(instruction);
|
||||
return event;
|
||||
}
|
||||
|
||||
const gd::StandardEvent UseSceneVariableInExpression(const gd::String &name) {
|
||||
gd::StandardEvent event;
|
||||
gd::Instruction instruction;
|
||||
instruction.SetType("DoSomething");
|
||||
instruction.SetParametersCount(1);
|
||||
instruction.SetParameter(0, gd::Expression("1 + Variable(" + name + ")"));
|
||||
event.GetActions().Insert(instruction);
|
||||
return event;
|
||||
}
|
||||
|
||||
const gd::StandardEvent
|
||||
UseObjectVariableInExpression(const gd::String &objectName,
|
||||
const gd::String &variableName) {
|
||||
gd::StandardEvent event;
|
||||
gd::Instruction instruction;
|
||||
instruction.SetType("DoSomething");
|
||||
instruction.SetParametersCount(1);
|
||||
instruction.SetParameter(
|
||||
0,
|
||||
gd::Expression("1 + " + objectName + ".Variable(" + variableName + ")"));
|
||||
event.GetActions().Insert(instruction);
|
||||
return event;
|
||||
}
|
||||
|
||||
const void UseExternalEvents(gd::Layout &layout,
|
||||
gd::ExternalEvents &externalEvents) {
|
||||
gd::LinkEvent linkEvent;
|
||||
linkEvent.SetTarget(externalEvents.GetName());
|
||||
layout.GetEvents().InsertEvent(linkEvent);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("EventsVariablesFinder (FindAllGlobalVariables)", "[common]") {
|
||||
SECTION("Can find global variables in scenes") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareVariableExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
layout.GetEvents().InsertEvent(UseGlobalVariable("MyGlobalVariable"));
|
||||
|
||||
auto variableNames =
|
||||
gd::EventsVariablesFinder::FindAllGlobalVariables(platform, project);
|
||||
|
||||
REQUIRE(variableNames.size() == 1);
|
||||
REQUIRE(*(variableNames.begin()) == "MyGlobalVariable");
|
||||
}
|
||||
|
||||
SECTION("Can find global variables in scene expressions") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareVariableExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
layout.GetEvents().InsertEvent(
|
||||
UseGlobalVariableInExpression("MyGlobalVariable"));
|
||||
|
||||
auto variableNames =
|
||||
gd::EventsVariablesFinder::FindAllGlobalVariables(platform, project);
|
||||
|
||||
REQUIRE(variableNames.size() == 1);
|
||||
REQUIRE(*(variableNames.begin()) == "MyGlobalVariable");
|
||||
}
|
||||
|
||||
SECTION("Can find global variables in external layouts") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareVariableExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0);
|
||||
externalEvents.GetEvents().InsertEvent(
|
||||
UseGlobalVariable("MyGlobalVariable"));
|
||||
UseExternalEvents(layout, externalEvents);
|
||||
|
||||
auto variableNames =
|
||||
gd::EventsVariablesFinder::FindAllGlobalVariables(platform, project);
|
||||
|
||||
REQUIRE(variableNames.size() == 1);
|
||||
REQUIRE(*(variableNames.begin()) == "MyGlobalVariable");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("EventsVariablesFinder (FindAllLayoutVariables)", "[common]") {
|
||||
SECTION("Can find scene variables in scenes") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareVariableExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
layout.GetEvents().InsertEvent(UseSceneVariable("MySceneVariable"));
|
||||
|
||||
auto variableNames = gd::EventsVariablesFinder::FindAllLayoutVariables(
|
||||
platform, project, layout);
|
||||
|
||||
REQUIRE(variableNames.size() == 1);
|
||||
REQUIRE(*(variableNames.begin()) == "MySceneVariable");
|
||||
}
|
||||
|
||||
SECTION("Can find scene variables in scene expressions") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareVariableExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
layout.GetEvents().InsertEvent(
|
||||
UseSceneVariableInExpression("MySceneVariable"));
|
||||
|
||||
auto variableNames = gd::EventsVariablesFinder::FindAllLayoutVariables(
|
||||
platform, project, layout);
|
||||
|
||||
REQUIRE(variableNames.size() == 1);
|
||||
REQUIRE(*(variableNames.begin()) == "MySceneVariable");
|
||||
}
|
||||
|
||||
SECTION("Can find scene variables in external layouts") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareVariableExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0);
|
||||
externalEvents.GetEvents().InsertEvent(UseSceneVariable("MySceneVariable"));
|
||||
UseExternalEvents(layout, externalEvents);
|
||||
|
||||
auto variableNames = gd::EventsVariablesFinder::FindAllLayoutVariables(
|
||||
platform, project, layout);
|
||||
|
||||
REQUIRE(variableNames.size() == 1);
|
||||
REQUIRE(*(variableNames.begin()) == "MySceneVariable");
|
||||
}
|
||||
|
||||
SECTION("Can find scene variables the right scene") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareVariableExtension(project, platform);
|
||||
|
||||
auto &layout1 = project.InsertNewLayout("Layout1", 0);
|
||||
layout1.GetEvents().InsertEvent(
|
||||
UseSceneVariable("MySceneVariableInLayout1"));
|
||||
|
||||
auto &layout2 = project.InsertNewLayout("Layout2", 0);
|
||||
layout2.GetEvents().InsertEvent(
|
||||
UseSceneVariable("MySceneVariableInLayout2"));
|
||||
|
||||
auto variableNames = gd::EventsVariablesFinder::FindAllLayoutVariables(
|
||||
platform, project, layout1);
|
||||
|
||||
REQUIRE(variableNames.size() == 1);
|
||||
REQUIRE(*(variableNames.begin()) == "MySceneVariableInLayout1");
|
||||
}
|
||||
|
||||
SECTION("Can find scene variables in the right external layouts") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareVariableExtension(project, platform);
|
||||
|
||||
auto &layout1 = project.InsertNewLayout("Layout1", 0);
|
||||
auto &externalEvents1 =
|
||||
project.InsertNewExternalEvents("ExternalEvents1", 0);
|
||||
externalEvents1.GetEvents().InsertEvent(
|
||||
UseSceneVariable("MySceneVariableInExternalEvents1"));
|
||||
UseExternalEvents(layout1, externalEvents1);
|
||||
|
||||
auto &layout2 = project.InsertNewLayout("Layout2", 0);
|
||||
auto &externalEvents2 =
|
||||
project.InsertNewExternalEvents("ExternalEvents2", 0);
|
||||
externalEvents2.GetEvents().InsertEvent(
|
||||
UseSceneVariable("MySceneVariableInExternalEvents2"));
|
||||
UseExternalEvents(layout2, externalEvents2);
|
||||
|
||||
auto variableNames = gd::EventsVariablesFinder::FindAllLayoutVariables(
|
||||
platform, project, layout1);
|
||||
|
||||
REQUIRE(variableNames.size() == 1);
|
||||
REQUIRE(*(variableNames.begin()) == "MySceneVariableInExternalEvents1");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("EventsVariablesFinder (FindAllObjectVariables)", "[common]") {
|
||||
SECTION("Can find object variables in scenes") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareVariableExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
auto &object = layout.InsertNewObject(project, "", "MyObject", 0);
|
||||
layout.GetEvents().InsertEvent(
|
||||
UseObjectVariable("MyObject", "MyObjectVariable"));
|
||||
|
||||
auto variableNames = gd::EventsVariablesFinder::FindAllObjectVariables(
|
||||
platform, project, layout, object);
|
||||
|
||||
REQUIRE(variableNames.size() == 1);
|
||||
REQUIRE(*(variableNames.begin()) == "MyObjectVariable");
|
||||
}
|
||||
|
||||
SECTION("Can find object variables in scene expressions") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareVariableExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
auto &object = layout.InsertNewObject(project, "", "MyObject", 0);
|
||||
layout.GetEvents().InsertEvent(
|
||||
UseObjectVariableInExpression("MyObject", "MyObjectVariable"));
|
||||
|
||||
auto variableNames = gd::EventsVariablesFinder::FindAllObjectVariables(
|
||||
platform, project, layout, object);
|
||||
|
||||
REQUIRE(variableNames.size() == 1);
|
||||
REQUIRE(*(variableNames.begin()) == "MyObjectVariable");
|
||||
}
|
||||
|
||||
SECTION("Can find object variables in external layouts") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareVariableExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
auto &object = layout.InsertNewObject(project, "", "MyObject", 0);
|
||||
auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0);
|
||||
externalEvents.GetEvents().InsertEvent(
|
||||
UseObjectVariable("MyObject", "MyObjectVariable"));
|
||||
UseExternalEvents(layout, externalEvents);
|
||||
|
||||
auto variableNames = gd::EventsVariablesFinder::FindAllObjectVariables(
|
||||
platform, project, layout, object);
|
||||
|
||||
REQUIRE(variableNames.size() == 1);
|
||||
REQUIRE(*(variableNames.begin()) == "MyObjectVariable");
|
||||
}
|
||||
|
||||
SECTION("Can find object variables in scenes for the right object") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareVariableExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
auto &object1 = layout.InsertNewObject(project, "", "MyObject1", 0);
|
||||
auto &object2 = layout.InsertNewObject(project, "", "MyObject2", 0);
|
||||
layout.GetEvents().InsertEvent(
|
||||
UseObjectVariable("MyObject1", "MyObjectVariable1"));
|
||||
layout.GetEvents().InsertEvent(
|
||||
UseObjectVariable("MyObject2", "MyObjectVariable2"));
|
||||
|
||||
auto variableNames = gd::EventsVariablesFinder::FindAllObjectVariables(
|
||||
platform, project, layout, object1);
|
||||
|
||||
REQUIRE(variableNames.size() == 1);
|
||||
REQUIRE(*(variableNames.begin()) == "MyObjectVariable1");
|
||||
}
|
||||
|
||||
SECTION(
|
||||
"Can find object variables in external layouts for the right object") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
DeclareVariableExtension(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("Layout1", 0);
|
||||
auto &object1 = layout.InsertNewObject(project, "", "MyObject1", 0);
|
||||
auto &object2 = layout.InsertNewObject(project, "", "MyObject2", 0);
|
||||
auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0);
|
||||
externalEvents.GetEvents().InsertEvent(
|
||||
UseObjectVariable("MyObject1", "MyObjectVariable1"));
|
||||
externalEvents.GetEvents().InsertEvent(
|
||||
UseObjectVariable("MyObject2", "MyObjectVariable2"));
|
||||
UseExternalEvents(layout, externalEvents);
|
||||
|
||||
auto variableNames = gd::EventsVariablesFinder::FindAllObjectVariables(
|
||||
platform, project, layout, object1);
|
||||
|
||||
REQUIRE(variableNames.size() == 1);
|
||||
REQUIRE(*(variableNames.begin()) == "MyObjectVariable1");
|
||||
}
|
||||
}
|
@@ -12,11 +12,12 @@
|
||||
#include "GDCore/Events/Event.h"
|
||||
#include "GDCore/Events/EventsList.h"
|
||||
#include "GDCore/Events/Serialization.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteObject.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Project/CustomObjectConfiguration.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteObject.h"
|
||||
#include "GDCore/Project/ObjectsContainer.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/Variable.h"
|
||||
@@ -29,14 +30,7 @@ using namespace gd;
|
||||
|
||||
namespace {
|
||||
|
||||
void SetupProject(gd::Project &project, gd::Platform &platform) {
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
|
||||
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
|
||||
gd::Object &object =
|
||||
layout.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
|
||||
|
||||
auto &configuration = object.GetConfiguration();
|
||||
void SetupSpriteConfiguration(gd::ObjectConfiguration &configuration) {
|
||||
auto *spriteConfiguration = dynamic_cast<gd::SpriteObject *>(&configuration);
|
||||
REQUIRE(spriteConfiguration != nullptr);
|
||||
gd::Animation animation;
|
||||
@@ -44,11 +38,30 @@ void SetupProject(gd::Project &project, gd::Platform &platform) {
|
||||
spriteConfiguration->AddAnimation(animation);
|
||||
};
|
||||
|
||||
void CheckSpriteConfiguration(
|
||||
SerializerElement &objectContainerElement) {
|
||||
gd::Object &SetupProjectWithSprite(gd::Project &project,
|
||||
gd::Platform &platform) {
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
|
||||
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
|
||||
gd::Object &object =
|
||||
layout.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
|
||||
SetupSpriteConfiguration(object.GetConfiguration());
|
||||
|
||||
return object;
|
||||
};
|
||||
|
||||
void CheckSpriteConfigurationInElement(SerializerElement &projectElement) {
|
||||
void CheckSpriteConfigurationInObjectElement(SerializerElement &objectElement) {
|
||||
REQUIRE(objectElement.HasChild("animations"));
|
||||
auto &animationsElement = objectElement.GetChild("animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation");
|
||||
REQUIRE(animationsElement.GetChildrenCount() == 1);
|
||||
auto &animationElement = animationsElement.GetChild(0);
|
||||
|
||||
REQUIRE(animationElement.GetStringAttribute("name") == "Idle");
|
||||
};
|
||||
|
||||
void CheckSpriteConfigurationInProjectElement(
|
||||
SerializerElement &projectElement) {
|
||||
auto &layoutsElement = projectElement.GetChild("layouts");
|
||||
layoutsElement.ConsiderAsArrayOf("layout");
|
||||
REQUIRE(layoutsElement.GetChildrenCount() == 1);
|
||||
@@ -64,24 +77,10 @@ void CheckSpriteConfigurationInElement(SerializerElement &projectElement) {
|
||||
|
||||
REQUIRE(objectElement.GetStringAttribute("name") == "MyObject");
|
||||
REQUIRE(objectElement.GetStringAttribute("type") == "MyExtension::Sprite");
|
||||
|
||||
REQUIRE(objectElement.HasChild("animations"));
|
||||
auto &animationsElement = objectElement.GetChild("animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation");
|
||||
REQUIRE(animationsElement.GetChildrenCount() == 1);
|
||||
auto &animationElement = animationsElement.GetChild(0);
|
||||
|
||||
REQUIRE(animationElement.GetStringAttribute("name") ==
|
||||
"Idle");
|
||||
CheckSpriteConfigurationInObjectElement(objectElement);
|
||||
};
|
||||
|
||||
void CheckSpriteConfiguration(gd::Project &project) {
|
||||
auto &layout = project.GetLayout("Scene");
|
||||
auto &object = layout.GetObject("MyObject");
|
||||
REQUIRE(object.GetName() == "MyObject");
|
||||
REQUIRE(object.GetType() == "MyExtension::Sprite");
|
||||
|
||||
auto &configuration = object.GetConfiguration();
|
||||
void CheckSpriteConfiguration(gd::ObjectConfiguration &configuration) {
|
||||
auto *spriteConfiguration = dynamic_cast<gd::SpriteObject *>(&configuration);
|
||||
REQUIRE(spriteConfiguration);
|
||||
REQUIRE(spriteConfiguration->GetAnimationsCount() == 1);
|
||||
@@ -89,6 +88,88 @@ void CheckSpriteConfiguration(gd::Project &project) {
|
||||
auto &animation = spriteConfiguration->GetAnimation(0);
|
||||
REQUIRE(animation.GetName() == "Idle");
|
||||
};
|
||||
|
||||
void CheckSpriteConfiguration(gd::Object &object) {
|
||||
REQUIRE(object.GetName() == "MyObject");
|
||||
REQUIRE(object.GetType() == "MyExtension::Sprite");
|
||||
|
||||
CheckSpriteConfiguration(object.GetConfiguration());
|
||||
};
|
||||
|
||||
void CheckSpriteConfiguration(gd::Project &project) {
|
||||
auto &layout = project.GetLayout("Scene");
|
||||
auto &object = layout.GetObject("MyObject");
|
||||
CheckSpriteConfiguration(object);
|
||||
};
|
||||
|
||||
gd::Object &SetupProjectWithCustomObject(gd::Project &project,
|
||||
gd::Platform &platform) {
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
|
||||
auto &eventsExtension =
|
||||
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
|
||||
auto &eventsBasedObject = eventsExtension.GetEventsBasedObjects().InsertNew(
|
||||
"MyEventsBasedObject", 0);
|
||||
eventsBasedObject.SetFullName("My events based object");
|
||||
eventsBasedObject.SetDescription("An events based object for test");
|
||||
eventsBasedObject.InsertNewObject(project, "MyExtension::Sprite", "MyChild",
|
||||
0);
|
||||
|
||||
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
|
||||
gd::Object &object = layout.InsertNewObject(
|
||||
project, "MyEventsExtension::MyEventsBasedObject", "MyObject", 0);
|
||||
auto &configuration = object.GetConfiguration();
|
||||
auto *customObjectConfiguration =
|
||||
dynamic_cast<gd::CustomObjectConfiguration *>(&configuration);
|
||||
auto &spriteConfiguration =
|
||||
customObjectConfiguration->GetChildObjectConfiguration("MyChild");
|
||||
SetupSpriteConfiguration(spriteConfiguration);
|
||||
|
||||
return object;
|
||||
};
|
||||
|
||||
void CheckCustomObjectConfigurationInProjectElement(
|
||||
SerializerElement &projectElement) {
|
||||
auto &layoutsElement = projectElement.GetChild("layouts");
|
||||
layoutsElement.ConsiderAsArrayOf("layout");
|
||||
REQUIRE(layoutsElement.GetChildrenCount() == 1);
|
||||
auto &layoutElement = layoutsElement.GetChild(0);
|
||||
|
||||
REQUIRE(layoutElement.GetStringAttribute("name") == "Scene");
|
||||
REQUIRE(layoutElement.HasChild("objects"));
|
||||
|
||||
auto &objectsElement = layoutElement.GetChild("objects");
|
||||
objectsElement.ConsiderAsArrayOf("object");
|
||||
REQUIRE(objectsElement.GetChildrenCount() == 1);
|
||||
auto &objectElement = objectsElement.GetChild(0);
|
||||
|
||||
REQUIRE(objectElement.GetStringAttribute("name") == "MyObject");
|
||||
REQUIRE(objectElement.GetStringAttribute("type") ==
|
||||
"MyEventsExtension::MyEventsBasedObject");
|
||||
auto &childrenContentElement = objectElement.GetChild("childrenContent");
|
||||
|
||||
REQUIRE(childrenContentElement.HasChild("MyChild"));
|
||||
auto &childElement = childrenContentElement.GetChild("MyChild");
|
||||
CheckSpriteConfigurationInObjectElement(childElement);
|
||||
};
|
||||
|
||||
void CheckCustomObjectConfiguration(gd::Object &object) {
|
||||
REQUIRE(object.GetName() == "MyObject");
|
||||
REQUIRE(object.GetType() == "MyEventsExtension::MyEventsBasedObject");
|
||||
|
||||
auto &configuration = object.GetConfiguration();
|
||||
auto *customObjectConfiguration =
|
||||
dynamic_cast<gd::CustomObjectConfiguration *>(&configuration);
|
||||
auto &spriteConfiguration =
|
||||
customObjectConfiguration->GetChildObjectConfiguration("MyChild");
|
||||
CheckSpriteConfiguration(spriteConfiguration);
|
||||
};
|
||||
|
||||
void CheckCustomObjectConfiguration(gd::Project &project) {
|
||||
auto &layout = project.GetLayout("Scene");
|
||||
auto &object = layout.GetObject("MyObject");
|
||||
CheckCustomObjectConfiguration(object);
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("ObjectSerialization", "[common]") {
|
||||
@@ -96,16 +177,52 @@ TEST_CASE("ObjectSerialization", "[common]") {
|
||||
SECTION("Save and load a project with a sprite configuration") {
|
||||
gd::Platform platform;
|
||||
gd::Project writtenProject;
|
||||
SetupProject(writtenProject, platform);
|
||||
SetupProjectWithSprite(writtenProject, platform);
|
||||
CheckSpriteConfiguration(writtenProject);
|
||||
|
||||
SerializerElement projectElement;
|
||||
writtenProject.SerializeTo(projectElement);
|
||||
CheckSpriteConfigurationInElement(projectElement);
|
||||
CheckSpriteConfigurationInProjectElement(projectElement);
|
||||
|
||||
gd::Project readProject;
|
||||
readProject.AddPlatform(platform);
|
||||
readProject.UnserializeFrom(projectElement);
|
||||
CheckSpriteConfiguration(readProject);
|
||||
}
|
||||
|
||||
SECTION("Clone a sprite object") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
auto &object = SetupProjectWithSprite(project, platform);
|
||||
CheckSpriteConfiguration(object);
|
||||
|
||||
auto clonedObject = object.Clone();
|
||||
CheckSpriteConfiguration(*(clonedObject.get()));
|
||||
}
|
||||
|
||||
SECTION("Save and load a project with a custom object configuration") {
|
||||
gd::Platform platform;
|
||||
gd::Project writtenProject;
|
||||
SetupProjectWithCustomObject(writtenProject, platform);
|
||||
CheckCustomObjectConfiguration(writtenProject);
|
||||
|
||||
SerializerElement projectElement;
|
||||
writtenProject.SerializeTo(projectElement);
|
||||
CheckCustomObjectConfigurationInProjectElement(projectElement);
|
||||
|
||||
gd::Project readProject;
|
||||
readProject.AddPlatform(platform);
|
||||
readProject.UnserializeFrom(projectElement);
|
||||
CheckCustomObjectConfiguration(readProject);
|
||||
}
|
||||
|
||||
SECTION("Clone a custom object") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
auto &object = SetupProjectWithCustomObject(project, platform);
|
||||
CheckCustomObjectConfiguration(object);
|
||||
|
||||
auto clonedObject = object.Clone();
|
||||
CheckCustomObjectConfiguration(*(clonedObject.get()));
|
||||
}
|
||||
}
|
@@ -30,6 +30,8 @@
|
||||
#include "GDCore/Project/Variable.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
// TODO EBO Add a test where a child is removed form the EventsBasedObject
|
||||
// and check the configuration still gives access to other child configuration.
|
||||
namespace {
|
||||
|
||||
const gd::StandardEvent &EnsureStandardEvent(const gd::BaseEvent &baseEvent) {
|
||||
|
@@ -28,7 +28,7 @@ module.exports = {
|
||||
'Arthur Pacaud (arthuro555)',
|
||||
'MIT'
|
||||
)
|
||||
.setCategory('Device');
|
||||
.setCategory('User interface');
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata(_('Advanced window management'))
|
||||
.setIcon('res/actions/window24.png');
|
||||
|
@@ -105,9 +105,9 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
|
||||
|
||||
properties[("useLegacyBottomAndRightAnchors")]
|
||||
.SetLabel(_(
|
||||
"Stretch object when anchoring right or bottom ledge (deprecated, "
|
||||
"it's recommended to let this unchecked and anchor both sides if you "
|
||||
"want Sprite to stretch instead.)"))
|
||||
"Stretch object when anchoring right or bottom edge (deprecated, "
|
||||
"it's recommended to leave this unchecked and anchor both sides if "
|
||||
"you want Sprite to stretch instead.)"))
|
||||
.SetGroup(_("Deprecated options (advanced)"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute(
|
||||
"useLegacyBottomAndRightAnchors", true)
|
||||
|
@@ -17,6 +17,7 @@ void DeclareAnchorBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
_("Anchor objects to the window's bounds."),
|
||||
"Victor Levasseur",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("User interface")
|
||||
.SetExtensionHelpPath("/behaviors/anchor");
|
||||
|
||||
gd::BehaviorMetadata& aut = extension.AddBehavior(
|
||||
|
@@ -17,8 +17,12 @@ namespace gdjs {
|
||||
_bottomEdgeDistance: number = 0;
|
||||
_useLegacyBottomAndRightAnchors: boolean = false;
|
||||
|
||||
constructor(runtimeScene, behaviorData, owner) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
behaviorData,
|
||||
owner: gdjs.RuntimeObject
|
||||
) {
|
||||
super(instanceContainer, behaviorData, owner);
|
||||
this._relativeToOriginalWindowSize = !!behaviorData.relativeToOriginalWindowSize;
|
||||
this._leftEdgeAnchor = behaviorData.leftEdgeAnchor;
|
||||
this._rightEdgeAnchor = behaviorData.rightEdgeAnchor;
|
||||
@@ -65,11 +69,15 @@ namespace gdjs {
|
||||
this._invalidDistances = true;
|
||||
}
|
||||
|
||||
doStepPreEvents(runtimeScene) {
|
||||
const game = runtimeScene.getGame();
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
const workingPoint: FloatPoint = gdjs.staticArray(
|
||||
gdjs.AnchorRuntimeBehavior.prototype.doStepPreEvents
|
||||
) as FloatPoint;
|
||||
// TODO EBO Make it work with event based objects or hide this behavior for them.
|
||||
const game = instanceContainer.getGame();
|
||||
let rendererWidth = game.getGameResolutionWidth();
|
||||
let rendererHeight = game.getGameResolutionHeight();
|
||||
const layer = runtimeScene.getLayer(this.owner.getLayer());
|
||||
const layer = instanceContainer.getLayer(this.owner.getLayer());
|
||||
if (this._invalidDistances) {
|
||||
if (this._relativeToOriginalWindowSize) {
|
||||
rendererWidth = game.getOriginalWidth();
|
||||
@@ -79,7 +87,9 @@ namespace gdjs {
|
||||
//Calculate the distances from the window's bounds.
|
||||
const topLeftPixel = layer.convertCoords(
|
||||
this.owner.getDrawableX(),
|
||||
this.owner.getDrawableY()
|
||||
this.owner.getDrawableY(),
|
||||
0,
|
||||
workingPoint
|
||||
);
|
||||
|
||||
//Left edge
|
||||
@@ -125,9 +135,12 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
}
|
||||
// It's fine to reuse workingPoint as topLeftPixel is no longer used.
|
||||
const bottomRightPixel = layer.convertCoords(
|
||||
this.owner.getDrawableX() + this.owner.getWidth(),
|
||||
this.owner.getDrawableY() + this.owner.getHeight()
|
||||
this.owner.getDrawableY() + this.owner.getHeight(),
|
||||
0,
|
||||
workingPoint
|
||||
);
|
||||
|
||||
//Right edge
|
||||
@@ -268,11 +281,24 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
}
|
||||
const topLeftCoord = layer.convertInverseCoords(leftPixel, topPixel);
|
||||
// It's fine to reuse workingPoint as topLeftPixel is no longer used.
|
||||
const topLeftCoord = layer.convertInverseCoords(
|
||||
leftPixel,
|
||||
topPixel,
|
||||
0,
|
||||
workingPoint
|
||||
);
|
||||
const left = topLeftCoord[0];
|
||||
const top = topLeftCoord[1];
|
||||
|
||||
const bottomRightCoord = layer.convertInverseCoords(
|
||||
rightPixel,
|
||||
bottomPixel
|
||||
bottomPixel,
|
||||
0,
|
||||
workingPoint
|
||||
);
|
||||
const right = bottomRightCoord[0];
|
||||
const bottom = bottomRightCoord[1];
|
||||
|
||||
// Compatibility with GD <= 5.0.133
|
||||
if (this._useLegacyBottomAndRightAnchors) {
|
||||
@@ -281,25 +307,25 @@ namespace gdjs {
|
||||
this._rightEdgeAnchor !==
|
||||
AnchorRuntimeBehavior.HorizontalAnchor.NONE
|
||||
) {
|
||||
this.owner.setWidth(bottomRightCoord[0] - topLeftCoord[0]);
|
||||
this.owner.setWidth(right - left);
|
||||
}
|
||||
if (
|
||||
this._bottomEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
|
||||
) {
|
||||
this.owner.setHeight(bottomRightCoord[1] - topLeftCoord[1]);
|
||||
this.owner.setHeight(bottom - top);
|
||||
}
|
||||
if (
|
||||
this._leftEdgeAnchor !== AnchorRuntimeBehavior.HorizontalAnchor.NONE
|
||||
) {
|
||||
this.owner.setX(
|
||||
topLeftCoord[0] + this.owner.getX() - this.owner.getDrawableX()
|
||||
left + this.owner.getX() - this.owner.getDrawableX()
|
||||
);
|
||||
}
|
||||
if (
|
||||
this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
|
||||
) {
|
||||
this.owner.setY(
|
||||
topLeftCoord[1] + this.owner.getY() - this.owner.getDrawableY()
|
||||
top + this.owner.getY() - this.owner.getDrawableY()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -311,15 +337,15 @@ namespace gdjs {
|
||||
AnchorRuntimeBehavior.HorizontalAnchor.NONE &&
|
||||
this._leftEdgeAnchor !== AnchorRuntimeBehavior.HorizontalAnchor.NONE
|
||||
) {
|
||||
this.owner.setWidth(bottomRightCoord[0] - topLeftCoord[0]);
|
||||
this.owner.setX(topLeftCoord[0]);
|
||||
this.owner.setWidth(right - left);
|
||||
this.owner.setX(left);
|
||||
} else {
|
||||
if (
|
||||
this._leftEdgeAnchor !==
|
||||
AnchorRuntimeBehavior.HorizontalAnchor.NONE
|
||||
) {
|
||||
this.owner.setX(
|
||||
topLeftCoord[0] + this.owner.getX() - this.owner.getDrawableX()
|
||||
left + this.owner.getX() - this.owner.getDrawableX()
|
||||
);
|
||||
}
|
||||
if (
|
||||
@@ -327,7 +353,7 @@ namespace gdjs {
|
||||
AnchorRuntimeBehavior.HorizontalAnchor.NONE
|
||||
) {
|
||||
this.owner.setX(
|
||||
bottomRightCoord[0] +
|
||||
right +
|
||||
this.owner.getX() -
|
||||
this.owner.getDrawableX() -
|
||||
this.owner.getWidth()
|
||||
@@ -340,14 +366,14 @@ namespace gdjs {
|
||||
AnchorRuntimeBehavior.VerticalAnchor.NONE &&
|
||||
this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
|
||||
) {
|
||||
this.owner.setHeight(bottomRightCoord[1] - topLeftCoord[1]);
|
||||
this.owner.setY(topLeftCoord[1]);
|
||||
this.owner.setHeight(bottom - top);
|
||||
this.owner.setY(top);
|
||||
} else {
|
||||
if (
|
||||
this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
|
||||
) {
|
||||
this.owner.setY(
|
||||
topLeftCoord[1] + this.owner.getY() - this.owner.getDrawableY()
|
||||
top + this.owner.getY() - this.owner.getDrawableY()
|
||||
);
|
||||
}
|
||||
if (
|
||||
@@ -355,7 +381,7 @@ namespace gdjs {
|
||||
AnchorRuntimeBehavior.VerticalAnchor.NONE
|
||||
) {
|
||||
this.owner.setY(
|
||||
bottomRightCoord[1] +
|
||||
bottom +
|
||||
this.owner.getY() -
|
||||
this.owner.getDrawableY() -
|
||||
this.owner.getHeight()
|
||||
@@ -366,7 +392,7 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
doStepPostEvents(runtimeScene) {}
|
||||
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
|
||||
|
||||
static HorizontalAnchor = {
|
||||
NONE: 0,
|
||||
|
@@ -33,7 +33,10 @@ module.exports = {
|
||||
'Todor Imreorov',
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/objects/bbtext');
|
||||
.setExtensionHelpPath('/objects/bbtext')
|
||||
.setCategory('User interface');
|
||||
extension.addInstructionOrExpressionGroupMetadata(_("BBCode Text Object"))
|
||||
.setIcon("JsPlatform/Extensions/bbcode32.png");
|
||||
|
||||
var objectBBText = new gd.ObjectJsImplementation();
|
||||
// $FlowExpectedError
|
||||
@@ -168,7 +171,7 @@ module.exports = {
|
||||
.addIncludeFile(
|
||||
'Extensions/BBText/pixi-multistyle-text/dist/pixi-multistyle-text.umd.js'
|
||||
)
|
||||
.setCategoryFullName(_('Texts'));
|
||||
.setCategoryFullName(_('User interface'));
|
||||
|
||||
/**
|
||||
* Utility function to add both a setter and a getter to a property from a list.
|
||||
@@ -498,7 +501,7 @@ module.exports = {
|
||||
RenderedBBTextInstance.getThumbnail = function (
|
||||
project,
|
||||
resourcesLoader,
|
||||
object
|
||||
objectConfiguration
|
||||
) {
|
||||
return 'JsPlatform/Extensions/bbcode24.png';
|
||||
};
|
||||
|
@@ -10,11 +10,11 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* @param runtimeObject The object to render
|
||||
* @param runtimeScene The gdjs.RuntimeScene in which the object is
|
||||
* @param instanceContainer The gdjs.RuntimeInstanceContainer in which the object is
|
||||
*/
|
||||
constructor(
|
||||
runtimeObject: gdjs.BBTextRuntimeObject,
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
this._object = runtimeObject;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace gdjs {
|
||||
if (this._pixiObject === undefined) {
|
||||
this._pixiObject = new MultiStyleText(runtimeObject._text, {
|
||||
default: {
|
||||
fontFamily: runtimeScene
|
||||
fontFamily: instanceContainer
|
||||
.getGame()
|
||||
.getFontManager()
|
||||
.getFontFamily(runtimeObject._fontFamily),
|
||||
@@ -44,7 +44,7 @@ namespace gdjs {
|
||||
this.updateFontFamily();
|
||||
this.updateFontSize();
|
||||
}
|
||||
runtimeScene
|
||||
instanceContainer
|
||||
.getLayer('')
|
||||
.getRenderer()
|
||||
.addRendererObject(this._pixiObject, runtimeObject.getZOrder());
|
||||
@@ -95,7 +95,8 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateFontFamily(): void {
|
||||
this._pixiObject.textStyles.default.fontFamily = this._object._runtimeScene
|
||||
this._pixiObject.textStyles.default.fontFamily = this._object
|
||||
.getInstanceContainer()
|
||||
.getGame()
|
||||
.getFontManager()
|
||||
.getFontFamily(this._object._fontFamily);
|
||||
|
@@ -48,11 +48,14 @@ namespace gdjs {
|
||||
hidden: boolean;
|
||||
|
||||
/**
|
||||
* @param runtimeScene The scene the object belongs to.
|
||||
* @param instanceContainer The container the object belongs to.
|
||||
* @param objectData The object data used to initialize the object
|
||||
*/
|
||||
constructor(runtimeScene: gdjs.RuntimeScene, objectData: BBTextObjectData) {
|
||||
super(runtimeScene, objectData);
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
objectData: BBTextObjectData
|
||||
) {
|
||||
super(instanceContainer, objectData);
|
||||
// @ts-ignore - parseFloat should not be required, but GDevelop 5.0 beta 92 and below were storing it as a string.
|
||||
this._opacity = parseFloat(objectData.content.opacity);
|
||||
this._text = objectData.content.text;
|
||||
@@ -62,7 +65,10 @@ namespace gdjs {
|
||||
this._fontSize = parseFloat(objectData.content.fontSize);
|
||||
this._wordWrap = objectData.content.wordWrap;
|
||||
this._align = objectData.content.align;
|
||||
this._renderer = new gdjs.BBTextRuntimeObjectRenderer(this, runtimeScene);
|
||||
this._renderer = new gdjs.BBTextRuntimeObjectRenderer(
|
||||
this,
|
||||
instanceContainer
|
||||
);
|
||||
this.hidden = !objectData.content.visible;
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
@@ -122,8 +128,8 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
onDestroyFromScene(runtimeScene): void {
|
||||
super.onDestroyFromScene(runtimeScene);
|
||||
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
super.onDestroyFromScene(instanceContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,7 +245,7 @@ namespace gdjs {
|
||||
|
||||
this._wrappingWidth = width;
|
||||
this._renderer.updateWrappingWidth();
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,7 +260,7 @@ namespace gdjs {
|
||||
|
||||
this._wordWrap = wordWrap;
|
||||
this._renderer.updateWordWrap();
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
getWordWrap() {
|
||||
|
@@ -35,7 +35,10 @@ module.exports = {
|
||||
'Aurélien Vivet',
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/objects/bitmap_text');
|
||||
.setExtensionHelpPath('/objects/bitmap_text')
|
||||
.setCategory('User interface');
|
||||
extension.addInstructionOrExpressionGroupMetadata(_("Bitmap Text"))
|
||||
.setIcon("JsPlatform/Extensions/bitmapfont32.png");
|
||||
|
||||
const bitmapTextObject = new gd.ObjectJsImplementation();
|
||||
// $FlowExpectedError
|
||||
@@ -171,7 +174,7 @@ module.exports = {
|
||||
.addIncludeFile(
|
||||
'Extensions/BitmapText/bitmaptextruntimeobject-pixi-renderer.js'
|
||||
)
|
||||
.setCategoryFullName(_('Texts'));
|
||||
.setCategoryFullName(_('User interface'));
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
@@ -627,7 +630,7 @@ module.exports = {
|
||||
RenderedBitmapTextInstance.getThumbnail = function (
|
||||
project,
|
||||
resourcesLoader,
|
||||
object
|
||||
objectConfiguration
|
||||
) {
|
||||
return 'JsPlatform/Extensions/bitmapfont24.png';
|
||||
};
|
||||
|
@@ -10,16 +10,16 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* @param runtimeObject The object to render
|
||||
* @param runtimeScene The gdjs.RuntimeScene in which the object is
|
||||
* @param instanceContainer The container in which the object is
|
||||
*/
|
||||
constructor(
|
||||
runtimeObject: gdjs.BitmapTextRuntimeObject,
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
this._object = runtimeObject;
|
||||
|
||||
// Obtain the bitmap font to use in the object.
|
||||
const bitmapFont = runtimeScene
|
||||
const bitmapFont = instanceContainer
|
||||
.getGame()
|
||||
.getBitmapFontManager()
|
||||
.obtainBitmapFont(
|
||||
@@ -32,7 +32,7 @@ namespace gdjs {
|
||||
});
|
||||
|
||||
// Set the object on the scene
|
||||
runtimeScene
|
||||
instanceContainer
|
||||
.getLayer('')
|
||||
.getRenderer()
|
||||
.addRendererObject(this._pixiObject, runtimeObject.getZOrder());
|
||||
@@ -59,7 +59,8 @@ namespace gdjs {
|
||||
|
||||
onDestroy() {
|
||||
// Mark the font from the object as not used anymore.
|
||||
this._object._runtimeScene
|
||||
this._object
|
||||
.getInstanceContainer()
|
||||
.getGame()
|
||||
.getBitmapFontManager()
|
||||
.releaseBitmapFont(this._pixiObject.fontName);
|
||||
@@ -73,7 +74,8 @@ namespace gdjs {
|
||||
|
||||
updateFont(): void {
|
||||
// Get the new bitmap font to use
|
||||
const bitmapFont = this._object._runtimeScene
|
||||
const bitmapFont = this._object
|
||||
.getInstanceContainer()
|
||||
.getGame()
|
||||
.getBitmapFontManager()
|
||||
.obtainBitmapFont(
|
||||
@@ -82,7 +84,8 @@ namespace gdjs {
|
||||
);
|
||||
|
||||
// Mark the old font as not used anymore
|
||||
this._object._runtimeScene
|
||||
this._object
|
||||
.getInstanceContainer()
|
||||
.getGame()
|
||||
.getBitmapFontManager()
|
||||
.releaseBitmapFont(this._pixiObject.fontName);
|
||||
|
@@ -50,14 +50,14 @@ namespace gdjs {
|
||||
_renderer: gdjs.BitmapTextRuntimeObjectPixiRenderer;
|
||||
|
||||
/**
|
||||
* @param runtimeScene The scene the object belongs to.
|
||||
* @param instanceContainer The container the object belongs to.
|
||||
* @param objectData The object data used to initialize the object
|
||||
*/
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
objectData: BitmapTextObjectData
|
||||
) {
|
||||
super(runtimeScene, objectData);
|
||||
super(instanceContainer, objectData);
|
||||
|
||||
this._opacity = objectData.content.opacity;
|
||||
this._text = objectData.content.text;
|
||||
@@ -73,7 +73,7 @@ namespace gdjs {
|
||||
|
||||
this._renderer = new gdjs.BitmapTextRuntimeObjectRenderer(
|
||||
this,
|
||||
runtimeScene
|
||||
instanceContainer
|
||||
);
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
@@ -137,8 +137,8 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
onDestroyFromScene(runtimeScene: gdjs.RuntimeScene): void {
|
||||
super.onDestroyFromScene(runtimeScene);
|
||||
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
super.onDestroyFromScene(instanceContainer);
|
||||
this._renderer.onDestroy();
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace gdjs {
|
||||
setText(text: string): void {
|
||||
this._text = text;
|
||||
this._renderer.updateTextContent();
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,7 +170,7 @@ namespace gdjs {
|
||||
setScale(scale: float): void {
|
||||
this._scale = scale;
|
||||
this._renderer.updateScale();
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
getScale(): float {
|
||||
@@ -276,7 +276,7 @@ namespace gdjs {
|
||||
setWrappingWidth(width: float): void {
|
||||
this._wrappingWidth = width;
|
||||
this._renderer.updateWrappingWidth();
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,7 +289,7 @@ namespace gdjs {
|
||||
setWordWrap(wordWrap: boolean): void {
|
||||
this._wordWrap = wordWrap;
|
||||
this._renderer.updateWrappingWidth();
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
getWordWrap(): boolean {
|
||||
|
@@ -7,10 +7,12 @@ namespace gdjs {
|
||||
export namespace debuggerTools {
|
||||
/**
|
||||
* Stop the game execution.
|
||||
* @param runtimeScene - The current scene.
|
||||
* @param instanceContainer - The current container.
|
||||
*/
|
||||
export const pause = function (runtimeScene: gdjs.RuntimeScene) {
|
||||
runtimeScene.getGame().pause(true);
|
||||
export const pause = function (
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
instanceContainer.getGame().pause(true);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -29,20 +31,20 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Enable or disable the debug draw.
|
||||
* @param runtimeScene - The current scene.
|
||||
* @param instanceContainer - The current container.
|
||||
* @param enableDebugDraw - true to enable the debug draw, false to disable it.
|
||||
* @param showHiddenInstances - true to apply the debug draw to hidden objects.
|
||||
* @param showPointsNames - true to show point names.
|
||||
* @param showCustomPoints - true to show custom points of Sprite objects.
|
||||
*/
|
||||
export const enableDebugDraw = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
enableDebugDraw: boolean,
|
||||
showHiddenInstances: boolean,
|
||||
showPointsNames: boolean,
|
||||
showCustomPoints: boolean
|
||||
) {
|
||||
runtimeScene.enableDebugDraw(
|
||||
instanceContainer.enableDebugDraw(
|
||||
enableDebugDraw,
|
||||
showHiddenInstances,
|
||||
showPointsNames,
|
||||
|
@@ -20,6 +20,7 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"or other short-lived objects."),
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("Game mechanic")
|
||||
.SetExtensionHelpPath("/behaviors/destroyoutside");
|
||||
|
||||
gd::BehaviorMetadata& aut =
|
||||
|
@@ -11,8 +11,12 @@ namespace gdjs {
|
||||
export class DestroyOutsideRuntimeBehavior extends gdjs.RuntimeBehavior {
|
||||
_extraBorder: any;
|
||||
|
||||
constructor(runtimeScene, behaviorData, owner) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
behaviorData,
|
||||
owner
|
||||
) {
|
||||
super(instanceContainer, behaviorData, owner);
|
||||
this._extraBorder = behaviorData.extraBorder || 0;
|
||||
}
|
||||
|
||||
@@ -23,14 +27,14 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
doStepPostEvents(runtimeScene) {
|
||||
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
// 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).
|
||||
const ow = this.owner.getWidth();
|
||||
const oh = this.owner.getHeight();
|
||||
const ocx = this.owner.getDrawableX() + this.owner.getCenterX();
|
||||
const ocy = this.owner.getDrawableY() + this.owner.getCenterY();
|
||||
const layer = runtimeScene.getLayer(this.owner.getLayer());
|
||||
const layer = instanceContainer.getLayer(this.owner.getLayer());
|
||||
const boundingCircleRadius = Math.sqrt(ow * ow + oh * oh) / 2.0;
|
||||
if (
|
||||
ocx + boundingCircleRadius + this._extraBorder <
|
||||
@@ -43,7 +47,7 @@ namespace gdjs {
|
||||
layer.getCameraY() + layer.getCameraHeight() / 2
|
||||
) {
|
||||
//We are outside the camera area.
|
||||
this.owner.deleteFromScene(runtimeScene);
|
||||
this.owner.deleteFromScene(instanceContainer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -31,7 +31,7 @@ module.exports = {
|
||||
"Matthias Meike",
|
||||
"Open source (MIT License)"
|
||||
).setExtensionHelpPath("/all-features/device-sensors")
|
||||
.setCategory('Device');
|
||||
.setCategory('Input');
|
||||
extension.addInstructionOrExpressionGroupMetadata(_("Device sensors"))
|
||||
.setIcon("JsPlatform/Extensions/orientation_active32.png");
|
||||
|
||||
|
@@ -35,7 +35,7 @@ module.exports = {
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/all-features/device-vibration')
|
||||
.setCategory('Device');
|
||||
.setCategory('User interface');
|
||||
extension.addInstructionOrExpressionGroupMetadata(_("Device vibration"))
|
||||
.setIcon("JsPlatform/Extensions/vibration_start32.png");
|
||||
|
||||
|
@@ -34,7 +34,7 @@ module.exports = {
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/all-features/dialogue-tree')
|
||||
.setCategory('Advanced');
|
||||
.setCategory('Game mechanic');
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata(_('Dialogue Tree (experimental)'))
|
||||
.setIcon('JsPlatform/Extensions/yarn32.png');
|
||||
|
@@ -30,16 +30,16 @@ namespace gdjs {
|
||||
/**
|
||||
* Load the Dialogue Tree data from a JSON resource.
|
||||
*
|
||||
* @param runtimeScene The scene where the dialogue is running.
|
||||
* @param instanceContainer The scene where the dialogue is running.
|
||||
* @param jsonResourceName The JSON resource where to load the Dialogue Tree data from. The data is a JSON string usually created with [Yarn Dialogue Editor](https://github.com/InfiniteAmmoInc/Yarn).
|
||||
* @param startDialogueNode The Dialogue Branch to start the Dialogue Tree from. If left empty, the data will only be loaded, but can later be initialized via another action
|
||||
*/
|
||||
gdjs.dialogueTree.loadFromJsonFile = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
jsonResourceName: string,
|
||||
startDialogueNode: string
|
||||
) {
|
||||
runtimeScene
|
||||
instanceContainer
|
||||
.getGame()
|
||||
.getJsonManager()
|
||||
.loadJson(jsonResourceName, function (error, content) {
|
||||
|
@@ -20,6 +20,7 @@ void DeclareDraggableBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"or disable the behavior when needed."),
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("User interface")
|
||||
.SetExtensionHelpPath("/behaviors/draggable");
|
||||
|
||||
gd::BehaviorMetadata& aut = extension.AddBehavior(
|
||||
|
@@ -16,8 +16,12 @@ namespace gdjs {
|
||||
_draggedByDraggableManager: DraggableManager | null = null;
|
||||
_checkCollisionMask: boolean;
|
||||
|
||||
constructor(runtimeScene, behaviorData, owner) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
behaviorData,
|
||||
owner
|
||||
) {
|
||||
super(instanceContainer, behaviorData, owner);
|
||||
this._checkCollisionMask = behaviorData.checkCollisionMask ? true : false;
|
||||
}
|
||||
|
||||
@@ -45,21 +49,21 @@ namespace gdjs {
|
||||
this._draggedByDraggableManager = null;
|
||||
}
|
||||
|
||||
_tryBeginDrag(runtimeScene) {
|
||||
_tryBeginDrag(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
if (this._draggedByDraggableManager) {
|
||||
return false;
|
||||
}
|
||||
const inputManager = runtimeScene.getGame().getInputManager();
|
||||
const inputManager = instanceContainer.getGame().getInputManager();
|
||||
|
||||
//Try mouse
|
||||
const mouseDraggableManager = DraggableManager.getMouseManager(
|
||||
runtimeScene
|
||||
instanceContainer
|
||||
);
|
||||
if (
|
||||
inputManager.isMouseButtonPressed(0) &&
|
||||
!mouseDraggableManager.isDragging(this)
|
||||
) {
|
||||
if (mouseDraggableManager.tryAndTakeDragging(runtimeScene, this)) {
|
||||
if (mouseDraggableManager.tryAndTakeDragging(instanceContainer, this)) {
|
||||
this._draggedByDraggableManager = mouseDraggableManager;
|
||||
return true;
|
||||
}
|
||||
@@ -68,13 +72,15 @@ namespace gdjs {
|
||||
const touchIds = inputManager.getStartedTouchIdentifiers();
|
||||
for (let i = 0; i < touchIds.length; ++i) {
|
||||
const touchDraggableManager = DraggableManager.getTouchManager(
|
||||
runtimeScene,
|
||||
instanceContainer,
|
||||
touchIds[i]
|
||||
);
|
||||
if (touchDraggableManager.isDragging(this)) {
|
||||
continue;
|
||||
}
|
||||
if (touchDraggableManager.tryAndTakeDragging(runtimeScene, this)) {
|
||||
if (
|
||||
touchDraggableManager.tryAndTakeDragging(instanceContainer, this)
|
||||
) {
|
||||
this._draggedByDraggableManager = touchDraggableManager;
|
||||
return true;
|
||||
}
|
||||
@@ -83,40 +89,46 @@ namespace gdjs {
|
||||
return false;
|
||||
}
|
||||
|
||||
_shouldEndDrag(runtimeScene) {
|
||||
_shouldEndDrag(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
if (!this._draggedByDraggableManager) {
|
||||
return false;
|
||||
}
|
||||
return this._draggedByDraggableManager.shouldEndDrag(runtimeScene, this);
|
||||
return this._draggedByDraggableManager.shouldEndDrag(
|
||||
instanceContainer,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
_updateObjectPosition(runtimeScene) {
|
||||
_updateObjectPosition(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
if (!this._draggedByDraggableManager) {
|
||||
return false;
|
||||
}
|
||||
this._draggedByDraggableManager.updateObjectPosition(runtimeScene, this);
|
||||
this._draggedByDraggableManager.updateObjectPosition(
|
||||
instanceContainer,
|
||||
this
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
doStepPreEvents(runtimeScene) {
|
||||
this._tryBeginDrag(runtimeScene);
|
||||
if (this._shouldEndDrag(runtimeScene)) {
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
this._tryBeginDrag(instanceContainer);
|
||||
if (this._shouldEndDrag(instanceContainer)) {
|
||||
this._endDrag();
|
||||
}
|
||||
this._updateObjectPosition(runtimeScene);
|
||||
this._updateObjectPosition(instanceContainer);
|
||||
}
|
||||
|
||||
doStepPostEvents(runtimeScene) {
|
||||
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
const mouseDraggableManager = DraggableManager.getMouseManager(
|
||||
runtimeScene
|
||||
instanceContainer
|
||||
);
|
||||
mouseDraggableManager.leftPressedLastFrame = runtimeScene
|
||||
mouseDraggableManager.leftPressedLastFrame = instanceContainer
|
||||
.getGame()
|
||||
.getInputManager()
|
||||
.isMouseButtonPressed(0);
|
||||
}
|
||||
|
||||
isDragged(runtimeScene): boolean {
|
||||
isDragged(instanceContainer: gdjs.RuntimeInstanceContainer): boolean {
|
||||
return !!this._draggedByDraggableManager;
|
||||
}
|
||||
}
|
||||
@@ -137,53 +149,53 @@ namespace gdjs {
|
||||
protected _xOffset: number = 0;
|
||||
protected _yOffset: number = 0;
|
||||
|
||||
constructor(runtimeScene: gdjs.RuntimeScene) {}
|
||||
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {}
|
||||
|
||||
/**
|
||||
* Get the platforms manager of a scene.
|
||||
* Get the platforms manager of an instance container.
|
||||
*/
|
||||
static getMouseManager(
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): MouseDraggableManager {
|
||||
// @ts-ignore
|
||||
if (!runtimeScene.mouseDraggableManager) {
|
||||
if (!instanceContainer.mouseDraggableManager) {
|
||||
//Create the shared manager if necessary.
|
||||
// @ts-ignore
|
||||
runtimeScene.mouseDraggableManager = new MouseDraggableManager(
|
||||
runtimeScene
|
||||
instanceContainer.mouseDraggableManager = new MouseDraggableManager(
|
||||
instanceContainer
|
||||
);
|
||||
}
|
||||
// @ts-ignore
|
||||
return runtimeScene.mouseDraggableManager;
|
||||
return instanceContainer.mouseDraggableManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the platforms manager of a scene.
|
||||
* Get the platforms manager of an instance container.
|
||||
*/
|
||||
static getTouchManager(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
touchId: integer
|
||||
): DraggableManager {
|
||||
// @ts-ignore
|
||||
if (!runtimeScene.touchDraggableManagers) {
|
||||
if (!instanceContainer.touchDraggableManagers) {
|
||||
//Create the shared manager if necessary.
|
||||
// @ts-ignore
|
||||
runtimeScene.touchDraggableManagers = [];
|
||||
instanceContainer.touchDraggableManagers = [];
|
||||
}
|
||||
// @ts-ignore
|
||||
if (!runtimeScene.touchDraggableManagers[touchId]) {
|
||||
if (!instanceContainer.touchDraggableManagers[touchId]) {
|
||||
//Create the shared manager if necessary.
|
||||
// @ts-ignore
|
||||
runtimeScene.touchDraggableManagers[
|
||||
instanceContainer.touchDraggableManagers[
|
||||
touchId
|
||||
] = new TouchDraggableManager(runtimeScene, touchId);
|
||||
] = new TouchDraggableManager(instanceContainer, touchId);
|
||||
}
|
||||
// @ts-ignore
|
||||
return runtimeScene.touchDraggableManagers[touchId];
|
||||
return instanceContainer.touchDraggableManagers[touchId];
|
||||
}
|
||||
|
||||
tryAndTakeDragging(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
draggableRuntimeBehavior: DraggableRuntimeBehavior
|
||||
) {
|
||||
if (
|
||||
@@ -193,7 +205,10 @@ namespace gdjs {
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const position = this.getPosition(runtimeScene, draggableRuntimeBehavior);
|
||||
const position = this.getPosition(
|
||||
instanceContainer,
|
||||
draggableRuntimeBehavior
|
||||
);
|
||||
if (
|
||||
!draggableRuntimeBehavior.owner.insideObject(position[0], position[1])
|
||||
) {
|
||||
@@ -218,10 +233,13 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateObjectPosition(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
draggableRuntimeBehavior: DraggableRuntimeBehavior
|
||||
) {
|
||||
const position = this.getPosition(runtimeScene, draggableRuntimeBehavior);
|
||||
const position = this.getPosition(
|
||||
instanceContainer,
|
||||
draggableRuntimeBehavior
|
||||
);
|
||||
if (
|
||||
draggableRuntimeBehavior.owner.getX() != position[0] - this._xOffset ||
|
||||
draggableRuntimeBehavior.owner.getY() != position[1] - this._yOffset
|
||||
@@ -241,11 +259,11 @@ namespace gdjs {
|
||||
draggableRuntimeBehavior: DraggableRuntimeBehavior
|
||||
): boolean;
|
||||
abstract shouldEndDrag(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
draggableRuntimeBehavior: DraggableRuntimeBehavior
|
||||
): boolean;
|
||||
abstract getPosition(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
draggableRuntimeBehavior: DraggableRuntimeBehavior
|
||||
): FloatPoint;
|
||||
}
|
||||
@@ -257,8 +275,8 @@ namespace gdjs {
|
||||
/** Used to only start dragging when clicking. */
|
||||
leftPressedLastFrame = false;
|
||||
|
||||
constructor(runtimeScene: gdjs.RuntimeScene) {
|
||||
super(runtimeScene);
|
||||
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
super(instanceContainer);
|
||||
}
|
||||
|
||||
isDragging(draggableRuntimeBehavior: DraggableRuntimeBehavior): boolean {
|
||||
@@ -266,20 +284,28 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
getPosition(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
draggableRuntimeBehavior: DraggableRuntimeBehavior
|
||||
): FloatPoint {
|
||||
const inputManager = runtimeScene.getGame().getInputManager();
|
||||
return runtimeScene
|
||||
const workingPoint: FloatPoint = gdjs.staticArray(
|
||||
MouseDraggableManager.prototype.getPosition
|
||||
) as FloatPoint;
|
||||
const inputManager = instanceContainer.getGame().getInputManager();
|
||||
return instanceContainer
|
||||
.getLayer(draggableRuntimeBehavior.owner.getLayer())
|
||||
.convertCoords(inputManager.getMouseX(), inputManager.getMouseY());
|
||||
.convertCoords(
|
||||
inputManager.getMouseX(),
|
||||
inputManager.getMouseY(),
|
||||
0,
|
||||
workingPoint
|
||||
);
|
||||
}
|
||||
|
||||
shouldEndDrag(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
draggableRuntimeBehavior: DraggableRuntimeBehavior
|
||||
): boolean {
|
||||
const inputManager = runtimeScene.getGame().getInputManager();
|
||||
const inputManager = instanceContainer.getGame().getInputManager();
|
||||
return !inputManager.isMouseButtonPressed(0);
|
||||
}
|
||||
}
|
||||
@@ -290,8 +316,11 @@ namespace gdjs {
|
||||
class TouchDraggableManager extends DraggableManager {
|
||||
private _touchId: integer;
|
||||
|
||||
constructor(runtimeScene: gdjs.RuntimeScene, touchId: integer) {
|
||||
super(runtimeScene);
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
touchId: integer
|
||||
) {
|
||||
super(instanceContainer);
|
||||
this._touchId = touchId;
|
||||
}
|
||||
|
||||
@@ -300,23 +329,28 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
getPosition(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
draggableRuntimeBehavior: DraggableRuntimeBehavior
|
||||
): FloatPoint {
|
||||
const inputManager = runtimeScene.getGame().getInputManager();
|
||||
return runtimeScene
|
||||
const workingPoint: FloatPoint = gdjs.staticArray(
|
||||
TouchDraggableManager.prototype.getPosition
|
||||
) as FloatPoint;
|
||||
const inputManager = instanceContainer.getGame().getInputManager();
|
||||
return instanceContainer
|
||||
.getLayer(draggableRuntimeBehavior.owner.getLayer())
|
||||
.convertCoords(
|
||||
inputManager.getTouchX(this._touchId),
|
||||
inputManager.getTouchY(this._touchId)
|
||||
inputManager.getTouchY(this._touchId),
|
||||
0,
|
||||
workingPoint
|
||||
);
|
||||
}
|
||||
|
||||
shouldEndDrag(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
draggableRuntimeBehavior: DraggableRuntimeBehavior
|
||||
): boolean {
|
||||
const inputManager = runtimeScene.getGame().getInputManager();
|
||||
const inputManager = instanceContainer.getGame().getInputManager();
|
||||
return (
|
||||
inputManager.getAllTouchIdentifiers().indexOf(this._touchId) === -1
|
||||
);
|
||||
|
@@ -31,7 +31,9 @@ module.exports = {
|
||||
'Lots of different effects to be used in your game.',
|
||||
'Various contributors from PixiJS, PixiJS filters and GDevelop',
|
||||
'MIT'
|
||||
).setExtensionHelpPath('/interface/scene-editor/layer-effects');
|
||||
)
|
||||
.setCategory('Visual effect')
|
||||
.setExtensionHelpPath('/interface/scene-editor/layer-effects');
|
||||
|
||||
// ℹ️ You can declare an effect here. Please order the effects by alphabetical order.
|
||||
// This file is for common effects that are well-known/"battle-tested". If you have an
|
||||
|
@@ -539,7 +539,7 @@ module.exports = {
|
||||
RenderedDummyObjectInstance.getThumbnail = function (
|
||||
project,
|
||||
resourcesLoader,
|
||||
object
|
||||
objectConfiguration
|
||||
) {
|
||||
return 'CppPlatform/Extensions/texticon24.png';
|
||||
};
|
||||
|
@@ -11,11 +11,11 @@ namespace gdjs {
|
||||
_textToSet: string;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
behaviorData: any,
|
||||
owner: gdjs.RuntimeObject
|
||||
) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
super(instanceContainer, behaviorData, owner);
|
||||
|
||||
// Here you can access to the behavior data (JSON declared in JsExtension.js)
|
||||
// using behaviorData:
|
||||
@@ -37,7 +37,7 @@ namespace gdjs {
|
||||
|
||||
onDeActivate() {}
|
||||
|
||||
doStepPreEvents(runtimeScene) {
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
// This is run at every frame, before events are launched.
|
||||
this.owner
|
||||
.getVariables()
|
||||
@@ -45,7 +45,7 @@ namespace gdjs {
|
||||
.setString(this._textToSet);
|
||||
}
|
||||
|
||||
doStepPostEvents(runtimeScene) {
|
||||
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
// This is run at every frame, after events are launched.
|
||||
}
|
||||
}
|
||||
|
@@ -11,11 +11,11 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* @param runtimeObject The object to render
|
||||
* @param runtimeScene The gdjs.RuntimeScene in which the object is
|
||||
* @param instanceContainer The gdjs.RuntimeScene in which the object is
|
||||
*/
|
||||
constructor(
|
||||
runtimeObject: gdjs.DummyRuntimeObject,
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
this._object = runtimeObject;
|
||||
|
||||
@@ -27,12 +27,12 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
// You can also create a PIXI sprite or other PIXI object
|
||||
// this._imageManager = runtimeScene.getGame().getImageManager();
|
||||
// this._imageManager = instanceContainer.getGame().getImageManager();
|
||||
// if ( this._sprite === undefined )
|
||||
// this._sprite = new PIXI.Sprite(this._imageManager.getInvalidPIXITexture());
|
||||
this._text.anchor.x = 0.5;
|
||||
this._text.anchor.y = 0.5;
|
||||
runtimeScene
|
||||
instanceContainer
|
||||
.getLayer('')
|
||||
.getRenderer()
|
||||
.addRendererObject(this._text, runtimeObject.getZOrder());
|
||||
|
@@ -14,11 +14,14 @@ namespace gdjs {
|
||||
// @ts-expect-error ts-migrate(2564) FIXME: Property 'opacity' has no initializer and is not d... Remove this comment to see the full error message
|
||||
opacity: float;
|
||||
|
||||
constructor(runtimeScene, objectData) {
|
||||
constructor(instanceContainer: gdjs.RuntimeInstanceContainer, objectData) {
|
||||
// *ALWAYS* call the base gdjs.RuntimeObject constructor.
|
||||
super(runtimeScene, objectData);
|
||||
super(instanceContainer, objectData);
|
||||
this._property1 = objectData.content.property1;
|
||||
this._renderer = new gdjs.DummyRuntimeObjectRenderer(this, runtimeScene);
|
||||
this._renderer = new gdjs.DummyRuntimeObjectRenderer(
|
||||
this,
|
||||
instanceContainer
|
||||
);
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
this.onCreated();
|
||||
@@ -40,9 +43,9 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Called once during the game loop, before events and rendering.
|
||||
* @param runtimeScene The gdjs.RuntimeScene the object belongs to.
|
||||
* @param instanceContainer The gdjs.RuntimeScene the object belongs to.
|
||||
*/
|
||||
update(runtimeScene: gdjs.RuntimeScene): void {
|
||||
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
// This is an example: typically you want to make sure the renderer
|
||||
// is up to date with the object.
|
||||
this._renderer.ensureUpToDate();
|
||||
|
@@ -4,20 +4,20 @@ namespace gdjs {
|
||||
_textToSet: string;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
behaviorData: any,
|
||||
owner: gdjs.RuntimeObject
|
||||
) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
super(instanceContainer, behaviorData, owner);
|
||||
|
||||
// Here you can access to the behavior data (JSON declared in JsExtension.js)
|
||||
// using behaviorData:
|
||||
this._textToSet = behaviorData.property1;
|
||||
|
||||
// You can also access to the shared data:
|
||||
const sharedData = runtimeScene.getInitialSharedDataForBehavior(
|
||||
behaviorData.name
|
||||
);
|
||||
const sharedData = instanceContainer
|
||||
.getScene()
|
||||
.getInitialSharedDataForBehavior(behaviorData.name);
|
||||
this._textToSet = (sharedData as any).sharedProperty1;
|
||||
|
||||
// You can also run arbitrary code at the creation of the behavior:
|
||||
@@ -40,7 +40,7 @@ namespace gdjs {
|
||||
|
||||
onDeActivate() {}
|
||||
|
||||
doStepPreEvents(runtimeScene) {
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
// This is run at every frame, before events are launched.
|
||||
this.owner
|
||||
.getVariables()
|
||||
@@ -48,7 +48,7 @@ namespace gdjs {
|
||||
.setString(this._textToSet);
|
||||
}
|
||||
|
||||
doStepPostEvents(runtimeScene) {
|
||||
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
// This is run at every frame, after events are launched.
|
||||
}
|
||||
}
|
||||
|
@@ -25,7 +25,9 @@ namespace gdjs {
|
||||
* In **rare cases** you may want to run code at the start of the scene. You can define a callback
|
||||
* that will be called at this moment.
|
||||
*/
|
||||
gdjs.registerRuntimeSceneLoadedCallback(function (runtimeScene) {
|
||||
gdjs.registerRuntimeSceneLoadedCallback(function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) {
|
||||
logger.log('A gdjs.RuntimeScene was loaded:', runtimeScene);
|
||||
});
|
||||
|
||||
@@ -33,7 +35,9 @@ namespace gdjs {
|
||||
* In **rare cases** you may want to run code at the end of a scene. You can define a callback
|
||||
* that will be called at this moment.
|
||||
*/
|
||||
gdjs.registerRuntimeSceneUnloadedCallback(function (runtimeScene) {
|
||||
gdjs.registerRuntimeSceneUnloadedCallback(function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) {
|
||||
logger.log('A gdjs.RuntimeScene was unloaded:', runtimeScene);
|
||||
});
|
||||
|
||||
@@ -41,12 +45,12 @@ namespace gdjs {
|
||||
* In **very rare cases** you may want to run code whenever an object is deleted.
|
||||
*/
|
||||
gdjs.registerObjectDeletedFromSceneCallback(function (
|
||||
runtimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
runtimeObject
|
||||
) {
|
||||
logger.log(
|
||||
'A gdjs.RuntimeObject was deleted from a gdjs.RuntimeScene:',
|
||||
runtimeScene,
|
||||
instanceContainer,
|
||||
runtimeObject
|
||||
);
|
||||
});
|
||||
|
@@ -34,7 +34,7 @@ module.exports = {
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/all-features/filesystem')
|
||||
.setCategory('Device');
|
||||
.setCategory('Advanced');
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata(_('File system'))
|
||||
.setIcon('JsPlatform/Extensions/filesystem_create_folder32.png');
|
||||
@@ -203,6 +203,18 @@ module.exports = {
|
||||
'',
|
||||
true
|
||||
)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Normalize the file content (recommended)'),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'This replaces Windows new lines characters ("CRLF") by a single new line character.'
|
||||
)
|
||||
)
|
||||
.setDefaultValue('yes')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/FileSystem/filesystemtools.js')
|
||||
.setFunctionName('gdjs.fileSystem.loadStringFromFileAsync');
|
||||
@@ -229,6 +241,18 @@ module.exports = {
|
||||
'',
|
||||
true
|
||||
)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Normalize the file content (recommended)'),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'This replaces Windows new lines characters ("CRLF") by a single new line character.'
|
||||
)
|
||||
)
|
||||
.setDefaultValue('yes')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/FileSystem/filesystemtools.js')
|
||||
.setFunctionName('gdjs.fileSystem.loadStringFromFile');
|
||||
@@ -255,6 +279,18 @@ module.exports = {
|
||||
'',
|
||||
true
|
||||
)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Normalize the file content (recommended)'),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'This replaces Windows new lines characters ("CRLF") by a single new line character.'
|
||||
)
|
||||
)
|
||||
.setDefaultValue('yes')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/FileSystem/filesystemtools.js')
|
||||
.setFunctionName('gdjs.fileSystem.loadVariableFromJSONFile');
|
||||
@@ -281,6 +317,18 @@ module.exports = {
|
||||
'',
|
||||
true
|
||||
)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Normalize the file content (recommended)'),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'This replaces Windows new lines characters ("CRLF") by a single new line character.'
|
||||
)
|
||||
)
|
||||
.setDefaultValue('yes')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/FileSystem/filesystemtools.js')
|
||||
.setFunctionName('gdjs.fileSystem.loadVariableFromJSONFileAsync');
|
||||
|
@@ -48,13 +48,16 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Get the path to 'Desktop' folder.
|
||||
* @param runtimeScene The current scene
|
||||
* @param instanceContainer The current container
|
||||
* @return The path to the desktop folder
|
||||
*/
|
||||
export const getDesktopPath = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): string {
|
||||
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
|
||||
const remote = instanceContainer
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getElectronRemote();
|
||||
const app = remote ? remote.app : null;
|
||||
if (app) {
|
||||
return app.getPath('desktop') || '';
|
||||
@@ -65,13 +68,16 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Get the path to 'Documents' folder.
|
||||
* @param runtimeScene The current scene
|
||||
* @param instanceContainer The current container
|
||||
* @return The path to the documents folder
|
||||
*/
|
||||
export const getDocumentsPath = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): string {
|
||||
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
|
||||
const remote = instanceContainer
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getElectronRemote();
|
||||
const app = remote ? remote.app : null;
|
||||
if (app) {
|
||||
return app.getPath('documents') || '';
|
||||
@@ -82,13 +88,16 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Get the path to 'Pictures' folder.
|
||||
* @param runtimeScene The current scene
|
||||
* @param instanceContainer The current container
|
||||
* @return The path to the pictures folder
|
||||
*/
|
||||
export const getPicturesPath = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): string {
|
||||
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
|
||||
const remote = instanceContainer
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getElectronRemote();
|
||||
const app = remote ? remote.app : null;
|
||||
if (app) {
|
||||
return app.getPath('pictures') || '';
|
||||
@@ -99,13 +108,16 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Get the path to this application 'Executable' file.
|
||||
* @param runtimeScene The current scene
|
||||
* @param instanceContainer The current container
|
||||
* @return The path to this applications executable file
|
||||
*/
|
||||
export const getExecutablePath = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): string {
|
||||
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
|
||||
const remote = instanceContainer
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getElectronRemote();
|
||||
const app = remote ? remote.app : null;
|
||||
if (app) {
|
||||
return app.getPath('exe') || '';
|
||||
@@ -116,14 +128,16 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Get the path to this application 'Executable' folder.
|
||||
* @param runtimeScene The current scene
|
||||
* @param instanceContainer The current container
|
||||
* @return The path to this applications executable folder
|
||||
*/
|
||||
export const getExecutableFolderPath = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): string {
|
||||
const path = gdjs.fileSystem._getPath();
|
||||
const executablePath = gdjs.fileSystem.getExecutablePath(runtimeScene);
|
||||
const executablePath = gdjs.fileSystem.getExecutablePath(
|
||||
instanceContainer
|
||||
);
|
||||
if (!path) {
|
||||
return '';
|
||||
}
|
||||
@@ -132,13 +146,16 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Get the path to 'UserData' folder.
|
||||
* @param runtimeScene The current scene
|
||||
* @param instanceContainer The current container
|
||||
* @return The path to userdata folder
|
||||
*/
|
||||
export const getUserdataPath = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): string {
|
||||
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
|
||||
const remote = instanceContainer
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getElectronRemote();
|
||||
const app = remote ? remote.app : null;
|
||||
if (app) {
|
||||
return app.getPath('userData') || '';
|
||||
@@ -152,9 +169,12 @@ namespace gdjs {
|
||||
* @return The path to user's "home" folder
|
||||
*/
|
||||
export const getUserHomePath = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): string {
|
||||
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
|
||||
const remote = instanceContainer
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getElectronRemote();
|
||||
const app = remote ? remote.app : null;
|
||||
if (app) {
|
||||
return app.getPath('home') || '';
|
||||
@@ -165,13 +185,16 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Get the path to 'Temp' folder.
|
||||
* @param runtimeScene The current scene
|
||||
* @param instanceContainer The current container
|
||||
* @return The path to temp folder
|
||||
*/
|
||||
export const getTempPath = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): string {
|
||||
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
|
||||
const remote = instanceContainer
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getElectronRemote();
|
||||
const app = remote ? remote.app : null;
|
||||
if (app) {
|
||||
return app.getPath('temp') || '';
|
||||
@@ -338,11 +361,13 @@ namespace gdjs {
|
||||
* @param stringVar Variable where to store the content
|
||||
* @param loadPath Path to the file
|
||||
* @param resultVar The variable where to store the result of the operation
|
||||
* @param removeCRCharacters If true, will remove \r characters usually added by Windows when editing files
|
||||
*/
|
||||
export const loadStringFromFile = function (
|
||||
stringVar: gdjs.Variable,
|
||||
loadPath: string,
|
||||
resultVar: gdjs.Variable
|
||||
resultVar: gdjs.Variable,
|
||||
removeCRCharacters: boolean
|
||||
) {
|
||||
const fileSystem = gdjs.fileSystem._getFs();
|
||||
let result = 'error';
|
||||
@@ -350,7 +375,9 @@ namespace gdjs {
|
||||
try {
|
||||
const data = fileSystem.readFileSync(loadPath, 'utf8');
|
||||
if (data) {
|
||||
stringVar.setString(data);
|
||||
stringVar.setString(
|
||||
removeCRCharacters ? data.replace(/\r/g, '') : data
|
||||
);
|
||||
result = 'ok';
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -368,11 +395,13 @@ namespace gdjs {
|
||||
* @param variable Variable to store the variable
|
||||
* @param loadPath Path to the file
|
||||
* @param resultVar The variable where to store the result of the operation
|
||||
* @param removeCRCharacters If true, will remove \r characters usually added by Windows when editing files
|
||||
*/
|
||||
export const loadVariableFromJSONFile = function (
|
||||
variable: gdjs.Variable,
|
||||
loadPath: string,
|
||||
resultVar: gdjs.Variable
|
||||
resultVar: gdjs.Variable,
|
||||
removeCRCharacters: boolean
|
||||
) {
|
||||
const fileSystem = gdjs.fileSystem._getFs();
|
||||
let result = 'error';
|
||||
@@ -380,7 +409,9 @@ namespace gdjs {
|
||||
try {
|
||||
const data = fileSystem.readFileSync(loadPath, 'utf8');
|
||||
if (data) {
|
||||
variable.fromJSON(data);
|
||||
variable.fromJSON(
|
||||
removeCRCharacters ? data.replace(/\r/g, '') : data
|
||||
);
|
||||
result = 'ok';
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -400,17 +431,21 @@ namespace gdjs {
|
||||
* @param variable Variable to store the variable
|
||||
* @param loadPath Path to the file
|
||||
* @param resultVar The variable where to store the result of the operation
|
||||
* @param removeCRCharacters If true, will remove \r characters usually added by Windows when editing files
|
||||
*/
|
||||
export const loadVariableFromJSONFileAsync = function (
|
||||
variable: gdjs.Variable,
|
||||
loadPath: string,
|
||||
resultVar: gdjs.Variable
|
||||
resultVar: gdjs.Variable,
|
||||
removeCRCharacters: boolean
|
||||
) {
|
||||
const fileSystem = gdjs.fileSystem._getFs();
|
||||
if (fileSystem) {
|
||||
fileSystem.readFile(loadPath, 'utf8', (err, data) => {
|
||||
if (data) {
|
||||
variable.fromJSON(data);
|
||||
variable.fromJSON(
|
||||
removeCRCharacters ? data.replace(/\r/g, '') : data
|
||||
);
|
||||
resultVar.setString('ok');
|
||||
}
|
||||
if (err) {
|
||||
@@ -431,17 +466,21 @@ namespace gdjs {
|
||||
* @param stringVar Variable where to store the content
|
||||
* @param loadPath Path to the file
|
||||
* @param resultVar The variable where to store the result of the operation
|
||||
* @param removeCRCharacters If true, will remove \r characters usually added by Windows when editing files
|
||||
*/
|
||||
export const loadStringFromFileAsync = function (
|
||||
stringVar: gdjs.Variable,
|
||||
loadPath: string,
|
||||
resultVar: gdjs.Variable
|
||||
resultVar: gdjs.Variable,
|
||||
removeCRCharacters: boolean
|
||||
) {
|
||||
const fileSystem = gdjs.fileSystem._getFs();
|
||||
if (fileSystem) {
|
||||
fileSystem.readFile(loadPath, 'utf8', (err, data) => {
|
||||
if (data) {
|
||||
stringVar.setString(data);
|
||||
stringVar.setString(
|
||||
removeCRCharacters ? data.replace(/\r/g, '') : data
|
||||
);
|
||||
resultVar.setString('ok');
|
||||
}
|
||||
if (err) {
|
||||
|
@@ -19,7 +19,7 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/all-features/inventory")
|
||||
.SetCategory("Advanced");
|
||||
.SetCategory("Game mechanic");
|
||||
extension
|
||||
.AddInstructionOrExpressionGroupMetadata(_("Inventories"))
|
||||
.SetIcon("CppPlatform/Extensions/Inventoryicon.png");
|
||||
|
@@ -1,7 +1,13 @@
|
||||
namespace gdjs {
|
||||
export interface RuntimeGame {
|
||||
inventories: { [name: string]: gdjs.Inventory };
|
||||
}
|
||||
export class InventoryManager {
|
||||
static get(runtimeScene, name): gdjs.Inventory {
|
||||
const game = runtimeScene.getGame();
|
||||
static get(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
name: string
|
||||
): gdjs.Inventory {
|
||||
const game = instanceContainer.getGame();
|
||||
if (!game.inventories) {
|
||||
game.inventories = {};
|
||||
}
|
||||
@@ -15,70 +21,106 @@ namespace gdjs {
|
||||
|
||||
export namespace evtTools {
|
||||
export namespace inventory {
|
||||
export const add = function (runtimeScene, inventoryName, name) {
|
||||
return InventoryManager.get(runtimeScene, inventoryName).add(name);
|
||||
export const add = function (
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
inventoryName: string,
|
||||
name: string
|
||||
) {
|
||||
return InventoryManager.get(instanceContainer, inventoryName).add(name);
|
||||
};
|
||||
|
||||
export const remove = function (runtimeScene, inventoryName, name) {
|
||||
return InventoryManager.get(runtimeScene, inventoryName).remove(name);
|
||||
export const remove = function (
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
inventoryName: string,
|
||||
name: string
|
||||
) {
|
||||
return InventoryManager.get(instanceContainer, inventoryName).remove(
|
||||
name
|
||||
);
|
||||
};
|
||||
|
||||
export const count = function (runtimeScene, inventoryName, name) {
|
||||
return InventoryManager.get(runtimeScene, inventoryName).count(name);
|
||||
export const count = function (
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
inventoryName: string,
|
||||
name: string
|
||||
) {
|
||||
return InventoryManager.get(instanceContainer, inventoryName).count(
|
||||
name
|
||||
);
|
||||
};
|
||||
|
||||
export const has = function (runtimeScene, inventoryName, name) {
|
||||
return InventoryManager.get(runtimeScene, inventoryName).has(name);
|
||||
export const has = function (
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
inventoryName: string,
|
||||
name: string
|
||||
) {
|
||||
return InventoryManager.get(instanceContainer, inventoryName).has(name);
|
||||
};
|
||||
|
||||
export const setMaximum = function (
|
||||
runtimeScene,
|
||||
inventoryName,
|
||||
name,
|
||||
maxCount
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
inventoryName: string,
|
||||
name: string,
|
||||
maxCount: number
|
||||
) {
|
||||
return InventoryManager.get(runtimeScene, inventoryName).setMaximum(
|
||||
name,
|
||||
maxCount
|
||||
);
|
||||
return InventoryManager.get(
|
||||
instanceContainer,
|
||||
inventoryName
|
||||
).setMaximum(name, maxCount);
|
||||
};
|
||||
|
||||
export const setUnlimited = function (
|
||||
runtimeScene,
|
||||
inventoryName,
|
||||
name,
|
||||
enable
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
inventoryName: string,
|
||||
name: string,
|
||||
enable: boolean
|
||||
) {
|
||||
return InventoryManager.get(runtimeScene, inventoryName).setUnlimited(
|
||||
name,
|
||||
enable
|
||||
return InventoryManager.get(
|
||||
instanceContainer,
|
||||
inventoryName
|
||||
).setUnlimited(name, enable);
|
||||
};
|
||||
|
||||
export const isFull = function (
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
inventoryName: string,
|
||||
name: string
|
||||
) {
|
||||
return InventoryManager.get(instanceContainer, inventoryName).isFull(
|
||||
name
|
||||
);
|
||||
};
|
||||
|
||||
export const isFull = function (runtimeScene, inventoryName, name) {
|
||||
return InventoryManager.get(runtimeScene, inventoryName).isFull(name);
|
||||
};
|
||||
|
||||
export const equip = function (runtimeScene, inventoryName, name, equip) {
|
||||
return InventoryManager.get(runtimeScene, inventoryName).equip(
|
||||
export const equip = function (
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
inventoryName: string,
|
||||
name: string,
|
||||
equip: boolean
|
||||
) {
|
||||
return InventoryManager.get(instanceContainer, inventoryName).equip(
|
||||
name,
|
||||
equip
|
||||
);
|
||||
};
|
||||
|
||||
export const isEquipped = function (runtimeScene, inventoryName, name) {
|
||||
return InventoryManager.get(runtimeScene, inventoryName).isEquipped(
|
||||
name
|
||||
);
|
||||
export const isEquipped = function (
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
inventoryName: string,
|
||||
name: string
|
||||
) {
|
||||
return InventoryManager.get(
|
||||
instanceContainer,
|
||||
inventoryName
|
||||
).isEquipped(name);
|
||||
};
|
||||
|
||||
export const serializeToVariable = function (
|
||||
runtimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
inventoryName: string,
|
||||
variable: gdjs.Variable
|
||||
) {
|
||||
const allItems = gdjs.InventoryManager.get(
|
||||
runtimeScene,
|
||||
instanceContainer,
|
||||
inventoryName
|
||||
).getAllItems();
|
||||
for (const name in allItems) {
|
||||
@@ -92,12 +134,12 @@ namespace gdjs {
|
||||
};
|
||||
|
||||
export const unserializeFromVariable = function (
|
||||
runtimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
inventoryName: string,
|
||||
variable: gdjs.Variable
|
||||
) {
|
||||
const inventory = gdjs.InventoryManager.get(
|
||||
runtimeScene,
|
||||
instanceContainer,
|
||||
inventoryName
|
||||
);
|
||||
inventory.clear();
|
||||
|
@@ -20,7 +20,7 @@ export type ObjectsRenderingService = {
|
||||
RenderedInstance: any,
|
||||
registerInstanceRenderer: (objectType: string, renderer: any) => void,
|
||||
requireModule: (dirname: string, moduleName: string) => any,
|
||||
getThumbnail: (project: gdProject, object: gdObject) => string,
|
||||
getThumbnail: (project: gdProject, objectConfiguration: gdObjectConfiguration) => string,
|
||||
rgbOrHexToHexNumber: (value: string) => number,
|
||||
};
|
||||
export type ObjectsEditorService = {
|
||||
|
@@ -34,7 +34,7 @@ module.exports = {
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/all-features/leaderboards')
|
||||
.setCategory('Leaderboards')
|
||||
.setCategory('Players')
|
||||
.addInstructionOrExpressionGroupMetadata(_('Leaderboards (experimental)'))
|
||||
.setIcon('JsPlatform/Extensions/leaderboard.svg');
|
||||
|
||||
@@ -65,6 +65,32 @@ module.exports = {
|
||||
.addIncludeFile('Extensions/Leaderboards/leaderboardstools.js')
|
||||
.setFunctionName('gdjs.evtTools.leaderboards.savePlayerScore');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'SaveConnectedPlayerScore',
|
||||
_('Save connected player score'),
|
||||
_("Save the connected player's score to the given leaderboard."),
|
||||
_(
|
||||
'Send to leaderboard _PARAM1_ the score _PARAM2_ for the connected player'
|
||||
),
|
||||
_('Save score'),
|
||||
'JsPlatform/Extensions/leaderboard.svg',
|
||||
'JsPlatform/Extensions/leaderboard.svg'
|
||||
)
|
||||
.addCodeOnlyParameter('currentScene', '')
|
||||
.addParameter('leaderboardId', _('Leaderboard'), '', false)
|
||||
.addParameter(
|
||||
'expression',
|
||||
_('Score to register for the player'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.setHelpPath('/all-features/leaderboards')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/Leaderboards/sha256.js')
|
||||
.addIncludeFile('Extensions/Leaderboards/leaderboardstools.js')
|
||||
.setFunctionName('gdjs.evtTools.leaderboards.saveConnectedPlayerScore');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'HasLastSaveErrored',
|
||||
|
@@ -1,5 +1,6 @@
|
||||
/// <reference path="sha256.d.ts" />
|
||||
|
||||
// TODO EBO Replace runtimeScene to instanceContainer.
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Leaderboards');
|
||||
export namespace evtTools {
|
||||
@@ -16,8 +17,10 @@ namespace gdjs {
|
||||
lastScoreSavingSucceededAt: number | null;
|
||||
currentlySavingScore: number | null;
|
||||
currentlySavingPlayerName: string | null;
|
||||
currentlySavingPlayerId: string | null;
|
||||
lastSavedScore: number | null;
|
||||
lastSavedPlayerName: string | null;
|
||||
lastSavedPlayerId: string | null;
|
||||
lastSaveError: string | null;
|
||||
isScoreSaving: boolean;
|
||||
hasScoreBeenSaved: boolean;
|
||||
@@ -28,49 +31,79 @@ namespace gdjs {
|
||||
this.lastScoreSavingSucceededAt = null;
|
||||
this.currentlySavingScore = null;
|
||||
this.currentlySavingPlayerName = null;
|
||||
this.currentlySavingPlayerId = null;
|
||||
this.lastSavedScore = null;
|
||||
this.lastSavedPlayerName = null;
|
||||
this.lastSavedPlayerId = null;
|
||||
this.lastSaveError = null;
|
||||
this.isScoreSaving = false;
|
||||
this.hasScoreBeenSaved = false;
|
||||
this.hasScoreSavingErrored = false;
|
||||
}
|
||||
|
||||
isSameAsLastScore(playerName: string, score: number): boolean {
|
||||
isSameAsLastScore({
|
||||
playerName,
|
||||
playerId,
|
||||
score,
|
||||
}: {
|
||||
playerName?: string;
|
||||
playerId?: string;
|
||||
score: number;
|
||||
}): boolean {
|
||||
return (
|
||||
this.lastSavedPlayerName === playerName &&
|
||||
((!!playerName && this.lastSavedPlayerName === playerName) ||
|
||||
(!!playerId && this.lastSavedPlayerId === playerId)) &&
|
||||
this.lastSavedScore === score
|
||||
);
|
||||
}
|
||||
|
||||
isAlreadySavingThisScore(playerName: string, score: number): boolean {
|
||||
isAlreadySavingThisScore({
|
||||
playerName,
|
||||
playerId,
|
||||
score,
|
||||
}: {
|
||||
playerName?: string;
|
||||
playerId?: string;
|
||||
score: number;
|
||||
}): boolean {
|
||||
return (
|
||||
((!!playerName && this.currentlySavingPlayerName === playerName) ||
|
||||
(!!playerId && this.currentlySavingPlayerId === playerId)) &&
|
||||
this.isScoreSaving &&
|
||||
this.currentlySavingPlayerName === playerName &&
|
||||
this.currentlySavingScore === score
|
||||
);
|
||||
}
|
||||
|
||||
isTooSoonToSaveAnotherScore(): boolean {
|
||||
return (
|
||||
!!this.lastScoreSavingSucceededAt &&
|
||||
Date.now() - this.lastScoreSavingSucceededAt < 500
|
||||
!!this.lastScoreSavingStartedAt &&
|
||||
Date.now() - this.lastScoreSavingStartedAt < 500
|
||||
);
|
||||
}
|
||||
|
||||
startSaving(playerName: string, score: number): void {
|
||||
startSaving({
|
||||
playerName,
|
||||
playerId,
|
||||
score,
|
||||
}: {
|
||||
playerName?: string;
|
||||
playerId?: string;
|
||||
score: number;
|
||||
}): void {
|
||||
this.lastScoreSavingStartedAt = Date.now();
|
||||
this.isScoreSaving = true;
|
||||
this.hasScoreBeenSaved = false;
|
||||
this.hasScoreSavingErrored = false;
|
||||
this.currentlySavingScore = score;
|
||||
this.currentlySavingPlayerName = playerName;
|
||||
if (playerName) this.currentlySavingPlayerName = playerName;
|
||||
if (playerId) this.currentlySavingPlayerId = playerId;
|
||||
}
|
||||
|
||||
closeSaving(): void {
|
||||
this.lastScoreSavingSucceededAt = Date.now();
|
||||
this.lastSavedScore = this.currentlySavingScore;
|
||||
this.lastSavedPlayerName = this.currentlySavingPlayerName;
|
||||
this.lastSavedPlayerId = this.currentlySavingPlayerId;
|
||||
this.isScoreSaving = false;
|
||||
this.hasScoreBeenSaved = true;
|
||||
}
|
||||
@@ -155,50 +188,29 @@ namespace gdjs {
|
||||
return lastScoreSavingState;
|
||||
};
|
||||
|
||||
export const savePlayerScore = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
leaderboardId: string,
|
||||
score: float,
|
||||
playerName: string
|
||||
) {
|
||||
let scoreSavingState: ScoreSavingState;
|
||||
if (_scoreSavingStateByLeaderboard[leaderboardId]) {
|
||||
scoreSavingState = _scoreSavingStateByLeaderboard[leaderboardId];
|
||||
if (scoreSavingState.isAlreadySavingThisScore(playerName, score)) {
|
||||
logger.warn(
|
||||
'There is already a request to save with this player name and this score. Ignoring this one.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (scoreSavingState.isSameAsLastScore(playerName, score)) {
|
||||
logger.warn(
|
||||
'The player and score to be sent are the same as previous one. Ignoring this one.'
|
||||
);
|
||||
const errorCode = 'SAME_AS_PREVIOUS';
|
||||
scoreSavingState.setError(errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (scoreSavingState.isTooSoonToSaveAnotherScore()) {
|
||||
logger.warn(
|
||||
'Last entry was sent too little time ago. Ignoring this one.'
|
||||
);
|
||||
const errorCode = 'TOO_FAST';
|
||||
scoreSavingState.setError(errorCode);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
scoreSavingState = new ScoreSavingState();
|
||||
_scoreSavingStateByLeaderboard[leaderboardId] = scoreSavingState;
|
||||
}
|
||||
|
||||
scoreSavingState.startSaving(playerName, score);
|
||||
|
||||
const baseUrl = 'https://api.gdevelop-app.com/play';
|
||||
const saveScore = function ({
|
||||
leaderboardId,
|
||||
playerName,
|
||||
authenticatedPlayerData,
|
||||
score,
|
||||
scoreSavingState,
|
||||
runtimeScene,
|
||||
}: {
|
||||
leaderboardId: string;
|
||||
playerName?: string | null;
|
||||
authenticatedPlayerData?: { playerId: string; playerToken: string };
|
||||
score: number;
|
||||
scoreSavingState: ScoreSavingState;
|
||||
runtimeScene: gdjs.RuntimeScene;
|
||||
}) {
|
||||
const rootApi = runtimeScene
|
||||
.getGame()
|
||||
.isUsingGDevelopDevelopmentEnvironment()
|
||||
? 'https://api-dev.gdevelop.io'
|
||||
: 'https://api.gdevelop.io';
|
||||
const baseUrl = `${rootApi}/play`;
|
||||
const game = runtimeScene.getGame();
|
||||
const payload = JSON.stringify({
|
||||
playerName: formatPlayerName(playerName),
|
||||
const payloadObject = {
|
||||
score: score,
|
||||
sessionId: game.getSessionId(),
|
||||
clientPlayerId: game.getPlayerId(),
|
||||
@@ -206,18 +218,28 @@ namespace gdjs {
|
||||
typeof window !== 'undefined' && (window as any).location
|
||||
? (window as any).location.href
|
||||
: '',
|
||||
});
|
||||
fetch(
|
||||
`${baseUrl}/game/${gdjs.projectData.properties.projectUuid}/leaderboard/${leaderboardId}/entry`,
|
||||
{
|
||||
body: payload,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Digest: computeDigest(payload),
|
||||
},
|
||||
}
|
||||
).then(
|
||||
};
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
let leaderboardEntryCreationUrl = `${baseUrl}/game/${gdjs.projectData.properties.projectUuid}/leaderboard/${leaderboardId}/entry`;
|
||||
if (authenticatedPlayerData) {
|
||||
headers[
|
||||
'Authorization'
|
||||
] = `player-game-token ${authenticatedPlayerData.playerToken}`;
|
||||
leaderboardEntryCreationUrl += `?playerId=${authenticatedPlayerData.playerId}`;
|
||||
} else {
|
||||
// In case playerName is empty or undefined, the formatting will generate a random name.
|
||||
payloadObject['playerName'] = formatPlayerName(playerName);
|
||||
}
|
||||
const payload = JSON.stringify(payloadObject);
|
||||
headers['Digest'] = computeDigest(payload);
|
||||
|
||||
fetch(leaderboardEntryCreationUrl, {
|
||||
body: payload,
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
}).then(
|
||||
(response) => {
|
||||
if (!response.ok) {
|
||||
const errorCode = response.status.toString();
|
||||
@@ -250,6 +272,139 @@ namespace gdjs {
|
||||
);
|
||||
};
|
||||
|
||||
export const savePlayerScore = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
leaderboardId: string,
|
||||
score: float,
|
||||
playerName: string
|
||||
) {
|
||||
let scoreSavingState: ScoreSavingState;
|
||||
if (_scoreSavingStateByLeaderboard[leaderboardId]) {
|
||||
scoreSavingState = _scoreSavingStateByLeaderboard[leaderboardId];
|
||||
let shouldStartSaving = true;
|
||||
if (
|
||||
shouldStartSaving &&
|
||||
scoreSavingState.isAlreadySavingThisScore({ playerName, score })
|
||||
) {
|
||||
logger.warn(
|
||||
'There is already a request to save with this player name and this score. Ignoring this one.'
|
||||
);
|
||||
shouldStartSaving = false;
|
||||
}
|
||||
|
||||
if (
|
||||
shouldStartSaving &&
|
||||
scoreSavingState.isSameAsLastScore({ playerName, score })
|
||||
) {
|
||||
logger.warn(
|
||||
'The player and score to be sent are the same as previous one. Ignoring this one.'
|
||||
);
|
||||
scoreSavingState.setError('SAME_AS_PREVIOUS');
|
||||
shouldStartSaving = false;
|
||||
}
|
||||
|
||||
if (
|
||||
shouldStartSaving &&
|
||||
scoreSavingState.isTooSoonToSaveAnotherScore()
|
||||
) {
|
||||
logger.warn(
|
||||
'Last entry was sent too little time ago. Ignoring this one.'
|
||||
);
|
||||
scoreSavingState.setError('TOO_FAST');
|
||||
shouldStartSaving = false;
|
||||
// Set the starting time to cancel all the following attempts that
|
||||
// are started too early after this one.
|
||||
scoreSavingState.lastScoreSavingStartedAt = Date.now();
|
||||
}
|
||||
if (!shouldStartSaving) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
scoreSavingState = new ScoreSavingState();
|
||||
_scoreSavingStateByLeaderboard[leaderboardId] = scoreSavingState;
|
||||
}
|
||||
|
||||
scoreSavingState.startSaving({ playerName, score });
|
||||
|
||||
saveScore({
|
||||
leaderboardId,
|
||||
playerName,
|
||||
score,
|
||||
scoreSavingState,
|
||||
runtimeScene,
|
||||
});
|
||||
};
|
||||
|
||||
export const saveConnectedPlayerScore = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
leaderboardId: string,
|
||||
score: float
|
||||
) {
|
||||
let scoreSavingState: ScoreSavingState;
|
||||
const playerId = gdjs.playerAuthentication.getUserId();
|
||||
const playerToken = gdjs.playerAuthentication.getUserToken();
|
||||
if (!playerId || !playerToken) {
|
||||
logger.warn(
|
||||
'Cannot save a score for a connected player if the player is not connected.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (_scoreSavingStateByLeaderboard[leaderboardId]) {
|
||||
scoreSavingState = _scoreSavingStateByLeaderboard[leaderboardId];
|
||||
let shouldStartSaving = true;
|
||||
if (
|
||||
shouldStartSaving &&
|
||||
scoreSavingState.isAlreadySavingThisScore({ playerId, score })
|
||||
) {
|
||||
logger.warn(
|
||||
'There is already a request to save with this player ID and this score. Ignoring this one.'
|
||||
);
|
||||
shouldStartSaving = false;
|
||||
}
|
||||
|
||||
if (
|
||||
shouldStartSaving &&
|
||||
scoreSavingState.isSameAsLastScore({ playerId, score })
|
||||
) {
|
||||
logger.warn(
|
||||
'The player and score to be sent are the same as previous one. Ignoring this one.'
|
||||
);
|
||||
scoreSavingState.setError('SAME_AS_PREVIOUS');
|
||||
shouldStartSaving = false;
|
||||
}
|
||||
|
||||
if (
|
||||
shouldStartSaving &&
|
||||
scoreSavingState.isTooSoonToSaveAnotherScore()
|
||||
) {
|
||||
logger.warn(
|
||||
'Last entry was sent too little time ago. Ignoring this one.'
|
||||
);
|
||||
scoreSavingState.setError('TOO_FAST');
|
||||
shouldStartSaving = false;
|
||||
// Set the starting time to cancel all the following attempts that
|
||||
// are started too early after this one.
|
||||
scoreSavingState.lastScoreSavingStartedAt = Date.now();
|
||||
}
|
||||
if (!shouldStartSaving) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
scoreSavingState = new ScoreSavingState();
|
||||
_scoreSavingStateByLeaderboard[leaderboardId] = scoreSavingState;
|
||||
}
|
||||
|
||||
scoreSavingState.startSaving({ playerId, score });
|
||||
|
||||
saveScore({
|
||||
leaderboardId,
|
||||
authenticatedPlayerData: { playerId, playerToken },
|
||||
score,
|
||||
scoreSavingState,
|
||||
runtimeScene,
|
||||
});
|
||||
};
|
||||
|
||||
export const isSaving = function (leaderboardId?: string): boolean {
|
||||
if (leaderboardId) {
|
||||
return _scoreSavingStateByLeaderboard[leaderboardId]
|
||||
@@ -315,7 +470,9 @@ namespace gdjs {
|
||||
: 'NO_DATA_ERROR';
|
||||
};
|
||||
|
||||
export const formatPlayerName = function (rawName: string): string {
|
||||
export const formatPlayerName = function (
|
||||
rawName?: string | null
|
||||
): string {
|
||||
if (
|
||||
!rawName ||
|
||||
typeof rawName !== 'string' ||
|
||||
@@ -512,7 +669,12 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
const gameId = gdjs.projectData.properties.projectUuid;
|
||||
const targetUrl = `https://liluo.io/games/${gameId}/leaderboard/${leaderboardId}?inGameEmbedded=true`;
|
||||
const isDev = runtimeScene
|
||||
.getGame()
|
||||
.isUsingGDevelopDevelopmentEnvironment();
|
||||
const targetUrl = `https://liluo.io/games/${gameId}/leaderboard/${leaderboardId}?inGameEmbedded=true${
|
||||
isDev ? '&dev=true' : ''
|
||||
}`;
|
||||
checkLeaderboardAvailability(targetUrl).then(
|
||||
(isAvailable) => {
|
||||
if (leaderboardId !== _requestedLeaderboardId) {
|
||||
|
@@ -32,7 +32,8 @@ module.exports = {
|
||||
'This provides a light object, and a behavior to mark other objects as being obstacles for the lights. This is a great way to create a special atmosphere to your game, along with effects, make it more realistic or to create gameplays based on lights.',
|
||||
'Harsimran Virk',
|
||||
'MIT'
|
||||
);
|
||||
)
|
||||
.setCategory('Visual effect');
|
||||
|
||||
const lightObstacleBehavior = new gd.BehaviorJsImplementation();
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
@@ -195,7 +196,7 @@ module.exports = {
|
||||
.setIncludeFile('Extensions/Lighting/lightruntimeobject.js')
|
||||
.addIncludeFile('Extensions/Lighting/lightruntimeobject-pixi-renderer.js')
|
||||
.addIncludeFile('Extensions/Lighting/lightobstacleruntimebehavior.js')
|
||||
.setCategoryFullName(_('Lights'));
|
||||
.setCategoryFullName(_('Visual effect'));
|
||||
|
||||
object
|
||||
.addAction(
|
||||
@@ -370,7 +371,7 @@ module.exports = {
|
||||
RenderedLightObjectInstance.getThumbnail = function (
|
||||
project,
|
||||
resourcesLoader,
|
||||
object
|
||||
objectConfiguration
|
||||
) {
|
||||
return 'CppPlatform/Extensions/lightIcon32.png';
|
||||
};
|
||||
|
@@ -4,26 +4,26 @@ namespace gdjs {
|
||||
export class LightObstaclesManager {
|
||||
_obstacleRBush: any;
|
||||
|
||||
constructor(runtimeScene: gdjs.RuntimeScene) {
|
||||
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
this._obstacleRBush = new rbush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the light obstacles manager of a scene.
|
||||
* Get the light obstacles manager of an instance container.
|
||||
*/
|
||||
static getManager(
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): gdjs.LightObstaclesManager {
|
||||
// @ts-ignore
|
||||
if (!runtimeScene._lightObstaclesManager) {
|
||||
if (!instanceContainer._lightObstaclesManager) {
|
||||
// Create the shared manager if necessary.
|
||||
// @ts-ignore
|
||||
runtimeScene._lightObstaclesManager = new gdjs.LightObstaclesManager(
|
||||
runtimeScene
|
||||
instanceContainer._lightObstaclesManager = new gdjs.LightObstaclesManager(
|
||||
instanceContainer
|
||||
);
|
||||
}
|
||||
// @ts-ignore
|
||||
return runtimeScene._lightObstaclesManager;
|
||||
return instanceContainer._lightObstaclesManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,15 +92,15 @@ namespace gdjs {
|
||||
_registeredInManager: boolean = false;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
behaviorData,
|
||||
owner: gdjs.RuntimeObject
|
||||
) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
this._manager = LightObstaclesManager.getManager(runtimeScene);
|
||||
super(instanceContainer, behaviorData, owner);
|
||||
this._manager = LightObstaclesManager.getManager(instanceContainer);
|
||||
}
|
||||
|
||||
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
// Make sure the obstacle is or is not in the obstacles manager.
|
||||
if (!this.activated() && this._registeredInManager) {
|
||||
this._manager.removeObstacle(this);
|
||||
|
@@ -7,7 +7,7 @@ namespace gdjs {
|
||||
*/
|
||||
export class LightRuntimeObjectPixiRenderer {
|
||||
_object: gdjs.LightRuntimeObject;
|
||||
_runtimeScene: gdjs.RuntimeScene;
|
||||
_instanceContainer: gdjs.RuntimeInstanceContainer;
|
||||
_manager: gdjs.LightObstaclesManager;
|
||||
_radius: number;
|
||||
_color: [number, number, number];
|
||||
@@ -30,10 +30,10 @@ namespace gdjs {
|
||||
|
||||
constructor(
|
||||
runtimeObject: gdjs.LightRuntimeObject,
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
this._object = runtimeObject;
|
||||
this._runtimeScene = runtimeScene;
|
||||
this._instanceContainer = instanceContainer;
|
||||
this._manager = runtimeObject.getObstaclesManager();
|
||||
this._radius = runtimeObject.getRadius();
|
||||
const objectColor = runtimeObject._color;
|
||||
@@ -57,14 +57,14 @@ namespace gdjs {
|
||||
]);
|
||||
this._indexBuffer = new Uint16Array([0, 1, 2, 0, 2, 3]);
|
||||
this.updateMesh();
|
||||
this._isPreview = runtimeScene.getGame().isPreview();
|
||||
this._isPreview = instanceContainer.getGame().isPreview();
|
||||
this._lightBoundingPoly = gdjs.Polygon.createRectangle(0, 0);
|
||||
|
||||
this.updateDebugMode();
|
||||
|
||||
// Objects will be added in lighting layer, this is just to maintain consistency.
|
||||
if (this._light) {
|
||||
runtimeScene
|
||||
instanceContainer
|
||||
.getLayer('')
|
||||
.getRenderer()
|
||||
.addRendererObject(
|
||||
@@ -198,7 +198,7 @@ namespace gdjs {
|
||||
const texture = this._object.getTexture();
|
||||
this._texture =
|
||||
texture !== ''
|
||||
? (this._runtimeScene
|
||||
? (this._instanceContainer
|
||||
.getGame()
|
||||
.getImageManager() as gdjs.PixiImageManager).getPIXITexture(
|
||||
texture
|
||||
|
@@ -27,7 +27,7 @@ namespace gdjs {
|
||||
_texture: string;
|
||||
_obstaclesManager: gdjs.LightObstaclesManager;
|
||||
_renderer: gdjs.LightRuntimeObjectRenderer;
|
||||
_runtimeScene: gdjs.RuntimeScene;
|
||||
_instanceContainer: gdjs.RuntimeScene;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
@@ -43,7 +43,7 @@ namespace gdjs {
|
||||
runtimeScene
|
||||
);
|
||||
this._renderer = new gdjs.LightRuntimeObjectRenderer(this, runtimeScene);
|
||||
this._runtimeScene = runtimeScene;
|
||||
this._instanceContainer = runtimeScene;
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
this.onCreated();
|
||||
|
@@ -12,15 +12,17 @@ namespace gdjs {
|
||||
/**
|
||||
* Get the links manager of a scene.
|
||||
*/
|
||||
static getManager(runtimeScene: gdjs.RuntimeScene): gdjs.LinksManager {
|
||||
static getManager(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): gdjs.LinksManager {
|
||||
// @ts-ignore
|
||||
if (!runtimeScene.linkedObjectsManager) {
|
||||
if (!instanceContainer.linkedObjectsManager) {
|
||||
//Create the shared manager if necessary.
|
||||
// @ts-ignore
|
||||
runtimeScene.linkedObjectsManager = new gdjs.LinksManager();
|
||||
instanceContainer.linkedObjectsManager = new gdjs.LinksManager();
|
||||
}
|
||||
// @ts-ignore
|
||||
return runtimeScene.linkedObjectsManager;
|
||||
return instanceContainer.linkedObjectsManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,44 +186,50 @@ namespace gdjs {
|
||||
|
||||
export namespace evtTools {
|
||||
export namespace linkedObjects {
|
||||
gdjs.registerObjectDeletedFromSceneCallback(function (runtimeScene, obj) {
|
||||
LinksManager.getManager(runtimeScene).removeAllLinksOf(obj);
|
||||
gdjs.registerObjectDeletedFromSceneCallback(function (
|
||||
instanceContainer,
|
||||
obj
|
||||
) {
|
||||
LinksManager.getManager(instanceContainer).removeAllLinksOf(obj);
|
||||
});
|
||||
|
||||
export const linkObjects = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
objA: gdjs.RuntimeObject,
|
||||
objB: gdjs.RuntimeObject
|
||||
) {
|
||||
if (objA === null || objB === null) {
|
||||
return;
|
||||
}
|
||||
LinksManager.getManager(runtimeScene).linkObjects(objA, objB);
|
||||
LinksManager.getManager(instanceContainer).linkObjects(objA, objB);
|
||||
};
|
||||
|
||||
export const removeLinkBetween = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
objA: gdjs.RuntimeObject,
|
||||
objB: gdjs.RuntimeObject
|
||||
) {
|
||||
if (objA === null || objB === null) {
|
||||
return;
|
||||
}
|
||||
LinksManager.getManager(runtimeScene).removeLinkBetween(objA, objB);
|
||||
LinksManager.getManager(instanceContainer).removeLinkBetween(
|
||||
objA,
|
||||
objB
|
||||
);
|
||||
};
|
||||
|
||||
export const removeAllLinksOf = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
objA: gdjs.RuntimeObject
|
||||
) {
|
||||
if (objA === null) {
|
||||
return;
|
||||
}
|
||||
LinksManager.getManager(runtimeScene).removeAllLinksOf(objA);
|
||||
LinksManager.getManager(instanceContainer).removeAllLinksOf(objA);
|
||||
};
|
||||
|
||||
export const pickObjectsLinkedTo = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
objectsLists: Hashtable<gdjs.RuntimeObject[]>,
|
||||
obj: gdjs.RuntimeObject,
|
||||
eventsFunctionContext: EventsFunctionContext | undefined
|
||||
@@ -230,7 +238,7 @@ namespace gdjs {
|
||||
return false;
|
||||
}
|
||||
const linkedObjectMap = LinksManager.getManager(
|
||||
runtimeScene
|
||||
instanceContainer
|
||||
)._getMapOfObjectsLinkedWith(obj);
|
||||
|
||||
let pickedSomething = false;
|
||||
@@ -273,7 +281,7 @@ namespace gdjs {
|
||||
// avoid running an intersection with the picked objects later.
|
||||
let objectCount = 0;
|
||||
for (const objectName of parentEventPickedObjectNames) {
|
||||
objectCount += runtimeScene.getObjects(objectName).length;
|
||||
objectCount += instanceContainer.getObjects(objectName)!.length;
|
||||
}
|
||||
|
||||
if (parentEventPickedObjects.length === objectCount) {
|
||||
|
@@ -24,6 +24,8 @@ void DeclarePanelSpriteObjectExtension(gd::PlatformExtension& extension) {
|
||||
"Victor Levasseur and Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/objects/panel_sprite");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Panel Sprite (9-patch) Object"))
|
||||
.SetIcon("CppPlatform/Extensions/PanelSpriteIcon.png");
|
||||
|
||||
gd::ObjectMetadata& obj =
|
||||
extension
|
||||
|
@@ -20,12 +20,12 @@ namespace gdjs {
|
||||
|
||||
constructor(
|
||||
runtimeObject: gdjs.PanelSpriteRuntimeObject,
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
textureName: string,
|
||||
tiled: boolean
|
||||
) {
|
||||
this._object = runtimeObject;
|
||||
const texture = (runtimeScene
|
||||
const texture = (instanceContainer
|
||||
.getGame()
|
||||
.getImageManager() as gdjs.PixiImageManager).getPIXITexture(
|
||||
textureName
|
||||
@@ -58,14 +58,14 @@ namespace gdjs {
|
||||
];
|
||||
|
||||
//Bottom-Right
|
||||
this.setTexture(textureName, runtimeScene);
|
||||
this.setTexture(textureName, instanceContainer);
|
||||
this._spritesContainer.removeChildren();
|
||||
this._spritesContainer.addChild(this._centerSprite);
|
||||
for (let i = 0; i < this._borderSprites.length; ++i) {
|
||||
this._spritesContainer.addChild(this._borderSprites[i]);
|
||||
}
|
||||
this._wrapperContainer.addChild(this._spritesContainer);
|
||||
runtimeScene
|
||||
instanceContainer
|
||||
.getLayer('')
|
||||
.getRenderer()
|
||||
.addRendererObject(this._wrapperContainer, runtimeObject.getZOrder());
|
||||
@@ -188,12 +188,19 @@ namespace gdjs {
|
||||
this._spritesContainer.cacheAsBitmap = false;
|
||||
}
|
||||
|
||||
setTexture(textureName, runtimeScene): void {
|
||||
setTexture(
|
||||
textureName: string,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
const obj = this._object;
|
||||
const texture = runtimeScene
|
||||
// @ts-ignore
|
||||
const texture = instanceContainer
|
||||
.getGame()
|
||||
.getImageManager()
|
||||
.getPIXITexture(textureName);
|
||||
.getPIXITexture(textureName) as PIXI.BaseTexture<
|
||||
PIXI.Resource,
|
||||
PIXI.IAutoDetectOptions
|
||||
>;
|
||||
this._textureWidth = texture.width;
|
||||
this._textureHeight = texture.height;
|
||||
|
||||
|
@@ -43,14 +43,14 @@ namespace gdjs {
|
||||
_renderer: gdjs.PanelSpriteRuntimeObjectRenderer;
|
||||
|
||||
/**
|
||||
* @param runtimeScene The scene the object belongs to.
|
||||
* @param instanceContainer The container the object belongs to.
|
||||
* @param panelSpriteObjectData The initial properties of the object
|
||||
*/
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
panelSpriteObjectData: PanelSpriteObjectData
|
||||
) {
|
||||
super(runtimeScene, panelSpriteObjectData);
|
||||
super(instanceContainer, panelSpriteObjectData);
|
||||
this._rBorder = panelSpriteObjectData.rightMargin;
|
||||
this._lBorder = panelSpriteObjectData.leftMargin;
|
||||
this._tBorder = panelSpriteObjectData.topMargin;
|
||||
@@ -60,7 +60,7 @@ namespace gdjs {
|
||||
this._height = panelSpriteObjectData.height;
|
||||
this._renderer = new gdjs.PanelSpriteRuntimeObjectRenderer(
|
||||
this,
|
||||
runtimeScene,
|
||||
instanceContainer,
|
||||
panelSpriteObjectData.texture,
|
||||
panelSpriteObjectData.tiled
|
||||
);
|
||||
@@ -100,7 +100,7 @@ namespace gdjs {
|
||||
updateTexture = true;
|
||||
}
|
||||
if (updateTexture) {
|
||||
this.setTexture(newObjectData.texture, this._runtimeScene);
|
||||
this.setTexture(newObjectData.texture, this.getRuntimeScene());
|
||||
}
|
||||
if (oldObjectData.tiled !== newObjectData.tiled) {
|
||||
return false;
|
||||
@@ -112,8 +112,8 @@ namespace gdjs {
|
||||
return this._renderer.getRendererObject();
|
||||
}
|
||||
|
||||
onDestroyFromScene(runtimeScene): void {
|
||||
super.onDestroyFromScene(runtimeScene);
|
||||
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
super.onDestroyFromScene(instanceContainer);
|
||||
// @ts-ignore
|
||||
if (this._renderer.onDestroy) {
|
||||
// @ts-ignore
|
||||
@@ -121,7 +121,7 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
update(runtimeScene: gdjs.RuntimeScene): void {
|
||||
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
this._renderer.ensureUpToDate();
|
||||
}
|
||||
|
||||
@@ -156,10 +156,13 @@ namespace gdjs {
|
||||
/**
|
||||
* Set the texture of the panel sprite.
|
||||
* @param textureName The name of the texture.
|
||||
* @param runtimeScene The scene the object lives in.
|
||||
* @param instanceContainer The container the object lives in.
|
||||
*/
|
||||
setTexture(textureName: string, runtimeScene: gdjs.RuntimeScene): void {
|
||||
this._renderer.setTexture(textureName, runtimeScene);
|
||||
setTexture(
|
||||
textureName: string,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
this._renderer.setTexture(textureName, instanceContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,7 +199,7 @@ namespace gdjs {
|
||||
|
||||
this._width = width;
|
||||
this._renderer.updateWidth();
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,7 +211,7 @@ namespace gdjs {
|
||||
|
||||
this._height = height;
|
||||
this._renderer.updateHeight();
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -25,7 +25,10 @@ void DeclareParticleSystemExtension(gd::PlatformExtension& extension) {
|
||||
"explosions, magical effects, etc...",
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("Visual effect")
|
||||
.SetExtensionHelpPath("/objects/particles_emitter");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Particle system"))
|
||||
.SetIcon("CppPlatform/Extensions/particleSystemicon.png");
|
||||
|
||||
// Declaration of all objects available
|
||||
{
|
||||
@@ -37,7 +40,7 @@ void DeclareParticleSystemExtension(gd::PlatformExtension& extension) {
|
||||
_("Displays a large number of small particles to create visual "
|
||||
"effects."),
|
||||
"CppPlatform/Extensions/particleSystemicon.png")
|
||||
.SetCategoryFullName(_("General"));
|
||||
.SetCategoryFullName(_("Visual effect"));
|
||||
|
||||
// Declaration is too big to be compiled by GCC in one file, unless you have
|
||||
// 4GB+ ram. :/
|
||||
|
@@ -43,9 +43,7 @@ void ExtensionSubDeclaration1(gd::ObjectMetadata& obj) {
|
||||
"CppPlatform/Extensions/particleSystemicon24.png",
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardOperatorParameters("number")
|
||||
.SetFunctionName("SetAngle")
|
||||
.SetGetter("GetAngle");
|
||||
.UseStandardOperatorParameters("number");
|
||||
|
||||
obj.AddCondition("EmitterAngle",
|
||||
_("Emission angle"),
|
||||
@@ -65,7 +63,8 @@ void ExtensionSubDeclaration1(gd::ObjectMetadata& obj) {
|
||||
"CppPlatform/Extensions/particleSystemicon24.png",
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardOperatorParameters("number");
|
||||
.UseStandardOperatorParameters("number")
|
||||
.SetHidden(); // Angle A is not used.
|
||||
|
||||
obj.AddCondition("EmitterAngleA",
|
||||
_("Emission angle 1"),
|
||||
@@ -75,7 +74,8 @@ void ExtensionSubDeclaration1(gd::ObjectMetadata& obj) {
|
||||
"CppPlatform/Extensions/particleSystemicon24.png",
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardRelationalOperatorParameters("number");
|
||||
.UseStandardRelationalOperatorParameters("number")
|
||||
.SetHidden(); // Angle A is not used.
|
||||
|
||||
obj.AddAction("EmitterAngleB",
|
||||
_("Emission angle 2"),
|
||||
@@ -85,7 +85,8 @@ void ExtensionSubDeclaration1(gd::ObjectMetadata& obj) {
|
||||
"CppPlatform/Extensions/particleSystemicon24.png",
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardOperatorParameters("number");
|
||||
.UseStandardOperatorParameters("number")
|
||||
.SetHidden(); // Angle B is the same as cone spray angle
|
||||
|
||||
obj.AddCondition("EmitterAngleB",
|
||||
_("Emission angle 2"),
|
||||
@@ -95,7 +96,8 @@ void ExtensionSubDeclaration1(gd::ObjectMetadata& obj) {
|
||||
"CppPlatform/Extensions/particleSystemicon24.png",
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardRelationalOperatorParameters("number");
|
||||
.UseStandardRelationalOperatorParameters("number")
|
||||
.SetHidden(); // Angle B is the same as cone spray angle
|
||||
|
||||
obj.AddAction(
|
||||
"ConeSprayAngle",
|
||||
|
@@ -7,6 +7,7 @@ This project is released under the MIT License.
|
||||
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
|
||||
|
||||
#include "Extension.h"
|
||||
#include "ParticleEmitterObject.h"
|
||||
@@ -207,48 +208,6 @@ void ExtensionSubDeclaration2(gd::ObjectMetadata& obj) {
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardRelationalOperatorParameters("number");
|
||||
|
||||
obj.AddAction(
|
||||
"ParticleAngle1",
|
||||
_("Angle, parameter 1"),
|
||||
_("Modify parameter 1 of the angle of particles."),
|
||||
_("the parameter 1 of angle"),
|
||||
_("Common"),
|
||||
"CppPlatform/Extensions/particleSystemicon24.png",
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardOperatorParameters("number");
|
||||
|
||||
obj.AddCondition("ParticleAngle1",
|
||||
_("Angle, parameter 1"),
|
||||
_("Compare parameter 1 of the angle of particles."),
|
||||
_("the parameter 1 of angle"),
|
||||
_("Common"),
|
||||
"CppPlatform/Extensions/particleSystemicon24.png",
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardRelationalOperatorParameters("number");
|
||||
|
||||
obj.AddAction(
|
||||
"ParticleAngle2",
|
||||
_("Angle, parameter 2"),
|
||||
_("Modify parameter 2 of the angle of particles"),
|
||||
_("the parameter 2 of angle"),
|
||||
_("Common"),
|
||||
"CppPlatform/Extensions/particleSystemicon24.png",
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardOperatorParameters("number");
|
||||
|
||||
obj.AddCondition("ParticleAngle2",
|
||||
_("Angle, parameter 2"),
|
||||
_("Compare parameter 2 of the angle of particles."),
|
||||
_("the parameter 2 of angle"),
|
||||
_("Common"),
|
||||
"CppPlatform/Extensions/particleSystemicon24.png",
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardRelationalOperatorParameters("number");
|
||||
|
||||
obj.AddAction("ParticleAlpha1",
|
||||
_("Start opacity"),
|
||||
_("Modify the start opacity of particles."),
|
||||
@@ -301,4 +260,59 @@ void ExtensionSubDeclaration2(gd::ObjectMetadata& obj) {
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter");
|
||||
|
||||
obj.AddExpressionAndConditionAndAction(
|
||||
"number",
|
||||
"ParticleRotationMinSpeed",
|
||||
_("Particle rotation min speed"),
|
||||
_("the minimum rotation speed of the particles"),
|
||||
_("the particles minimum rotation speed"),
|
||||
_("Common"),
|
||||
"CppPlatform/Extensions/particleSystemicon24.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardParameters("number")
|
||||
.MarkAsAdvanced()
|
||||
.SetFunctionName("setParticleRotationMinSpeed")
|
||||
.SetGetter("getParticleRotationMinSpeed");
|
||||
|
||||
obj.AddExpressionAndConditionAndAction(
|
||||
"number",
|
||||
"ParticleRotationMaxSpeed",
|
||||
_("Particle rotation max speed"),
|
||||
_("the maximum rotation speed of the particles"),
|
||||
_("the particles maximum rotation speed"),
|
||||
_("Common"),
|
||||
"CppPlatform/Extensions/particleSystemicon24.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardParameters("number")
|
||||
.MarkAsAdvanced()
|
||||
.SetFunctionName("setParticleRotationMaxSpeed")
|
||||
.SetGetter("getParticleRotationMaxSpeed");
|
||||
|
||||
obj.AddExpressionAndConditionAndAction(
|
||||
"number",
|
||||
"MaxParticlesCount",
|
||||
_("Number of displayed particles"),
|
||||
_("the maximum number of displayed particles"),
|
||||
_("the maximum number of displayed particles"),
|
||||
_("Common"),
|
||||
"CppPlatform/Extensions/particleSystemicon24.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardParameters("number")
|
||||
.SetFunctionName("setMaxParticlesCount")
|
||||
.SetGetter("getMaxParticlesCount");
|
||||
|
||||
obj.AddExpressionAndConditionAndAction(
|
||||
"boolean",
|
||||
"AdditiveRendering",
|
||||
_("Activate particles additive rendering"),
|
||||
_("the particles additive rendering is activated"),
|
||||
_("displaying particles with additive rendering activated"),
|
||||
_("Common"),
|
||||
"CppPlatform/Extensions/particleSystemicon24.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter")
|
||||
.UseStandardParameters("boolean")
|
||||
.MarkAsAdvanced()
|
||||
.SetFunctionName("setAdditiveRendering")
|
||||
.SetGetter("getAdditiveRendering");
|
||||
|
||||
}
|
||||
|
@@ -5,10 +5,9 @@ Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "Extension.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
|
||||
#include "Extension.h"
|
||||
#include "ParticleEmitterObject.h"
|
||||
|
||||
/**
|
||||
@@ -241,12 +240,20 @@ void ExtensionSubDeclaration3(gd::ObjectMetadata& obj) {
|
||||
_("Emission angle A"),
|
||||
_("Advanced"),
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter", false);
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter", false)
|
||||
.SetHidden();
|
||||
obj.AddExpression("EmitterAngleB",
|
||||
_("Emission angle B"),
|
||||
_("Emission angle B"),
|
||||
_("Advanced"),
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter", false)
|
||||
.SetHidden();
|
||||
obj.AddExpression("ConeSprayAngle",
|
||||
_("Angle of the spray cone"),
|
||||
_("Angle of the spray cone"),
|
||||
_("Common"),
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter", false);
|
||||
obj.AddExpression("ZoneRadius",
|
||||
_("Radius of emission zone"),
|
||||
@@ -350,17 +357,4 @@ void ExtensionSubDeclaration3(gd::ObjectMetadata& obj) {
|
||||
_("Setup"),
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter", false);
|
||||
obj.AddExpression("ParticleAngle1",
|
||||
_("Parameter 1 of angle"),
|
||||
_("Parameter 1 of angle"),
|
||||
_("Setup"),
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter", false);
|
||||
obj.AddExpression("ParticleAngle2",
|
||||
_("Parameter 2 of angle"),
|
||||
_("Parameter 2 of angle"),
|
||||
_("Setup"),
|
||||
"CppPlatform/Extensions/particleSystemicon16.png")
|
||||
.AddParameter("object", _("Object"), "ParticleEmitter", false);
|
||||
|
||||
}
|
||||
|
@@ -191,6 +191,7 @@ class ParticleSystemJsExtension : public gd::PlatformExtension {
|
||||
expressions["EmitterAngle"].SetFunctionName("getAngle");
|
||||
expressions["EmitterAngleA"].SetFunctionName("getEmitterAngleA");
|
||||
expressions["EmitterAngleB"].SetFunctionName("getEmitterAngleB");
|
||||
expressions["ConeSprayAngle"].SetFunctionName("getConeSprayAngle");
|
||||
expressions["ZoneRadius"].SetFunctionName("getZoneRadius");
|
||||
expressions["ParticleGravityX"].SetFunctionName("getParticleGravityX");
|
||||
expressions["ParticleGravityY"].SetFunctionName("getParticleGravityY");
|
||||
|
@@ -12,7 +12,7 @@ namespace gdjs {
|
||||
started: boolean = false;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
runtimeObject: gdjs.RuntimeObject,
|
||||
objectData: any
|
||||
) {
|
||||
@@ -24,7 +24,7 @@ namespace gdjs {
|
||||
graphics.drawCircle(0, 0, objectData.rendererParam1);
|
||||
} else if (objectData.rendererType === 'Line') {
|
||||
graphics.drawRect(
|
||||
objectData.rendererParam1,
|
||||
0,
|
||||
0,
|
||||
objectData.rendererParam1,
|
||||
objectData.rendererParam2
|
||||
@@ -40,7 +40,7 @@ namespace gdjs {
|
||||
);
|
||||
} else if (objectData.textureParticleName) {
|
||||
const sprite = new PIXI.Sprite(
|
||||
(runtimeScene
|
||||
(instanceContainer
|
||||
.getGame()
|
||||
.getImageManager() as gdjs.PixiImageManager).getPIXITexture(
|
||||
objectData.textureParticleName
|
||||
@@ -62,7 +62,7 @@ namespace gdjs {
|
||||
// Render the texture from graphics using the PIXI Renderer.
|
||||
// TODO: could be optimized by generating the texture only once per object type,
|
||||
// instead of at each object creation.
|
||||
const pixiRenderer = runtimeScene
|
||||
const pixiRenderer = instanceContainer
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getPIXIRenderer();
|
||||
@@ -149,25 +149,28 @@ namespace gdjs {
|
||||
minimumScaleMultiplier: m,
|
||||
isStepped: false,
|
||||
};
|
||||
// Angle of the spray cone
|
||||
// @ts-ignore
|
||||
config.startRotation = {
|
||||
min: -objectData.emitterAngleB / 2.0,
|
||||
max: objectData.emitterAngleB / 2.0,
|
||||
};
|
||||
const mediumLifetime =
|
||||
(objectData.particleLifeTimeMin + objectData.particleLifeTimeMax) / 2.0;
|
||||
// Rotation speed of the particles
|
||||
// @ts-ignore
|
||||
config.rotationSpeed = {
|
||||
min: objectData.particleAngle1 / mediumLifetime,
|
||||
max: objectData.particleAngle2 / mediumLifetime,
|
||||
min: objectData.particleAngle1,
|
||||
max: objectData.particleAngle2,
|
||||
};
|
||||
// @ts-ignore
|
||||
config.blendMode = objectData.additive ? 'ADD' : 'NORMAL';
|
||||
this.renderer = new PIXI.Container();
|
||||
// The embedded particle emitter is supposed to be the last minor version
|
||||
// of the version 4 of the particle emitter object
|
||||
// See source https://github.com/pixijs/particle-emitter/blob/v4.3.1/src/Emitter.ts
|
||||
// @ts-ignore
|
||||
this.emitter = new PIXI.particles.Emitter(this.renderer, texture, config);
|
||||
this.start();
|
||||
const layer = runtimeScene.getLayer(runtimeObject.getLayer());
|
||||
const layer = instanceContainer.getLayer(runtimeObject.getLayer());
|
||||
if (layer) {
|
||||
layer
|
||||
.getRenderer()
|
||||
@@ -197,8 +200,12 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
setForce(min: float, max: float): void {
|
||||
this.emitter.startSpeed.value = max;
|
||||
this.emitter.minimumSpeedMultiplier = max !== 0 ? min / max : 1;
|
||||
// If max force is zero, PIXI seems to not be able to compute correctly
|
||||
// the angle of the emitter, resulting in it staying at 0° or 180°.
|
||||
// See https://github.com/4ian/GDevelop/issues/4312.
|
||||
const _max = max || 0.000001;
|
||||
this.emitter.startSpeed.value = _max;
|
||||
this.emitter.minimumSpeedMultiplier = min / _max;
|
||||
}
|
||||
|
||||
setZoneRadius(radius: float): void {
|
||||
@@ -242,6 +249,21 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
setParticleRotationSpeed(min: float, max: float): void {
|
||||
this.emitter.minRotationSpeed = min;
|
||||
this.emitter.maxRotationSpeed = max;
|
||||
}
|
||||
|
||||
setMaxParticlesCount(count: float): void {
|
||||
this.emitter.maxParticles = count;
|
||||
}
|
||||
|
||||
setAdditiveRendering(enabled: boolean): void {
|
||||
this.emitter.particleBlendMode = enabled
|
||||
? PIXI.BLEND_MODES.ADD
|
||||
: PIXI.BLEND_MODES.NORMAL;
|
||||
}
|
||||
|
||||
setAlpha(alpha1: number, alpha2: number): void {
|
||||
this.emitter.startAlpha.value = alpha1 / 255.0;
|
||||
if (this.emitter.startAlpha.next) {
|
||||
@@ -267,25 +289,28 @@ namespace gdjs {
|
||||
|
||||
isTextureNameValid(
|
||||
texture: string,
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): boolean {
|
||||
const invalidPixiTexture = runtimeScene
|
||||
const invalidPixiTexture = instanceContainer
|
||||
.getGame()
|
||||
.getImageManager()
|
||||
.getInvalidPIXITexture();
|
||||
const pixiTexture = runtimeScene
|
||||
const pixiTexture = instanceContainer
|
||||
.getGame()
|
||||
.getImageManager()
|
||||
.getPIXITexture(texture);
|
||||
return pixiTexture.valid && pixiTexture !== invalidPixiTexture;
|
||||
}
|
||||
|
||||
setTextureName(texture: string, runtimeScene: gdjs.RuntimeScene): void {
|
||||
const invalidPixiTexture = runtimeScene
|
||||
setTextureName(
|
||||
texture: string,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
const invalidPixiTexture = instanceContainer
|
||||
.getGame()
|
||||
.getImageManager()
|
||||
.getInvalidPIXITexture();
|
||||
const pixiTexture = runtimeScene
|
||||
const pixiTexture = instanceContainer
|
||||
.getGame()
|
||||
.getImageManager()
|
||||
.getPIXITexture(texture);
|
||||
|
@@ -6,8 +6,14 @@
|
||||
|
||||
namespace gdjs {
|
||||
export type ParticleEmitterObjectDataType = {
|
||||
/**
|
||||
* @deprecated Data not used
|
||||
*/
|
||||
emitterAngleA: number;
|
||||
emitterForceMin: number;
|
||||
/**
|
||||
* Cone spray angle (degrees)
|
||||
*/
|
||||
emitterAngleB: number;
|
||||
zoneRadius: number;
|
||||
emitterForceMax: number;
|
||||
@@ -23,7 +29,13 @@ namespace gdjs {
|
||||
particleBlue1: number;
|
||||
particleSize2: number;
|
||||
particleSize1: number;
|
||||
/**
|
||||
* Particle max rotation speed (degrees/second)
|
||||
*/
|
||||
particleAngle2: number;
|
||||
/**
|
||||
* Particle min rotation speed (degrees/second)
|
||||
*/
|
||||
particleAngle1: number;
|
||||
particleAlpha1: number;
|
||||
rendererType: string;
|
||||
@@ -49,6 +61,9 @@ namespace gdjs {
|
||||
* Displays particles.
|
||||
*/
|
||||
export class ParticleEmitterObject extends gdjs.RuntimeObject {
|
||||
/**
|
||||
* @deprecated Data not used
|
||||
*/
|
||||
angleA: number;
|
||||
angleB: number;
|
||||
forceMin: number;
|
||||
@@ -75,6 +90,10 @@ namespace gdjs {
|
||||
flow: number;
|
||||
tank: number;
|
||||
destroyWhenNoParticles: boolean;
|
||||
particleRotationMinSpeed: number;
|
||||
particleRotationMaxSpeed: number;
|
||||
maxParticlesCount: number;
|
||||
additiveRendering: boolean;
|
||||
_posDirty: boolean = true;
|
||||
_angleDirty: boolean = true;
|
||||
_forceDirty: boolean = true;
|
||||
@@ -86,6 +105,9 @@ namespace gdjs {
|
||||
_alphaDirty: boolean = true;
|
||||
_flowDirty: boolean = true;
|
||||
_tankDirty: boolean = true;
|
||||
_particleRotationSpeedDirty: boolean = true;
|
||||
_maxParticlesCountDirty: boolean = true;
|
||||
_additiveRenderingDirty: boolean = true;
|
||||
// Don't mark texture as dirty if not using one.
|
||||
_textureDirty: boolean;
|
||||
|
||||
@@ -93,16 +115,16 @@ namespace gdjs {
|
||||
_renderer: gdjs.ParticleEmitterObjectRenderer;
|
||||
|
||||
/**
|
||||
* @param the object belongs to
|
||||
* @param instanceContainer the container the object belongs to
|
||||
* @param particleObjectData The initial properties of the object
|
||||
*/
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
particleObjectData: ParticleEmitterObjectData
|
||||
) {
|
||||
super(runtimeScene, particleObjectData);
|
||||
super(instanceContainer, particleObjectData);
|
||||
this._renderer = new gdjs.ParticleEmitterObjectRenderer(
|
||||
runtimeScene,
|
||||
instanceContainer,
|
||||
this,
|
||||
particleObjectData
|
||||
);
|
||||
@@ -132,6 +154,10 @@ namespace gdjs {
|
||||
this.flow = particleObjectData.flow;
|
||||
this.tank = particleObjectData.tank;
|
||||
this.destroyWhenNoParticles = particleObjectData.destroyWhenNoParticles;
|
||||
this.particleRotationMinSpeed = particleObjectData.particleAngle1;
|
||||
this.particleRotationMaxSpeed = particleObjectData.particleAngle2;
|
||||
this.maxParticlesCount = particleObjectData.maxParticleNb;
|
||||
this.additiveRendering = particleObjectData.additive;
|
||||
this._textureDirty = this.texture !== '';
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
@@ -176,6 +202,18 @@ namespace gdjs {
|
||||
if (oldObjectData.emitterForceMin !== newObjectData.emitterForceMin) {
|
||||
this.setEmitterForceMin(newObjectData.emitterForceMin);
|
||||
}
|
||||
if (oldObjectData.particleAngle1 !== newObjectData.particleAngle1) {
|
||||
this.setParticleRotationMinSpeed(newObjectData.particleAngle1);
|
||||
}
|
||||
if (oldObjectData.particleAngle2 !== newObjectData.particleAngle2) {
|
||||
this.setParticleRotationMaxSpeed(newObjectData.particleAngle2);
|
||||
}
|
||||
if (oldObjectData.maxParticleNb !== newObjectData.maxParticleNb) {
|
||||
this.setMaxParticlesCount(newObjectData.maxParticleNb);
|
||||
}
|
||||
if (oldObjectData.additive !== newObjectData.additive) {
|
||||
this.setAdditiveRendering(newObjectData.additive);
|
||||
}
|
||||
if (oldObjectData.emitterForceMax !== newObjectData.emitterForceMax) {
|
||||
this.setEmitterForceMax(newObjectData.emitterForceMax);
|
||||
}
|
||||
@@ -231,7 +269,10 @@ namespace gdjs {
|
||||
if (
|
||||
oldObjectData.textureParticleName !== newObjectData.textureParticleName
|
||||
) {
|
||||
this.setTexture(newObjectData.textureParticleName, this._runtimeScene);
|
||||
this.setTexture(
|
||||
newObjectData.textureParticleName,
|
||||
this.getRuntimeScene()
|
||||
);
|
||||
}
|
||||
if (oldObjectData.flow !== newObjectData.flow) {
|
||||
this.setFlow(newObjectData.flow);
|
||||
@@ -259,7 +300,7 @@ namespace gdjs {
|
||||
oldObjectData.rendererParam2 !== newObjectData.rendererParam2
|
||||
) {
|
||||
// Destroy the renderer, ensure it's removed from the layer.
|
||||
const layer = this._runtimeScene.getLayer(this.layer);
|
||||
const layer = this.getInstanceContainer().getLayer(this.layer);
|
||||
layer
|
||||
.getRenderer()
|
||||
.removeRendererObject(this._renderer.getRendererObject());
|
||||
@@ -267,7 +308,7 @@ namespace gdjs {
|
||||
|
||||
// and recreate the renderer, which will add itself to the layer.
|
||||
this._renderer = new gdjs.ParticleEmitterObjectRenderer(
|
||||
this._runtimeScene,
|
||||
this.getInstanceContainer(),
|
||||
this,
|
||||
newObjectData
|
||||
);
|
||||
@@ -281,15 +322,27 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
update(runtimeScene): void {
|
||||
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
if (this._posDirty) {
|
||||
this._renderer.setPosition(this.getX(), this.getY());
|
||||
}
|
||||
if (this._particleRotationSpeedDirty) {
|
||||
this._renderer.setParticleRotationSpeed(
|
||||
this.particleRotationMinSpeed,
|
||||
this.particleRotationMaxSpeed
|
||||
);
|
||||
}
|
||||
if (this._maxParticlesCountDirty) {
|
||||
this._renderer.setMaxParticlesCount(this.maxParticlesCount);
|
||||
}
|
||||
if (this._additiveRenderingDirty) {
|
||||
this._renderer.setAdditiveRendering(this.additiveRendering);
|
||||
}
|
||||
if (this._angleDirty) {
|
||||
const angle = this.getAngle();
|
||||
this._renderer.setAngle(
|
||||
this.angle - this.angleB / 2.0,
|
||||
this.angle + this.angleB / 2.0
|
||||
angle - this.angleB / 2.0,
|
||||
angle + this.angleB / 2.0
|
||||
);
|
||||
}
|
||||
if (this._forceDirty) {
|
||||
@@ -324,24 +377,25 @@ namespace gdjs {
|
||||
this._renderer.resetEmission(this.flow, this.tank);
|
||||
}
|
||||
if (this._textureDirty) {
|
||||
this._renderer.setTextureName(this.texture, runtimeScene);
|
||||
this._renderer.setTextureName(this.texture, instanceContainer);
|
||||
}
|
||||
this._posDirty = this._angleDirty = this._forceDirty = this._zoneRadiusDirty = false;
|
||||
this._lifeTimeDirty = this._gravityDirty = this._colorDirty = this._sizeDirty = false;
|
||||
this._alphaDirty = this._flowDirty = this._textureDirty = this._tankDirty = false;
|
||||
this._renderer.update(this.getElapsedTime(runtimeScene) / 1000.0);
|
||||
this._additiveRenderingDirty = this._maxParticlesCountDirty = this._particleRotationSpeedDirty = false;
|
||||
this._renderer.update(this.getElapsedTime() / 1000.0);
|
||||
if (
|
||||
this._renderer.hasStarted() &&
|
||||
this.getParticleCount() === 0 &&
|
||||
this.destroyWhenNoParticles
|
||||
) {
|
||||
this.deleteFromScene(runtimeScene);
|
||||
this.deleteFromScene(instanceContainer);
|
||||
}
|
||||
}
|
||||
|
||||
onDestroyFromScene(runtimeScene: gdjs.RuntimeScene): void {
|
||||
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
this._renderer.destroy();
|
||||
super.onDestroyFromScene(runtimeScene);
|
||||
super.onDestroyFromScene(instanceContainer);
|
||||
}
|
||||
|
||||
getEmitterForceMin(): number {
|
||||
@@ -372,10 +426,60 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
setParticleRotationMinSpeed(speed: number): void {
|
||||
if (this.particleRotationMinSpeed !== speed) {
|
||||
this._particleRotationSpeedDirty = true;
|
||||
this.particleRotationMinSpeed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
getParticleRotationMinSpeed(): number {
|
||||
return this.particleRotationMinSpeed;
|
||||
}
|
||||
|
||||
setParticleRotationMaxSpeed(speed: number): void {
|
||||
if (this.particleRotationMaxSpeed !== speed) {
|
||||
this._particleRotationSpeedDirty = true;
|
||||
this.particleRotationMaxSpeed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
getParticleRotationMaxSpeed(): number {
|
||||
return this.particleRotationMaxSpeed;
|
||||
}
|
||||
|
||||
setMaxParticlesCount(count: number): void {
|
||||
if (this.maxParticlesCount !== count) {
|
||||
this._maxParticlesCountDirty = true;
|
||||
this.maxParticlesCount = count;
|
||||
}
|
||||
}
|
||||
|
||||
getMaxParticlesCount(): number {
|
||||
return this.maxParticlesCount;
|
||||
}
|
||||
|
||||
setAdditiveRendering(enabled: boolean) {
|
||||
if (this.additiveRendering !== enabled) {
|
||||
this._additiveRenderingDirty = true;
|
||||
this.additiveRendering = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
getAdditiveRendering(): boolean {
|
||||
return this.additiveRendering;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Prefer using getAngle
|
||||
*/
|
||||
getEmitterAngle(): float {
|
||||
return (this.angleA + this.angleB) / 2.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Prefer using setAngle
|
||||
*/
|
||||
setEmitterAngle(angle: float): void {
|
||||
const oldAngle = this.getEmitterAngle();
|
||||
if (angle !== oldAngle) {
|
||||
@@ -385,10 +489,16 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This function returns data that is not used.
|
||||
*/
|
||||
getEmitterAngleA(): float {
|
||||
return this.angleA;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This function sets data that is not used.
|
||||
*/
|
||||
setEmitterAngleA(angle: float): void {
|
||||
if (this.angleA !== angle) {
|
||||
this._angleDirty = true;
|
||||
@@ -408,17 +518,11 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
getConeSprayAngle(): float {
|
||||
return Math.abs(this.angleB - this.angleA);
|
||||
return this.getEmitterAngleB();
|
||||
}
|
||||
|
||||
setConeSprayAngle(angle: float): void {
|
||||
const oldCone = this.getConeSprayAngle();
|
||||
if (oldCone !== angle) {
|
||||
this._angleDirty = true;
|
||||
const midAngle = this.getEmitterAngle();
|
||||
this.angleA = midAngle - angle / 2.0;
|
||||
this.angleB = midAngle + angle / 2.0;
|
||||
}
|
||||
this.setEmitterAngleB(angle);
|
||||
}
|
||||
|
||||
getZoneRadius(): float {
|
||||
@@ -734,9 +838,12 @@ namespace gdjs {
|
||||
return this.texture;
|
||||
}
|
||||
|
||||
setTexture(texture: string, runtimeScene: gdjs.RuntimeScene): void {
|
||||
setTexture(
|
||||
texture: string,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
if (this.texture !== texture) {
|
||||
if (this._renderer.isTextureNameValid(texture, runtimeScene)) {
|
||||
if (this._renderer.isTextureNameValid(texture, instanceContainer)) {
|
||||
this.texture = texture;
|
||||
this._textureDirty = true;
|
||||
}
|
||||
|
@@ -21,7 +21,10 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"avoiding obstacles on the way.",
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("Movement")
|
||||
.SetExtensionHelpPath("/behaviors/pathfinding");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Pathfinding behavior"))
|
||||
.SetIcon("CppPlatform/Extensions/AStaricon16.png");
|
||||
|
||||
{
|
||||
gd::BehaviorMetadata& aut =
|
||||
|
@@ -4,35 +4,37 @@ Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
*/
|
||||
|
||||
namespace gdjs {
|
||||
export interface RuntimeInstanceContainer {
|
||||
pathfindingObstaclesManager: gdjs.PathfindingObstaclesManager;
|
||||
}
|
||||
declare var rbush: any;
|
||||
|
||||
/**
|
||||
* PathfindingObstaclesManager manages the common objects shared by objects having a
|
||||
* pathfinding behavior: In particular, the obstacles behaviors are required to declare
|
||||
* themselves (see `PathfindingObstaclesManager.addObstacle`) to the manager of their associated scene
|
||||
* (see `gdjs.PathfindingRuntimeBehavior.obstaclesManagers`).
|
||||
* PathfindingObstaclesManager manages the common objects shared by objects
|
||||
* having a pathfinding behavior: In particular, the obstacles behaviors are
|
||||
* required to declare themselves (see
|
||||
* `PathfindingObstaclesManager.addObstacle`) to the manager of their
|
||||
* associated container (see
|
||||
* `gdjs.PathfindingRuntimeBehavior.obstaclesManagers`).
|
||||
*/
|
||||
export class PathfindingObstaclesManager {
|
||||
_obstaclesRBush: any;
|
||||
|
||||
/**
|
||||
* @param object The object
|
||||
*/
|
||||
constructor(runtimeScene: gdjs.RuntimeScene) {
|
||||
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
this._obstaclesRBush = new rbush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the obstacles manager of a scene.
|
||||
* Get the obstacles manager of an instance container.
|
||||
*/
|
||||
static getManager(runtimeScene) {
|
||||
if (!runtimeScene.pathfindingObstaclesManager) {
|
||||
static getManager(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
if (!instanceContainer.pathfindingObstaclesManager) {
|
||||
//Create the shared manager if necessary.
|
||||
runtimeScene.pathfindingObstaclesManager = new gdjs.PathfindingObstaclesManager(
|
||||
runtimeScene
|
||||
instanceContainer.pathfindingObstaclesManager = new gdjs.PathfindingObstaclesManager(
|
||||
instanceContainer
|
||||
);
|
||||
}
|
||||
return runtimeScene.pathfindingObstaclesManager;
|
||||
return instanceContainer.pathfindingObstaclesManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,14 +113,14 @@ namespace gdjs {
|
||||
> | null = null;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
behaviorData,
|
||||
owner: gdjs.RuntimeObject
|
||||
) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
super(instanceContainer, behaviorData, owner);
|
||||
this._impassable = behaviorData.impassable;
|
||||
this._cost = behaviorData.cost;
|
||||
this._manager = PathfindingObstaclesManager.getManager(runtimeScene);
|
||||
this._manager = PathfindingObstaclesManager.getManager(instanceContainer);
|
||||
|
||||
//Note that we can't use getX(), getWidth()... of owner here:
|
||||
//The owner is not yet fully constructed.
|
||||
@@ -140,7 +142,7 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
//Make sure the obstacle is or is not in the obstacles manager.
|
||||
if (!this.activated() && this._registeredInManager) {
|
||||
this._manager.removeObstacle(this);
|
||||
@@ -170,7 +172,7 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {}
|
||||
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
|
||||
|
||||
getAABB() {
|
||||
return this.owner.getAABB();
|
||||
|
@@ -38,11 +38,11 @@ namespace gdjs {
|
||||
_movementAngle: float = 0;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
behaviorData,
|
||||
owner: gdjs.RuntimeObject
|
||||
) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
super(instanceContainer, behaviorData, owner);
|
||||
|
||||
//The path computed and followed by the object (Array of arrays containing x and y position)
|
||||
if (this._path === undefined) {
|
||||
@@ -60,7 +60,9 @@ namespace gdjs {
|
||||
this._gridOffsetX = behaviorData.gridOffsetX || 0;
|
||||
this._gridOffsetY = behaviorData.gridOffsetY || 0;
|
||||
this._extraBorder = behaviorData.extraBorder;
|
||||
this._manager = gdjs.PathfindingObstaclesManager.getManager(runtimeScene);
|
||||
this._manager = gdjs.PathfindingObstaclesManager.getManager(
|
||||
instanceContainer
|
||||
);
|
||||
this._searchContext = new gdjs.PathfindingRuntimeBehavior.SearchContext(
|
||||
this._manager
|
||||
);
|
||||
@@ -313,7 +315,11 @@ namespace gdjs {
|
||||
/**
|
||||
* Compute and move on the path to the specified destination.
|
||||
*/
|
||||
moveTo(runtimeScene: gdjs.RuntimeScene, x: float, y: float) {
|
||||
moveTo(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
x: float,
|
||||
y: float
|
||||
) {
|
||||
const owner = this.owner;
|
||||
|
||||
//First be sure that there is a path to compute.
|
||||
@@ -403,13 +409,13 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
if (this._path.length === 0 || this._reachedEnd) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the speed of the object
|
||||
const timeDelta = this.owner.getElapsedTime(runtimeScene) / 1000;
|
||||
const timeDelta = this.owner.getElapsedTime() / 1000;
|
||||
const previousSpeed = this._speed;
|
||||
if (this._speed !== this._maxSpeed) {
|
||||
this._speed += this._acceleration * timeDelta;
|
||||
@@ -452,8 +458,7 @@ namespace gdjs {
|
||||
) {
|
||||
this.owner.rotateTowardAngle(
|
||||
this._movementAngle + this._angleOffset,
|
||||
this._angularSpeed,
|
||||
runtimeScene
|
||||
this._angularSpeed
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -463,7 +468,7 @@ namespace gdjs {
|
||||
this.owner.setY(newPos[1]);
|
||||
}
|
||||
|
||||
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {}
|
||||
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
|
||||
|
||||
/**
|
||||
* Compute the euclidean distance between two positions.
|
||||
|
@@ -61,9 +61,14 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
sprites: [
|
||||
{
|
||||
originPoint: objectCenteredOnCells
|
||||
? { x: 80, y: 80 }
|
||||
: { x: 87, y: 87 },
|
||||
centerPoint: { x: 80, y: 80 },
|
||||
? { name: 'Origin', x: 80, y: 80 }
|
||||
: { name: 'Origin', x: 87, y: 87 },
|
||||
centerPoint: {
|
||||
name: 'Center',
|
||||
x: 80,
|
||||
y: 80,
|
||||
automatic: false,
|
||||
},
|
||||
points: [
|
||||
{ name: 'Center', x: 80, y: 80 },
|
||||
objectCenteredOnCells
|
||||
@@ -71,13 +76,17 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
: { name: 'Origin', x: 87, y: 87 },
|
||||
],
|
||||
hasCustomCollisionMask: false,
|
||||
customCollisionMask: [],
|
||||
image: '',
|
||||
},
|
||||
],
|
||||
timeBetweenFrames: 0,
|
||||
looping: false,
|
||||
},
|
||||
],
|
||||
useMultipleDirections: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PathfindingBehavior::PathfindingBehavior',
|
||||
@@ -93,6 +102,9 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
extraBorder: 0,
|
||||
},
|
||||
],
|
||||
variables: [],
|
||||
effects: [],
|
||||
updateIfNotVisible: true,
|
||||
});
|
||||
player.getWidth = function () {
|
||||
return 160;
|
||||
@@ -116,9 +128,14 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
sprites: [
|
||||
{
|
||||
originPoint: objectCenteredOnCells
|
||||
? { x: 80, y: 80 }
|
||||
: { x: 87, y: 87 },
|
||||
centerPoint: { x: 80, y: 80 },
|
||||
? { name: 'Origin', x: 80, y: 80 }
|
||||
: { name: 'Origin', x: 87, y: 87 },
|
||||
centerPoint: {
|
||||
name: 'Center',
|
||||
x: 80,
|
||||
y: 80,
|
||||
automatic: false,
|
||||
},
|
||||
points: [
|
||||
{ name: 'Center', x: 80, y: 80 },
|
||||
objectCenteredOnCells
|
||||
@@ -126,13 +143,17 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
: { name: 'Origin', x: 87, y: 87 },
|
||||
],
|
||||
hasCustomCollisionMask: false,
|
||||
customCollisionMask: [],
|
||||
image: '',
|
||||
},
|
||||
],
|
||||
timeBetweenFrames: 0,
|
||||
looping: false,
|
||||
},
|
||||
],
|
||||
useMultipleDirections: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PathfindingBehavior::PathfindingObstacleBehavior',
|
||||
@@ -140,6 +161,9 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
cost: 2,
|
||||
},
|
||||
],
|
||||
variables: [],
|
||||
effects: [],
|
||||
updateIfNotVisible: true,
|
||||
});
|
||||
obstacle.getWidth = function () {
|
||||
return 160;
|
||||
|
@@ -34,7 +34,7 @@ module.exports = {
|
||||
'MIT'
|
||||
)
|
||||
.setExtensionHelpPath('/behaviors/physics2')
|
||||
.setCategory('Advanced');
|
||||
.setCategory('Movement');
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata(_('Physics Engine 2.0'))
|
||||
.setIcon('res/physics32.png');
|
||||
@@ -3816,6 +3816,23 @@ module.exports = {
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
return [];
|
||||
const dummyBehavior = extension
|
||||
.getBehaviorMetadata('Physics2::Physics2Behavior')
|
||||
.get();
|
||||
const sharedData = extension
|
||||
.getBehaviorMetadata('Physics2::Physics2Behavior')
|
||||
.getSharedDataInstance();
|
||||
return [
|
||||
gd.ProjectHelper.sanityCheckBehaviorProperty(
|
||||
dummyBehavior,
|
||||
'density',
|
||||
'123'
|
||||
),
|
||||
gd.ProjectHelper.sanityCheckBehaviorsSharedDataProperty(
|
||||
sharedData,
|
||||
'gravityY',
|
||||
'456'
|
||||
),
|
||||
];
|
||||
},
|
||||
};
|
||||
|
@@ -35,7 +35,7 @@ namespace gdjs {
|
||||
*/
|
||||
_registeredBehaviors: Set<Physics2RuntimeBehavior>;
|
||||
|
||||
constructor(runtimeScene: gdjs.RuntimeScene, sharedData) {
|
||||
constructor(instanceContainer: gdjs.RuntimeInstanceContainer, sharedData) {
|
||||
this._registeredBehaviors = new Set();
|
||||
this.gravityX = sharedData.gravityX;
|
||||
this.gravityY = sharedData.gravityY;
|
||||
@@ -349,11 +349,11 @@ namespace gdjs {
|
||||
_verticesBuffer: integer = 0;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
behaviorData,
|
||||
owner: gdjs.RuntimeObject
|
||||
) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
super(instanceContainer, behaviorData, owner);
|
||||
this.bodyType = behaviorData.bodyType;
|
||||
this.bullet = behaviorData.bullet;
|
||||
this.fixedRotation = behaviorData.fixedRotation;
|
||||
@@ -382,7 +382,7 @@ namespace gdjs {
|
||||
this.currentContacts.length = 0;
|
||||
this.destroyedDuringFrameLogic = false;
|
||||
this._sharedData = Physics2SharedData.getSharedData(
|
||||
runtimeScene,
|
||||
instanceContainer.getScene(),
|
||||
behaviorData.name
|
||||
);
|
||||
this._tempb2Vec2 = new Box2D.b2Vec2();
|
||||
@@ -824,14 +824,15 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
// Step the world if not done this frame yet
|
||||
if (!this._sharedData.stepped) {
|
||||
// Reset started and ended contacts array for all physics instances.
|
||||
this._sharedData.resetStartedAndEndedCollisions();
|
||||
this._sharedData.updateBodiesFromObjects();
|
||||
this._sharedData.step(
|
||||
runtimeScene.getTimeManager().getElapsedTime() / 1000.0
|
||||
instanceContainer.getScene().getTimeManager().getElapsedTime() /
|
||||
1000.0
|
||||
);
|
||||
}
|
||||
|
||||
@@ -862,7 +863,7 @@ namespace gdjs {
|
||||
this._objectOldAngle = this.owner.getAngle();
|
||||
}
|
||||
|
||||
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {
|
||||
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
// Reset world step to update next frame
|
||||
this._sharedData.stepped = false;
|
||||
}
|
||||
|
@@ -21,7 +21,10 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"This is the old, deprecated physics engine. Prefer to use the Physics Engine 2.0.",
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("Movement")
|
||||
.SetExtensionHelpPath("/behaviors/physics");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Physics Engine (deprecated)"))
|
||||
.SetIcon("res/physics16.png");
|
||||
|
||||
{
|
||||
gd::BehaviorMetadata& aut = extension.AddBehavior(
|
||||
|
@@ -27,6 +27,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"could be used.",
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("Movement")
|
||||
.SetExtensionHelpPath("/behaviors/platformer");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Platform behavior"))
|
||||
.SetIcon("CppPlatform/Extensions/platformerobjecticon.png");
|
||||
|
@@ -99,7 +99,7 @@ PlatformerObjectBehavior::GetProperties(
|
||||
behaviorContent.GetDoubleAttribute("xGrabTolerance", 10)));
|
||||
properties["useLegacyTrajectory"]
|
||||
.SetLabel(_("Use frame rate dependent trajectories (deprecated, it's "
|
||||
"recommended to let this unchecked)"))
|
||||
"recommended to leave this unchecked)"))
|
||||
.SetGroup(_("Deprecated options (advanced)"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute("useLegacyTrajectory", true)
|
||||
? "true"
|
||||
|
@@ -117,11 +117,11 @@ namespace gdjs {
|
||||
private _manager: gdjs.PlatformObjectsManager;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
behaviorData,
|
||||
owner: gdjs.RuntimeObject
|
||||
) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
super(instanceContainer, behaviorData, owner);
|
||||
this._gravity = behaviorData.gravity;
|
||||
this._maxFallingSpeed = behaviorData.maxFallingSpeed;
|
||||
this._ladderClimbingSpeed = behaviorData.ladderClimbingSpeed || 150;
|
||||
@@ -146,7 +146,7 @@ namespace gdjs {
|
||||
this._potentialCollidingObjects = [];
|
||||
this._overlappedJumpThru = [];
|
||||
|
||||
this._manager = gdjs.PlatformObjectsManager.getManager(runtimeScene);
|
||||
this._manager = gdjs.PlatformObjectsManager.getManager(instanceContainer);
|
||||
|
||||
this._falling = new Falling(this);
|
||||
this._onFloor = new OnFloor(this);
|
||||
@@ -210,7 +210,7 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
const LEFTKEY = 37;
|
||||
const UPKEY = 38;
|
||||
const RIGHTKEY = 39;
|
||||
@@ -219,13 +219,13 @@ namespace gdjs {
|
||||
const RSHIFTKEY = 2016;
|
||||
const SPACEKEY = 32;
|
||||
const object = this.owner;
|
||||
const timeDelta = this.owner.getElapsedTime(runtimeScene) / 1000;
|
||||
const timeDelta = this.owner.getElapsedTime() / 1000;
|
||||
|
||||
//0.1) Get the player input:
|
||||
this._requestedDeltaX = 0;
|
||||
this._requestedDeltaY = 0;
|
||||
|
||||
const inputManager = runtimeScene.getGame().getInputManager();
|
||||
const inputManager = instanceContainer.getGame().getInputManager();
|
||||
this._leftKey ||
|
||||
(this._leftKey =
|
||||
!this._ignoreDefaultControls && inputManager.isKeyPressed(LEFTKEY));
|
||||
@@ -329,7 +329,7 @@ namespace gdjs {
|
||||
this._lastDeltaY = object.getY() - oldY;
|
||||
}
|
||||
|
||||
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {}
|
||||
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
|
||||
|
||||
private _updateSpeed(timeDelta: float): float {
|
||||
const previousSpeed = this._currentSpeed;
|
||||
|
@@ -8,34 +8,31 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Manages the common objects shared by objects having a
|
||||
* platform behavior: in particular, the platforms behaviors are required to declare
|
||||
* themselves (see PlatformObjectsManager.addPlatform) to the manager of their associated scene
|
||||
* (see PlatformRuntimeBehavior.getManager).
|
||||
* platform behavior: in particular, the platforms behaviors are required to
|
||||
* declare themselves (see PlatformObjectsManager.addPlatform) to the manager
|
||||
* of their associated container (see PlatformRuntimeBehavior.getManager).
|
||||
*/
|
||||
export class PlatformObjectsManager {
|
||||
private _platformRBush: any;
|
||||
|
||||
/**
|
||||
* @param object The object
|
||||
*/
|
||||
constructor(runtimeScene: gdjs.RuntimeScene) {
|
||||
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
this._platformRBush = new rbush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the platforms manager of a scene.
|
||||
* Get the platforms manager of an instance container.
|
||||
*/
|
||||
static getManager(runtimeScene: gdjs.RuntimeScene) {
|
||||
static getManager(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
// @ts-ignore
|
||||
if (!runtimeScene.platformsObjectsManager) {
|
||||
if (!instanceContainer.platformsObjectsManager) {
|
||||
//Create the shared manager if necessary.
|
||||
// @ts-ignore
|
||||
runtimeScene.platformsObjectsManager = new gdjs.PlatformObjectsManager(
|
||||
runtimeScene
|
||||
instanceContainer.platformsObjectsManager = new gdjs.PlatformObjectsManager(
|
||||
instanceContainer
|
||||
);
|
||||
}
|
||||
// @ts-ignore
|
||||
return runtimeScene.platformsObjectsManager;
|
||||
return instanceContainer.platformsObjectsManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,11 +129,11 @@ namespace gdjs {
|
||||
_registeredInManager: boolean = false;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
behaviorData,
|
||||
owner: gdjs.RuntimeObject
|
||||
) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
super(instanceContainer, behaviorData, owner);
|
||||
this._platformType = behaviorData.platformType;
|
||||
if (behaviorData.platformType === 'Ladder') {
|
||||
this._platformType = PlatformRuntimeBehavior.LADDER;
|
||||
@@ -147,7 +144,7 @@ namespace gdjs {
|
||||
}
|
||||
this._canBeGrabbed = behaviorData.canBeGrabbed || false;
|
||||
this._yGrabOffset = behaviorData.yGrabOffset || 0;
|
||||
this._manager = PlatformObjectsManager.getManager(runtimeScene);
|
||||
this._manager = PlatformObjectsManager.getManager(instanceContainer);
|
||||
}
|
||||
|
||||
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
|
||||
@@ -169,7 +166,7 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
//Scene change is not supported
|
||||
/*if ( parentScene != &scene ) //Parent scene has changed
|
||||
{
|
||||
@@ -211,7 +208,7 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {}
|
||||
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
|
||||
|
||||
onActivate() {
|
||||
if (this._registeredInManager) {
|
||||
|
197
Extensions/PlayerAuthentication/JsExtension.js
Normal file
197
Extensions/PlayerAuthentication/JsExtension.js
Normal file
@@ -0,0 +1,197 @@
|
||||
// @flow
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
* ℹ️ Changes in this file are watched and automatically imported if the editor
|
||||
* is running. You can also manually run `node import-GDJS-Runtime.js` (in newIDE/app/scripts).
|
||||
*
|
||||
* The file must be named "JsExtension.js", otherwise GDevelop won't load it.
|
||||
* ⚠️ If you make a change and the extension is not loaded, open the developer console
|
||||
* and search for any errors.
|
||||
*
|
||||
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
|
||||
*/
|
||||
|
||||
/*::
|
||||
// Import types to allow Flow to do static type checking on this file.
|
||||
// Extensions declaration are typed using Flow (like the editor), but the files
|
||||
// for the game engine are checked with TypeScript annotations.
|
||||
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
'PlayerAuthentication',
|
||||
_('Player Authentication (experimental)'),
|
||||
_('Allow your game to authenticate players.'),
|
||||
'Florian Rival',
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/all-features/player-authentication')
|
||||
.setCategory('Players');
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata(
|
||||
_('Player Authentication (experimental)')
|
||||
)
|
||||
.setIcon('JsPlatform/Extensions/authentication.svg');
|
||||
|
||||
extension
|
||||
.addDependency()
|
||||
.setName('InAppBrowser Cordova plugin')
|
||||
.setDependencyType('cordova')
|
||||
.setExportName('cordova-plugin-inappbrowser');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'DisplayAuthenticationBanner',
|
||||
_('Display authentication banner'),
|
||||
_(
|
||||
'Display an authentication banner at the top of the game screen, for the player to log in.'
|
||||
),
|
||||
_('Display an authentication banner'),
|
||||
'',
|
||||
'JsPlatform/Extensions/authentication.svg',
|
||||
'JsPlatform/Extensions/authentication.svg'
|
||||
)
|
||||
.addCodeOnlyParameter('currentScene', '')
|
||||
.setHelpPath('/all-features/player-authentication')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.playerAuthentication.displayAuthenticationBanner');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'OpenAuthenticationWindow',
|
||||
_('Open authentication window'),
|
||||
_('Open an authentication window for the player to log in.'),
|
||||
_('Open an authentication window'),
|
||||
'',
|
||||
'JsPlatform/Extensions/authentication.svg',
|
||||
'JsPlatform/Extensions/authentication.svg'
|
||||
)
|
||||
.addCodeOnlyParameter('currentScene', '')
|
||||
.setHelpPath('/all-features/player-authentication')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.playerAuthentication.openAuthenticationWindow');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'IsAuthenticationWindowOpen',
|
||||
_('Authentication window is open'),
|
||||
_('Check if the authentication window is open.'),
|
||||
_('Authentication window is open'),
|
||||
'',
|
||||
'JsPlatform/Extensions/authentication.svg',
|
||||
'JsPlatform/Extensions/authentication.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.playerAuthentication.isAuthenticationWindowOpen');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'LogOut',
|
||||
_('Log out the player'),
|
||||
_('Log out the player.'),
|
||||
_('Log out the player'),
|
||||
'',
|
||||
'JsPlatform/Extensions/authentication.svg',
|
||||
'JsPlatform/Extensions/authentication.svg'
|
||||
)
|
||||
.addCodeOnlyParameter('currentScene', '')
|
||||
.setHelpPath('/all-features/player-authentication')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.playerAuthentication.logout');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'Username',
|
||||
_('Username'),
|
||||
_('Get the username of the authenticated player.'),
|
||||
'',
|
||||
'JsPlatform/Extensions/authentication.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.playerAuthentication.getUsername');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'IsPlayerAuthenticated',
|
||||
_('Player is authenticated'),
|
||||
_('Check if the player is authenticated.'),
|
||||
_('Player is authenticated'),
|
||||
'',
|
||||
'JsPlatform/Extensions/authentication.svg',
|
||||
'JsPlatform/Extensions/authentication.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.playerAuthentication.isAuthenticated');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'HasPlayerLoggedIn',
|
||||
_('Player has logged in'),
|
||||
_('Check if the player has just logged in.'),
|
||||
_('Player has logged in'),
|
||||
'',
|
||||
'JsPlatform/Extensions/authentication.svg',
|
||||
'JsPlatform/Extensions/authentication.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.playerAuthentication.hasLoggedIn');
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
return [];
|
||||
},
|
||||
};
|
@@ -0,0 +1,448 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Player Authentication');
|
||||
export namespace playerAuthenticationComponents {
|
||||
const getPlayerLoginMessages = ({
|
||||
platform,
|
||||
isGameRegistered,
|
||||
}: {
|
||||
platform: 'cordova' | 'electron' | 'web';
|
||||
isGameRegistered: boolean;
|
||||
}) =>
|
||||
isGameRegistered
|
||||
? {
|
||||
title: 'Logging in...',
|
||||
text1:
|
||||
platform === 'cordova'
|
||||
? "One moment, we're opening a window for you to log in."
|
||||
: "One moment, we're opening a new page with your web browser for you to log in.",
|
||||
text2:
|
||||
'If the window did not open, please check your pop-up blocker and click the button below to try again.',
|
||||
}
|
||||
: {
|
||||
title: 'Your game is not registered!',
|
||||
text1:
|
||||
'In order to use player authentication, this game must be registered with GDevelop Services first.',
|
||||
text2: 'Head to your Game Dashboard, then try again.',
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a DOM element that will contain the loader or a message if the game is not registered.
|
||||
*/
|
||||
export const computeAuthenticationContainer = function (
|
||||
onCloseAuthenticationContainer: () => void
|
||||
): {
|
||||
rootContainer: HTMLDivElement;
|
||||
loaderContainer: HTMLDivElement;
|
||||
} {
|
||||
const rootContainer = document.createElement('div');
|
||||
rootContainer.id = 'authentication-root-container';
|
||||
rootContainer.style.position = 'relative';
|
||||
rootContainer.style.backgroundColor = 'rgba(14, 6, 45, 0.5)';
|
||||
rootContainer.style.opacity = '1';
|
||||
rootContainer.style.width = '100%';
|
||||
rootContainer.style.height = '100%';
|
||||
rootContainer.style.zIndex = '2';
|
||||
rootContainer.style.pointerEvents = 'all';
|
||||
|
||||
const subContainer = document.createElement('div');
|
||||
subContainer.id = 'authentication-sub-container';
|
||||
subContainer.style.backgroundColor = '#FFFFFF';
|
||||
subContainer.style.position = 'absolute';
|
||||
subContainer.style.top = '16px';
|
||||
subContainer.style.bottom = '16px';
|
||||
subContainer.style.left = '16px';
|
||||
subContainer.style.right = '16px';
|
||||
subContainer.style.borderRadius = '8px';
|
||||
subContainer.style.boxShadow = '0px 4px 4px rgba(0, 0, 0, 0.25)';
|
||||
subContainer.style.padding = '16px';
|
||||
|
||||
const _closeContainer: HTMLDivElement = document.createElement('div');
|
||||
_closeContainer.style.cursor = 'pointer';
|
||||
_closeContainer.style.display = 'flex';
|
||||
_closeContainer.style.justifyContent = 'right';
|
||||
_closeContainer.style.alignItems = 'center';
|
||||
_closeContainer.style.zIndex = '3';
|
||||
addTouchAndClickEventListeners(
|
||||
_closeContainer,
|
||||
onCloseAuthenticationContainer
|
||||
);
|
||||
|
||||
const _close = document.createElement('img');
|
||||
_close.setAttribute('width', '15px');
|
||||
_close.setAttribute(
|
||||
'src',
|
||||
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOCIgaGVpZ2h0PSI4IiB2aWV3Qm94PSIwIDAgOCA4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTcuODUzNTUgMC4xNDY0NDdDOC4wNDg4MiAwLjM0MTcwOSA4LjA0ODgyIDAuNjU4MjkxIDcuODUzNTUgMC44NTM1NTNMMC44NTM1NTMgNy44NTM1NUMwLjY1ODI5MSA4LjA0ODgyIDAuMzQxNzA5IDguMDQ4ODIgMC4xNDY0NDcgNy44NTM1NUMtMC4wNDg4MTU1IDcuNjU4MjkgLTAuMDQ4ODE1NSA3LjM0MTcxIDAuMTQ2NDQ3IDcuMTQ2NDVMNy4xNDY0NSAwLjE0NjQ0N0M3LjM0MTcxIC0wLjA0ODgxNTUgNy42NTgyOSAtMC4wNDg4MTU1IDcuODUzNTUgMC4xNDY0NDdaIiBmaWxsPSIjMUQxRDI2Ii8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMC4xNDY0NDcgMC4xNDY0NDdDMC4zNDE3MDkgLTAuMDQ4ODE1NSAwLjY1ODI5MSAtMC4wNDg4MTU1IDAuODUzNTUzIDAuMTQ2NDQ3TDcuODUzNTUgNy4xNDY0NUM4LjA0ODgyIDcuMzQxNzEgOC4wNDg4MiA3LjY1ODI5IDcuODUzNTUgNy44NTM1NUM3LjY1ODI5IDguMDQ4ODIgNy4zNDE3MSA4LjA0ODgyIDcuMTQ2NDUgNy44NTM1NUwwLjE0NjQ0NyAwLjg1MzU1M0MtMC4wNDg4MTU1IDAuNjU4MjkxIC0wLjA0ODgxNTUgMC4zNDE3MDkgMC4xNDY0NDcgMC4xNDY0NDdaIiBmaWxsPSIjMUQxRDI2Ii8+Cjwvc3ZnPgo='
|
||||
);
|
||||
_closeContainer.appendChild(_close);
|
||||
|
||||
const loaderContainer: HTMLDivElement = document.createElement('div');
|
||||
loaderContainer.id = 'authentication-container-loader';
|
||||
loaderContainer.style.display = 'flex';
|
||||
loaderContainer.style.flexDirection = 'column';
|
||||
loaderContainer.style.height = '100%';
|
||||
loaderContainer.style.width = '100%';
|
||||
loaderContainer.style.justifyContent = 'center';
|
||||
loaderContainer.style.alignItems = 'center';
|
||||
|
||||
const _loader = document.createElement('img');
|
||||
_loader.setAttribute('width', '28px');
|
||||
_loader.setAttribute(
|
||||
'src',
|
||||
'data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iYW5pbWF0ZS1zcGluIC1tbC0xIG1yLTMgaC01IHctNSB0ZXh0LXdoaXRlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CjxjaXJjbGUgb3BhY2l0eT0nMC4yNScgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSI0Ij48L2NpcmNsZT4KPHBhdGggb3BhY2l0eT0nMC43NScgZmlsbD0iY3VycmVudENvbG9yIiBkPSJNNCAxMmE4IDggMCAwMTgtOFYwQzUuMzczIDAgMCA1LjM3MyAwIDEyaDR6bTIgNS4yOTFBNy45NjIgNy45NjIgMCAwMTQgMTJIMGMwIDMuMDQyIDEuMTM1IDUuODI0IDMgNy45MzhsMy0yLjY0N3oiPjwvcGF0aD4KPC9zdmc+'
|
||||
);
|
||||
_loader.style.marginTop = '50px';
|
||||
try {
|
||||
_loader.animate(
|
||||
[{ transform: 'rotate(0deg)' }, { transform: 'rotate(359deg)' }],
|
||||
{
|
||||
duration: 3000,
|
||||
iterations: Infinity,
|
||||
}
|
||||
);
|
||||
} catch {
|
||||
logger.warn('Animation not supported, loader will be fixed.');
|
||||
}
|
||||
|
||||
loaderContainer.appendChild(_loader);
|
||||
|
||||
subContainer.appendChild(_closeContainer);
|
||||
subContainer.appendChild(loaderContainer);
|
||||
rootContainer.appendChild(subContainer);
|
||||
|
||||
return { rootContainer, loaderContainer };
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to add the texts to the authentication container
|
||||
* based on the platform or if the game is registered.
|
||||
*/
|
||||
export const addAuthenticationTextsToLoadingContainer = (
|
||||
loaderContainer: HTMLDivElement,
|
||||
platform,
|
||||
isGameRegistered
|
||||
) => {
|
||||
const textContainer: HTMLDivElement = document.createElement('div');
|
||||
textContainer.id = 'authentication-container-texts';
|
||||
textContainer.style.display = 'flex';
|
||||
textContainer.style.flexDirection = 'column';
|
||||
textContainer.style.width = '100%';
|
||||
textContainer.style.justifyContent = 'center';
|
||||
textContainer.style.alignItems = 'center';
|
||||
textContainer.style.position = 'relative';
|
||||
textContainer.style.zIndex = '3';
|
||||
textContainer.style.fontSize = '11pt';
|
||||
textContainer.style.fontFamily =
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"';
|
||||
|
||||
const messages = getPlayerLoginMessages({
|
||||
platform,
|
||||
isGameRegistered,
|
||||
});
|
||||
const title = document.createElement('h1');
|
||||
title.innerText = messages.title;
|
||||
title.style.fontSize = '20pt';
|
||||
title.style.fontWeight = 'bold';
|
||||
const text1 = document.createElement('p');
|
||||
text1.innerText = messages.text1;
|
||||
const text2 = document.createElement('p');
|
||||
text2.innerText = messages.text2;
|
||||
textContainer.appendChild(title);
|
||||
textContainer.appendChild(text1);
|
||||
textContainer.appendChild(text2);
|
||||
|
||||
if (!isGameRegistered) {
|
||||
// Remove the loader.
|
||||
loaderContainer.innerHTML = '';
|
||||
}
|
||||
|
||||
loaderContainer.prepend(textContainer);
|
||||
|
||||
return textContainer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to add the authentication link in case the window hasn't opened properly.
|
||||
* Useful for Electron & Web platforms.
|
||||
*/
|
||||
export const addAuthenticationUrlToTextsContainer = (
|
||||
onClick: () => void,
|
||||
textContainer: HTMLDivElement
|
||||
) => {
|
||||
const link = document.createElement('a');
|
||||
addTouchAndClickEventListeners(link, onClick);
|
||||
link.innerText = 'Click here to authenticate';
|
||||
link.style.color = '#0078d4';
|
||||
link.style.textDecoration = 'none';
|
||||
link.style.textDecoration = 'underline';
|
||||
link.style.cursor = 'pointer';
|
||||
|
||||
textContainer.appendChild(link);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a DOM element to display a dismissable banner.
|
||||
*/
|
||||
export const computeDismissableBanner = function (
|
||||
onDismissBanner: () => void
|
||||
): HTMLDivElement {
|
||||
const divContainer = document.createElement('div');
|
||||
|
||||
divContainer.id = 'authenticated-banner';
|
||||
divContainer.style.position = 'absolute';
|
||||
divContainer.style.pointerEvents = 'all';
|
||||
divContainer.style.backgroundColor = '#0E062D';
|
||||
divContainer.style.top = '0px';
|
||||
divContainer.style.height = '48px';
|
||||
divContainer.style.left = '0px';
|
||||
divContainer.style.width = '100%';
|
||||
divContainer.style.padding = '6px 16px';
|
||||
// Use zIndex 1 to make sure it is below the authentication iframe or webview.
|
||||
divContainer.style.zIndex = '1';
|
||||
divContainer.style.display = 'flex';
|
||||
divContainer.style.flexDirection = 'row-reverse';
|
||||
divContainer.style.justifyContent = 'space-between';
|
||||
divContainer.style.alignItems = 'center';
|
||||
divContainer.style.boxShadow = '0px 4px 4px rgba(0, 0, 0, 0.25)';
|
||||
divContainer.style.fontSize = '11pt';
|
||||
divContainer.style.color = '#FFFFFF';
|
||||
divContainer.style.fontFamily =
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"';
|
||||
|
||||
const _closeContainer: HTMLDivElement = document.createElement('div');
|
||||
_closeContainer.style.cursor = 'pointer';
|
||||
_closeContainer.style.display = 'flex';
|
||||
_closeContainer.style.justifyContent = 'center';
|
||||
_closeContainer.style.alignItems = 'center';
|
||||
_closeContainer.style.zIndex = '3';
|
||||
_closeContainer.style.marginRight = '32px';
|
||||
_closeContainer.style.height = '100%';
|
||||
addTouchAndClickEventListeners(_closeContainer, onDismissBanner);
|
||||
|
||||
const _close = document.createElement('img');
|
||||
_close.setAttribute('width', '30px');
|
||||
_close.setAttribute(
|
||||
'src',
|
||||
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMzAiIHZpZXdCb3g9IjAgMCAzMCAzMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIzLjc1IDguMDEyNUwyMS45ODc1IDYuMjVMMTUgMTMuMjM3NUw4LjAxMjUgNi4yNUw2LjI1IDguMDEyNUwxMy4yMzc1IDE1TDYuMjUgMjEuOTg3NUw4LjAxMjUgMjMuNzVMMTUgMTYuNzYyNUwyMS45ODc1IDIzLjc1TDIzLjc1IDIxLjk4NzVMMTYuNzYyNSAxNUwyMy43NSA4LjAxMjVaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K'
|
||||
);
|
||||
|
||||
_closeContainer.appendChild(_close);
|
||||
divContainer.appendChild(_closeContainer);
|
||||
|
||||
return divContainer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a DOM element representing a banner for the user to know which account
|
||||
* they're using and also to allow switching to another account.
|
||||
*/
|
||||
export const computeAuthenticatedBanner = function (
|
||||
onOpenAuthenticationWindow: () => void,
|
||||
onDismissBanner: () => void,
|
||||
username: string | null
|
||||
): HTMLDivElement {
|
||||
const divContainer = computeDismissableBanner(onDismissBanner);
|
||||
|
||||
const playerUsername = username || 'Anonymous';
|
||||
|
||||
const _textContainer: HTMLDivElement = document.createElement('div');
|
||||
const loggedText = document.createElement('p');
|
||||
loggedText.id = 'loggedText';
|
||||
loggedText.innerHTML = `<img style="margin-right:4px" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOSIgaGVpZ2h0PSI5IiB2aWV3Qm94PSIwIDAgOSA5IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNNC4xNjY2NyAwQzEuODY2NjcgMCAwIDEuODY2NjcgMCA0LjE2NjY3QzAgNi40NjY2NyAxLjg2NjY3IDguMzMzMzMgNC4xNjY2NyA4LjMzMzMzQzYuNDY2NjcgOC4zMzMzMyA4LjMzMzMzIDYuNDY2NjcgOC4zMzMzMyA0LjE2NjY3QzguMzMzMzMgMS44NjY2NyA2LjQ2NjY3IDAgNC4xNjY2NyAwWk0zLjMzMzMzIDYuMjVMMS4yNSA0LjE2NjY3TDEuODM3NSAzLjU3OTE3TDMuMzMzMzMgNS4wNzA4M0w2LjQ5NTgzIDEuOTA4MzNMNy4wODMzMyAyLjVMMy4zMzMzMyA2LjI1WiIgZmlsbD0iIzAwQ0M4MyIvPgo8L3N2Zz4K" />
|
||||
Logged as ${playerUsername}`;
|
||||
loggedText.style.margin = '0px';
|
||||
|
||||
const changeAccountText = document.createElement('p');
|
||||
changeAccountText.id = 'changeAccountText';
|
||||
changeAccountText.innerText = `Click here to switch to another account.`;
|
||||
changeAccountText.style.margin = '0px';
|
||||
changeAccountText.style.marginTop = '4px';
|
||||
changeAccountText.style.textDecoration = 'underline';
|
||||
changeAccountText.style.cursor = 'pointer';
|
||||
addTouchAndClickEventListeners(
|
||||
changeAccountText,
|
||||
onOpenAuthenticationWindow
|
||||
);
|
||||
|
||||
_textContainer.appendChild(loggedText);
|
||||
_textContainer.appendChild(changeAccountText);
|
||||
divContainer.appendChild(_textContainer);
|
||||
|
||||
return divContainer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a DOM element representing a banner for the user to know
|
||||
* they are not connected and to allow logging in.
|
||||
*/
|
||||
export const computeNotAuthenticatedBanner = function (
|
||||
onOpenAuthenticationWindow: () => void,
|
||||
onDismissBanner: () => void
|
||||
): HTMLDivElement {
|
||||
const divContainer = computeDismissableBanner(onDismissBanner);
|
||||
|
||||
const _textContainer: HTMLDivElement = document.createElement('div');
|
||||
const loggedText = document.createElement('p');
|
||||
loggedText.id = 'loggedText';
|
||||
loggedText.innerHTML = `You are not authenticated.`;
|
||||
loggedText.style.margin = '0px';
|
||||
|
||||
const changeAccountText = document.createElement('p');
|
||||
changeAccountText.id = 'changeAccountText';
|
||||
changeAccountText.innerText = `Click here to log in.`;
|
||||
changeAccountText.style.margin = '0px';
|
||||
changeAccountText.style.marginTop = '4px';
|
||||
changeAccountText.style.textDecoration = 'underline';
|
||||
changeAccountText.style.cursor = 'pointer';
|
||||
addTouchAndClickEventListeners(
|
||||
changeAccountText,
|
||||
onOpenAuthenticationWindow
|
||||
);
|
||||
|
||||
_textContainer.appendChild(loggedText);
|
||||
_textContainer.appendChild(changeAccountText);
|
||||
divContainer.appendChild(_textContainer);
|
||||
|
||||
return divContainer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create, display, and hide the logged in confirmation.
|
||||
*/
|
||||
export const displayLoggedInNotification = function (
|
||||
domContainer: HTMLDivElement,
|
||||
username: string
|
||||
) {
|
||||
showNotification(
|
||||
domContainer,
|
||||
'authenticated-notification',
|
||||
`<img style="margin-right:4px" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOSIgaGVpZ2h0PSI5IiB2aWV3Qm94PSIwIDAgOSA5IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNNC4xNjY2NyAwQzEuODY2NjcgMCAwIDEuODY2NjcgMCA0LjE2NjY3QzAgNi40NjY2NyAxLjg2NjY3IDguMzMzMzMgNC4xNjY2NyA4LjMzMzMzQzYuNDY2NjcgOC4zMzMzMyA4LjMzMzMzIDYuNDY2NjcgOC4zMzMzMyA0LjE2NjY3QzguMzMzMzMgMS44NjY2NyA2LjQ2NjY3IDAgNC4xNjY2NyAwWk0zLjMzMzMzIDYuMjVMMS4yNSA0LjE2NjY3TDEuODM3NSAzLjU3OTE3TDMuMzMzMzMgNS4wNzA4M0w2LjQ5NTgzIDEuOTA4MzNMNy4wODMzMyAyLjVMMy4zMzMzMyA2LjI1WiIgZmlsbD0iIzAwQ0M4MyIvPgo8L3N2Zz4K" />
|
||||
Logged as ${username}`,
|
||||
'success'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create, display, and hide the logged in confirmation.
|
||||
*/
|
||||
export const displayLoggedOutNotification = function (
|
||||
domContainer: HTMLDivElement
|
||||
) {
|
||||
showNotification(
|
||||
domContainer,
|
||||
'authenticated-notification',
|
||||
`<img style="margin-right:4px" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOSIgaGVpZ2h0PSI5IiB2aWV3Qm94PSIwIDAgOSA5IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNNC4xNjY2NyAwQzEuODY2NjcgMCAwIDEuODY2NjcgMCA0LjE2NjY3QzAgNi40NjY2NyAxLjg2NjY3IDguMzMzMzMgNC4xNjY2NyA4LjMzMzMzQzYuNDY2NjcgOC4zMzMzMyA4LjMzMzMzIDYuNDY2NjcgOC4zMzMzMyA0LjE2NjY3QzguMzMzMzMgMS44NjY2NyA2LjQ2NjY3IDAgNC4xNjY2NyAwWk0zLjMzMzMzIDYuMjVMMS4yNSA0LjE2NjY3TDEuODM3NSAzLjU3OTE3TDMuMzMzMzMgNS4wNzA4M0w2LjQ5NTgzIDEuOTA4MzNMNy4wODMzMyAyLjVMMy4zMzMzMyA2LjI1WiIgZmlsbD0iIzAwQ0M4MyIvPgo8L3N2Zz4K" />
|
||||
Logged out`,
|
||||
'success'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create, display, and hide an error notification.
|
||||
*/
|
||||
export const displayErrorNotification = function (
|
||||
domContainer: HTMLDivElement
|
||||
) {
|
||||
showNotification(
|
||||
domContainer,
|
||||
'error-notification',
|
||||
'An error occurred while authenticating, please try again.',
|
||||
'error'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to show a notification to the user, that disappears automatically.
|
||||
*/
|
||||
export const showNotification = function (
|
||||
domContainer: HTMLDivElement,
|
||||
id: string,
|
||||
content: string,
|
||||
type: 'success' | 'error'
|
||||
) {
|
||||
const divContainer = document.createElement('div');
|
||||
|
||||
divContainer.id = id;
|
||||
divContainer.style.position = 'absolute';
|
||||
divContainer.style.pointerEvents = 'all';
|
||||
divContainer.style.backgroundColor =
|
||||
type === 'success' ? '#0E062D' : 'red';
|
||||
divContainer.style.top = '12px';
|
||||
divContainer.style.right = '16px';
|
||||
divContainer.style.padding = '6px 32px 6px 6px';
|
||||
// Use zIndex 1 to make sure it is below the authentication iframe or webview.
|
||||
divContainer.style.zIndex = '1';
|
||||
divContainer.style.display = 'flex';
|
||||
divContainer.style.flexDirection = 'row-reverse';
|
||||
divContainer.style.justifyContent = 'space-between';
|
||||
divContainer.style.alignItems = 'center';
|
||||
divContainer.style.boxShadow = '0px 4px 4px rgba(0, 0, 0, 0.25)';
|
||||
divContainer.style.borderRadius = '4px';
|
||||
divContainer.style.fontSize = '11pt';
|
||||
divContainer.style.color = '#FFFFFF';
|
||||
divContainer.style.fontFamily =
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"';
|
||||
try {
|
||||
divContainer.animate(
|
||||
[
|
||||
{ transform: 'translateY(-30px)', opacity: 0 },
|
||||
{ transform: 'translateY(0px)', opacity: 1 },
|
||||
],
|
||||
{
|
||||
duration: 700,
|
||||
easing: 'ease-out',
|
||||
}
|
||||
);
|
||||
} catch {
|
||||
logger.warn('Animation not supported, div will be fixed.');
|
||||
}
|
||||
const loggedText = document.createElement('p');
|
||||
loggedText.id = 'loggedText';
|
||||
loggedText.innerHTML = content;
|
||||
loggedText.style.margin = '0px';
|
||||
|
||||
divContainer.appendChild(loggedText);
|
||||
|
||||
domContainer.appendChild(divContainer);
|
||||
const animationTime = 700;
|
||||
const notificationTime = 5000;
|
||||
setTimeout(() => {
|
||||
try {
|
||||
divContainer.animate(
|
||||
[
|
||||
{ transform: 'translateY(0px)', opacity: 1 },
|
||||
{ transform: 'translateY(-30px)', opacity: 0 },
|
||||
],
|
||||
{
|
||||
duration: animationTime,
|
||||
easing: 'ease-in',
|
||||
}
|
||||
);
|
||||
} catch {
|
||||
logger.warn('Animation not supported, div will be fixed.');
|
||||
}
|
||||
}, notificationTime);
|
||||
// Use timeout because onanimationend listener does not work.
|
||||
setTimeout(() => {
|
||||
divContainer.remove();
|
||||
}, notificationTime + animationTime);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to add event listeners on a pressable/clickable element
|
||||
* to work on both desktop and mobile.
|
||||
*/
|
||||
export const addTouchAndClickEventListeners = function (
|
||||
element: HTMLElement,
|
||||
action: () => void
|
||||
) {
|
||||
// Touch start event listener for mobile.
|
||||
element.addEventListener('touchstart', (event) => {
|
||||
action();
|
||||
});
|
||||
// Click event listener for desktop.
|
||||
element.addEventListener('click', (event) => {
|
||||
action();
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
740
Extensions/PlayerAuthentication/playerauthenticationtools.ts
Normal file
740
Extensions/PlayerAuthentication/playerauthenticationtools.ts
Normal file
@@ -0,0 +1,740 @@
|
||||
namespace gdjs {
|
||||
declare var cordova: any;
|
||||
|
||||
const logger = new gdjs.Logger('Player Authentication');
|
||||
const authComponents = gdjs.playerAuthenticationComponents;
|
||||
// TODO EBO Replace runtimeScene to instanceContainer.
|
||||
export namespace playerAuthentication {
|
||||
// Authentication information.
|
||||
let _username: string | null = null;
|
||||
let _userId: string | null = null;
|
||||
let _userToken: string | null = null;
|
||||
let _justLoggedIn = false;
|
||||
|
||||
let _checkedLocalStorage: boolean = false;
|
||||
|
||||
// Authentication display
|
||||
let _authenticationWindow: Window | null = null; // For Web.
|
||||
let _authenticationInAppWindow: Window | null = null; // For Cordova.
|
||||
let _authenticationRootContainer: HTMLDivElement | null = null;
|
||||
let _authenticationLoaderContainer: HTMLDivElement | null = null;
|
||||
let _authenticationTextContainer: HTMLDivElement | null = null;
|
||||
let _authenticationBanner: HTMLDivElement | null = null;
|
||||
let _authenticationTimeoutId: NodeJS.Timeout | null = null;
|
||||
|
||||
// Communication methods.
|
||||
let _authenticationMessageCallback:
|
||||
| ((event: MessageEvent) => void)
|
||||
| null = null;
|
||||
let _cordovaAuthenticationMessageCallback:
|
||||
| ((event: MessageEvent) => void)
|
||||
| null = null;
|
||||
let _websocket: WebSocket | null = null;
|
||||
|
||||
// Ensure that the condition "just logged in" is valid only for one frame.
|
||||
gdjs.registerRuntimeScenePostEventsCallback(() => {
|
||||
_justLoggedIn = false;
|
||||
});
|
||||
|
||||
const getLocalStorageKey = (gameId: string) =>
|
||||
`${gameId}_authenticatedUser`;
|
||||
|
||||
const getAuthWindowUrl = ({
|
||||
runtimeGame,
|
||||
gameId,
|
||||
connectionId,
|
||||
}: {
|
||||
runtimeGame: gdjs.RuntimeGame;
|
||||
gameId: string;
|
||||
connectionId?: string;
|
||||
}) =>
|
||||
`https://liluo.io/auth?gameId=${gameId}${
|
||||
connectionId ? `&connectionId=${connectionId}` : ''
|
||||
}${
|
||||
runtimeGame.isUsingGDevelopDevelopmentEnvironment() ? '&dev=true' : ''
|
||||
}`;
|
||||
|
||||
/**
|
||||
* Helper returning the platform.
|
||||
*/
|
||||
const getPlatform = (
|
||||
runtimeScene: RuntimeScene
|
||||
): 'electron' | 'cordova' | 'web' => {
|
||||
const electron = runtimeScene.getGame().getRenderer().getElectron();
|
||||
if (electron) {
|
||||
return 'electron';
|
||||
}
|
||||
if (typeof cordova !== 'undefined') return 'cordova';
|
||||
return 'web';
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if a user token is present in the local storage.
|
||||
*/
|
||||
export const isAuthenticated = () => {
|
||||
if (!_checkedLocalStorage) {
|
||||
readAuthenticatedUserFromLocalStorage();
|
||||
}
|
||||
return _userToken !== null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the user just logged in.
|
||||
* Useful to update username or trigger messages in the game.
|
||||
*/
|
||||
export const hasLoggedIn = () => _justLoggedIn;
|
||||
|
||||
/**
|
||||
* Returns the username from the local storage.
|
||||
*/
|
||||
export const getUsername = () => {
|
||||
if (!_checkedLocalStorage) {
|
||||
readAuthenticatedUserFromLocalStorage();
|
||||
}
|
||||
return _username || '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the user token from the local storage.
|
||||
*/
|
||||
export const getUserToken = () => {
|
||||
if (!_checkedLocalStorage) {
|
||||
readAuthenticatedUserFromLocalStorage();
|
||||
}
|
||||
return _userToken || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the username from the local storage.
|
||||
*/
|
||||
export const getUserId = () => {
|
||||
if (!_checkedLocalStorage) {
|
||||
readAuthenticatedUserFromLocalStorage();
|
||||
}
|
||||
return _userId || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the game is registered, false otherwise.
|
||||
* Useful to display a message to the user to register the game before logging in.
|
||||
*/
|
||||
const checkIfGameIsRegistered = (
|
||||
runtimeGame: gdjs.RuntimeGame,
|
||||
gameId: string,
|
||||
tries: number = 0
|
||||
): Promise<boolean> => {
|
||||
const rootApi = runtimeGame.isUsingGDevelopDevelopmentEnvironment()
|
||||
? 'https://api-dev.gdevelop.io'
|
||||
: 'https://api.gdevelop.io';
|
||||
const url = `${rootApi}/game/public-game/${gameId}`;
|
||||
return fetch(url, { method: 'HEAD' }).then(
|
||||
(response) => {
|
||||
if (response.status !== 200) {
|
||||
logger.warn(
|
||||
`Error while fetching the game: ${response.status} ${response.statusText}`
|
||||
);
|
||||
|
||||
// If the response is not 404, it may be a timeout, so retry a few times.
|
||||
if (response.status === 404 || tries > 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return checkIfGameIsRegistered(runtimeGame, gameId, tries + 1);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
(err) => {
|
||||
logger.error('Error while fetching game:', err);
|
||||
return false;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove the user information from the local storage.
|
||||
*/
|
||||
export const logout = (runtimeScene: RuntimeScene) => {
|
||||
_username = null;
|
||||
_userToken = null;
|
||||
_userId = null;
|
||||
|
||||
const gameId = gdjs.projectData.properties.projectUuid;
|
||||
if (!gameId) {
|
||||
logger.error('Missing game id in project properties.');
|
||||
return;
|
||||
}
|
||||
window.localStorage.removeItem(getLocalStorageKey(gameId));
|
||||
cleanUpAuthWindowAndCallbacks(runtimeScene);
|
||||
removeAuthenticationBanner(runtimeScene);
|
||||
const domElementContainer = runtimeScene
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getDomElementContainer();
|
||||
if (!domElementContainer) {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
"The div element covering the game couldn't be found, the authentication banner cannot be displayed."
|
||||
);
|
||||
return;
|
||||
}
|
||||
authComponents.displayLoggedOutNotification(domElementContainer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the user information from the local storage, and store
|
||||
* them in the extension variables.
|
||||
*/
|
||||
const readAuthenticatedUserFromLocalStorage = () => {
|
||||
const gameId = gdjs.projectData.properties.projectUuid;
|
||||
if (!gameId) {
|
||||
logger.error('Missing game id in project properties.');
|
||||
return;
|
||||
}
|
||||
const authenticatedUserStorageItem = window.localStorage.getItem(
|
||||
getLocalStorageKey(gameId)
|
||||
);
|
||||
if (!authenticatedUserStorageItem) {
|
||||
_checkedLocalStorage = true;
|
||||
return;
|
||||
}
|
||||
const authenticatedUser = JSON.parse(authenticatedUserStorageItem);
|
||||
|
||||
_username = authenticatedUser.username;
|
||||
_userId = authenticatedUser.userId;
|
||||
_userToken = authenticatedUser.userToken;
|
||||
_checkedLocalStorage = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to be called on login or error.
|
||||
* Removes all the UI and callbacks.
|
||||
*/
|
||||
const cleanUpAuthWindowAndCallbacks = (runtimeScene: RuntimeScene) => {
|
||||
removeAuthenticationContainer(runtimeScene);
|
||||
clearAuthenticationWindowTimeout();
|
||||
if (_websocket) {
|
||||
_websocket.close();
|
||||
_websocket = null;
|
||||
}
|
||||
// If a new window was opened (web), close it.
|
||||
if (_authenticationWindow) {
|
||||
_authenticationWindow.close();
|
||||
_authenticationWindow = null;
|
||||
}
|
||||
// If an in-app browser was used (cordova), close it.
|
||||
if (_authenticationInAppWindow) {
|
||||
_authenticationInAppWindow.close();
|
||||
_authenticationInAppWindow = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* When the websocket receives the authentication result, close all the
|
||||
* authentication windows, display the notification and focus on the game.
|
||||
*/
|
||||
const handleLoggedInEvent = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
userId: string,
|
||||
username: string | null,
|
||||
userToken: string
|
||||
) {
|
||||
if (!username) {
|
||||
logger.warn('The authenticated player does not have a username');
|
||||
}
|
||||
_username = username;
|
||||
_userId = userId;
|
||||
_userToken = userToken;
|
||||
_justLoggedIn = true;
|
||||
|
||||
const gameId = gdjs.projectData.properties.projectUuid;
|
||||
if (!gameId) {
|
||||
logger.error('Missing game id in project properties.');
|
||||
return;
|
||||
}
|
||||
window.localStorage.setItem(
|
||||
getLocalStorageKey(gameId),
|
||||
JSON.stringify({
|
||||
username: _username,
|
||||
userId: _userId,
|
||||
userToken: _userToken,
|
||||
})
|
||||
);
|
||||
cleanUpAuthWindowAndCallbacks(runtimeScene);
|
||||
removeAuthenticationBanner(runtimeScene);
|
||||
|
||||
const domElementContainer = runtimeScene
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getDomElementContainer();
|
||||
if (!domElementContainer) {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
"The div element covering the game couldn't be found, the authentication banner cannot be displayed."
|
||||
);
|
||||
return;
|
||||
}
|
||||
authComponents.displayLoggedInNotification(
|
||||
domElementContainer,
|
||||
_username || 'Anonymous'
|
||||
);
|
||||
focusOnGame(runtimeScene);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads the event sent by the authentication window and
|
||||
* display the appropriate banner.
|
||||
*/
|
||||
const receiveMessageFromAuthenticationWindow = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
event: MessageEvent,
|
||||
{ checkOrigin }: { checkOrigin: boolean }
|
||||
) {
|
||||
const allowedOrigin = 'https://liluo.io';
|
||||
|
||||
// Check origin of message.
|
||||
if (checkOrigin && event.origin !== allowedOrigin) {
|
||||
throw new Error(`Unexpected origin: ${event.origin}`);
|
||||
}
|
||||
// Check that message is not malformed.
|
||||
if (!event.data.id) {
|
||||
throw new Error('Malformed message');
|
||||
}
|
||||
|
||||
// Handle message.
|
||||
switch (event.data.id) {
|
||||
case 'authenticationResult': {
|
||||
if (!(event.data.body && event.data.body.token)) {
|
||||
throw new Error('Malformed message.');
|
||||
}
|
||||
|
||||
handleLoggedInEvent(
|
||||
runtimeScene,
|
||||
event.data.body.userId,
|
||||
event.data.body.username,
|
||||
event.data.body.token
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle any error that can occur as part of the authentication process.
|
||||
*/
|
||||
const handleAuthenticationError = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
message: string
|
||||
) {
|
||||
logger.error(message);
|
||||
cleanUpAuthWindowAndCallbacks(runtimeScene);
|
||||
|
||||
const domElementContainer = runtimeScene
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getDomElementContainer();
|
||||
if (!domElementContainer) {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
"The div element covering the game couldn't be found, the authentication banner cannot be displayed."
|
||||
);
|
||||
return;
|
||||
}
|
||||
authComponents.displayErrorNotification(domElementContainer);
|
||||
focusOnGame(runtimeScene);
|
||||
};
|
||||
|
||||
/**
|
||||
* If after 5min, no message has been received from the authentication window,
|
||||
* show a notification and remove the authentication container.
|
||||
*/
|
||||
const startAuthenticationWindowTimeout = (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) => {
|
||||
clearAuthenticationWindowTimeout();
|
||||
const time = 12 * 60 * 1000; // 12 minutes, in case the user needs time to authenticate.
|
||||
_authenticationTimeoutId = setTimeout(() => {
|
||||
logger.info(
|
||||
'Authentication window did not send message in time. Closing it.'
|
||||
);
|
||||
cleanUpAuthWindowAndCallbacks(runtimeScene);
|
||||
focusOnGame(runtimeScene);
|
||||
}, time);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the authentication window timeout.
|
||||
* Useful when:
|
||||
* - the authentication succeeded
|
||||
* - the authentication window is closed
|
||||
*/
|
||||
const clearAuthenticationWindowTimeout = () => {
|
||||
if (_authenticationTimeoutId) clearTimeout(_authenticationTimeoutId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Action to display the banner to the user, depending on their authentication status.
|
||||
*/
|
||||
export const displayAuthenticationBanner = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) {
|
||||
if (_authenticationBanner) {
|
||||
// Banner already displayed, ensure it's visible.
|
||||
_authenticationBanner.style.opacity = '1';
|
||||
return;
|
||||
}
|
||||
if (!_checkedLocalStorage) {
|
||||
readAuthenticatedUserFromLocalStorage();
|
||||
}
|
||||
|
||||
const domElementContainer = runtimeScene
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getDomElementContainer();
|
||||
if (!domElementContainer) {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
"The div element covering the game couldn't be found, the authentication banner cannot be displayed."
|
||||
);
|
||||
return;
|
||||
}
|
||||
const onDismissBanner = () => {
|
||||
removeAuthenticationBanner(runtimeScene);
|
||||
};
|
||||
const onOpenAuthenticationWindow = () => {
|
||||
openAuthenticationWindow(runtimeScene);
|
||||
};
|
||||
// We display the corresponding banner depending on the authentication status.
|
||||
_authenticationBanner = _userToken
|
||||
? authComponents.computeAuthenticatedBanner(
|
||||
onOpenAuthenticationWindow,
|
||||
onDismissBanner,
|
||||
_username
|
||||
)
|
||||
: authComponents.computeNotAuthenticatedBanner(
|
||||
onOpenAuthenticationWindow,
|
||||
onDismissBanner
|
||||
);
|
||||
domElementContainer.appendChild(_authenticationBanner);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to handle authentication window on Electron.
|
||||
* We open a new window, and create a websocket to know when the user is logged in.
|
||||
*/
|
||||
const openAuthenticationWindowForElectron = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
gameId: string
|
||||
) => {
|
||||
const wsPlayApi = runtimeScene
|
||||
.getGame()
|
||||
.isUsingGDevelopDevelopmentEnvironment()
|
||||
? 'wss://api-ws-dev.gdevelop.io/play'
|
||||
: 'wss://api-ws.gdevelop.io/play';
|
||||
_websocket = new WebSocket(wsPlayApi);
|
||||
_websocket.onopen = () => {
|
||||
// When socket is open, ask for the connectionId, so that we can open the authentication window.
|
||||
if (_websocket) {
|
||||
_websocket.send(JSON.stringify({ action: 'getConnectionId' }));
|
||||
}
|
||||
};
|
||||
_websocket.onerror = () => {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
'Error while connecting to the authentication server.'
|
||||
);
|
||||
};
|
||||
_websocket.onmessage = (event) => {
|
||||
if (event.data) {
|
||||
const messageContent = JSON.parse(event.data);
|
||||
switch (messageContent.type) {
|
||||
case 'authenticationResult': {
|
||||
const messageData = messageContent.data;
|
||||
handleLoggedInEvent(
|
||||
runtimeScene,
|
||||
messageData.userId,
|
||||
messageData.username,
|
||||
messageData.token
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'connectionId': {
|
||||
const messagegeData = messageContent.data;
|
||||
const connectionId = messagegeData.connectionId;
|
||||
if (!connectionId) {
|
||||
logger.error('No connectionId received');
|
||||
return;
|
||||
}
|
||||
|
||||
const targetUrl = getAuthWindowUrl({
|
||||
runtimeGame: runtimeScene.getGame(),
|
||||
gameId,
|
||||
connectionId,
|
||||
});
|
||||
|
||||
const electron = runtimeScene
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getElectron();
|
||||
const openWindow = () => electron.shell.openExternal(targetUrl);
|
||||
|
||||
openWindow();
|
||||
|
||||
// Add the link to the window in case a popup blocker is preventing the window from opening.
|
||||
if (_authenticationTextContainer) {
|
||||
authComponents.addAuthenticationUrlToTextsContainer(
|
||||
openWindow,
|
||||
_authenticationTextContainer
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to handle authentication window on Cordova.
|
||||
* We open an InAppBrowser window, and listen to messages posted on this window.
|
||||
*/
|
||||
const openAuthenticationWindowForCordova = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
gameId: string
|
||||
) => {
|
||||
const targetUrl = getAuthWindowUrl({
|
||||
runtimeGame: runtimeScene.getGame(),
|
||||
gameId,
|
||||
});
|
||||
|
||||
_authenticationInAppWindow = cordova.InAppBrowser.open(
|
||||
targetUrl,
|
||||
'authentication',
|
||||
'location=yes' // location=yes is important to show the URL bar to the user.
|
||||
);
|
||||
// Listen to messages posted on the authentication window, so that we can
|
||||
// know when the user is authenticated.
|
||||
if (_authenticationInAppWindow) {
|
||||
_cordovaAuthenticationMessageCallback = (event: MessageEvent) => {
|
||||
receiveMessageFromAuthenticationWindow(runtimeScene, event, {
|
||||
checkOrigin: false, // For Cordova we don't check the origin, as the message is read from the InAppBrowser directly.
|
||||
});
|
||||
};
|
||||
_authenticationInAppWindow.addEventListener(
|
||||
'message',
|
||||
_cordovaAuthenticationMessageCallback,
|
||||
true
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to handle authentication window on web.
|
||||
* We open a new window, and listen to messages posted back to the game window.
|
||||
*/
|
||||
const openAuthenticationWindowForWeb = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
gameId: string
|
||||
) => {
|
||||
// If we're on a browser, open a new window.
|
||||
const targetUrl = getAuthWindowUrl({
|
||||
runtimeGame: runtimeScene.getGame(),
|
||||
gameId,
|
||||
});
|
||||
|
||||
// Listen to messages posted by the authentication window, so that we can
|
||||
// know when the user is authenticated.
|
||||
_authenticationMessageCallback = (event: MessageEvent) => {
|
||||
receiveMessageFromAuthenticationWindow(runtimeScene, event, {
|
||||
checkOrigin: true,
|
||||
});
|
||||
};
|
||||
window.addEventListener('message', _authenticationMessageCallback, true);
|
||||
|
||||
const left = screen.width / 2 - 500 / 2;
|
||||
const top = screen.height / 2 - 600 / 2;
|
||||
const windowFeatures = `left=${left},top=${top},width=500,height=600`;
|
||||
const openWindow = () =>
|
||||
window.open(targetUrl, 'authentication', windowFeatures);
|
||||
_authenticationWindow = openWindow();
|
||||
|
||||
// Add the link to the window in case a popup blocker is preventing the window from opening.
|
||||
if (_authenticationTextContainer) {
|
||||
authComponents.addAuthenticationUrlToTextsContainer(
|
||||
openWindow,
|
||||
_authenticationTextContainer
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Action to display the authentication window to the user.
|
||||
*/
|
||||
export const openAuthenticationWindow = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) {
|
||||
// Create the authentication container for the player to wait.
|
||||
const domElementContainer = runtimeScene
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getDomElementContainer();
|
||||
if (!domElementContainer) {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
"The div element covering the game couldn't be found, the authentication window cannot be displayed."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const onAuthenticationContainerDismissed = () => {
|
||||
cleanUpAuthWindowAndCallbacks(runtimeScene);
|
||||
displayAuthenticationBanner(runtimeScene);
|
||||
};
|
||||
|
||||
const _gameId = gdjs.projectData.properties.projectUuid;
|
||||
if (!_gameId) {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
'The game ID is missing, the authentication window cannot be opened.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the banner is displayed, hide it, so that it can be shown again if the user closes the window.
|
||||
if (_authenticationBanner) _authenticationBanner.style.opacity = '0';
|
||||
|
||||
const platform = getPlatform(runtimeScene);
|
||||
const {
|
||||
rootContainer,
|
||||
loaderContainer,
|
||||
} = authComponents.computeAuthenticationContainer(
|
||||
onAuthenticationContainerDismissed
|
||||
);
|
||||
_authenticationRootContainer = rootContainer;
|
||||
_authenticationLoaderContainer = loaderContainer;
|
||||
|
||||
// Display the authentication window right away, to show a loader
|
||||
// while the call for game registration is happening.
|
||||
domElementContainer.appendChild(_authenticationRootContainer);
|
||||
|
||||
// If the game is registered, open the authentication window.
|
||||
// Otherwise, open the window indicating that the game is not registered.
|
||||
checkIfGameIsRegistered(runtimeScene.getGame(), _gameId)
|
||||
.then((isGameRegistered) => {
|
||||
if (_authenticationLoaderContainer) {
|
||||
_authenticationTextContainer = authComponents.addAuthenticationTextsToLoadingContainer(
|
||||
_authenticationLoaderContainer,
|
||||
platform,
|
||||
isGameRegistered
|
||||
);
|
||||
}
|
||||
if (isGameRegistered) {
|
||||
startAuthenticationWindowTimeout(runtimeScene);
|
||||
|
||||
// Based on which platform the game is running, we open the authentication window
|
||||
// with a different window, with or without a websocket.
|
||||
switch (platform) {
|
||||
case 'electron':
|
||||
openAuthenticationWindowForElectron(runtimeScene, _gameId);
|
||||
break;
|
||||
case 'cordova':
|
||||
openAuthenticationWindowForCordova(runtimeScene, _gameId);
|
||||
break;
|
||||
case 'web':
|
||||
default:
|
||||
openAuthenticationWindowForWeb(runtimeScene, _gameId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
'Error while checking if the game is registered.'
|
||||
);
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Condition to check if the window is open, so that the game can be paused in the background.
|
||||
*/
|
||||
export const isAuthenticationWindowOpen = function (): boolean {
|
||||
return !!_authenticationRootContainer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove the container displaying the authentication window and the callback.
|
||||
*/
|
||||
export const removeAuthenticationContainer = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) {
|
||||
const domElementContainer = runtimeScene
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getDomElementContainer();
|
||||
if (!domElementContainer) {
|
||||
logger.info(
|
||||
"The div element covering the game couldn't be found, the authentication must be already closed."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the authentication root container.
|
||||
if (_authenticationRootContainer) {
|
||||
domElementContainer.removeChild(_authenticationRootContainer);
|
||||
}
|
||||
|
||||
// Remove the authentication callbacks.
|
||||
if (_authenticationMessageCallback) {
|
||||
window.removeEventListener(
|
||||
'message',
|
||||
_authenticationMessageCallback,
|
||||
true
|
||||
);
|
||||
_authenticationMessageCallback = null;
|
||||
// No need to detach the callback from the InAppBrowser, as it's destroyed when the window is closed.
|
||||
_cordovaAuthenticationMessageCallback = null;
|
||||
}
|
||||
|
||||
_authenticationRootContainer = null;
|
||||
_authenticationLoaderContainer = null;
|
||||
_authenticationTextContainer = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove the banner displaying the authentication status.
|
||||
*/
|
||||
const removeAuthenticationBanner = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) {
|
||||
if (!_authenticationBanner) {
|
||||
logger.info(
|
||||
"The authentication banner couldn't be found, the authentication banner must be already closed."
|
||||
);
|
||||
return;
|
||||
}
|
||||
const domElementContainer = runtimeScene
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getDomElementContainer();
|
||||
if (!domElementContainer) {
|
||||
logger.info(
|
||||
"The div element covering the game couldn't be found, the authentication must be already closed."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
domElementContainer.removeChild(_authenticationBanner);
|
||||
_authenticationBanner = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus on game canvas to allow user to interact with it.
|
||||
*/
|
||||
const focusOnGame = function (runtimeScene: gdjs.RuntimeScene) {
|
||||
const gameCanvas = runtimeScene.getGame().getRenderer().getCanvas();
|
||||
if (gameCanvas) gameCanvas.focus();
|
||||
};
|
||||
}
|
||||
}
|
@@ -20,6 +20,8 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
|
||||
"Florian Rival and Aurélien Vivet",
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/objects/shape_painter");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Shape painter"))
|
||||
.SetIcon("CppPlatform/Extensions/primitivedrawingicon.png");
|
||||
|
||||
gd::ObjectMetadata& obj =
|
||||
extension
|
||||
|
@@ -27,11 +27,11 @@ namespace gdjs {
|
||||
|
||||
constructor(
|
||||
runtimeObject: gdjs.ShapePainterRuntimeObject,
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
this._object = runtimeObject;
|
||||
this._graphics = new PIXI.Graphics();
|
||||
runtimeScene
|
||||
instanceContainer
|
||||
.getLayer('')
|
||||
.getRenderer()
|
||||
.addRendererObject(this._graphics, runtimeObject.getZOrder());
|
||||
|
@@ -57,14 +57,14 @@ namespace gdjs {
|
||||
private static readonly _pointForTransformation: FloatPoint = [0, 0];
|
||||
|
||||
/**
|
||||
* @param runtimeScene The scene the object belongs to.
|
||||
* @param instanceContainer The container the object belongs to.
|
||||
* @param shapePainterObjectData The initial properties of the object
|
||||
*/
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
shapePainterObjectData: ShapePainterObjectData
|
||||
) {
|
||||
super(runtimeScene, shapePainterObjectData);
|
||||
super(instanceContainer, shapePainterObjectData);
|
||||
this._fillColor = parseInt(
|
||||
gdjs.rgbToHex(
|
||||
shapePainterObjectData.fillColor.r,
|
||||
@@ -88,7 +88,7 @@ namespace gdjs {
|
||||
this._clearBetweenFrames = shapePainterObjectData.clearBetweenFrames;
|
||||
this._renderer = new gdjs.ShapePainterRuntimeObjectRenderer(
|
||||
this,
|
||||
runtimeScene
|
||||
instanceContainer
|
||||
);
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
@@ -158,12 +158,12 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
stepBehaviorsPreEvents(runtimeScene: gdjs.RuntimeScene) {
|
||||
stepBehaviorsPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
//We redefine stepBehaviorsPreEvents just to clear the graphics before running events.
|
||||
if (this._clearBetweenFrames) {
|
||||
this.clear();
|
||||
}
|
||||
super.stepBehaviorsPreEvents(runtimeScene);
|
||||
super.stepBehaviorsPreEvents(instanceContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -468,7 +468,7 @@ namespace gdjs {
|
||||
}
|
||||
super.setAngle(angle);
|
||||
this._renderer.updateAngle();
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -584,7 +584,7 @@ namespace gdjs {
|
||||
}
|
||||
this._scaleX = newScale * (this._flippedX ? -1 : 1);
|
||||
this._renderer.updateScaleX();
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -601,7 +601,7 @@ namespace gdjs {
|
||||
}
|
||||
this._scaleY = newScale * (this._flippedY ? -1 : 1);
|
||||
this._renderer.updateScaleY();
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
flipX(enable: boolean): void {
|
||||
@@ -609,7 +609,7 @@ namespace gdjs {
|
||||
this._scaleX *= -1;
|
||||
this._flippedX = enable;
|
||||
this._renderer.updateScaleX();
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,7 +618,7 @@ namespace gdjs {
|
||||
this._scaleY *= -1;
|
||||
this._flippedY = enable;
|
||||
this._renderer.updateScaleY();
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,7 +660,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
invalidateBounds() {
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
getDrawableX(): float {
|
||||
@@ -679,7 +679,7 @@ namespace gdjs {
|
||||
return this._renderer.getHeight();
|
||||
}
|
||||
|
||||
updatePreRender(runtimeScene: gdjs.RuntimeScene): void {
|
||||
updatePreRender(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
this._renderer.updatePreRender();
|
||||
}
|
||||
|
||||
@@ -741,7 +741,7 @@ namespace gdjs {
|
||||
rectangle[3][0] = left;
|
||||
rectangle[3][1] = bottom;
|
||||
|
||||
this.hitBoxesDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
updateHitBoxes(): void {
|
||||
|
@@ -34,7 +34,7 @@ module.exports = {
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/all-features/screenshot')
|
||||
.setCategory('Device');
|
||||
.setCategory('Advanced');
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata(_('Screenshot'))
|
||||
.setIcon('JsPlatform/Extensions/take_screenshot32.png');
|
||||
|
@@ -4,16 +4,16 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Save a screenshot of the game.
|
||||
* @param runtimeScene The scene
|
||||
* @param instanceContainer The container
|
||||
* @param savePath The path where to save the screenshot
|
||||
*/
|
||||
export const takeScreenshot = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
savePath: string
|
||||
) {
|
||||
const fs = typeof require !== 'undefined' ? require('fs') : null;
|
||||
if (fs) {
|
||||
const canvas = runtimeScene.getGame().getRenderer().getCanvas();
|
||||
const canvas = instanceContainer.getGame().getRenderer().getCanvas();
|
||||
if (canvas) {
|
||||
const content = canvas
|
||||
.toDataURL('image/png')
|
||||
|
@@ -1,17 +1,24 @@
|
||||
namespace gdjs {
|
||||
export interface RuntimeGame {
|
||||
shopifyClients: { [name: string]: any };
|
||||
}
|
||||
declare var ShopifyBuy: any;
|
||||
|
||||
export class ShopifyClientsManager {
|
||||
static set(runtimeScene, name, shopifyClient) {
|
||||
const game = runtimeScene.getGame();
|
||||
static set(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
name: string,
|
||||
shopifyClient
|
||||
) {
|
||||
const game = instanceContainer.getGame();
|
||||
if (!game.shopifyClients) {
|
||||
game.shopifyClients = {};
|
||||
}
|
||||
game.shopifyClients[name] = shopifyClient;
|
||||
}
|
||||
|
||||
static get(runtimeScene, name) {
|
||||
const game = runtimeScene.getGame();
|
||||
static get(instanceContainer: gdjs.RuntimeInstanceContainer, name: string) {
|
||||
const game = instanceContainer.getGame();
|
||||
if (!game.shopifyClients) {
|
||||
game.shopifyClients = {};
|
||||
}
|
||||
@@ -22,11 +29,11 @@ namespace gdjs {
|
||||
export namespace evtTools {
|
||||
export namespace shopify {
|
||||
export const buildClient = function (
|
||||
runtimeScene,
|
||||
name,
|
||||
domain,
|
||||
appId,
|
||||
accessToken
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
name: string,
|
||||
domain: string,
|
||||
appId: string,
|
||||
accessToken: string
|
||||
) {
|
||||
if (typeof ShopifyBuy === 'undefined') {
|
||||
return;
|
||||
@@ -37,21 +44,24 @@ namespace gdjs {
|
||||
appId: appId,
|
||||
});
|
||||
const shopifyClient = ShopifyBuy.buildClient(config);
|
||||
ShopifyClientsManager.set(runtimeScene, name, shopifyClient);
|
||||
ShopifyClientsManager.set(instanceContainer, name, shopifyClient);
|
||||
};
|
||||
|
||||
export const getCheckoutUrlForProduct = function (
|
||||
runtimeScene,
|
||||
name,
|
||||
productId,
|
||||
quantity,
|
||||
variantIndex,
|
||||
successVariable,
|
||||
errorVariable
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
name: string,
|
||||
productId: string,
|
||||
quantity: number,
|
||||
variantIndex: number,
|
||||
successVariable: gdjs.Variable,
|
||||
errorVariable: gdjs.Variable
|
||||
) {
|
||||
errorVariable.setString('');
|
||||
successVariable.setString('');
|
||||
const shopifyClient = ShopifyClientsManager.get(runtimeScene, name);
|
||||
const shopifyClient = ShopifyClientsManager.get(
|
||||
instanceContainer,
|
||||
name
|
||||
);
|
||||
shopifyClient.fetchProduct(productId).then(
|
||||
function (product) {
|
||||
if (variantIndex < 0 || variantIndex >= product.variants.length) {
|
||||
|
@@ -3,13 +3,17 @@ namespace gdjs {
|
||||
export namespace spatialSound {
|
||||
const logger = new gdjs.Logger('Spatial Sound');
|
||||
export const setSoundPosition = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
channel: integer,
|
||||
x: float,
|
||||
y: float,
|
||||
z: float
|
||||
) => {
|
||||
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
|
||||
// TODO EBO The position must be transform to the scene position when it comes from a custom object.
|
||||
const sound = instanceContainer
|
||||
.getScene()
|
||||
.getSoundManager()
|
||||
.getSoundOnChannel(channel);
|
||||
if (sound) sound.setSpatialPosition(x, y, z);
|
||||
else
|
||||
logger.error(
|
||||
|
@@ -15,7 +15,7 @@ void DeclareSystemInfoExtension(gd::PlatformExtension& extension) {
|
||||
_("Get information about the system and device running the game."),
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("Device");
|
||||
.SetCategory("Advanced");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("System information"))
|
||||
.SetIcon("CppPlatform/Extensions/systeminfoicon.png");
|
||||
|
||||
|
@@ -83,18 +83,20 @@ namespace gdjs {
|
||||
* @returns true if WebGL is supported
|
||||
*/
|
||||
export const isWebGLSupported = (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): boolean => {
|
||||
return runtimeScene.getGame().getRenderer().isWebGLSupported();
|
||||
return instanceContainer.getGame().getRenderer().isWebGLSupported();
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the game is running as a preview, launched from an editor.
|
||||
* @param runtimeScene The current scene.
|
||||
* @param instanceContainer The current container.
|
||||
* @returns true if the game is running as a preview.
|
||||
*/
|
||||
export const isPreview = (runtimeScene: gdjs.RuntimeScene): boolean => {
|
||||
return runtimeScene.getGame().isPreview();
|
||||
export const isPreview = (
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): boolean => {
|
||||
return instanceContainer.getGame().isPreview();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ void DeclareTextEntryObjectExtension(gd::PlatformExtension& extension) {
|
||||
"entered with a keyboard by a player."),
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("User interface")
|
||||
.SetExtensionHelpPath("/objects/text_entry");
|
||||
|
||||
gd::ObjectMetadata& obj =
|
||||
@@ -27,7 +28,7 @@ void DeclareTextEntryObjectExtension(gd::PlatformExtension& extension) {
|
||||
_("Invisible object used to get the text "
|
||||
"entered with the keyboard."),
|
||||
"CppPlatform/Extensions/textentry.png")
|
||||
.SetCategoryFullName(_("Advanced"));
|
||||
.SetCategoryFullName(_("User interface"));
|
||||
|
||||
obj.AddAction("String",
|
||||
_("Text in memory"),
|
||||
|
@@ -1,11 +1,11 @@
|
||||
namespace gdjs {
|
||||
class TextEntryRuntimeObjectPixiRenderer {
|
||||
_object: any;
|
||||
_object: gdjs.TextEntryRuntimeObject;
|
||||
_pressHandler: any;
|
||||
_upHandler: any;
|
||||
_downHandler: any;
|
||||
|
||||
constructor(runtimeObject) {
|
||||
constructor(runtimeObject: gdjs.TextEntryRuntimeObject) {
|
||||
this._object = runtimeObject;
|
||||
this._pressHandler = function (evt) {
|
||||
if (!runtimeObject.isActivated()) {
|
||||
|
@@ -12,14 +12,14 @@ namespace gdjs {
|
||||
_renderer: gdjs.TextEntryRuntimeObjectRenderer;
|
||||
|
||||
/**
|
||||
* @param runtimeScene The scene the object belongs to.
|
||||
* @param instanceContainer The container the object belongs to.
|
||||
* @param textEntryObjectData The initial properties of the object
|
||||
*/
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
textEntryObjectData: ObjectData
|
||||
) {
|
||||
super(runtimeScene, textEntryObjectData);
|
||||
super(instanceContainer, textEntryObjectData);
|
||||
this._renderer = new gdjs.TextEntryRuntimeObjectRenderer(this);
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
@@ -31,14 +31,14 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
onDestroyFromScene(runtimeScene): void {
|
||||
super.onDestroyFromScene(runtimeScene);
|
||||
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
super.onDestroyFromScene(instanceContainer);
|
||||
if (this._renderer.onDestroy) {
|
||||
this._renderer.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
update(runtimeScene: gdjs.RuntimeScene): void {
|
||||
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
if ((this._renderer as any).getString) {
|
||||
this._str = (this._renderer as any).getString();
|
||||
}
|
||||
|
@@ -31,7 +31,10 @@ module.exports = {
|
||||
_('A text field the player can type text into.'),
|
||||
'Florian Rival',
|
||||
'MIT'
|
||||
);
|
||||
)
|
||||
.setCategory('User interface');
|
||||
extension.addInstructionOrExpressionGroupMetadata(_("Text Input"))
|
||||
.setIcon("JsPlatform/Extensions/text_input.svg");
|
||||
|
||||
const textInputObject = new gd.ObjectJsImplementation();
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object
|
||||
@@ -277,7 +280,7 @@ module.exports = {
|
||||
'JsPlatform/Extensions/text_input.svg',
|
||||
textInputObject
|
||||
)
|
||||
.setCategoryFullName(_('Form control'))
|
||||
.setCategoryFullName(_('User interface'))
|
||||
.addUnsupportedBaseObjectCapability('effect')
|
||||
.setIncludeFile('Extensions/TextInput/textinputruntimeobject.js')
|
||||
.addIncludeFile(
|
||||
@@ -612,7 +615,7 @@ module.exports = {
|
||||
this.update();
|
||||
}
|
||||
|
||||
static getThumbnail(project, resourcesLoader, object) {
|
||||
static getThumbnail(project, resourcesLoader, objectConfiguration) {
|
||||
return 'JsPlatform/Extensions/text_input.svg';
|
||||
}
|
||||
|
||||
|
@@ -29,13 +29,16 @@ namespace gdjs {
|
||||
class TextInputRuntimeObjectPixiRenderer {
|
||||
private _object: gdjs.TextInputRuntimeObject;
|
||||
private _input: HTMLInputElement | HTMLTextAreaElement | null = null;
|
||||
private _runtimeScene: gdjs.RuntimeScene;
|
||||
private _instanceContainer: gdjs.RuntimeInstanceContainer;
|
||||
private _runtimeGame: gdjs.RuntimeGame;
|
||||
|
||||
constructor(runtimeObject: gdjs.TextInputRuntimeObject) {
|
||||
constructor(
|
||||
runtimeObject: gdjs.TextInputRuntimeObject,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
this._object = runtimeObject;
|
||||
this._runtimeScene = runtimeObject.getRuntimeScene();
|
||||
this._runtimeGame = this._runtimeScene.getGame();
|
||||
this._instanceContainer = instanceContainer;
|
||||
this._runtimeGame = this._instanceContainer.getGame();
|
||||
|
||||
this._createElement();
|
||||
}
|
||||
@@ -123,51 +126,71 @@ namespace gdjs {
|
||||
// Hide the input entirely if the layer is not visible.
|
||||
// Because this object is rendered as a DOM element (and not part of the PixiJS
|
||||
// scene graph), we have to do this manually.
|
||||
const layer = this._runtimeScene.getLayer(this._object.getLayer());
|
||||
const layer = this._instanceContainer.getLayer(this._object.getLayer());
|
||||
if (!layer.isVisible()) {
|
||||
this._input.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const runtimeGame = this._runtimeScene.getGame();
|
||||
const workingPoint: FloatPoint = gdjs.staticArray(
|
||||
TextInputRuntimeObjectPixiRenderer.prototype.updatePreRender
|
||||
) as FloatPoint;
|
||||
|
||||
const runtimeGame = this._instanceContainer.getGame();
|
||||
const runtimeGameRenderer = runtimeGame.getRenderer();
|
||||
const topLeftCanvasCoordinates = layer.convertInverseCoords(
|
||||
this._object.x,
|
||||
this._object.y,
|
||||
0
|
||||
0,
|
||||
workingPoint
|
||||
);
|
||||
const canvasLeft = topLeftCanvasCoordinates[0];
|
||||
const canvasTop = topLeftCanvasCoordinates[1];
|
||||
|
||||
const bottomRightCanvasCoordinates = layer.convertInverseCoords(
|
||||
this._object.x + this._object.getWidth(),
|
||||
this._object.y + this._object.getHeight(),
|
||||
0
|
||||
0,
|
||||
workingPoint
|
||||
);
|
||||
const canvasRight = bottomRightCanvasCoordinates[0];
|
||||
const canvasBottom = bottomRightCanvasCoordinates[1];
|
||||
|
||||
// Hide the input entirely if not visible at all.
|
||||
const isOutsideCanvas =
|
||||
bottomRightCanvasCoordinates[0] < 0 ||
|
||||
bottomRightCanvasCoordinates[1] < 0 ||
|
||||
topLeftCanvasCoordinates[0] > runtimeGame.getGameResolutionWidth() ||
|
||||
topLeftCanvasCoordinates[1] > runtimeGame.getGameResolutionHeight();
|
||||
canvasRight < 0 ||
|
||||
canvasBottom < 0 ||
|
||||
canvasLeft > runtimeGame.getGameResolutionWidth() ||
|
||||
canvasTop > runtimeGame.getGameResolutionHeight();
|
||||
if (isOutsideCanvas) {
|
||||
this._input.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Position the input on the container on top of the canvas.
|
||||
workingPoint[0] = canvasLeft;
|
||||
workingPoint[1] = canvasRight;
|
||||
const topLeftPageCoordinates = runtimeGameRenderer.convertCanvasToDomElementContainerCoords(
|
||||
topLeftCanvasCoordinates
|
||||
workingPoint,
|
||||
workingPoint
|
||||
);
|
||||
const pageLeft = workingPoint[0];
|
||||
const pageTop = workingPoint[1];
|
||||
|
||||
workingPoint[0] = canvasRight;
|
||||
workingPoint[1] = canvasBottom;
|
||||
const bottomRightPageCoordinates = runtimeGameRenderer.convertCanvasToDomElementContainerCoords(
|
||||
bottomRightCanvasCoordinates
|
||||
workingPoint,
|
||||
workingPoint
|
||||
);
|
||||
const pageRight = workingPoint[0];
|
||||
const pageBottom = workingPoint[1];
|
||||
|
||||
const widthInContainer =
|
||||
bottomRightPageCoordinates[0] - topLeftPageCoordinates[0];
|
||||
const heightInContainer =
|
||||
bottomRightPageCoordinates[1] - topLeftPageCoordinates[1];
|
||||
const widthInContainer = pageRight - pageLeft;
|
||||
const heightInContainer = pageBottom - pageTop;
|
||||
|
||||
this._input.style.left = topLeftPageCoordinates[0] + 'px';
|
||||
this._input.style.top = topLeftPageCoordinates[1] + 'px';
|
||||
this._input.style.left = pageLeft + 'px';
|
||||
this._input.style.top = pageTop + 'px';
|
||||
this._input.style.width = widthInContainer + 'px';
|
||||
this._input.style.height = heightInContainer + 'px';
|
||||
this._input.style.transform =
|
||||
@@ -195,7 +218,7 @@ namespace gdjs {
|
||||
|
||||
updateFont() {
|
||||
if (!this._input) return;
|
||||
this._input.style.fontFamily = this._runtimeScene
|
||||
this._input.style.fontFamily = this._instanceContainer
|
||||
.getGame()
|
||||
.getFontManager()
|
||||
.getFontFamily(this._object.getFontResourceName());
|
||||
|
@@ -71,10 +71,10 @@ namespace gdjs {
|
||||
_renderer: TextInputRuntimeObjectRenderer;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
objectData: TextInputObjectData
|
||||
) {
|
||||
super(runtimeScene, objectData);
|
||||
super(instanceContainer, objectData);
|
||||
|
||||
this._string = objectData.content.initialValue;
|
||||
this._placeholder = objectData.content.placeholder;
|
||||
@@ -92,7 +92,10 @@ namespace gdjs {
|
||||
this._disabled = objectData.content.disabled;
|
||||
this._readOnly = objectData.content.readOnly;
|
||||
|
||||
this._renderer = new gdjs.TextInputRuntimeObjectRenderer(this);
|
||||
this._renderer = new gdjs.TextInputRuntimeObjectRenderer(
|
||||
this,
|
||||
instanceContainer
|
||||
);
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
this.onCreated();
|
||||
@@ -167,7 +170,7 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
updatePreRender(runtimeScene: RuntimeScene): void {
|
||||
updatePreRender(instanceContainer: RuntimeInstanceContainer): void {
|
||||
this._renderer.updatePreRender();
|
||||
}
|
||||
|
||||
@@ -196,8 +199,8 @@ namespace gdjs {
|
||||
this._renderer.onSceneResumed();
|
||||
}
|
||||
|
||||
onDestroyFromScene(runtimeScene: gdjs.RuntimeScene): void {
|
||||
super.onDestroyFromScene(runtimeScene);
|
||||
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
super.onDestroyFromScene(instanceContainer);
|
||||
this._renderer.onDestroy();
|
||||
}
|
||||
|
||||
|
@@ -23,7 +23,10 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
"some indicators, menu buttons, dialogues..."),
|
||||
"Florian Rival and Victor Levasseur",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("User interface")
|
||||
.SetExtensionHelpPath("/objects/text");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Text object"))
|
||||
.SetIcon("CppPlatform/Extensions/texticon.png");
|
||||
|
||||
gd::ObjectMetadata& obj =
|
||||
extension
|
||||
@@ -31,7 +34,7 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
_("Text"),
|
||||
_("Displays a text on the screen."),
|
||||
"CppPlatform/Extensions/texticon.png")
|
||||
.SetCategoryFullName(_("Texts"));
|
||||
.SetCategoryFullName(_("User interface"));
|
||||
|
||||
obj.AddAction("String",
|
||||
_("Modify the text"),
|
||||
@@ -149,8 +152,8 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
obj.AddAction("SetGradient",
|
||||
_("Gradient"),
|
||||
_("Change the gradient of the text."),
|
||||
_("Change gradient of _PARAM0_ to colors _PARAM1_ _PARAM2_ "
|
||||
"_PARAM3_ _PARAM4_ type _PARAM5_"),
|
||||
_("Change gradient of _PARAM0_ to colors _PARAM2_ "
|
||||
"_PARAM3_ _PARAM4_ _PARAM5_, type _PARAM1_"),
|
||||
_("Effects"),
|
||||
"res/actions/textGradient24.png",
|
||||
"res/actions/textGradient.png")
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user