Compare commits
15 Commits
d474c2a47e
...
experiment
Author | SHA1 | Date | |
---|---|---|---|
![]() |
428fb7cf3d | ||
![]() |
946b6eadb3 | ||
![]() |
2c8523c88d | ||
![]() |
b81cbbac81 | ||
![]() |
bb41026db6 | ||
![]() |
e94567fdb5 | ||
![]() |
e08491f5f1 | ||
![]() |
e24de3c164 | ||
![]() |
d550e36294 | ||
![]() |
f5dfca9811 | ||
![]() |
6993a05bd4 | ||
![]() |
a44891bedc | ||
![]() |
14de91a871 | ||
![]() |
3918896542 | ||
![]() |
5156431ee1 |
@@ -42,15 +42,19 @@ gd::String EventsCodeGenerator::GenerateRelationalOperatorCall(
|
||||
const vector<gd::String>& arguments,
|
||||
const gd::String& callStartString,
|
||||
std::size_t startFromArgument) {
|
||||
std::size_t relationalOperatorIndex = instrInfos.parameters.GetParametersCount();
|
||||
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
|
||||
std::size_t relationalOperatorIndex =
|
||||
instrInfos.parameters.GetParametersCount();
|
||||
for (std::size_t i = startFromArgument;
|
||||
i < instrInfos.parameters.GetParametersCount();
|
||||
++i) {
|
||||
if (instrInfos.parameters.GetParameter(i).GetType() == "relationalOperator") {
|
||||
if (instrInfos.parameters.GetParameter(i).GetType() ==
|
||||
"relationalOperator") {
|
||||
relationalOperatorIndex = i;
|
||||
}
|
||||
}
|
||||
// Ensure that there is at least one parameter after the relational operator
|
||||
if (relationalOperatorIndex + 1 >= instrInfos.parameters.GetParametersCount()) {
|
||||
if (relationalOperatorIndex + 1 >=
|
||||
instrInfos.parameters.GetParametersCount()) {
|
||||
ReportError();
|
||||
return "";
|
||||
}
|
||||
@@ -87,20 +91,23 @@ gd::String EventsCodeGenerator::GenerateRelationalOperation(
|
||||
const gd::String& relationalOperator,
|
||||
const gd::String& lhs,
|
||||
const gd::String& rhs) {
|
||||
return lhs + " " + GenerateRelationalOperatorCodes(relationalOperator) + " " + rhs;
|
||||
return lhs + " " + GenerateRelationalOperatorCodes(relationalOperator) + " " +
|
||||
rhs;
|
||||
}
|
||||
|
||||
const gd::String EventsCodeGenerator::GenerateRelationalOperatorCodes(const gd::String &operatorString) {
|
||||
if (operatorString == "=") {
|
||||
return "==";
|
||||
}
|
||||
if (operatorString != "<" && operatorString != ">" &&
|
||||
operatorString != "<=" && operatorString != ">=" && operatorString != "!=" &&
|
||||
operatorString != "startsWith" && operatorString != "endsWith" && operatorString != "contains") {
|
||||
cout << "Warning: Bad relational operator: Set to == by default." << endl;
|
||||
return "==";
|
||||
}
|
||||
return operatorString;
|
||||
const gd::String EventsCodeGenerator::GenerateRelationalOperatorCodes(
|
||||
const gd::String& operatorString) {
|
||||
if (operatorString == "=") {
|
||||
return "==";
|
||||
}
|
||||
if (operatorString != "<" && operatorString != ">" &&
|
||||
operatorString != "<=" && operatorString != ">=" &&
|
||||
operatorString != "!=" && operatorString != "startsWith" &&
|
||||
operatorString != "endsWith" && operatorString != "contains") {
|
||||
cout << "Warning: Bad relational operator: Set to == by default." << endl;
|
||||
return "==";
|
||||
}
|
||||
return operatorString;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,7 +131,8 @@ gd::String EventsCodeGenerator::GenerateOperatorCall(
|
||||
const gd::String& getterStartString,
|
||||
std::size_t startFromArgument) {
|
||||
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
|
||||
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
|
||||
for (std::size_t i = startFromArgument;
|
||||
i < instrInfos.parameters.GetParametersCount();
|
||||
++i) {
|
||||
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
|
||||
operatorIndex = i;
|
||||
@@ -195,7 +203,8 @@ gd::String EventsCodeGenerator::GenerateCompoundOperatorCall(
|
||||
const gd::String& callStartString,
|
||||
std::size_t startFromArgument) {
|
||||
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
|
||||
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
|
||||
for (std::size_t i = startFromArgument;
|
||||
i < instrInfos.parameters.GetParametersCount();
|
||||
++i) {
|
||||
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
|
||||
operatorIndex = i;
|
||||
@@ -248,7 +257,8 @@ gd::String EventsCodeGenerator::GenerateMutatorCall(
|
||||
const gd::String& callStartString,
|
||||
std::size_t startFromArgument) {
|
||||
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
|
||||
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
|
||||
for (std::size_t i = startFromArgument;
|
||||
i < instrInfos.parameters.GetParametersCount();
|
||||
++i) {
|
||||
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
|
||||
operatorIndex = i;
|
||||
@@ -323,34 +333,42 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
|
||||
}
|
||||
|
||||
// Insert code only parameters and be sure there is no lack of parameter.
|
||||
while (condition.GetParameters().size() < instrInfos.parameters.GetParametersCount()) {
|
||||
while (condition.GetParameters().size() <
|
||||
instrInfos.parameters.GetParametersCount()) {
|
||||
vector<gd::Expression> parameters = condition.GetParameters();
|
||||
parameters.push_back(gd::Expression(""));
|
||||
condition.SetParameters(parameters);
|
||||
}
|
||||
|
||||
// Verify that there are no mismatches between object type in parameters.
|
||||
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
|
||||
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
|
||||
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount();
|
||||
++pNb) {
|
||||
if (ParameterMetadata::IsObject(
|
||||
instrInfos.parameters.GetParameter(pNb).GetType())) {
|
||||
gd::String objectInParameter =
|
||||
condition.GetParameter(pNb).GetPlainString();
|
||||
|
||||
const auto &expectedObjectType =
|
||||
const auto& expectedObjectType =
|
||||
instrInfos.parameters.GetParameter(pNb).GetExtraInfo();
|
||||
const auto &actualObjectType =
|
||||
const auto& actualObjectType =
|
||||
GetObjectsContainersList().GetTypeOfObject(objectInParameter);
|
||||
if (!GetObjectsContainersList().HasObjectOrGroupNamed(
|
||||
objectInParameter)) {
|
||||
gd::ProjectDiagnostic projectDiagnostic(
|
||||
gd::ProjectDiagnostic::ErrorType::UnknownObject, "",
|
||||
objectInParameter, "");
|
||||
gd::ProjectDiagnostic::ErrorType::UnknownObject,
|
||||
"",
|
||||
objectInParameter,
|
||||
"");
|
||||
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
|
||||
return "/* Unknown object - skipped. */";
|
||||
} else if (!expectedObjectType.empty() &&
|
||||
actualObjectType != expectedObjectType) {
|
||||
gd::ProjectDiagnostic projectDiagnostic(
|
||||
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType, "",
|
||||
actualObjectType, expectedObjectType, objectInParameter);
|
||||
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType,
|
||||
"",
|
||||
actualObjectType,
|
||||
expectedObjectType,
|
||||
objectInParameter);
|
||||
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
|
||||
return "/* Mismatched object type - skipped. */";
|
||||
}
|
||||
@@ -366,44 +384,46 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
|
||||
gd::String objectName = condition.GetParameter(0).GetPlainString();
|
||||
if (!objectName.empty() && instrInfos.parameters.GetParametersCount() > 0) {
|
||||
std::vector<gd::String> realObjects =
|
||||
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
|
||||
GetObjectsContainersList().ExpandObjectName(
|
||||
objectName, context.GetCurrentObject());
|
||||
for (std::size_t i = 0; i < realObjects.size(); ++i) {
|
||||
// Set up the context
|
||||
gd::String objectType = GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
|
||||
gd::String objectType =
|
||||
GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
|
||||
const ObjectMetadata& objInfo =
|
||||
MetadataProvider::GetObjectMetadata(platform, objectType);
|
||||
|
||||
AddIncludeFiles(objInfo.includeFiles);
|
||||
context.SetCurrentObject(realObjects[i]);
|
||||
context.ObjectsListNeeded(realObjects[i]);
|
||||
AddIncludeFiles(objInfo.includeFiles);
|
||||
context.SetCurrentObject(realObjects[i]);
|
||||
context.ObjectsListNeeded(realObjects[i]);
|
||||
|
||||
// Prepare arguments and generate the condition whole code
|
||||
vector<gd::String> arguments = GenerateParametersCodes(
|
||||
condition.GetParameters(), instrInfos.parameters, context);
|
||||
conditionCode += GenerateObjectCondition(realObjects[i],
|
||||
objInfo,
|
||||
arguments,
|
||||
instrInfos,
|
||||
returnBoolean,
|
||||
condition.IsInverted(),
|
||||
context);
|
||||
// Prepare arguments and generate the condition whole code
|
||||
vector<gd::String> arguments = GenerateParametersCodes(
|
||||
condition.GetParameters(), instrInfos.parameters, context);
|
||||
conditionCode += GenerateObjectCondition(realObjects[i],
|
||||
objInfo,
|
||||
arguments,
|
||||
instrInfos,
|
||||
returnBoolean,
|
||||
condition.IsInverted(),
|
||||
context);
|
||||
|
||||
context.SetNoCurrentObject();
|
||||
context.SetNoCurrentObject();
|
||||
}
|
||||
}
|
||||
} else if (instrInfos.IsBehaviorInstruction()) {
|
||||
if (instrInfos.parameters.GetParametersCount() >= 2) {
|
||||
const gd::String &objectName = condition.GetParameter(0).GetPlainString();
|
||||
const gd::String &behaviorName =
|
||||
const gd::String& objectName = condition.GetParameter(0).GetPlainString();
|
||||
const gd::String& behaviorName =
|
||||
condition.GetParameter(1).GetPlainString();
|
||||
const gd::String &actualBehaviorType =
|
||||
const gd::String& actualBehaviorType =
|
||||
GetObjectsContainersList().GetTypeOfBehavior(behaviorName);
|
||||
|
||||
std::vector<gd::String> realObjects =
|
||||
GetObjectsContainersList().ExpandObjectName(
|
||||
objectName, context.GetCurrentObject());
|
||||
|
||||
const BehaviorMetadata &autoInfo =
|
||||
const BehaviorMetadata& autoInfo =
|
||||
MetadataProvider::GetBehaviorMetadata(platform, actualBehaviorType);
|
||||
|
||||
for (std::size_t i = 0; i < realObjects.size(); ++i) {
|
||||
@@ -415,15 +435,14 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
|
||||
// Prepare arguments and generate the whole condition code
|
||||
vector<gd::String> arguments = GenerateParametersCodes(
|
||||
condition.GetParameters(), instrInfos.parameters, context);
|
||||
conditionCode += GenerateBehaviorCondition(
|
||||
realObjects[i],
|
||||
behaviorName,
|
||||
autoInfo,
|
||||
arguments,
|
||||
instrInfos,
|
||||
returnBoolean,
|
||||
condition.IsInverted(),
|
||||
context);
|
||||
conditionCode += GenerateBehaviorCondition(realObjects[i],
|
||||
behaviorName,
|
||||
autoInfo,
|
||||
arguments,
|
||||
instrInfos,
|
||||
returnBoolean,
|
||||
condition.IsInverted(),
|
||||
context);
|
||||
|
||||
context.SetNoCurrentObject();
|
||||
}
|
||||
@@ -493,26 +512,29 @@ gd::String EventsCodeGenerator::GenerateConditionsListCode(
|
||||
}
|
||||
|
||||
bool EventsCodeGenerator::CheckBehaviorParameters(
|
||||
const gd::Instruction &instruction,
|
||||
const gd::InstructionMetadata &instrInfos) {
|
||||
const gd::Instruction& instruction,
|
||||
const gd::InstructionMetadata& instrInfos) {
|
||||
bool isAnyBehaviorMissing = false;
|
||||
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
|
||||
instruction.GetParameters(), instrInfos.parameters,
|
||||
[this, &isAnyBehaviorMissing,
|
||||
&instrInfos](const gd::ParameterMetadata ¶meterMetadata,
|
||||
const gd::Expression ¶meterValue, size_t parameterIndex,
|
||||
const gd::String &lastObjectName, size_t lastObjectIndex) {
|
||||
instruction.GetParameters(),
|
||||
instrInfos.parameters,
|
||||
[this, &isAnyBehaviorMissing, &instrInfos](
|
||||
const gd::ParameterMetadata& parameterMetadata,
|
||||
const gd::Expression& parameterValue,
|
||||
size_t parameterIndex,
|
||||
const gd::String& lastObjectName,
|
||||
size_t lastObjectIndex) {
|
||||
if (ParameterMetadata::IsBehavior(parameterMetadata.GetType())) {
|
||||
const gd::String &behaviorName = parameterValue.GetPlainString();
|
||||
const gd::String &actualBehaviorType =
|
||||
const gd::String& behaviorName = parameterValue.GetPlainString();
|
||||
const gd::String& actualBehaviorType =
|
||||
GetObjectsContainersList().GetTypeOfBehaviorInObjectOrGroup(
|
||||
lastObjectName, behaviorName);
|
||||
const gd::String &expectedBehaviorType =
|
||||
const gd::String& expectedBehaviorType =
|
||||
parameterMetadata.GetExtraInfo();
|
||||
|
||||
if (!expectedBehaviorType.empty() &&
|
||||
actualBehaviorType != expectedBehaviorType) {
|
||||
const auto &objectParameterMetadata =
|
||||
const auto& objectParameterMetadata =
|
||||
instrInfos.GetParameter(lastObjectIndex);
|
||||
// Event functions crash if some objects in a group are missing
|
||||
// the required behaviors, since they lose reference to the original
|
||||
@@ -523,10 +545,12 @@ bool EventsCodeGenerator::CheckBehaviorParameters(
|
||||
isAnyBehaviorMissing = true;
|
||||
}
|
||||
gd::ProjectDiagnostic projectDiagnostic(
|
||||
gd::ProjectDiagnostic::ErrorType::MissingBehavior, "",
|
||||
actualBehaviorType, expectedBehaviorType, lastObjectName);
|
||||
if (diagnosticReport)
|
||||
diagnosticReport->Add(projectDiagnostic);
|
||||
gd::ProjectDiagnostic::ErrorType::MissingBehavior,
|
||||
"",
|
||||
actualBehaviorType,
|
||||
expectedBehaviorType,
|
||||
lastObjectName);
|
||||
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -539,7 +563,8 @@ bool EventsCodeGenerator::CheckBehaviorParameters(
|
||||
gd::String EventsCodeGenerator::GenerateActionCode(
|
||||
gd::Instruction& action,
|
||||
EventsCodeGenerationContext& context,
|
||||
const gd::String& optionalAsyncCallbackName) {
|
||||
const gd::String& optionalAsyncCallbackName,
|
||||
const gd::String& optionalAsyncCallbackId) {
|
||||
gd::String actionCode;
|
||||
|
||||
const gd::InstructionMetadata& instrInfos =
|
||||
@@ -564,33 +589,41 @@ gd::String EventsCodeGenerator::GenerateActionCode(
|
||||
: instrInfos.codeExtraInformation.functionCallName;
|
||||
|
||||
// Be sure there is no lack of parameter.
|
||||
while (action.GetParameters().size() < instrInfos.parameters.GetParametersCount()) {
|
||||
while (action.GetParameters().size() <
|
||||
instrInfos.parameters.GetParametersCount()) {
|
||||
vector<gd::Expression> parameters = action.GetParameters();
|
||||
parameters.push_back(gd::Expression(""));
|
||||
action.SetParameters(parameters);
|
||||
}
|
||||
|
||||
// Verify that there are no mismatches between object type in parameters.
|
||||
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
|
||||
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
|
||||
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount();
|
||||
++pNb) {
|
||||
if (ParameterMetadata::IsObject(
|
||||
instrInfos.parameters.GetParameter(pNb).GetType())) {
|
||||
gd::String objectInParameter = action.GetParameter(pNb).GetPlainString();
|
||||
|
||||
const auto &expectedObjectType =
|
||||
const auto& expectedObjectType =
|
||||
instrInfos.parameters.GetParameter(pNb).GetExtraInfo();
|
||||
const auto &actualObjectType =
|
||||
const auto& actualObjectType =
|
||||
GetObjectsContainersList().GetTypeOfObject(objectInParameter);
|
||||
if (!GetObjectsContainersList().HasObjectOrGroupNamed(
|
||||
objectInParameter)) {
|
||||
gd::ProjectDiagnostic projectDiagnostic(
|
||||
gd::ProjectDiagnostic::ErrorType::UnknownObject, "",
|
||||
objectInParameter, "");
|
||||
gd::ProjectDiagnostic::ErrorType::UnknownObject,
|
||||
"",
|
||||
objectInParameter,
|
||||
"");
|
||||
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
|
||||
return "/* Unknown object - skipped. */";
|
||||
} else if (!expectedObjectType.empty() &&
|
||||
actualObjectType != expectedObjectType) {
|
||||
gd::ProjectDiagnostic projectDiagnostic(
|
||||
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType, "",
|
||||
actualObjectType, expectedObjectType, objectInParameter);
|
||||
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType,
|
||||
"",
|
||||
actualObjectType,
|
||||
expectedObjectType,
|
||||
objectInParameter);
|
||||
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
|
||||
return "/* Mismatched object type - skipped. */";
|
||||
}
|
||||
@@ -608,43 +641,46 @@ gd::String EventsCodeGenerator::GenerateActionCode(
|
||||
|
||||
if (instrInfos.parameters.GetParametersCount() > 0) {
|
||||
std::vector<gd::String> realObjects =
|
||||
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
|
||||
GetObjectsContainersList().ExpandObjectName(
|
||||
objectName, context.GetCurrentObject());
|
||||
for (std::size_t i = 0; i < realObjects.size(); ++i) {
|
||||
// Setup context
|
||||
gd::String objectType = GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
|
||||
gd::String objectType =
|
||||
GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
|
||||
const ObjectMetadata& objInfo =
|
||||
MetadataProvider::GetObjectMetadata(platform, objectType);
|
||||
|
||||
AddIncludeFiles(objInfo.includeFiles);
|
||||
context.SetCurrentObject(realObjects[i]);
|
||||
context.ObjectsListNeeded(realObjects[i]);
|
||||
AddIncludeFiles(objInfo.includeFiles);
|
||||
context.SetCurrentObject(realObjects[i]);
|
||||
context.ObjectsListNeeded(realObjects[i]);
|
||||
|
||||
// Prepare arguments and generate the whole action code
|
||||
vector<gd::String> arguments = GenerateParametersCodes(
|
||||
action.GetParameters(), instrInfos.parameters, context);
|
||||
actionCode += GenerateObjectAction(realObjects[i],
|
||||
objInfo,
|
||||
functionCallName,
|
||||
arguments,
|
||||
instrInfos,
|
||||
context,
|
||||
optionalAsyncCallbackName);
|
||||
// Prepare arguments and generate the whole action code
|
||||
vector<gd::String> arguments = GenerateParametersCodes(
|
||||
action.GetParameters(), instrInfos.parameters, context);
|
||||
actionCode += GenerateObjectAction(realObjects[i],
|
||||
objInfo,
|
||||
functionCallName,
|
||||
arguments,
|
||||
instrInfos,
|
||||
context,
|
||||
optionalAsyncCallbackName,
|
||||
optionalAsyncCallbackId);
|
||||
|
||||
context.SetNoCurrentObject();
|
||||
context.SetNoCurrentObject();
|
||||
}
|
||||
}
|
||||
} else if (instrInfos.IsBehaviorInstruction()) {
|
||||
if (instrInfos.parameters.GetParametersCount() >= 2) {
|
||||
const gd::String &objectName = action.GetParameter(0).GetPlainString();
|
||||
const gd::String &behaviorName = action.GetParameter(1).GetPlainString();
|
||||
const gd::String &actualBehaviorType =
|
||||
const gd::String& objectName = action.GetParameter(0).GetPlainString();
|
||||
const gd::String& behaviorName = action.GetParameter(1).GetPlainString();
|
||||
const gd::String& actualBehaviorType =
|
||||
GetObjectsContainersList().GetTypeOfBehavior(behaviorName);
|
||||
|
||||
std::vector<gd::String> realObjects =
|
||||
GetObjectsContainersList().ExpandObjectName(
|
||||
objectName, context.GetCurrentObject());
|
||||
|
||||
const BehaviorMetadata &autoInfo =
|
||||
const BehaviorMetadata& autoInfo =
|
||||
MetadataProvider::GetBehaviorMetadata(platform, actualBehaviorType);
|
||||
|
||||
AddIncludeFiles(autoInfo.includeFiles);
|
||||
@@ -656,15 +692,15 @@ gd::String EventsCodeGenerator::GenerateActionCode(
|
||||
// Prepare arguments and generate the whole action code
|
||||
vector<gd::String> arguments = GenerateParametersCodes(
|
||||
action.GetParameters(), instrInfos.parameters, context);
|
||||
actionCode +=
|
||||
GenerateBehaviorAction(realObjects[i],
|
||||
behaviorName,
|
||||
autoInfo,
|
||||
functionCallName,
|
||||
arguments,
|
||||
instrInfos,
|
||||
context,
|
||||
optionalAsyncCallbackName);
|
||||
actionCode += GenerateBehaviorAction(realObjects[i],
|
||||
behaviorName,
|
||||
autoInfo,
|
||||
functionCallName,
|
||||
arguments,
|
||||
instrInfos,
|
||||
context,
|
||||
optionalAsyncCallbackName,
|
||||
optionalAsyncCallbackId);
|
||||
|
||||
context.SetNoCurrentObject();
|
||||
}
|
||||
@@ -676,7 +712,8 @@ gd::String EventsCodeGenerator::GenerateActionCode(
|
||||
arguments,
|
||||
instrInfos,
|
||||
context,
|
||||
optionalAsyncCallbackName);
|
||||
optionalAsyncCallbackName,
|
||||
optionalAsyncCallbackId);
|
||||
}
|
||||
|
||||
return actionCode;
|
||||
@@ -689,8 +726,8 @@ gd::String EventsCodeGenerator::GenerateLocalVariablesStackAccessor() {
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GenerateAnyOrSceneVariableGetter(
|
||||
const gd::Expression &variableExpression,
|
||||
EventsCodeGenerationContext &context) {
|
||||
const gd::Expression& variableExpression,
|
||||
EventsCodeGenerationContext& context) {
|
||||
const auto variableName = gd::ExpressionVariableNameFinder::GetVariableName(
|
||||
*variableExpression.GetRootNode());
|
||||
|
||||
@@ -701,8 +738,12 @@ gd::String EventsCodeGenerator::GenerateAnyOrSceneVariableGetter(
|
||||
: "scenevar";
|
||||
|
||||
return gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
||||
*this, context, variableParameterType,
|
||||
variableExpression.GetPlainString(), "", "AllowUndeclaredVariable");
|
||||
*this,
|
||||
context,
|
||||
variableParameterType,
|
||||
variableExpression.GetPlainString(),
|
||||
"",
|
||||
"AllowUndeclaredVariable");
|
||||
}
|
||||
|
||||
const EventsCodeGenerator::CallbackDescriptor
|
||||
@@ -749,6 +790,11 @@ EventsCodeGenerator::GenerateCallback(
|
||||
|
||||
AddCustomCodeOutsideMain(callbackCode);
|
||||
|
||||
const gd::String idToCallbackMapUpdate = GetCodeNamespaceAccessor() +
|
||||
"idToCallbackMap.set(" + callbackID +
|
||||
", " + callbackFunctionName + ");\n";
|
||||
AddCustomCodeOutsideMain(idToCallbackMapUpdate);
|
||||
|
||||
std::set<gd::String> requiredObjects;
|
||||
// Build the list of all objects required by the callback. Any object that has
|
||||
// already been declared could have gone through previous object picking, so
|
||||
@@ -808,13 +854,28 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
|
||||
|
||||
if (ParameterMetadata::IsExpression("number", metadata.GetType())) {
|
||||
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
||||
*this, context, "number", parameter, lastObjectName, metadata.GetExtraInfo());
|
||||
*this,
|
||||
context,
|
||||
"number",
|
||||
parameter,
|
||||
lastObjectName,
|
||||
metadata.GetExtraInfo());
|
||||
} else if (ParameterMetadata::IsExpression("string", metadata.GetType())) {
|
||||
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
||||
*this, context, "string", parameter, lastObjectName, metadata.GetExtraInfo());
|
||||
*this,
|
||||
context,
|
||||
"string",
|
||||
parameter,
|
||||
lastObjectName,
|
||||
metadata.GetExtraInfo());
|
||||
} else if (ParameterMetadata::IsExpression("variable", metadata.GetType())) {
|
||||
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
||||
*this, context, metadata.GetType(), parameter, lastObjectName, metadata.GetExtraInfo());
|
||||
*this,
|
||||
context,
|
||||
metadata.GetType(),
|
||||
parameter,
|
||||
lastObjectName,
|
||||
metadata.GetExtraInfo());
|
||||
} else if (ParameterMetadata::IsObject(metadata.GetType())) {
|
||||
// It would be possible to run a gd::ExpressionCodeGenerator if later
|
||||
// objects can have nested objects, or function returning objects.
|
||||
@@ -849,7 +910,8 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
|
||||
metadata.GetType() == "atlasResource" ||
|
||||
metadata.GetType() == "spineResource" ||
|
||||
// Deprecated, old parameter names:
|
||||
metadata.GetType() == "password" || metadata.GetType() == "musicfile" ||
|
||||
metadata.GetType() == "password" ||
|
||||
metadata.GetType() == "musicfile" ||
|
||||
metadata.GetType() == "soundfile") {
|
||||
argOutput = "\"" + ConvertToString(parameter.GetPlainString()) + "\"";
|
||||
} else if (metadata.GetType() == "mouse") {
|
||||
@@ -999,7 +1061,8 @@ gd::String EventsCodeGenerator::GenerateEventsListCode(
|
||||
for (std::size_t eId = 0; eId < events.size(); ++eId) {
|
||||
auto& event = events[eId];
|
||||
if (event.HasVariables()) {
|
||||
GetProjectScopedContainers().GetVariablesContainersList().Push(event.GetVariables());
|
||||
GetProjectScopedContainers().GetVariablesContainersList().Push(
|
||||
event.GetVariables());
|
||||
}
|
||||
|
||||
// Each event has its own context : Objects picked in an event are totally
|
||||
@@ -1124,7 +1187,7 @@ gd::String EventsCodeGenerator::GenerateFreeCondition(
|
||||
instrInfos.codeExtraInformation.functionCallName);
|
||||
} else {
|
||||
predicate = instrInfos.codeExtraInformation.functionCallName + "(" +
|
||||
GenerateArgumentsList(arguments, 0) + ")";
|
||||
GenerateArgumentsList(arguments, 0) + ")";
|
||||
}
|
||||
|
||||
// Add logical not if needed
|
||||
@@ -1168,7 +1231,7 @@ gd::String EventsCodeGenerator::GenerateObjectCondition(
|
||||
instrInfos, arguments, objectFunctionCallNamePart, 1);
|
||||
} else {
|
||||
predicate = objectFunctionCallNamePart + "(" +
|
||||
GenerateArgumentsList(arguments, 1) + ")";
|
||||
GenerateArgumentsList(arguments, 1) + ")";
|
||||
}
|
||||
if (conditionInverted) predicate = GenerateNegatedPredicate(predicate);
|
||||
|
||||
@@ -1200,18 +1263,20 @@ gd::String EventsCodeGenerator::GenerateBehaviorCondition(
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GenerateFreeAction(
|
||||
const gd::String& functionCallName,
|
||||
const gd::String& functionCallName,
|
||||
const std::vector<gd::String>& arguments,
|
||||
const gd::InstructionMetadata& instrInfos,
|
||||
gd::EventsCodeGenerationContext& context,
|
||||
const gd::String& optionalAsyncCallbackName) {
|
||||
const gd::String& optionalAsyncCallbackName,
|
||||
const gd::String& optionalAsyncCallbackId) {
|
||||
// Generate call
|
||||
gd::String call;
|
||||
if (instrInfos.codeExtraInformation.type == "number" ||
|
||||
instrInfos.codeExtraInformation.type == "string" ||
|
||||
// Boolean actions declared with addExpressionAndConditionAndAction uses
|
||||
// MutatorAndOrAccessor even though they don't declare an operator parameter.
|
||||
// Boolean operators are only used with SetMutators or SetCustomCodeGenerator.
|
||||
// MutatorAndOrAccessor even though they don't declare an operator
|
||||
// parameter. Boolean operators are only used with SetMutators or
|
||||
// SetCustomCodeGenerator.
|
||||
(instrInfos.codeExtraInformation.type == "boolean" &&
|
||||
instrInfos.codeExtraInformation.accessType ==
|
||||
gd::InstructionMetadata::ExtraInformation::AccessType::Mutators)) {
|
||||
@@ -1224,23 +1289,19 @@ gd::String EventsCodeGenerator::GenerateFreeAction(
|
||||
instrInfos.codeExtraInformation.optionalAssociatedInstruction);
|
||||
else if (instrInfos.codeExtraInformation.accessType ==
|
||||
gd::InstructionMetadata::ExtraInformation::Mutators)
|
||||
call =
|
||||
GenerateMutatorCall(instrInfos,
|
||||
arguments,
|
||||
functionCallName);
|
||||
call = GenerateMutatorCall(instrInfos, arguments, functionCallName);
|
||||
else
|
||||
call = GenerateCompoundOperatorCall(
|
||||
instrInfos,
|
||||
arguments,
|
||||
functionCallName);
|
||||
call =
|
||||
GenerateCompoundOperatorCall(instrInfos, arguments, functionCallName);
|
||||
} else {
|
||||
call = functionCallName + "(" +
|
||||
GenerateArgumentsList(arguments) + ")";
|
||||
call = functionCallName + "(" + GenerateArgumentsList(arguments) + ")";
|
||||
}
|
||||
|
||||
if (!optionalAsyncCallbackName.empty())
|
||||
if (!optionalAsyncCallbackName.empty() && !optionalAsyncCallbackId.empty()) {
|
||||
call = "runtimeScene.getAsyncTasksManager().addTask(" + call + ", " +
|
||||
optionalAsyncCallbackName + ")";
|
||||
optionalAsyncCallbackName + ", " + optionalAsyncCallbackId +
|
||||
", asyncObjectsList)";
|
||||
}
|
||||
|
||||
return call + ";\n";
|
||||
}
|
||||
@@ -1252,7 +1313,8 @@ gd::String EventsCodeGenerator::GenerateObjectAction(
|
||||
const std::vector<gd::String>& arguments,
|
||||
const gd::InstructionMetadata& instrInfos,
|
||||
gd::EventsCodeGenerationContext& context,
|
||||
const gd::String& optionalAsyncCallbackName) {
|
||||
const gd::String& optionalAsyncCallbackName,
|
||||
const gd::String& optionalAsyncCallbackId) {
|
||||
// Create call
|
||||
gd::String call;
|
||||
if ((instrInfos.codeExtraInformation.type == "number" ||
|
||||
@@ -1293,7 +1355,8 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
|
||||
const std::vector<gd::String>& arguments,
|
||||
const gd::InstructionMetadata& instrInfos,
|
||||
gd::EventsCodeGenerationContext& context,
|
||||
const gd::String& optionalAsyncCallbackName) {
|
||||
const gd::String& optionalAsyncCallbackName,
|
||||
const gd::String& optionalAsyncCallbackId) {
|
||||
// Create call
|
||||
gd::String call;
|
||||
if ((instrInfos.codeExtraInformation.type == "number" ||
|
||||
@@ -1308,17 +1371,13 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
|
||||
2);
|
||||
else
|
||||
call = GenerateCompoundOperatorCall(
|
||||
instrInfos,
|
||||
arguments,
|
||||
functionCallName,
|
||||
2);
|
||||
instrInfos, arguments, functionCallName, 2);
|
||||
return "For each picked object \"" + objectName + "\", call " + call +
|
||||
" for behavior \"" + behaviorName + "\".\n";
|
||||
} else {
|
||||
gd::String argumentsStr = GenerateArgumentsList(arguments, 2);
|
||||
|
||||
call = functionCallName + "(" +
|
||||
argumentsStr + ")";
|
||||
call = functionCallName + "(" + argumentsStr + ")";
|
||||
|
||||
return "For each picked object \"" + objectName + "\", call " + call + "(" +
|
||||
argumentsStr + ")" + " for behavior \"" + behaviorName + "\"" +
|
||||
@@ -1373,42 +1432,47 @@ gd::String EventsCodeGenerator::GenerateArgumentsList(
|
||||
return argumentsStr;
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GeneratePropertyGetter(const gd::PropertiesContainer& propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor& property,
|
||||
const gd::String& type,
|
||||
gd::EventsCodeGenerationContext& context) {
|
||||
gd::String EventsCodeGenerator::GeneratePropertyGetter(
|
||||
const gd::PropertiesContainer& propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor& property,
|
||||
const gd::String& type,
|
||||
gd::EventsCodeGenerationContext& context) {
|
||||
return "getProperty" + property.GetName() + "As" + type + "()";
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GeneratePropertyGetterWithoutCasting(
|
||||
const gd::PropertiesContainer &propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor &property) {
|
||||
const gd::PropertiesContainer& propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor& property) {
|
||||
return "getProperty" + property.GetName() + "()";
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GeneratePropertySetterWithoutCasting(
|
||||
const gd::PropertiesContainer &propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor &property,
|
||||
const gd::String &operandCode) {
|
||||
const gd::PropertiesContainer& propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor& property,
|
||||
const gd::String& operandCode) {
|
||||
return "setProperty" + property.GetName() + "(" + operandCode + ")";
|
||||
}
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GenerateParameterGetter(const gd::ParameterMetadata& parameter,
|
||||
const gd::String& type,
|
||||
gd::EventsCodeGenerationContext& context) {
|
||||
gd::String EventsCodeGenerator::GenerateParameterGetter(
|
||||
const gd::ParameterMetadata& parameter,
|
||||
const gd::String& type,
|
||||
gd::EventsCodeGenerationContext& context) {
|
||||
return "getParameter" + parameter.GetName() + "As" + type + "()";
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GenerateParameterGetterWithoutCasting(
|
||||
const gd::ParameterMetadata ¶meter) {
|
||||
return "getParameter" + parameter.GetName() + "()";
|
||||
}
|
||||
const gd::ParameterMetadata& parameter) {
|
||||
return "getParameter" + parameter.GetName() + "()";
|
||||
}
|
||||
|
||||
EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
|
||||
const gd::Layout& layout,
|
||||
const gd::Platform& platform_)
|
||||
: platform(platform_),
|
||||
projectScopedContainers(gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project_, layout)),
|
||||
projectScopedContainers(
|
||||
gd::ProjectScopedContainers::
|
||||
MakeNewProjectScopedContainersForProjectAndLayout(project_,
|
||||
layout)),
|
||||
hasProjectAndLayout(true),
|
||||
project(&project_),
|
||||
scene(&layout),
|
||||
@@ -1417,7 +1481,7 @@ EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
|
||||
maxCustomConditionsDepth(0),
|
||||
maxConditionsListsSize(0),
|
||||
eventsListNextUniqueId(0),
|
||||
diagnosticReport(nullptr){};
|
||||
diagnosticReport(nullptr) {};
|
||||
|
||||
EventsCodeGenerator::EventsCodeGenerator(
|
||||
const gd::Platform& platform_,
|
||||
@@ -1432,6 +1496,6 @@ EventsCodeGenerator::EventsCodeGenerator(
|
||||
maxCustomConditionsDepth(0),
|
||||
maxConditionsListsSize(0),
|
||||
eventsListNextUniqueId(0),
|
||||
diagnosticReport(nullptr){};
|
||||
diagnosticReport(nullptr) {};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -9,9 +9,9 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/Events/CodeGeneration/DiagnosticReport.h"
|
||||
#include "GDCore/Events/Event.h"
|
||||
#include "GDCore/Events/Instruction.h"
|
||||
#include "GDCore/Events/CodeGeneration/DiagnosticReport.h"
|
||||
#include "GDCore/Project/ProjectScopedContainers.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
@@ -62,7 +62,7 @@ class GD_CORE_API EventsCodeGenerator {
|
||||
EventsCodeGenerator(
|
||||
const gd::Platform& platform,
|
||||
const gd::ProjectScopedContainers& projectScopedContainers_);
|
||||
virtual ~EventsCodeGenerator(){};
|
||||
virtual ~EventsCodeGenerator() {};
|
||||
|
||||
/**
|
||||
* \brief Preprocess an events list (replacing for example links with the
|
||||
@@ -160,7 +160,8 @@ class GD_CORE_API EventsCodeGenerator {
|
||||
gd::String GenerateActionCode(
|
||||
gd::Instruction& action,
|
||||
EventsCodeGenerationContext& context,
|
||||
const gd::String& optionalAsyncCallbackName = "");
|
||||
const gd::String& optionalAsyncCallbackName = "",
|
||||
const gd::String& optionalAsyncCallbackId = "");
|
||||
|
||||
struct CallbackDescriptor {
|
||||
CallbackDescriptor(const gd::String functionName_,
|
||||
@@ -168,7 +169,7 @@ class GD_CORE_API EventsCodeGenerator {
|
||||
const std::set<gd::String> requiredObjects_)
|
||||
: functionName(functionName_),
|
||||
argumentsList(argumentsList_),
|
||||
requiredObjects(requiredObjects_){};
|
||||
requiredObjects(requiredObjects_) {};
|
||||
/**
|
||||
* The name by which the function can be invoked.
|
||||
*/
|
||||
@@ -338,9 +339,9 @@ class GD_CORE_API EventsCodeGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Give access to the project scoped containers as code generation might
|
||||
* push and pop variable containers (for local variables).
|
||||
* This could be passed as a parameter recursively in code generation, but this requires
|
||||
* @brief Give access to the project scoped containers as code generation
|
||||
* might push and pop variable containers (for local variables). This could be
|
||||
* passed as a parameter recursively in code generation, but this requires
|
||||
* heavy refactoring. Instead, we use this single instance.
|
||||
*/
|
||||
gd::ProjectScopedContainers& GetProjectScopedContainers() {
|
||||
@@ -387,9 +388,7 @@ class GD_CORE_API EventsCodeGenerator {
|
||||
diagnosticReport = diagnosticReport_;
|
||||
}
|
||||
|
||||
gd::DiagnosticReport* GetDiagnosticReport() {
|
||||
return diagnosticReport;
|
||||
}
|
||||
gd::DiagnosticReport* GetDiagnosticReport() { return diagnosticReport; }
|
||||
|
||||
/**
|
||||
* \brief Generate the full name for accessing to a boolean variable used for
|
||||
@@ -513,16 +512,16 @@ class GD_CORE_API EventsCodeGenerator {
|
||||
* \brief Generate an any variable getter that fallbacks on scene variable for
|
||||
* compatibility reason.
|
||||
*/
|
||||
gd::String
|
||||
GenerateAnyOrSceneVariableGetter(const gd::Expression &variableExpression,
|
||||
EventsCodeGenerationContext &context);
|
||||
gd::String GenerateAnyOrSceneVariableGetter(
|
||||
const gd::Expression& variableExpression,
|
||||
EventsCodeGenerationContext& context);
|
||||
|
||||
virtual gd::String GeneratePropertySetterWithoutCasting(
|
||||
const gd::PropertiesContainer &propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor &property,
|
||||
const gd::String &operandCode);
|
||||
const gd::PropertiesContainer& propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor& property,
|
||||
const gd::String& operandCode);
|
||||
|
||||
protected:
|
||||
protected:
|
||||
virtual const gd::String GenerateRelationalOperatorCodes(
|
||||
const gd::String& operatorString);
|
||||
|
||||
@@ -643,16 +642,16 @@ protected:
|
||||
gd::EventsCodeGenerationContext& context);
|
||||
|
||||
virtual gd::String GeneratePropertyGetterWithoutCasting(
|
||||
const gd::PropertiesContainer &propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor &property);
|
||||
const gd::PropertiesContainer& propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor& property);
|
||||
|
||||
virtual gd::String GenerateParameterGetter(
|
||||
const gd::ParameterMetadata& parameter,
|
||||
const gd::String& type,
|
||||
gd::EventsCodeGenerationContext& context);
|
||||
|
||||
virtual gd::String
|
||||
GenerateParameterGetterWithoutCasting(const gd::ParameterMetadata ¶meter);
|
||||
virtual gd::String GenerateParameterGetterWithoutCasting(
|
||||
const gd::ParameterMetadata& parameter);
|
||||
|
||||
/**
|
||||
* \brief Generate the code to reference an object which is
|
||||
@@ -769,7 +768,8 @@ protected:
|
||||
const std::vector<gd::String>& arguments,
|
||||
const gd::InstructionMetadata& instrInfos,
|
||||
gd::EventsCodeGenerationContext& context,
|
||||
const gd::String& optionalAsyncCallbackName = "");
|
||||
const gd::String& optionalAsyncCallbackName = "",
|
||||
const gd::String& optionalAsyncCallbackId = "");
|
||||
|
||||
virtual gd::String GenerateObjectAction(
|
||||
const gd::String& objectName,
|
||||
@@ -778,7 +778,8 @@ protected:
|
||||
const std::vector<gd::String>& arguments,
|
||||
const gd::InstructionMetadata& instrInfos,
|
||||
gd::EventsCodeGenerationContext& context,
|
||||
const gd::String& optionalAsyncCallbackName = "");
|
||||
const gd::String& optionalAsyncCallbackName = "",
|
||||
const gd::String& optionalAsyncCallbackId = "");
|
||||
|
||||
virtual gd::String GenerateBehaviorAction(
|
||||
const gd::String& objectName,
|
||||
@@ -788,7 +789,8 @@ protected:
|
||||
const std::vector<gd::String>& arguments,
|
||||
const gd::InstructionMetadata& instrInfos,
|
||||
gd::EventsCodeGenerationContext& context,
|
||||
const gd::String& optionalAsyncCallbackName = "");
|
||||
const gd::String& optionalAsyncCallbackName = "",
|
||||
const gd::String& optionalAsyncCallbackId = "");
|
||||
|
||||
gd::String GenerateRelationalOperatorCall(
|
||||
const gd::InstructionMetadata& instrInfos,
|
||||
@@ -837,9 +839,8 @@ protected:
|
||||
virtual gd::String GenerateGetBehaviorNameCode(
|
||||
const gd::String& behaviorName);
|
||||
|
||||
bool CheckBehaviorParameters(
|
||||
const gd::Instruction &instruction,
|
||||
const gd::InstructionMetadata &instrInfos);
|
||||
bool CheckBehaviorParameters(const gd::Instruction& instruction,
|
||||
const gd::InstructionMetadata& instrInfos);
|
||||
|
||||
const gd::Platform& platform; ///< The platform being used.
|
||||
|
||||
@@ -876,4 +877,3 @@ protected:
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
|
@@ -110,9 +110,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): Object3DNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): Object3DNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
z: this.getZ(),
|
||||
d: this.getDepth(),
|
||||
rx: this.getRotationX(),
|
||||
@@ -123,8 +125,11 @@ namespace gdjs {
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(networkSyncData: Object3DNetworkSyncData) {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: Object3DNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
|
||||
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
|
||||
if (networkSyncData.rx !== undefined)
|
||||
|
@@ -452,9 +452,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): Cube3DObjectNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): Cube3DObjectNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
mt: this._materialType,
|
||||
fo: this._facesOrientation,
|
||||
bfu: this._backFaceUpThroughWhichAxisRotation,
|
||||
@@ -466,9 +468,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: Cube3DObjectNetworkSyncData
|
||||
networkSyncData: Cube3DObjectNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
if (networkSyncData.mt !== undefined) {
|
||||
this._materialType = networkSyncData.mt;
|
||||
|
@@ -1,12 +1,16 @@
|
||||
namespace gdjs {
|
||||
type CustomObject3DNetworkSyncDataType = CustomObjectNetworkSyncDataType & {
|
||||
type CustomObject3DNetworkSyncDataType = {
|
||||
z: float;
|
||||
d: float;
|
||||
rx: float;
|
||||
ry: float;
|
||||
ifz: boolean;
|
||||
ccz: float;
|
||||
};
|
||||
|
||||
type CustomObject3DNetworkSyncData = CustomObjectNetworkSyncData &
|
||||
CustomObject3DNetworkSyncDataType;
|
||||
|
||||
/**
|
||||
* Base class for 3D custom objects.
|
||||
*/
|
||||
@@ -85,21 +89,25 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
getNetworkSyncData(): CustomObject3DNetworkSyncDataType {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): CustomObject3DNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
z: this.getZ(),
|
||||
d: this.getDepth(),
|
||||
rx: this.getRotationX(),
|
||||
ry: this.getRotationY(),
|
||||
ifz: this.isFlippedZ(),
|
||||
ccz: this._customCenterZ,
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: CustomObject3DNetworkSyncDataType
|
||||
networkSyncData: CustomObject3DNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
|
||||
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
|
||||
if (networkSyncData.rx !== undefined)
|
||||
@@ -107,6 +115,8 @@ namespace gdjs {
|
||||
if (networkSyncData.ry !== undefined)
|
||||
this.setRotationY(networkSyncData.ry);
|
||||
if (networkSyncData.ifz !== undefined) this.flipZ(networkSyncData.ifz);
|
||||
if (networkSyncData.ccz !== undefined)
|
||||
this._customCenterZ = networkSyncData.ccz;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -216,9 +216,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): Model3DObjectNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): Model3DObjectNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
mt: this._materialType,
|
||||
op: this._originPoint,
|
||||
cp: this._centerPoint,
|
||||
@@ -231,9 +233,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: Model3DObjectNetworkSyncData
|
||||
networkSyncData: Model3DObjectNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
if (networkSyncData.mt !== undefined) {
|
||||
this._materialType = networkSyncData.mt;
|
||||
|
@@ -145,9 +145,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
override getNetworkSyncData(): BBTextObjectNetworkSyncData {
|
||||
override getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): BBTextObjectNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
text: this._text,
|
||||
o: this._opacity,
|
||||
c: this._color,
|
||||
@@ -162,9 +164,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
override updateFromNetworkSyncData(
|
||||
networkSyncData: BBTextObjectNetworkSyncData
|
||||
networkSyncData: BBTextObjectNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
if (this._text !== undefined) {
|
||||
this.setBBText(networkSyncData.text);
|
||||
}
|
||||
|
@@ -155,9 +155,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
override getNetworkSyncData(): BitmapTextObjectNetworkSyncData {
|
||||
override getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): BitmapTextObjectNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
text: this._text,
|
||||
opa: this._opacity,
|
||||
tint: this._tint,
|
||||
@@ -172,9 +174,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
override updateFromNetworkSyncData(
|
||||
networkSyncData: BitmapTextObjectNetworkSyncData
|
||||
networkSyncData: BitmapTextObjectNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
if (this._text !== undefined) {
|
||||
this.setText(networkSyncData.text);
|
||||
}
|
||||
|
@@ -87,16 +87,21 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): LightNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): LightNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
rad: this.getRadius(),
|
||||
col: this.getColor(),
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(networkSyncData: LightNetworkSyncData): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: LightNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
if (networkSyncData.rad !== undefined) {
|
||||
this.setRadius(networkSyncData.rad);
|
||||
|
@@ -729,7 +729,9 @@ namespace gdjs {
|
||||
behavior.playerNumber = ownerPlayerNumber;
|
||||
}
|
||||
|
||||
instance.updateFromNetworkSyncData(messageData);
|
||||
instance.updateFromNetworkSyncData(messageData, {
|
||||
clearInputs: false,
|
||||
});
|
||||
|
||||
setLastClockReceivedForInstanceOnScene({
|
||||
sceneNetworkId,
|
||||
@@ -1737,7 +1739,7 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
|
||||
runtimeScene.updateFromNetworkSyncData(messageData);
|
||||
runtimeScene.updateFromNetworkSyncData(messageData, {});
|
||||
} else {
|
||||
// If the game is not ready to receive game update messages, we need to save the data for later use.
|
||||
// This can happen when joining a game that is already running.
|
||||
@@ -1890,7 +1892,7 @@ namespace gdjs {
|
||||
const messageData = message.getData();
|
||||
const messageSender = message.getSender();
|
||||
if (gdjs.multiplayer.isReadyToSendOrReceiveGameUpdateMessages()) {
|
||||
runtimeScene.getGame().updateFromNetworkSyncData(messageData);
|
||||
runtimeScene.getGame().updateFromNetworkSyncData(messageData, {});
|
||||
} else {
|
||||
// If the game is not ready to receive game update messages, we need to save the data for later use.
|
||||
// This can happen when joining a game that is already running.
|
||||
@@ -1918,7 +1920,7 @@ namespace gdjs {
|
||||
// Reapply the game saved updates.
|
||||
lastReceivedGameSyncDataUpdates.getUpdates().forEach((messageData) => {
|
||||
debugLogger.info(`Reapplying saved update of game.`);
|
||||
runtimeScene.getGame().updateFromNetworkSyncData(messageData);
|
||||
runtimeScene.getGame().updateFromNetworkSyncData(messageData, {});
|
||||
});
|
||||
// Game updates are always applied properly, so we can clear them.
|
||||
lastReceivedGameSyncDataUpdates.clear();
|
||||
@@ -1937,7 +1939,7 @@ namespace gdjs {
|
||||
|
||||
debugLogger.info(`Reapplying saved update of scene ${sceneNetworkId}.`);
|
||||
|
||||
runtimeScene.updateFromNetworkSyncData(messageData);
|
||||
runtimeScene.updateFromNetworkSyncData(messageData, {});
|
||||
// We only remove the message if it was successfully applied, so it can be reapplied later,
|
||||
// in case we were not on the right scene.
|
||||
lastReceivedSceneSyncDataUpdates.remove(messageData);
|
||||
|
@@ -278,7 +278,7 @@ namespace gdjs {
|
||||
|
||||
const instanceNetworkId = this._getOrCreateInstanceNetworkId();
|
||||
const objectName = this.owner.getName();
|
||||
const objectNetworkSyncData = this.owner.getNetworkSyncData();
|
||||
const objectNetworkSyncData = this.owner.getNetworkSyncData({});
|
||||
|
||||
// this._logToConsoleWithThrottle(
|
||||
// `Synchronizing object ${this.owner.getName()} (instance ${
|
||||
@@ -448,7 +448,7 @@ namespace gdjs {
|
||||
objectOwner: this.playerNumber,
|
||||
objectName,
|
||||
instanceNetworkId,
|
||||
objectNetworkSyncData: this.owner.getNetworkSyncData(),
|
||||
objectNetworkSyncData: this.owner.getNetworkSyncData({}),
|
||||
sceneNetworkId,
|
||||
});
|
||||
this._sendDataToPeersWithIncreasedClock(
|
||||
@@ -598,7 +598,7 @@ namespace gdjs {
|
||||
debugLogger.info(
|
||||
'Sending update message to move the object immediately.'
|
||||
);
|
||||
const objectNetworkSyncData = this.owner.getNetworkSyncData();
|
||||
const objectNetworkSyncData = this.owner.getNetworkSyncData({});
|
||||
const {
|
||||
messageName: updateMessageName,
|
||||
messageData: updateMessageData,
|
||||
|
@@ -119,18 +119,21 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): PanelSpriteNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): PanelSpriteNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
op: this.getOpacity(),
|
||||
color: this.getColor(),
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: PanelSpriteNetworkSyncData
|
||||
networkSyncData: PanelSpriteNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
// Texture is not synchronized, see if this is asked or not.
|
||||
|
||||
|
@@ -370,9 +370,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): ParticleEmitterObjectNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): ParticleEmitterObjectNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
prms: this.particleRotationMinSpeed,
|
||||
prmx: this.particleRotationMaxSpeed,
|
||||
mpc: this.maxParticlesCount,
|
||||
@@ -399,9 +401,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
syncData: ParticleEmitterObjectNetworkSyncData
|
||||
syncData: ParticleEmitterObjectNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(syncData);
|
||||
super.updateFromNetworkSyncData(syncData, options);
|
||||
if (syncData.x !== undefined) {
|
||||
this.setX(syncData.x);
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ namespace gdjs {
|
||||
const logger = new gdjs.Logger('Pathfinding behavior');
|
||||
|
||||
interface PathfindingNetworkSyncDataType {
|
||||
// Syncing the path should be enough to have a good prediction.
|
||||
// Syncing the path and its position on it should be enough to have a good prediction.
|
||||
path: FloatPoint[];
|
||||
pf: boolean;
|
||||
sp: number;
|
||||
@@ -15,6 +15,7 @@ namespace gdjs {
|
||||
tss: number;
|
||||
re: boolean;
|
||||
ma: number;
|
||||
dos: number;
|
||||
}
|
||||
|
||||
export interface PathfindingNetworkSyncData extends BehaviorNetworkSyncData {
|
||||
@@ -133,9 +134,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): PathfindingNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
options: GetNetworkSyncDataOptions
|
||||
): PathfindingNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(options),
|
||||
props: {
|
||||
path: this._path,
|
||||
pf: this._pathFound,
|
||||
@@ -145,14 +148,16 @@ namespace gdjs {
|
||||
tss: this._totalSegmentDistance,
|
||||
re: this._reachedEnd,
|
||||
ma: this._movementAngle,
|
||||
dos: this._distanceOnSegment,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: PathfindingNetworkSyncData
|
||||
networkSyncData: PathfindingNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
const behaviorSpecificProps = networkSyncData.props;
|
||||
if (behaviorSpecificProps.path !== undefined) {
|
||||
this._path = behaviorSpecificProps.path;
|
||||
@@ -181,6 +186,9 @@ namespace gdjs {
|
||||
if (behaviorSpecificProps.ma !== undefined) {
|
||||
this._movementAngle = behaviorSpecificProps.ma;
|
||||
}
|
||||
if (behaviorSpecificProps.dos !== undefined) {
|
||||
this._distanceOnSegment = behaviorSpecificProps.dos;
|
||||
}
|
||||
}
|
||||
|
||||
setCellWidth(width: float): void {
|
||||
|
@@ -499,7 +499,9 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): Physics2NetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
options: GetNetworkSyncDataOptions
|
||||
): Physics2NetworkSyncData {
|
||||
const bodyProps = this._body
|
||||
? {
|
||||
tpx: this._body.GetTransform().get_p().get_x(),
|
||||
@@ -520,7 +522,7 @@ namespace gdjs {
|
||||
aw: undefined,
|
||||
};
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(options),
|
||||
props: {
|
||||
...bodyProps,
|
||||
layers: this.layers,
|
||||
@@ -529,45 +531,13 @@ namespace gdjs {
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(networkSyncData: Physics2NetworkSyncData) {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: Physics2NetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
const behaviorSpecificProps = networkSyncData.props;
|
||||
if (
|
||||
behaviorSpecificProps.tpx !== undefined &&
|
||||
behaviorSpecificProps.tpy !== undefined &&
|
||||
behaviorSpecificProps.tqa !== undefined
|
||||
) {
|
||||
if (this._body) {
|
||||
this._body.SetTransform(
|
||||
this.b2Vec2(behaviorSpecificProps.tpx, behaviorSpecificProps.tpy),
|
||||
behaviorSpecificProps.tqa
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
behaviorSpecificProps.lvx !== undefined &&
|
||||
behaviorSpecificProps.lvy !== undefined
|
||||
) {
|
||||
if (this._body) {
|
||||
this._body.SetLinearVelocity(
|
||||
this.b2Vec2(behaviorSpecificProps.lvx, behaviorSpecificProps.lvy)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (behaviorSpecificProps.av !== undefined) {
|
||||
if (this._body) {
|
||||
this._body.SetAngularVelocity(behaviorSpecificProps.av);
|
||||
}
|
||||
}
|
||||
|
||||
if (behaviorSpecificProps.aw !== undefined) {
|
||||
if (this._body) {
|
||||
this._body.SetAwake(behaviorSpecificProps.aw);
|
||||
}
|
||||
}
|
||||
|
||||
if (behaviorSpecificProps.layers !== undefined) {
|
||||
this.layers = behaviorSpecificProps.layers;
|
||||
@@ -576,6 +546,38 @@ namespace gdjs {
|
||||
if (behaviorSpecificProps.masks !== undefined) {
|
||||
this.masks = behaviorSpecificProps.masks;
|
||||
}
|
||||
|
||||
this.updateBodyFromObject();
|
||||
|
||||
if (!this._body) return;
|
||||
|
||||
if (
|
||||
behaviorSpecificProps.tpx !== undefined &&
|
||||
behaviorSpecificProps.tpy !== undefined &&
|
||||
behaviorSpecificProps.tqa !== undefined
|
||||
) {
|
||||
this._body.SetTransform(
|
||||
this.b2Vec2(behaviorSpecificProps.tpx, behaviorSpecificProps.tpy),
|
||||
behaviorSpecificProps.tqa
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
behaviorSpecificProps.lvx !== undefined &&
|
||||
behaviorSpecificProps.lvy !== undefined
|
||||
) {
|
||||
this._body.SetLinearVelocity(
|
||||
this.b2Vec2(behaviorSpecificProps.lvx, behaviorSpecificProps.lvy)
|
||||
);
|
||||
}
|
||||
|
||||
if (behaviorSpecificProps.av !== undefined) {
|
||||
this._body.SetAngularVelocity(behaviorSpecificProps.av);
|
||||
}
|
||||
|
||||
if (behaviorSpecificProps.aw !== undefined) {
|
||||
this._body.SetAwake(behaviorSpecificProps.aw);
|
||||
}
|
||||
}
|
||||
|
||||
onDeActivate() {
|
||||
|
@@ -495,7 +495,9 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
override getNetworkSyncData(): Physics3DNetworkSyncData {
|
||||
override getNetworkSyncData(
|
||||
options: GetNetworkSyncDataOptions
|
||||
): Physics3DNetworkSyncData {
|
||||
let bodyProps;
|
||||
if (this._body) {
|
||||
const position = this._body.GetPosition();
|
||||
@@ -537,7 +539,7 @@ namespace gdjs {
|
||||
};
|
||||
}
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(options),
|
||||
props: {
|
||||
...bodyProps,
|
||||
layers: this.layers,
|
||||
@@ -547,27 +549,40 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
override updateFromNetworkSyncData(
|
||||
networkSyncData: Physics3DNetworkSyncData
|
||||
networkSyncData: Physics3DNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
const behaviorSpecificProps = networkSyncData.props;
|
||||
|
||||
if (behaviorSpecificProps.layers !== undefined) {
|
||||
this.layers = behaviorSpecificProps.layers;
|
||||
}
|
||||
if (behaviorSpecificProps.masks !== undefined) {
|
||||
this.masks = behaviorSpecificProps.masks;
|
||||
}
|
||||
|
||||
this._needToRecreateShape = true;
|
||||
this._needToRecreateBody = true;
|
||||
this.updateBodyFromObject();
|
||||
|
||||
if (!this._body) return;
|
||||
|
||||
if (
|
||||
behaviorSpecificProps.px !== undefined &&
|
||||
behaviorSpecificProps.py !== undefined &&
|
||||
behaviorSpecificProps.pz !== undefined
|
||||
) {
|
||||
if (this._body) {
|
||||
this._sharedData.bodyInterface.SetPosition(
|
||||
this._body.GetID(),
|
||||
this.getRVec3(
|
||||
behaviorSpecificProps.px,
|
||||
behaviorSpecificProps.py,
|
||||
behaviorSpecificProps.pz
|
||||
),
|
||||
Jolt.EActivation_DontActivate
|
||||
);
|
||||
}
|
||||
this._sharedData.bodyInterface.SetPosition(
|
||||
this._body.GetID(),
|
||||
this.getRVec3(
|
||||
behaviorSpecificProps.px,
|
||||
behaviorSpecificProps.py,
|
||||
behaviorSpecificProps.pz
|
||||
),
|
||||
Jolt.EActivation_DontActivate
|
||||
);
|
||||
}
|
||||
if (
|
||||
behaviorSpecificProps.rx !== undefined &&
|
||||
@@ -575,56 +590,44 @@ namespace gdjs {
|
||||
behaviorSpecificProps.rz !== undefined &&
|
||||
behaviorSpecificProps.rw !== undefined
|
||||
) {
|
||||
if (this._body) {
|
||||
this._sharedData.bodyInterface.SetRotation(
|
||||
this._body.GetID(),
|
||||
this.getQuat(
|
||||
behaviorSpecificProps.rx,
|
||||
behaviorSpecificProps.ry,
|
||||
behaviorSpecificProps.rz,
|
||||
behaviorSpecificProps.rw
|
||||
),
|
||||
Jolt.EActivation_DontActivate
|
||||
);
|
||||
}
|
||||
this._sharedData.bodyInterface.SetRotation(
|
||||
this._body.GetID(),
|
||||
this.getQuat(
|
||||
behaviorSpecificProps.rx,
|
||||
behaviorSpecificProps.ry,
|
||||
behaviorSpecificProps.rz,
|
||||
behaviorSpecificProps.rw
|
||||
),
|
||||
Jolt.EActivation_DontActivate
|
||||
);
|
||||
}
|
||||
if (
|
||||
behaviorSpecificProps.lvx !== undefined &&
|
||||
behaviorSpecificProps.lvy !== undefined &&
|
||||
behaviorSpecificProps.lvz !== undefined
|
||||
) {
|
||||
if (this._body) {
|
||||
this._sharedData.bodyInterface.SetLinearVelocity(
|
||||
this._body.GetID(),
|
||||
this.getVec3(
|
||||
behaviorSpecificProps.lvx,
|
||||
behaviorSpecificProps.lvy,
|
||||
behaviorSpecificProps.lvz
|
||||
)
|
||||
);
|
||||
}
|
||||
this._sharedData.bodyInterface.SetLinearVelocity(
|
||||
this._body.GetID(),
|
||||
this.getVec3(
|
||||
behaviorSpecificProps.lvx,
|
||||
behaviorSpecificProps.lvy,
|
||||
behaviorSpecificProps.lvz
|
||||
)
|
||||
);
|
||||
}
|
||||
if (
|
||||
behaviorSpecificProps.avx !== undefined &&
|
||||
behaviorSpecificProps.avy !== undefined &&
|
||||
behaviorSpecificProps.avz !== undefined
|
||||
) {
|
||||
if (this._body) {
|
||||
this._sharedData.bodyInterface.SetAngularVelocity(
|
||||
this._body.GetID(),
|
||||
this.getVec3(
|
||||
behaviorSpecificProps.avx,
|
||||
behaviorSpecificProps.avy,
|
||||
behaviorSpecificProps.avz
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (behaviorSpecificProps.layers !== undefined) {
|
||||
this.layers = behaviorSpecificProps.layers;
|
||||
}
|
||||
if (behaviorSpecificProps.masks !== undefined) {
|
||||
this.masks = behaviorSpecificProps.masks;
|
||||
this._sharedData.bodyInterface.SetAngularVelocity(
|
||||
this._body.GetID(),
|
||||
this.getVec3(
|
||||
behaviorSpecificProps.avx,
|
||||
behaviorSpecificProps.avy,
|
||||
behaviorSpecificProps.avz
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -921,31 +924,58 @@ namespace gdjs {
|
||||
this.updateBodyFromObject();
|
||||
}
|
||||
|
||||
recreateBody() {
|
||||
if (!this._body) {
|
||||
this._createBody();
|
||||
return;
|
||||
}
|
||||
|
||||
recreateBody(previousBodyData?: {
|
||||
linearVelocityX: float;
|
||||
linearVelocityY: float;
|
||||
linearVelocityZ: float;
|
||||
angularVelocityX: float;
|
||||
angularVelocityY: float;
|
||||
angularVelocityZ: float;
|
||||
}) {
|
||||
const bodyInterface = this._sharedData.bodyInterface;
|
||||
const linearVelocity = this._body.GetLinearVelocity();
|
||||
const linearVelocityX = linearVelocity.GetX();
|
||||
const linearVelocityY = linearVelocity.GetY();
|
||||
const linearVelocityZ = linearVelocity.GetZ();
|
||||
const angularVelocity = this._body.GetAngularVelocity();
|
||||
const angularVelocityX = angularVelocity.GetX();
|
||||
const angularVelocityY = angularVelocity.GetY();
|
||||
const angularVelocityZ = angularVelocity.GetZ();
|
||||
const linearVelocityX = previousBodyData
|
||||
? previousBodyData.linearVelocityX
|
||||
: this._body
|
||||
? this._body.GetLinearVelocity().GetX()
|
||||
: 0;
|
||||
const linearVelocityY = previousBodyData
|
||||
? previousBodyData.linearVelocityY
|
||||
: this._body
|
||||
? this._body.GetLinearVelocity().GetY()
|
||||
: 0;
|
||||
const linearVelocityZ = previousBodyData
|
||||
? previousBodyData.linearVelocityZ
|
||||
: this._body
|
||||
? this._body.GetLinearVelocity().GetZ()
|
||||
: 0;
|
||||
const angularVelocityX = previousBodyData
|
||||
? previousBodyData.angularVelocityX
|
||||
: this._body
|
||||
? this._body.GetAngularVelocity().GetX()
|
||||
: 0;
|
||||
const angularVelocityY = previousBodyData
|
||||
? previousBodyData.angularVelocityY
|
||||
: this._body
|
||||
? this._body.GetAngularVelocity().GetY()
|
||||
: 0;
|
||||
const angularVelocityZ = previousBodyData
|
||||
? previousBodyData.angularVelocityZ
|
||||
: this._body
|
||||
? this._body.GetAngularVelocity().GetZ()
|
||||
: 0;
|
||||
|
||||
this.bodyUpdater.destroyBody();
|
||||
this._contactsEndedThisFrame.length = 0;
|
||||
this._contactsStartedThisFrame.length = 0;
|
||||
this._currentContacts.length = 0;
|
||||
if (this._body) {
|
||||
this.bodyUpdater.destroyBody();
|
||||
this._contactsEndedThisFrame.length = 0;
|
||||
this._contactsStartedThisFrame.length = 0;
|
||||
this._currentContacts.length = 0;
|
||||
}
|
||||
|
||||
this._createBody();
|
||||
if (!this._body) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bodyID = this._body.GetID();
|
||||
bodyInterface.SetLinearVelocity(
|
||||
bodyID,
|
||||
|
@@ -12,6 +12,7 @@ namespace gdjs {
|
||||
etm: float;
|
||||
esm: float;
|
||||
ei: float;
|
||||
es: float;
|
||||
}
|
||||
|
||||
export interface PhysicsCar3DNetworkSyncData extends BehaviorNetworkSyncData {
|
||||
@@ -96,7 +97,7 @@ namespace gdjs {
|
||||
// This is useful when the object is synchronized by an external source
|
||||
// like in a multiplayer game, and we want to be able to predict the
|
||||
// movement of the object, even if the inputs are not updated every frame.
|
||||
private _dontClearInputsBetweenFrames: boolean = false;
|
||||
private _clearInputsBetweenFrames: boolean = true;
|
||||
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
@@ -168,12 +169,37 @@ namespace gdjs {
|
||||
this._isHookedToPhysicsStep = true;
|
||||
}
|
||||
|
||||
// Destroy the body before switching the bodyUpdater,
|
||||
// to ensure the body of the previous bodyUpdater is not left alive.
|
||||
// (would be a memory leak and would create a phantom body in the physics world)
|
||||
// But transfer the linear and angular velocity to the new body,
|
||||
// so the body doesn't stop when it is recreated.
|
||||
let previousBodyData = {
|
||||
linearVelocityX: 0,
|
||||
linearVelocityY: 0,
|
||||
linearVelocityZ: 0,
|
||||
angularVelocityX: 0,
|
||||
angularVelocityY: 0,
|
||||
angularVelocityZ: 0,
|
||||
};
|
||||
if (behavior._body) {
|
||||
const linearVelocity = behavior._body.GetLinearVelocity();
|
||||
previousBodyData.linearVelocityX = linearVelocity.GetX();
|
||||
previousBodyData.linearVelocityY = linearVelocity.GetY();
|
||||
previousBodyData.linearVelocityZ = linearVelocity.GetZ();
|
||||
const angularVelocity = behavior._body.GetAngularVelocity();
|
||||
previousBodyData.angularVelocityX = angularVelocity.GetX();
|
||||
previousBodyData.angularVelocityY = angularVelocity.GetY();
|
||||
previousBodyData.angularVelocityZ = angularVelocity.GetZ();
|
||||
behavior.bodyUpdater.destroyBody();
|
||||
}
|
||||
|
||||
behavior.bodyUpdater =
|
||||
new gdjs.PhysicsCar3DRuntimeBehavior.VehicleBodyUpdater(
|
||||
this,
|
||||
behavior.bodyUpdater
|
||||
);
|
||||
behavior.recreateBody();
|
||||
behavior.recreateBody(previousBodyData);
|
||||
|
||||
return this._physics3D;
|
||||
}
|
||||
@@ -273,13 +299,15 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
override getNetworkSyncData(): PhysicsCar3DNetworkSyncData {
|
||||
override getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): PhysicsCar3DNetworkSyncData {
|
||||
// This method is called, so we are synchronizing this object.
|
||||
// Let's clear the inputs between frames as we control it.
|
||||
this._dontClearInputsBetweenFrames = false;
|
||||
this._clearInputsBetweenFrames = true;
|
||||
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
props: {
|
||||
lek: this._wasLeftKeyPressed,
|
||||
rik: this._wasRightKeyPressed,
|
||||
@@ -291,14 +319,16 @@ namespace gdjs {
|
||||
etm: this._engineTorqueMax,
|
||||
esm: this._engineSpeedMax,
|
||||
ei: this._engineInertia,
|
||||
es: this.getEngineSpeed(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
override updateFromNetworkSyncData(
|
||||
networkSyncData: PhysicsCar3DNetworkSyncData
|
||||
networkSyncData: PhysicsCar3DNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
const behaviorSpecificProps = networkSyncData.props;
|
||||
this._hasPressedForwardKey = behaviorSpecificProps.upk;
|
||||
@@ -311,9 +341,15 @@ namespace gdjs {
|
||||
this._engineTorqueMax = behaviorSpecificProps.etm;
|
||||
this._engineSpeedMax = behaviorSpecificProps.esm;
|
||||
this._engineInertia = behaviorSpecificProps.ei;
|
||||
if (this._vehicleController) {
|
||||
this._vehicleController
|
||||
.GetEngine()
|
||||
.SetCurrentRPM(behaviorSpecificProps.es);
|
||||
}
|
||||
|
||||
// When the object is synchronized from the network, the inputs must not be cleared.
|
||||
this._dontClearInputsBetweenFrames = true;
|
||||
// When the object is synchronized from the network, the inputs must not be cleared,
|
||||
// except if asked specifically.
|
||||
this._clearInputsBetweenFrames = !!options.clearInputs;
|
||||
}
|
||||
|
||||
_getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
|
||||
@@ -490,7 +526,7 @@ namespace gdjs {
|
||||
this._previousAcceleratorStickForce = this._acceleratorStickForce;
|
||||
this._previousSteeringStickForce = this._steeringStickForce;
|
||||
|
||||
if (!this._dontClearInputsBetweenFrames) {
|
||||
if (this._clearInputsBetweenFrames) {
|
||||
this._hasPressedForwardKey = false;
|
||||
this._hasPressedBackwardKey = false;
|
||||
this._hasPressedRightKey = false;
|
||||
|
@@ -102,7 +102,7 @@ namespace gdjs {
|
||||
// This is useful when the object is synchronized by an external source
|
||||
// like in a multiplayer game, and we want to be able to predict the
|
||||
// movement of the object, even if the inputs are not updated every frame.
|
||||
private _dontClearInputsBetweenFrames: boolean = false;
|
||||
private _clearInputsBetweenFrames: boolean = true;
|
||||
|
||||
/**
|
||||
* A very small value compare to 1 pixel, yet very huge compare to rounding errors.
|
||||
@@ -207,10 +207,35 @@ namespace gdjs {
|
||||
this._isHookedToPhysicsStep = true;
|
||||
}
|
||||
|
||||
// Destroy the body before switching the bodyUpdater,
|
||||
// to ensure the body of the previous bodyUpdater is not left alive.
|
||||
// (would be a memory leak and would create a phantom body in the physics world)
|
||||
// But transfer the linear and angular velocity to the new body,
|
||||
// so the body doesn't stop when it is recreated.
|
||||
let previousBodyData = {
|
||||
linearVelocityX: 0,
|
||||
linearVelocityY: 0,
|
||||
linearVelocityZ: 0,
|
||||
angularVelocityX: 0,
|
||||
angularVelocityY: 0,
|
||||
angularVelocityZ: 0,
|
||||
};
|
||||
if (behavior._body) {
|
||||
const linearVelocity = behavior._body.GetLinearVelocity();
|
||||
previousBodyData.linearVelocityX = linearVelocity.GetX();
|
||||
previousBodyData.linearVelocityY = linearVelocity.GetY();
|
||||
previousBodyData.linearVelocityZ = linearVelocity.GetZ();
|
||||
const angularVelocity = behavior._body.GetAngularVelocity();
|
||||
previousBodyData.angularVelocityX = angularVelocity.GetX();
|
||||
previousBodyData.angularVelocityY = angularVelocity.GetY();
|
||||
previousBodyData.angularVelocityZ = angularVelocity.GetZ();
|
||||
behavior.bodyUpdater.destroyBody();
|
||||
}
|
||||
|
||||
behavior.bodyUpdater =
|
||||
new gdjs.PhysicsCharacter3DRuntimeBehavior.CharacterBodyUpdater(this);
|
||||
behavior.collisionChecker = this.collisionChecker;
|
||||
behavior.recreateBody();
|
||||
behavior.recreateBody(previousBodyData);
|
||||
|
||||
// Always begin in the direction of the object.
|
||||
this._forwardAngle = this.owner.getAngle();
|
||||
@@ -277,13 +302,15 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
override getNetworkSyncData(): PhysicsCharacter3DNetworkSyncData {
|
||||
override getNetworkSyncData(
|
||||
options: GetNetworkSyncDataOptions
|
||||
): PhysicsCharacter3DNetworkSyncData {
|
||||
// This method is called, so we are synchronizing this object.
|
||||
// Let's clear the inputs between frames as we control it.
|
||||
this._dontClearInputsBetweenFrames = false;
|
||||
this._clearInputsBetweenFrames = true;
|
||||
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(options),
|
||||
props: {
|
||||
fwa: this._forwardAngle,
|
||||
fws: this._currentForwardSpeed,
|
||||
@@ -306,9 +333,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
override updateFromNetworkSyncData(
|
||||
networkSyncData: PhysicsCharacter3DNetworkSyncData
|
||||
networkSyncData: PhysicsCharacter3DNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
const behaviorSpecificProps = networkSyncData.props;
|
||||
this._forwardAngle = behaviorSpecificProps.fwa;
|
||||
@@ -328,8 +356,8 @@ namespace gdjs {
|
||||
this._timeSinceCurrentJumpStart = behaviorSpecificProps.tscjs;
|
||||
this._jumpKeyHeldSinceJumpStart = behaviorSpecificProps.jkhsjs;
|
||||
|
||||
// When the object is synchronized from the network, the inputs must not be cleared.
|
||||
this._dontClearInputsBetweenFrames = true;
|
||||
// Clear user inputs between frames only if requested.
|
||||
this._clearInputsBetweenFrames = !!options.clearInputs;
|
||||
}
|
||||
|
||||
_getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
|
||||
@@ -650,7 +678,7 @@ namespace gdjs {
|
||||
this._wasJumpKeyPressed = this._hasPressedJumpKey;
|
||||
this._wasStickUsed = this._hasUsedStick;
|
||||
|
||||
if (!this._dontClearInputsBetweenFrames) {
|
||||
if (this._clearInputsBetweenFrames) {
|
||||
this._hasPressedForwardKey = false;
|
||||
this._hasPressedBackwardKey = false;
|
||||
this._hasPressedRightKey = false;
|
||||
|
@@ -147,7 +147,7 @@ namespace gdjs {
|
||||
// This is useful when the object is synchronized by an external source
|
||||
// like in a multiplayer game, and we want to be able to predict the
|
||||
// movement of the object, even if the inputs are not updated every frame.
|
||||
_dontClearInputsBetweenFrames: boolean = false;
|
||||
private _clearInputsBetweenFrames: boolean = true;
|
||||
// This is useful when the object is synchronized over the network,
|
||||
// object is controlled by the network and we want to ensure the current player
|
||||
// cannot control it.
|
||||
@@ -227,14 +227,16 @@ namespace gdjs {
|
||||
this._state = this._falling;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): PlatformerObjectNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): PlatformerObjectNetworkSyncData {
|
||||
// This method is called, so we are synchronizing this object.
|
||||
// Let's clear the inputs between frames as we control it.
|
||||
this._dontClearInputsBetweenFrames = false;
|
||||
this._clearInputsBetweenFrames = true;
|
||||
this._ignoreDefaultControlsAsSyncedByNetwork = false;
|
||||
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
props: {
|
||||
cs: this._currentSpeed,
|
||||
|
||||
@@ -263,9 +265,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: PlatformerObjectNetworkSyncData
|
||||
networkSyncData: PlatformerObjectNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
const behaviorSpecificProps = networkSyncData.props;
|
||||
if (behaviorSpecificProps.cs !== this._currentSpeed) {
|
||||
@@ -346,10 +349,10 @@ namespace gdjs {
|
||||
this._state.updateFromNetworkSyncData(behaviorSpecificProps.ssd);
|
||||
}
|
||||
|
||||
// When the object is synchronized from the network, the inputs must not be cleared.
|
||||
this._dontClearInputsBetweenFrames = true;
|
||||
// Clear user inputs between frames only if requested.
|
||||
this._clearInputsBetweenFrames = !!options.clearInputs;
|
||||
// And we are not using the default controls.
|
||||
this._ignoreDefaultControlsAsSyncedByNetwork = true;
|
||||
this._ignoreDefaultControlsAsSyncedByNetwork = !options.keepControl;
|
||||
}
|
||||
|
||||
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
|
||||
@@ -545,8 +548,9 @@ namespace gdjs {
|
||||
this._wasJumpKeyPressed = this._jumpKey;
|
||||
this._wasReleasePlatformKeyPressed = this._releasePlatformKey;
|
||||
this._wasReleaseLadderKeyPressed = this._releaseLadderKey;
|
||||
|
||||
//4) Do not forget to reset pressed keys
|
||||
if (!this._dontClearInputsBetweenFrames) {
|
||||
if (this._clearInputsBetweenFrames) {
|
||||
// Reset the keys only if the inputs are not supposed to survive between frames.
|
||||
// (Most of the time, except if this object is synchronized by an external source)
|
||||
this._leftKey = false;
|
||||
|
191
Extensions/SaveState/JsExtension.js
Normal file
@@ -0,0 +1,191 @@
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
'SaveState',
|
||||
_('Save State'),
|
||||
_('Allows to save and load the full state of a game.'),
|
||||
'Neyl Mahfouf',
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/all-features/save-state')
|
||||
.setCategory('Save & Load');
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata(_('Save State'))
|
||||
.setIcon('res/actions/saveDown.svg');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'SaveGameSnapshotToVariable',
|
||||
_('Save game to a variable'),
|
||||
_('Takes a snapshot of the game and save it to a variable.'),
|
||||
_('Save the game in variable _PARAM1_'),
|
||||
_('Save'),
|
||||
'res/actions/saveDown.svg',
|
||||
'res/actions/saveDown.svg'
|
||||
)
|
||||
.addCodeOnlyParameter('currentScene', '')
|
||||
.addParameter('variable', _('Variable to store the save to'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/SaveState/savestatetools.js')
|
||||
.setFunctionName('gdjs.saveState.saveVariableGameSnapshot');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'SaveGameSnapshotToStorage',
|
||||
_('Save game to device storage'),
|
||||
_('Takes a snapshot of the game and save it to device storage.'),
|
||||
_('Save the game to device storage under key _PARAM1_'),
|
||||
_('Save'),
|
||||
'res/actions/saveDown.svg',
|
||||
'res/actions/saveDown.svg'
|
||||
)
|
||||
.addCodeOnlyParameter('currentScene', '')
|
||||
.addParameter('string', _('Storage key to save to'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/SaveState/savestatetools.js')
|
||||
.setFunctionName('gdjs.saveState.saveStorageGameSnapshot');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'LoadGameSnapshotFromVariable',
|
||||
_('Load game from variable'),
|
||||
_('Load game from a variable save snapshot.'),
|
||||
_('Load the game from variable _PARAM0_'),
|
||||
_('Load'),
|
||||
'res/actions/saveUp.svg',
|
||||
'res/actions/saveUp.svg'
|
||||
)
|
||||
.addParameter('variable', _('Variable to load the game from'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/SaveState/savestatetools.js')
|
||||
.setFunctionName('gdjs.saveState.loadGameFromVariableSnapshot');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'LoadGameSnapshotFromStorage',
|
||||
_('Load game from device storage'),
|
||||
_('Load game from device storage save snapshot.'),
|
||||
_('Load the game from device storage under key _PARAM0_.'),
|
||||
_('Load'),
|
||||
'res/actions/saveUp.svg',
|
||||
'res/actions/saveUp.svg'
|
||||
)
|
||||
.addParameter('string', _('Storage key to load the game from'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/SaveState/savestatetools.js')
|
||||
.setFunctionName('gdjs.saveState.loadGameFromStorageSnapshot');
|
||||
|
||||
extension
|
||||
.addExpressionAndCondition(
|
||||
'number',
|
||||
'TimeSinceLastSave',
|
||||
_('Time since last save'),
|
||||
_(
|
||||
'Time since the last save, in seconds. Returns -1 if no save happened, and a positive number otherwise.'
|
||||
),
|
||||
_('Time since the last save'),
|
||||
'',
|
||||
'res/actions/saveDown.svg'
|
||||
)
|
||||
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
|
||||
.setIncludeFile('Extensions/SaveState/savestatetools.js')
|
||||
.setFunctionName('gdjs.saveState.getSecondsSinceLastSave')
|
||||
.setGetter('gdjs.saveState.getSecondsSinceLastSave');
|
||||
|
||||
extension
|
||||
.addExpressionAndCondition(
|
||||
'number',
|
||||
'TimeSinceLastLoad',
|
||||
_('Time since last load'),
|
||||
_(
|
||||
'Time since the last load, in seconds. Returns -1 if no load happened, and a positive number otherwise.'
|
||||
),
|
||||
_('Time since the last load'),
|
||||
'',
|
||||
'res/actions/saveDown.svg'
|
||||
)
|
||||
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
|
||||
.setIncludeFile('Extensions/SaveState/savestatetools.js')
|
||||
.setFunctionName('gdjs.saveState.getSecondsSinceLastLoad')
|
||||
.setGetter('gdjs.saveState.getSecondsSinceLastLoad');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'SaveJustSucceeded',
|
||||
_('Save just succeeded'),
|
||||
_('the save just succeeded'),
|
||||
_('the save just succeeded'),
|
||||
_('Save'),
|
||||
'res/actions/saveDown.svg',
|
||||
'res/actions/saveDown.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/SaveState/savestatetools.js')
|
||||
.setFunctionName('gdjs.saveState.hasSaveJustSucceeded');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'SaveJustFailed',
|
||||
_('Save just failed'),
|
||||
_('the save just failed'),
|
||||
_('the save just failed'),
|
||||
_('Save'),
|
||||
'res/actions/saveDown.svg',
|
||||
'res/actions/saveDown.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/SaveState/savestatetools.js')
|
||||
.setFunctionName('gdjs.saveState.hasSaveJustFailed');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'LoadJustSucceeded',
|
||||
_('Load just succeeded'),
|
||||
_('the load just succeeded'),
|
||||
_('the load just succeeded'),
|
||||
_('Load'),
|
||||
'res/actions/saveUp.svg',
|
||||
'res/actions/saveUp.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/SaveState/savestatetools.js')
|
||||
.setFunctionName('gdjs.saveState.hasLoadJustSucceeded');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'LoadJustFailed',
|
||||
_('Load just failed'),
|
||||
_('the load just failed'),
|
||||
_('the load just failed'),
|
||||
_('Load'),
|
||||
'res/actions/saveUp.svg',
|
||||
'res/actions/saveUp.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/SaveState/savestatetools.js')
|
||||
.setFunctionName('gdjs.saveState.hasLoadJustFailed');
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
360
Extensions/SaveState/savestatetools.ts
Normal file
@@ -0,0 +1,360 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Save state');
|
||||
export type LoadRequestOptions = {
|
||||
loadStorageName?: string;
|
||||
loadVariable?: gdjs.Variable;
|
||||
};
|
||||
|
||||
export namespace saveState {
|
||||
export const getIndexedDbDatabaseName = () => {
|
||||
const gameId = gdjs.projectData.properties.projectUuid;
|
||||
return `gdevelop-game-${gameId}`;
|
||||
};
|
||||
export const getIndexedDbObjectStore = () => {
|
||||
return `game-saves`;
|
||||
};
|
||||
export const getIndexedDbStorageKey = (key: string) => {
|
||||
return `save-${key}`;
|
||||
};
|
||||
|
||||
const getNetworkSyncOptions: GetNetworkSyncDataOptions = {
|
||||
syncObjectIdentifiers: true,
|
||||
syncAllVariables: true,
|
||||
syncAllBehaviors: true,
|
||||
syncSceneTimers: true,
|
||||
syncOnceTriggers: true,
|
||||
syncSounds: true,
|
||||
syncTweens: true,
|
||||
syncLayers: true,
|
||||
syncAsyncTasks: true,
|
||||
syncSceneVisualProps: true,
|
||||
syncFullTileMaps: true,
|
||||
};
|
||||
const updateFromNetworkSyncDataOptions: UpdateFromNetworkSyncDataOptions = {
|
||||
clearSceneStack: true,
|
||||
preventInitialInstancesCreation: true,
|
||||
preventSoundsStoppingOnStartup: true,
|
||||
clearInputs: true,
|
||||
keepControl: true,
|
||||
ignoreVariableOwnership: true,
|
||||
};
|
||||
|
||||
let lastSaveTime: number | null = null;
|
||||
let lastLoadTime: number | null = null;
|
||||
let saveJustSucceeded: boolean = false;
|
||||
let saveJustFailed: boolean = false;
|
||||
let loadJustSucceeded: boolean = false;
|
||||
let loadJustFailed: boolean = false;
|
||||
|
||||
let loadRequestOptions: LoadRequestOptions | null = null;
|
||||
|
||||
export const getSecondsSinceLastSave = (): number => {
|
||||
if (!lastSaveTime) return -1;
|
||||
return Math.floor((Date.now() - lastSaveTime) / 1000);
|
||||
};
|
||||
export const getSecondsSinceLastLoad = (): number => {
|
||||
if (!lastLoadTime) return -1;
|
||||
return Math.floor((Date.now() - lastLoadTime) / 1000);
|
||||
};
|
||||
export const hasSaveJustSucceeded = () => {
|
||||
return saveJustSucceeded;
|
||||
};
|
||||
export const hasLoadJustSucceeded = () => {
|
||||
return loadJustSucceeded;
|
||||
};
|
||||
export const hasSaveJustFailed = () => {
|
||||
return saveJustFailed;
|
||||
};
|
||||
export const hasLoadJustFailed = () => {
|
||||
return loadJustFailed;
|
||||
};
|
||||
export const markSaveJustSucceeded = () => {
|
||||
saveJustSucceeded = true;
|
||||
lastSaveTime = Date.now();
|
||||
};
|
||||
export const markLoadJustSucceeded = () => {
|
||||
loadJustSucceeded = true;
|
||||
lastLoadTime = Date.now();
|
||||
};
|
||||
export const markSaveJustFailed = () => {
|
||||
saveJustFailed = true;
|
||||
};
|
||||
export const markLoadJustFailed = () => {
|
||||
loadJustFailed = true;
|
||||
};
|
||||
|
||||
// Ensure that the condition "save/load just succeeded/failed" are valid only for one frame.
|
||||
gdjs.registerRuntimeScenePostEventsCallback(() => {
|
||||
saveJustSucceeded = false;
|
||||
saveJustFailed = false;
|
||||
loadJustSucceeded = false;
|
||||
loadJustFailed = false;
|
||||
});
|
||||
|
||||
gdjs.registerRuntimeScenePostEventsCallback(
|
||||
(runtimeScene: gdjs.RuntimeScene) => {
|
||||
loadGameSnapshotAtTheEndOfFrameIfAny(runtimeScene);
|
||||
}
|
||||
);
|
||||
|
||||
const getGameSaveState = (runtimeScene: RuntimeScene) => {
|
||||
const gameSaveState: GameSaveState = {
|
||||
gameNetworkSyncData: {},
|
||||
layoutNetworkSyncDatas: [],
|
||||
};
|
||||
|
||||
const gameData = runtimeScene
|
||||
.getGame()
|
||||
.getNetworkSyncData(getNetworkSyncOptions);
|
||||
const scenes = runtimeScene.getGame().getSceneStack().getAllScenes();
|
||||
gameSaveState.gameNetworkSyncData = gameData || {};
|
||||
|
||||
scenes.forEach((scene, index) => {
|
||||
gameSaveState.layoutNetworkSyncDatas[index] = {
|
||||
sceneData: {} as LayoutNetworkSyncData,
|
||||
objectDatas: {},
|
||||
};
|
||||
|
||||
// First collect all object sync data, as they may generate unique
|
||||
// identifiers like their networkId.
|
||||
const sceneRuntimeObjects = scene.getAdhocListOfAllInstances();
|
||||
for (const key in sceneRuntimeObjects) {
|
||||
if (sceneRuntimeObjects.hasOwnProperty(key)) {
|
||||
const object = sceneRuntimeObjects[key];
|
||||
const objectSyncData = object.getNetworkSyncData(
|
||||
getNetworkSyncOptions
|
||||
);
|
||||
gameSaveState.layoutNetworkSyncDatas[index].objectDatas[object.id] =
|
||||
objectSyncData;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all scene data in the end.
|
||||
const sceneDatas = (scene.getNetworkSyncData(getNetworkSyncOptions) ||
|
||||
[]) as LayoutNetworkSyncData;
|
||||
|
||||
gameSaveState.layoutNetworkSyncDatas[index].sceneData = sceneDatas;
|
||||
});
|
||||
|
||||
return gameSaveState;
|
||||
};
|
||||
|
||||
export const saveVariableGameSnapshot = async function (
|
||||
currentScene: RuntimeScene,
|
||||
variable: gdjs.Variable
|
||||
) {
|
||||
try {
|
||||
const gameSaveState = getGameSaveState(currentScene);
|
||||
variable.fromJSObject(gameSaveState);
|
||||
markSaveJustSucceeded();
|
||||
} catch (error) {
|
||||
logger.error('Error saving to variable:', error);
|
||||
markSaveJustFailed();
|
||||
}
|
||||
};
|
||||
|
||||
export const saveStorageGameSnapshot = async function (
|
||||
currentScene: RuntimeScene,
|
||||
storageKey: string
|
||||
) {
|
||||
try {
|
||||
const gameSaveState = getGameSaveState(currentScene);
|
||||
await gdjs.indexedDb.saveToIndexedDB(
|
||||
getIndexedDbDatabaseName(),
|
||||
getIndexedDbObjectStore(),
|
||||
getIndexedDbStorageKey(storageKey),
|
||||
gameSaveState
|
||||
);
|
||||
markSaveJustSucceeded();
|
||||
} catch (error) {
|
||||
logger.error('Error saving to IndexedDB:', error);
|
||||
markSaveJustFailed();
|
||||
}
|
||||
};
|
||||
|
||||
export const loadGameFromVariableSnapshot = async function (
|
||||
variable: gdjs.Variable
|
||||
) {
|
||||
// The information is saved, so that the load can be done
|
||||
// at the end of the frame,
|
||||
// and avoid possible conflicts with running events.
|
||||
loadRequestOptions = {
|
||||
loadVariable: variable,
|
||||
};
|
||||
};
|
||||
|
||||
export const loadGameFromStorageSnapshot = async function (
|
||||
storageName: string
|
||||
) {
|
||||
// The information is saved, so that the load can be done
|
||||
// at the end of the frame,
|
||||
// and avoid possible conflicts with running events.
|
||||
loadRequestOptions = {
|
||||
loadStorageName: storageName,
|
||||
};
|
||||
};
|
||||
|
||||
const loadGameSnapshotAtTheEndOfFrameIfAny = function (
|
||||
runtimeScene: RuntimeScene
|
||||
) {
|
||||
if (!loadRequestOptions) return;
|
||||
|
||||
const optionsToApply = loadRequestOptions;
|
||||
// Reset it so we don't load it twice.
|
||||
loadRequestOptions = null;
|
||||
|
||||
if (optionsToApply.loadVariable) {
|
||||
const sceneVariables = runtimeScene.getVariables();
|
||||
const variablePathInScene =
|
||||
sceneVariables.getVariablePathInContainerByLoopingThroughAllVariables(
|
||||
optionsToApply.loadVariable
|
||||
);
|
||||
const gameVariables = runtimeScene.getGame().getVariables();
|
||||
const variablePathIngame =
|
||||
gameVariables.getVariablePathInContainerByLoopingThroughAllVariables(
|
||||
optionsToApply.loadVariable
|
||||
);
|
||||
const saveState =
|
||||
optionsToApply.loadVariable.toJSObject() as GameSaveState;
|
||||
|
||||
try {
|
||||
loadGameFromSave(runtimeScene, saveState, {
|
||||
variableToRehydrate: optionsToApply.loadVariable,
|
||||
variablePathInScene: variablePathInScene,
|
||||
variablePathInGame: variablePathIngame,
|
||||
});
|
||||
markLoadJustSucceeded();
|
||||
} catch (error) {
|
||||
logger.error('Error loading from variable:', error);
|
||||
markLoadJustFailed();
|
||||
}
|
||||
} else if (optionsToApply.loadStorageName) {
|
||||
gdjs.indexedDb
|
||||
.loadFromIndexedDB(
|
||||
getIndexedDbDatabaseName(),
|
||||
getIndexedDbObjectStore(),
|
||||
getIndexedDbStorageKey(optionsToApply.loadStorageName)
|
||||
)
|
||||
.then((jsonData) => {
|
||||
const saveState = jsonData as GameSaveState;
|
||||
loadGameFromSave(runtimeScene, saveState);
|
||||
markLoadJustSucceeded();
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error loading from IndexedDB:', error);
|
||||
markLoadJustFailed();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const loadGameFromSave = (
|
||||
runtimeScene: RuntimeScene,
|
||||
saveState: GameSaveState,
|
||||
saveOptions?: {
|
||||
variableToRehydrate: gdjs.Variable;
|
||||
variablePathInScene: string[] | null;
|
||||
variablePathInGame: string[] | null;
|
||||
}
|
||||
): void => {
|
||||
// Save the content of the save, as it will be erased after the load.
|
||||
const variableToRehydrateNetworkSyncData = saveOptions
|
||||
? saveOptions.variableToRehydrate.getNetworkSyncData(
|
||||
getNetworkSyncOptions
|
||||
)
|
||||
: null;
|
||||
|
||||
// First update the game, which will update the variables,
|
||||
// and set the scene stack to update when ready.
|
||||
const runtimeGame = runtimeScene.getGame();
|
||||
runtimeGame.updateFromNetworkSyncData(
|
||||
saveState.gameNetworkSyncData,
|
||||
updateFromNetworkSyncDataOptions
|
||||
);
|
||||
|
||||
// Apply the scene stack updates, as we are at the end of a frame,
|
||||
// we can safely do it.
|
||||
const sceneStack = runtimeGame.getSceneStack();
|
||||
sceneStack.applyUpdateFromNetworkSyncDataIfAny(
|
||||
updateFromNetworkSyncDataOptions
|
||||
);
|
||||
|
||||
// Then get all scenes, which we assume will be the expected ones
|
||||
// after the load has been done, so we can update them,
|
||||
// and create their objects.
|
||||
const runtimeScenes = sceneStack.getAllScenes();
|
||||
runtimeScenes.forEach((scene, index) => {
|
||||
const layoutSyncData = saveState.layoutNetworkSyncDatas[index];
|
||||
if (!layoutSyncData) return;
|
||||
|
||||
// Create objects first, so they are available for the scene update,
|
||||
// especially so that they have a networkId defined.
|
||||
const objectDatas = layoutSyncData.objectDatas;
|
||||
for (const id in objectDatas) {
|
||||
const objectNetworkSyncData = objectDatas[id];
|
||||
const objectName = objectNetworkSyncData.n;
|
||||
if (!objectName) {
|
||||
logger.warn('Tried to recreate an object without a name.');
|
||||
continue;
|
||||
}
|
||||
const object = scene.createObject(objectName);
|
||||
if (object) {
|
||||
object.updateFromNetworkSyncData(
|
||||
objectNetworkSyncData,
|
||||
updateFromNetworkSyncDataOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the scene last.
|
||||
scene.updateFromNetworkSyncData(
|
||||
layoutSyncData.sceneData,
|
||||
updateFromNetworkSyncDataOptions
|
||||
);
|
||||
});
|
||||
|
||||
// Finally, if the save was done in a variable,
|
||||
// rehydrate the variable where the save was done,
|
||||
// as it has been erased by the load.
|
||||
if (saveOptions && variableToRehydrateNetworkSyncData) {
|
||||
const currentScene = sceneStack.getCurrentScene();
|
||||
if (!currentScene) return;
|
||||
const sceneVariables = currentScene.getVariables();
|
||||
const gameVariables = currentScene.getGame().getVariables();
|
||||
const { variablePathInScene, variablePathInGame } = saveOptions;
|
||||
|
||||
if (variablePathInScene && variablePathInScene.length > 0) {
|
||||
const variableName =
|
||||
variablePathInScene[variablePathInScene.length - 1];
|
||||
const variableInScene =
|
||||
sceneVariables.getVariableFromPath(variablePathInScene);
|
||||
if (variableInScene) {
|
||||
const variableNetworkSyncData: VariableNetworkSyncData = {
|
||||
name: variableName,
|
||||
...variableToRehydrateNetworkSyncData,
|
||||
};
|
||||
variableInScene.updateFromNetworkSyncData(
|
||||
variableNetworkSyncData,
|
||||
updateFromNetworkSyncDataOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
if (variablePathInGame && variablePathInGame.length > 0) {
|
||||
const variableName =
|
||||
variablePathInGame[variablePathInGame.length - 1];
|
||||
const variableInGame =
|
||||
gameVariables.getVariableFromPath(variablePathInGame);
|
||||
if (variableInGame) {
|
||||
const variableNetworkSyncData: VariableNetworkSyncData = {
|
||||
name: variableName,
|
||||
...variableToRehydrateNetworkSyncData,
|
||||
};
|
||||
variableInGame.updateFromNetworkSyncData(
|
||||
variableNetworkSyncData,
|
||||
updateFromNetworkSyncDataOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@@ -111,9 +111,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): SpineNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): SpineNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
opa: this._opacity,
|
||||
scaX: this.getScaleX(),
|
||||
scaY: this.getScaleY(),
|
||||
@@ -127,8 +129,11 @@ namespace gdjs {
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(syncData: SpineNetworkSyncData): void {
|
||||
super.updateFromNetworkSyncData(syncData);
|
||||
updateFromNetworkSyncData(
|
||||
syncData: SpineNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(syncData, options);
|
||||
|
||||
if (syncData.opa !== undefined && syncData.opa !== this._opacity) {
|
||||
this.setOpacity(syncData.opa);
|
||||
|
@@ -267,9 +267,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): TextInputNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): TextInputNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
opa: this.getOpacity(),
|
||||
txt: this.getText(),
|
||||
frn: this.getFontResourceName(),
|
||||
@@ -288,8 +290,11 @@ namespace gdjs {
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(syncData: TextInputNetworkSyncData): void {
|
||||
super.updateFromNetworkSyncData(syncData);
|
||||
updateFromNetworkSyncData(
|
||||
syncData: TextInputNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(syncData, options);
|
||||
|
||||
if (syncData.opa !== undefined) this.setOpacity(syncData.opa);
|
||||
if (syncData.txt !== undefined) this.setText(syncData.txt);
|
||||
|
@@ -52,6 +52,7 @@ namespace gdjs {
|
||||
c: number[];
|
||||
scale: number;
|
||||
ta: string;
|
||||
vta: string;
|
||||
wrap: boolean;
|
||||
wrapw: float;
|
||||
oena: boolean;
|
||||
@@ -179,9 +180,6 @@ namespace gdjs {
|
||||
if (oldContent.text !== newContent.text) {
|
||||
this.setText(newContent.text);
|
||||
}
|
||||
if (oldContent.underlined !== newContent.underlined) {
|
||||
return false;
|
||||
}
|
||||
if (oldContent.textAlignment !== newContent.textAlignment) {
|
||||
this.setTextAlignment(newContent.textAlignment);
|
||||
}
|
||||
@@ -220,12 +218,18 @@ namespace gdjs {
|
||||
if ((oldContent.lineHeight || 0) !== (newContent.lineHeight || 0)) {
|
||||
this.setLineHeight(newContent.lineHeight || 0);
|
||||
}
|
||||
if (oldContent.underlined !== newContent.underlined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
override getNetworkSyncData(): TextObjectNetworkSyncData {
|
||||
override getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): TextObjectNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
str: this._str,
|
||||
o: this.opacity,
|
||||
cs: this._characterSize,
|
||||
@@ -236,6 +240,7 @@ namespace gdjs {
|
||||
c: this._color,
|
||||
scale: this.getScale(),
|
||||
ta: this._textAlign,
|
||||
vta: this._verticalTextAlignment,
|
||||
wrap: this._wrapping,
|
||||
wrapw: this._wrappingWidth,
|
||||
oena: this._isOutlineEnabled,
|
||||
@@ -253,9 +258,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
override updateFromNetworkSyncData(
|
||||
networkSyncData: TextObjectNetworkSyncData
|
||||
networkSyncData: TextObjectNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
if (networkSyncData.str !== undefined) {
|
||||
this.setText(networkSyncData.str);
|
||||
}
|
||||
@@ -286,8 +292,8 @@ namespace gdjs {
|
||||
if (networkSyncData.ta !== undefined) {
|
||||
this.setTextAlignment(networkSyncData.ta);
|
||||
}
|
||||
if (networkSyncData.ta !== undefined) {
|
||||
this.setVerticalTextAlignment(networkSyncData.ta);
|
||||
if (networkSyncData.vta !== undefined) {
|
||||
this.setVerticalTextAlignment(networkSyncData.vta);
|
||||
}
|
||||
if (networkSyncData.wrap !== undefined) {
|
||||
this.setWrapping(networkSyncData.wrap);
|
||||
|
@@ -9,7 +9,7 @@ import {
|
||||
* A tile map model.
|
||||
*
|
||||
* Tile map files are parsed into this model by {@link TiledTileMapLoader} or {@link LDtkTileMapLoader}.
|
||||
* This model is used for rending ({@link TileMapRuntimeObjectPixiRenderer})
|
||||
* This model is used for rendering ({@link TileMapRuntimeObjectPixiRenderer})
|
||||
* and hitboxes handling ({@link TransformedCollisionTileMap}).
|
||||
* This allows to support new file format with only a new parser.
|
||||
*/
|
||||
@@ -71,7 +71,7 @@ export declare class EditableTileMap {
|
||||
tileSetRowCount: number;
|
||||
}
|
||||
): EditableTileMap;
|
||||
toJSObject(): Object;
|
||||
toJSObject(): EditableTileMapAsJsObject;
|
||||
/**
|
||||
* @returns The tile map width in pixels.
|
||||
*/
|
||||
@@ -205,7 +205,7 @@ declare abstract class AbstractEditableLayer {
|
||||
*/
|
||||
constructor(tileMap: EditableTileMap, id: integer);
|
||||
setVisible(visible: boolean): void;
|
||||
toJSObject(): Object;
|
||||
toJSObject(): EditableTileMapLayerAsJsObject;
|
||||
/**
|
||||
* @returns true if the layer is visible.
|
||||
*/
|
||||
@@ -284,7 +284,7 @@ export declare class EditableTileMapLayer extends AbstractEditableLayer {
|
||||
tileMap: EditableTileMap,
|
||||
isTileIdValid: (tileId: number) => boolean
|
||||
): EditableTileMapLayer;
|
||||
toJSObject(): Object;
|
||||
toJSObject(): EditableTileMapLayerAsJsObject;
|
||||
/**
|
||||
* The opacity (between 0-1) of the layer
|
||||
*/
|
||||
|
@@ -2,7 +2,6 @@
|
||||
namespace gdjs {
|
||||
export type SimpleTileMapObjectDataType = {
|
||||
content: {
|
||||
opacity: number;
|
||||
atlasImage: string;
|
||||
rowCount: number;
|
||||
columnCount: number;
|
||||
@@ -16,8 +15,7 @@ namespace gdjs {
|
||||
|
||||
export type SimpleTileMapNetworkSyncDataType = {
|
||||
op: number;
|
||||
ai: string;
|
||||
// TODO: Support tilemap synchronization. Find an efficient way to send tiles changes.
|
||||
tm?: TileMapHelper.EditableTileMapAsJsObject;
|
||||
};
|
||||
|
||||
export type SimpleTileMapNetworkSyncData = ObjectNetworkSyncData &
|
||||
@@ -38,6 +36,7 @@ namespace gdjs {
|
||||
_opacity: float = 255;
|
||||
_atlasImage: string;
|
||||
_tileMapManager: gdjs.TileMap.TileMapRuntimeManager;
|
||||
_tileMap: TileMapHelper.EditableTileMap | null = null;
|
||||
_renderer: gdjs.TileMapRuntimeObjectPixiRenderer;
|
||||
readonly _rowCount: number;
|
||||
readonly _columnCount: number;
|
||||
@@ -62,7 +61,6 @@ namespace gdjs {
|
||||
objectData: SimpleTileMapObjectDataType
|
||||
) {
|
||||
super(instanceContainer, objectData);
|
||||
this._opacity = objectData.content.opacity;
|
||||
this._atlasImage = objectData.content.atlasImage;
|
||||
this._rowCount = objectData.content.rowCount;
|
||||
this._columnCount = objectData.content.columnCount;
|
||||
@@ -87,16 +85,19 @@ namespace gdjs {
|
||||
instanceContainer
|
||||
);
|
||||
|
||||
this._loadInitialTileMap((tileMap: TileMapHelper.EditableTileMap) => {
|
||||
this._renderer.updatePosition();
|
||||
this._loadTileMap(
|
||||
this._initialTileMapAsJsObject,
|
||||
(tileMap: TileMapHelper.EditableTileMap) => {
|
||||
this._renderer.updatePosition();
|
||||
|
||||
this._collisionTileMap = new gdjs.TileMap.TransformedCollisionTileMap(
|
||||
tileMap,
|
||||
this._hitBoxTag
|
||||
);
|
||||
this._collisionTileMap = new gdjs.TileMap.TransformedCollisionTileMap(
|
||||
tileMap,
|
||||
this._hitBoxTag
|
||||
);
|
||||
|
||||
this.updateTransformation();
|
||||
});
|
||||
this.updateTransformation();
|
||||
}
|
||||
);
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
this.onCreated();
|
||||
@@ -139,8 +140,8 @@ namespace gdjs {
|
||||
);
|
||||
if (!shouldContinue) return;
|
||||
if (this._collisionTileMap) {
|
||||
const tileMap = this._renderer.getTileMap();
|
||||
if (tileMap) this._collisionTileMap.updateFromTileMap(tileMap);
|
||||
if (this._tileMap)
|
||||
this._collisionTileMap.updateFromTileMap(this._tileMap);
|
||||
}
|
||||
this._isTileMapDirty = false;
|
||||
}
|
||||
@@ -150,9 +151,6 @@ namespace gdjs {
|
||||
oldObjectData: SimpleTileMapObjectData,
|
||||
newObjectData: SimpleTileMapObjectData
|
||||
): boolean {
|
||||
if (oldObjectData.content.opacity !== newObjectData.content.opacity) {
|
||||
this.setOpacity(newObjectData.content.opacity);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.atlasImage !== newObjectData.content.atlasImage
|
||||
) {
|
||||
@@ -163,27 +161,58 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): SimpleTileMapNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): SimpleTileMapNetworkSyncData {
|
||||
const syncData: SimpleTileMapNetworkSyncData = {
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
op: this._opacity,
|
||||
ai: this._atlasImage,
|
||||
};
|
||||
if (this._tileMap && syncOptions.syncFullTileMaps) {
|
||||
const currentTileMapAsJsObject = this._tileMap.toJSObject();
|
||||
syncData.tm = currentTileMapAsJsObject;
|
||||
}
|
||||
|
||||
return syncData;
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: SimpleTileMapNetworkSyncData
|
||||
networkSyncData: SimpleTileMapNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
if (
|
||||
networkSyncData.op !== undefined &&
|
||||
networkSyncData.op !== this._opacity
|
||||
) {
|
||||
this.setOpacity(networkSyncData.op);
|
||||
}
|
||||
if (networkSyncData.ai !== undefined) {
|
||||
// TODO: support changing the atlas texture
|
||||
if (networkSyncData.tm !== undefined) {
|
||||
this._loadTileMap(
|
||||
networkSyncData.tm,
|
||||
(tileMap: TileMapHelper.EditableTileMap) => {
|
||||
if (networkSyncData.w !== undefined) {
|
||||
this.setWidth(networkSyncData.w);
|
||||
}
|
||||
if (networkSyncData.h !== undefined) {
|
||||
this.setHeight(networkSyncData.h);
|
||||
}
|
||||
if (networkSyncData.op !== undefined) {
|
||||
this.setOpacity(networkSyncData.op);
|
||||
}
|
||||
|
||||
// 4. Update position (calculations based on renderer's dimensions).
|
||||
this._renderer.updatePosition();
|
||||
|
||||
if (this._collisionTileMap) {
|
||||
// If collision tile map is already defined, only update it.
|
||||
this._collisionTileMap.updateFromTileMap(tileMap);
|
||||
} else {
|
||||
this._collisionTileMap =
|
||||
new gdjs.TileMap.TransformedCollisionTileMap(
|
||||
tileMap,
|
||||
this._hitBoxTag
|
||||
);
|
||||
}
|
||||
|
||||
this.updateTransformation();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,39 +228,43 @@ namespace gdjs {
|
||||
|
||||
// 2. Update the renderer so that it updates the tilemap object
|
||||
// (used for width and position calculations).
|
||||
this._loadInitialTileMap((tileMap: TileMapHelper.EditableTileMap) => {
|
||||
// 3. Set custom dimensions & opacity if applicable.
|
||||
if (initialInstanceData.customSize) {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this._loadTileMap(
|
||||
this._initialTileMapAsJsObject,
|
||||
(tileMap: TileMapHelper.EditableTileMap) => {
|
||||
// 3. Set custom dimensions & opacity if applicable.
|
||||
if (initialInstanceData.customSize) {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
|
||||
// 4. Update position (calculations based on renderer's dimensions).
|
||||
this._renderer.updatePosition();
|
||||
// 4. Update position (calculations based on renderer's dimensions).
|
||||
this._renderer.updatePosition();
|
||||
|
||||
if (this._collisionTileMap) {
|
||||
// If collision tile map is already defined, there's a good chance it means
|
||||
// extraInitializationFromInitialInstance is called when hot reloading the
|
||||
// scene so the collision is tile map is updated instead of being re-created.
|
||||
this._collisionTileMap.updateFromTileMap(tileMap);
|
||||
} else {
|
||||
this._collisionTileMap = new gdjs.TileMap.TransformedCollisionTileMap(
|
||||
tileMap,
|
||||
this._hitBoxTag
|
||||
);
|
||||
if (this._collisionTileMap) {
|
||||
// If collision tile map is already defined, there's a good chance it means
|
||||
// extraInitializationFromInitialInstance is called when hot reloading the
|
||||
// scene so the collision is tile map is updated instead of being re-created.
|
||||
this._collisionTileMap.updateFromTileMap(tileMap);
|
||||
} else {
|
||||
this._collisionTileMap =
|
||||
new gdjs.TileMap.TransformedCollisionTileMap(
|
||||
tileMap,
|
||||
this._hitBoxTag
|
||||
);
|
||||
}
|
||||
|
||||
this.updateTransformation();
|
||||
}
|
||||
|
||||
this.updateTransformation();
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
private _loadInitialTileMap(
|
||||
private _loadTileMap(
|
||||
tileMapAsJsObject: TileMapHelper.EditableTileMapAsJsObject,
|
||||
tileMapLoadingCallback: (tileMap: TileMapHelper.EditableTileMap) => void
|
||||
): void {
|
||||
if (!this._initialTileMapAsJsObject) return;
|
||||
if (this._columnCount <= 0 || this._rowCount <= 0) {
|
||||
console.error(
|
||||
`Tilemap object ${this.name} is not configured properly.`
|
||||
@@ -240,7 +273,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
this._tileMapManager.getOrLoadSimpleTileMap(
|
||||
this._initialTileMapAsJsObject,
|
||||
tileMapAsJsObject,
|
||||
this.name,
|
||||
this._tileSize,
|
||||
this._columnCount,
|
||||
@@ -284,7 +317,8 @@ namespace gdjs {
|
||||
// getOrLoadTextureCache already log warns and errors.
|
||||
return;
|
||||
}
|
||||
this._renderer.updatePixiTileMap(tileMap, textureCache);
|
||||
this._tileMap = tileMap;
|
||||
this._renderer.refreshPixiTileMap(textureCache);
|
||||
tileMapLoadingCallback(tileMap);
|
||||
},
|
||||
(error) => {
|
||||
@@ -640,7 +674,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
getTileAtGridCoordinates(columnIndex: integer, rowIndex: integer): integer {
|
||||
return this._renderer.getTileId(columnIndex, rowIndex, 0);
|
||||
return this.getTileId(columnIndex, rowIndex, 0);
|
||||
}
|
||||
|
||||
setTileAtPosition(tileId: number, x: float, y: float) {
|
||||
@@ -654,11 +688,10 @@ namespace gdjs {
|
||||
columnIndex: integer,
|
||||
rowIndex: integer
|
||||
) {
|
||||
const tileMap = this._renderer._tileMap;
|
||||
if (!tileMap) {
|
||||
if (!this._tileMap) {
|
||||
return;
|
||||
}
|
||||
const layer = tileMap.getTileLayer(this._layerIndex);
|
||||
const layer = this._tileMap.getTileLayer(this._layerIndex);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
@@ -670,8 +703,8 @@ namespace gdjs {
|
||||
|
||||
if (this._collisionTileMap) {
|
||||
const oldTileDefinition =
|
||||
oldTileId !== undefined && tileMap.getTileDefinition(oldTileId);
|
||||
const newTileDefinition = tileMap.getTileDefinition(tileId);
|
||||
oldTileId !== undefined && this._tileMap.getTileDefinition(oldTileId);
|
||||
const newTileDefinition = this._tileMap.getTileDefinition(tileId);
|
||||
const hadFullHitBox =
|
||||
!!oldTileDefinition &&
|
||||
oldTileDefinition.hasFullHitBox(this._hitBoxTag);
|
||||
@@ -707,7 +740,7 @@ namespace gdjs {
|
||||
rowIndex: integer,
|
||||
flip: boolean
|
||||
) {
|
||||
this._renderer.flipTileOnY(columnIndex, rowIndex, 0, flip);
|
||||
this.flipTileOnY(columnIndex, rowIndex, 0, flip);
|
||||
this._isTileMapDirty = true;
|
||||
// No need to invalidate hit boxes since at the moment, collision mask
|
||||
// cannot be configured on each tile.
|
||||
@@ -718,7 +751,7 @@ namespace gdjs {
|
||||
rowIndex: integer,
|
||||
flip: boolean
|
||||
) {
|
||||
this._renderer.flipTileOnX(columnIndex, rowIndex, 0, flip);
|
||||
this.flipTileOnX(columnIndex, rowIndex, 0, flip);
|
||||
this._isTileMapDirty = true;
|
||||
// No need to invalidate hit boxes since at the moment, collision mask
|
||||
// cannot be configured on each tile.
|
||||
@@ -728,22 +761,22 @@ namespace gdjs {
|
||||
const [columnIndex, rowIndex] =
|
||||
this.getGridCoordinatesFromSceneCoordinates(x, y);
|
||||
|
||||
return this._renderer.isTileFlippedOnX(columnIndex, rowIndex, 0);
|
||||
return this.isTileFlippedOnX(columnIndex, rowIndex, 0);
|
||||
}
|
||||
|
||||
isTileFlippedOnXAtGridCoordinates(columnIndex: integer, rowIndex: integer) {
|
||||
return this._renderer.isTileFlippedOnX(columnIndex, rowIndex, 0);
|
||||
return this.isTileFlippedOnX(columnIndex, rowIndex, 0);
|
||||
}
|
||||
|
||||
isTileFlippedOnYAtPosition(x: float, y: float) {
|
||||
const [columnIndex, rowIndex] =
|
||||
this.getGridCoordinatesFromSceneCoordinates(x, y);
|
||||
|
||||
return this._renderer.isTileFlippedOnY(columnIndex, rowIndex, 0);
|
||||
return this.isTileFlippedOnY(columnIndex, rowIndex, 0);
|
||||
}
|
||||
|
||||
isTileFlippedOnYAtGridCoordinates(columnIndex: integer, rowIndex: integer) {
|
||||
return this._renderer.isTileFlippedOnY(columnIndex, rowIndex, 0);
|
||||
return this.isTileFlippedOnY(columnIndex, rowIndex, 0);
|
||||
}
|
||||
|
||||
removeTileAtPosition(x: float, y: float) {
|
||||
@@ -753,11 +786,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
removeTileAtGridCoordinates(columnIndex: integer, rowIndex: integer) {
|
||||
const tileMap = this._renderer._tileMap;
|
||||
if (!tileMap) {
|
||||
if (!this._tileMap) {
|
||||
return;
|
||||
}
|
||||
const layer = tileMap.getTileLayer(this._layerIndex);
|
||||
const layer = this._tileMap.getTileLayer(this._layerIndex);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
@@ -779,24 +811,28 @@ namespace gdjs {
|
||||
|
||||
setGridRowCount(targetRowCount: integer) {
|
||||
if (targetRowCount <= 0) return;
|
||||
this._renderer.setGridRowCount(targetRowCount);
|
||||
if (!this._tileMap) return;
|
||||
this._tileMap.setDimensionY(targetRowCount);
|
||||
this._isTileMapDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
setGridColumnCount(targetColumnCount: integer) {
|
||||
if (targetColumnCount <= 0) return;
|
||||
this._renderer.setGridColumnCount(targetColumnCount);
|
||||
if (!this._tileMap) return;
|
||||
this._tileMap.setDimensionX(targetColumnCount);
|
||||
this._isTileMapDirty = true;
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
getGridRowCount(): integer {
|
||||
return this._renderer.getGridRowCount();
|
||||
if (!this._tileMap) return 0;
|
||||
return this._tileMap.getDimensionY();
|
||||
}
|
||||
|
||||
getGridColumnCount(): integer {
|
||||
return this._renderer.getGridColumnCount();
|
||||
if (!this._tileMap) return 0;
|
||||
return this._tileMap.getDimensionX();
|
||||
}
|
||||
|
||||
getTilesetColumnCount(): integer {
|
||||
@@ -806,6 +842,73 @@ namespace gdjs {
|
||||
getTilesetRowCount(): integer {
|
||||
return this._rowCount;
|
||||
}
|
||||
|
||||
getTileMap(): TileMapHelper.EditableTileMap | null {
|
||||
return this._tileMap;
|
||||
}
|
||||
|
||||
getTileMapWidth() {
|
||||
const tileMap = this._tileMap;
|
||||
return tileMap ? tileMap.getWidth() : 20;
|
||||
}
|
||||
|
||||
getTileMapHeight() {
|
||||
const tileMap = this._tileMap;
|
||||
return tileMap ? tileMap.getHeight() : 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
* @returns The tile's id.
|
||||
*/
|
||||
getTileId(x: integer, y: integer, layerIndex: integer): integer {
|
||||
if (!this._tileMap) return -1;
|
||||
return this._tileMap.getTileId(x, y, layerIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
* @param flip true if the tile should be flipped.
|
||||
*/
|
||||
flipTileOnY(x: integer, y: integer, layerIndex: integer, flip: boolean) {
|
||||
if (!this._tileMap) return;
|
||||
this._tileMap.flipTileOnY(x, y, layerIndex, flip);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
* @param flip true if the tile should be flipped.
|
||||
*/
|
||||
flipTileOnX(x: integer, y: integer, layerIndex: integer, flip: boolean) {
|
||||
if (!this._tileMap) return;
|
||||
this._tileMap.flipTileOnX(x, y, layerIndex, flip);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
*/
|
||||
isTileFlippedOnX(x: integer, y: integer, layerIndex: integer): boolean {
|
||||
if (!this._tileMap) return false;
|
||||
return this._tileMap.isTileFlippedOnX(x, y, layerIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
*/
|
||||
isTileFlippedOnY(x: integer, y: integer, layerIndex: integer): boolean {
|
||||
if (!this._tileMap) return false;
|
||||
return this._tileMap.isTileFlippedOnY(x, y, layerIndex);
|
||||
}
|
||||
}
|
||||
gdjs.registerObject(
|
||||
'TileMap::SimpleTileMap',
|
||||
|
@@ -189,9 +189,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): TilemapCollisionMaskNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): TilemapCollisionMaskNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
tmjf: this.getTilemapJsonFile(),
|
||||
tsjf: this.getTilesetJsonFile(),
|
||||
dm: this.getDebugMode(),
|
||||
@@ -204,9 +206,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: TilemapCollisionMaskNetworkSyncData
|
||||
networkSyncData: TilemapCollisionMaskNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
if (networkSyncData.tmjf !== undefined) {
|
||||
this.setTilemapJsonFile(networkSyncData.tmjf);
|
||||
|
@@ -10,8 +10,6 @@ namespace gdjs {
|
||||
private _object:
|
||||
| gdjs.TileMapRuntimeObject
|
||||
| gdjs.SimpleTileMapRuntimeObject;
|
||||
// TODO Move this attribute in the object as it's a model.
|
||||
_tileMap: TileMapHelper.EditableTileMap | null = null;
|
||||
|
||||
private _pixiObject: PIXI.tilemap.CompositeTilemap;
|
||||
|
||||
@@ -51,40 +49,9 @@ namespace gdjs {
|
||||
this._pixiObject.tileAnim[0] += 1;
|
||||
}
|
||||
|
||||
updatePixiTileMap(
|
||||
tileMap: TileMapHelper.EditableTileMap,
|
||||
textureCache: TileMapHelper.TileTextureCache
|
||||
) {
|
||||
this._tileMap = tileMap;
|
||||
TileMapHelper.PixiTileMapHelper.updatePixiTileMap(
|
||||
this._pixiObject,
|
||||
tileMap,
|
||||
textureCache,
|
||||
// @ts-ignore
|
||||
this._object._displayMode,
|
||||
this._object._layerIndex
|
||||
);
|
||||
}
|
||||
|
||||
refreshPixiTileMap(textureCache: TileMapHelper.TileTextureCache) {
|
||||
if (!this._tileMap) return;
|
||||
TileMapHelper.PixiTileMapHelper.updatePixiTileMap(
|
||||
this._pixiObject,
|
||||
this._tileMap,
|
||||
textureCache,
|
||||
// @ts-ignore
|
||||
this._object._displayMode,
|
||||
this._object._layerIndex
|
||||
);
|
||||
}
|
||||
|
||||
getTileMap(): TileMapHelper.EditableTileMap | null {
|
||||
return this._tileMap;
|
||||
}
|
||||
|
||||
updatePosition(): void {
|
||||
this._pixiObject.pivot.x = this.getTileMapWidth() / 2;
|
||||
this._pixiObject.pivot.y = this.getTileMapHeight() / 2;
|
||||
this._pixiObject.pivot.x = this._object.getTileMapWidth() / 2;
|
||||
this._pixiObject.pivot.y = this._object.getTileMapHeight() / 2;
|
||||
this._pixiObject.position.x = this._object.x + this.getWidth() / 2;
|
||||
this._pixiObject.position.y = this._object.y + this.getHeight() / 2;
|
||||
}
|
||||
@@ -98,7 +65,7 @@ namespace gdjs {
|
||||
// opacity. Setting alpha on each layer tile might not be useful as
|
||||
// each layer would be separately transparent instead of the whole tilemap.
|
||||
this._pixiObject.alpha = this._object._opacity / 255;
|
||||
const tileMap = this._tileMap;
|
||||
const tileMap = this._object.getTileMap();
|
||||
if (!tileMap) return;
|
||||
for (const layer of tileMap.getLayers()) {
|
||||
if (
|
||||
@@ -114,44 +81,34 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
getTileMapWidth() {
|
||||
const tileMap = this._tileMap;
|
||||
return tileMap ? tileMap.getWidth() : 20;
|
||||
}
|
||||
|
||||
getTileMapHeight() {
|
||||
const tileMap = this._tileMap;
|
||||
return tileMap ? tileMap.getHeight() : 20;
|
||||
}
|
||||
|
||||
setWidth(width: float): void {
|
||||
this._pixiObject.scale.x = width / this.getTileMapWidth();
|
||||
this._pixiObject.scale.x = width / this._object.getTileMapWidth();
|
||||
this._pixiObject.position.x = this._object.x + width / 2;
|
||||
}
|
||||
|
||||
setHeight(height: float): void {
|
||||
this._pixiObject.scale.y = height / this.getTileMapHeight();
|
||||
this._pixiObject.scale.y = height / this._object.getTileMapHeight();
|
||||
this._pixiObject.position.y = this._object.y + height / 2;
|
||||
}
|
||||
|
||||
setScaleX(scaleX: float): void {
|
||||
this._pixiObject.scale.x = scaleX;
|
||||
const width = scaleX * this.getTileMapWidth();
|
||||
const width = scaleX * this._object.getTileMapWidth();
|
||||
this._pixiObject.position.x = this._object.x + width / 2;
|
||||
}
|
||||
|
||||
setScaleY(scaleY: float): void {
|
||||
this._pixiObject.scale.y = scaleY;
|
||||
const height = scaleY * this.getTileMapHeight();
|
||||
const height = scaleY * this._object.getTileMapHeight();
|
||||
this._pixiObject.position.y = this._object.y + height / 2;
|
||||
}
|
||||
|
||||
getWidth(): float {
|
||||
return this.getTileMapWidth() * this._pixiObject.scale.x;
|
||||
return this._object.getTileMapWidth() * this._pixiObject.scale.x;
|
||||
}
|
||||
|
||||
getHeight(): float {
|
||||
return this.getTileMapHeight() * this._pixiObject.scale.y;
|
||||
return this._object.getTileMapHeight() * this._pixiObject.scale.y;
|
||||
}
|
||||
|
||||
getScaleX(): float {
|
||||
@@ -162,91 +119,17 @@ namespace gdjs {
|
||||
return this._pixiObject.scale.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
* @returns The tile's id.
|
||||
*/
|
||||
getTileId(x: integer, y: integer, layerIndex: integer): integer {
|
||||
const tileMap = this._tileMap;
|
||||
if (!tileMap) return -1;
|
||||
return tileMap.getTileId(x, y, layerIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
* @param flip true if the tile should be flipped.
|
||||
*/
|
||||
flipTileOnY(x: integer, y: integer, layerIndex: integer, flip: boolean) {
|
||||
const tileMap = this._tileMap;
|
||||
refreshPixiTileMap(textureCache: TileMapHelper.TileTextureCache) {
|
||||
const tileMap = this._object.getTileMap();
|
||||
if (!tileMap) return;
|
||||
tileMap.flipTileOnY(x, y, layerIndex, flip);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
* @param flip true if the tile should be flipped.
|
||||
*/
|
||||
flipTileOnX(x: integer, y: integer, layerIndex: integer, flip: boolean) {
|
||||
const tileMap = this._tileMap;
|
||||
if (!tileMap) return;
|
||||
tileMap.flipTileOnX(x, y, layerIndex, flip);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
*/
|
||||
isTileFlippedOnX(x: integer, y: integer, layerIndex: integer): boolean {
|
||||
const tileMap = this._tileMap;
|
||||
if (!tileMap) return false;
|
||||
return tileMap.isTileFlippedOnX(x, y, layerIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
*/
|
||||
isTileFlippedOnY(x: integer, y: integer, layerIndex: integer): boolean {
|
||||
const tileMap = this._tileMap;
|
||||
if (!tileMap) return false;
|
||||
return tileMap.isTileFlippedOnY(x, y, layerIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param targetRowCount The number of rows to have.
|
||||
*/
|
||||
setGridRowCount(targetRowCount: integer) {
|
||||
const tileMap = this._tileMap;
|
||||
if (!tileMap) return;
|
||||
return tileMap.setDimensionY(targetRowCount);
|
||||
}
|
||||
/**
|
||||
* @param targetColumnCount The number of rows to have.
|
||||
*/
|
||||
setGridColumnCount(targetColumnCount: integer) {
|
||||
const tileMap = this._tileMap;
|
||||
if (!tileMap) return;
|
||||
return tileMap.setDimensionX(targetColumnCount);
|
||||
}
|
||||
|
||||
getGridRowCount(): integer {
|
||||
const tileMap = this._tileMap;
|
||||
if (!tileMap) return 0;
|
||||
return tileMap.getDimensionY();
|
||||
}
|
||||
|
||||
getGridColumnCount(): integer {
|
||||
const tileMap = this._tileMap;
|
||||
if (!tileMap) return 0;
|
||||
return tileMap.getDimensionX();
|
||||
TileMapHelper.PixiTileMapHelper.updatePixiTileMap(
|
||||
this._pixiObject,
|
||||
tileMap,
|
||||
textureCache,
|
||||
// @ts-ignore
|
||||
this._displayMode,
|
||||
this._object._layerIndex
|
||||
);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
|
@@ -2,7 +2,6 @@
|
||||
namespace gdjs {
|
||||
export type TilemapObjectDataType = {
|
||||
content: {
|
||||
opacity: number;
|
||||
tilemapJsonFile: string;
|
||||
tilesetJsonFile: string;
|
||||
tilemapAtlasImage: string;
|
||||
@@ -38,7 +37,7 @@ namespace gdjs {
|
||||
implements gdjs.Resizable, gdjs.Scalable, gdjs.OpacityHandler
|
||||
{
|
||||
_frameElapsedTime: float = 0;
|
||||
_opacity: float;
|
||||
_opacity: float = 255;
|
||||
_tilemapJsonFile: string;
|
||||
_tilesetJsonFile: string;
|
||||
_tilemapAtlasImage: string;
|
||||
@@ -48,11 +47,14 @@ namespace gdjs {
|
||||
_animationSpeedScale: number;
|
||||
_animationFps: number;
|
||||
_tileMapManager: gdjs.TileMap.TileMapRuntimeManager;
|
||||
_tileMap: TileMapHelper.EditableTileMap | null = null;
|
||||
_renderer: gdjs.TileMapRuntimeObjectPixiRenderer;
|
||||
|
||||
constructor(instanceContainer: gdjs.RuntimeInstanceContainer, objectData) {
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
objectData: TilemapObjectData
|
||||
) {
|
||||
super(instanceContainer, objectData);
|
||||
this._opacity = objectData.content.opacity;
|
||||
this._tilemapJsonFile = objectData.content.tilemapJsonFile;
|
||||
this._tilesetJsonFile = objectData.content.tilesetJsonFile;
|
||||
this._tilemapAtlasImage = objectData.content.tilemapAtlasImage;
|
||||
@@ -93,9 +95,6 @@ namespace gdjs {
|
||||
oldObjectData: TilemapObjectData,
|
||||
newObjectData: TilemapObjectData
|
||||
): boolean {
|
||||
if (oldObjectData.content.opacity !== newObjectData.content.opacity) {
|
||||
this.setOpacity(newObjectData.content.opacity);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.tilemapJsonFile !==
|
||||
newObjectData.content.tilemapJsonFile
|
||||
@@ -145,9 +144,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): TilemapNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): TilemapNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
op: this._opacity,
|
||||
tmjf: this._tilemapJsonFile,
|
||||
tsjf: this._tilesetJsonFile,
|
||||
@@ -159,8 +160,11 @@ namespace gdjs {
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(networkSyncData: TilemapNetworkSyncData): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: TilemapNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
if (networkSyncData.op !== undefined) {
|
||||
this.setOpacity(networkSyncData.op);
|
||||
@@ -230,7 +234,9 @@ namespace gdjs {
|
||||
// getOrLoadTextureCache already log warns and errors.
|
||||
return;
|
||||
}
|
||||
this._renderer.updatePixiTileMap(tileMap, textureCache);
|
||||
this._tileMap = tileMap;
|
||||
this._renderer.refreshPixiTileMap(textureCache);
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -427,6 +433,73 @@ namespace gdjs {
|
||||
getScaleY(): float {
|
||||
return this._renderer.getScaleY();
|
||||
}
|
||||
|
||||
getTileMap(): TileMapHelper.EditableTileMap | null {
|
||||
return this._tileMap;
|
||||
}
|
||||
|
||||
getTileMapWidth() {
|
||||
const tileMap = this._tileMap;
|
||||
return tileMap ? tileMap.getWidth() : 20;
|
||||
}
|
||||
|
||||
getTileMapHeight() {
|
||||
const tileMap = this._tileMap;
|
||||
return tileMap ? tileMap.getHeight() : 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
* @returns The tile's id.
|
||||
*/
|
||||
getTileId(x: integer, y: integer, layerIndex: integer): integer {
|
||||
if (!this._tileMap) return -1;
|
||||
return this._tileMap.getTileId(x, y, layerIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
* @param flip true if the tile should be flipped.
|
||||
*/
|
||||
flipTileOnY(x: integer, y: integer, layerIndex: integer, flip: boolean) {
|
||||
if (!this._tileMap) return;
|
||||
this._tileMap.flipTileOnY(x, y, layerIndex, flip);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
* @param flip true if the tile should be flipped.
|
||||
*/
|
||||
flipTileOnX(x: integer, y: integer, layerIndex: integer, flip: boolean) {
|
||||
if (!this._tileMap) return;
|
||||
this._tileMap.flipTileOnX(x, y, layerIndex, flip);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
*/
|
||||
isTileFlippedOnX(x: integer, y: integer, layerIndex: integer): boolean {
|
||||
if (!this._tileMap) return false;
|
||||
return this._tileMap.isTileFlippedOnX(x, y, layerIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x The layer column.
|
||||
* @param y The layer row.
|
||||
* @param layerIndex The layer index.
|
||||
*/
|
||||
isTileFlippedOnY(x: integer, y: integer, layerIndex: integer): boolean {
|
||||
if (!this._tileMap) return false;
|
||||
return this._tileMap.isTileFlippedOnY(x, y, layerIndex);
|
||||
}
|
||||
}
|
||||
gdjs.registerObject('TileMap::TileMap', gdjs.TileMapRuntimeObject);
|
||||
}
|
||||
|
@@ -78,9 +78,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): TiledSpriteNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptons: GetNetworkSyncDataOptions
|
||||
): TiledSpriteNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptons),
|
||||
xo: this.getXOffset(),
|
||||
yo: this.getYOffset(),
|
||||
op: this.getOpacity(),
|
||||
@@ -89,9 +91,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: TiledSpriteNetworkSyncData
|
||||
networkSyncData: TiledSpriteNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
// Texture is not synchronized, see if this is asked or not.
|
||||
|
||||
|
@@ -63,7 +63,7 @@ namespace gdjs {
|
||||
// This is useful when the object is synchronized by an external source
|
||||
// like in a multiplayer game, and we want to be able to predict the
|
||||
// movement of the object, even if the inputs are not updated every frame.
|
||||
_dontClearInputsBetweenFrames: boolean = false;
|
||||
private _clearInputsBetweenFrames: boolean = true;
|
||||
// This is useful when the object is synchronized over the network,
|
||||
// object is controlled by the network and we want to ensure the current player
|
||||
// cannot control it.
|
||||
@@ -109,14 +109,16 @@ namespace gdjs {
|
||||
: behaviorData.useLegacyTurnBack;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): TopDownMovementNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
options: GetNetworkSyncDataOptions
|
||||
): TopDownMovementNetworkSyncData {
|
||||
// This method is called, so we are synchronizing this object.
|
||||
// Let's clear the inputs between frames as we control it.
|
||||
this._dontClearInputsBetweenFrames = false;
|
||||
this._clearInputsBetweenFrames = true;
|
||||
this._ignoreDefaultControlsAsSyncedByNetwork = false;
|
||||
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(options),
|
||||
props: {
|
||||
a: this._angle,
|
||||
xv: this._xVelocity,
|
||||
@@ -134,9 +136,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: TopDownMovementNetworkSyncData
|
||||
networkSyncData: TopDownMovementNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
const behaviorSpecificProps = networkSyncData.props;
|
||||
if (behaviorSpecificProps.a !== undefined) {
|
||||
@@ -173,10 +176,10 @@ namespace gdjs {
|
||||
this._stickForce = behaviorSpecificProps.sf;
|
||||
}
|
||||
|
||||
// When the object is synchronized from the network, the inputs must not be cleared.
|
||||
this._dontClearInputsBetweenFrames = true;
|
||||
// And we are not using the default controls.
|
||||
this._ignoreDefaultControlsAsSyncedByNetwork = true;
|
||||
// Clear user inputs between frames only if requested.
|
||||
this._clearInputsBetweenFrames = !!options.clearInputs;
|
||||
// And ignore default controls if not asked otherwise.
|
||||
this._ignoreDefaultControlsAsSyncedByNetwork = !options.keepControl;
|
||||
}
|
||||
|
||||
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
|
||||
@@ -581,7 +584,7 @@ namespace gdjs {
|
||||
this._wasRightKeyPressed = this._rightKey;
|
||||
this._wasUpKeyPressed = this._upKey;
|
||||
this._wasDownKeyPressed = this._downKey;
|
||||
if (!this._dontClearInputsBetweenFrames) {
|
||||
if (this._clearInputsBetweenFrames) {
|
||||
this._leftKey = false;
|
||||
this._rightKey = false;
|
||||
this._upKey = false;
|
||||
|
@@ -4,6 +4,15 @@ Copyright (c) 2010-2023 Florian Rival (Florian.Rival@gmail.com)
|
||||
*/
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Tween');
|
||||
|
||||
interface TweenBehaviorNetworkSyncDataType {
|
||||
tweenManager: TweenManagerNetworkSyncData;
|
||||
}
|
||||
export interface TweenBehaviorNetworkSyncData
|
||||
extends BehaviorNetworkSyncData {
|
||||
props: TweenBehaviorNetworkSyncDataType;
|
||||
}
|
||||
|
||||
interface IColorable extends gdjs.RuntimeObject {
|
||||
setColor(color: string): void;
|
||||
getColor(): string;
|
||||
@@ -59,6 +68,305 @@ namespace gdjs {
|
||||
const exponentialInterpolation =
|
||||
gdjs.evtTools.common.exponentialInterpolation;
|
||||
|
||||
const tweenObjectValueSetter = () => {};
|
||||
const getTweenVariableSetter = (variable: gdjs.Variable) => {
|
||||
return (value: float) => variable.setNumber(value);
|
||||
};
|
||||
const getTweenObjectPositionXSetter = (object: gdjs.RuntimeObject) => {
|
||||
return (value: float) => object.setX(value);
|
||||
};
|
||||
const getTweenObjectPositionYSetter = (object: gdjs.RuntimeObject) => {
|
||||
return (value: float) => object.setY(value);
|
||||
};
|
||||
const getTweenObjectPositionZSetter = (object: gdjs.RuntimeObject) => {
|
||||
if (!is3D(object)) return () => {};
|
||||
return (value: float) => object.setZ(value);
|
||||
};
|
||||
const getTweenObjectPositionSetter = (object: gdjs.RuntimeObject) => {
|
||||
return ([x, y]: float[]) => object.setPosition(x, y);
|
||||
};
|
||||
const getTweenObjectAngleSetter = (object: gdjs.RuntimeObject) => {
|
||||
return (value: float) => object.setAngle(value);
|
||||
};
|
||||
const getTweenObjectWidthSetter = (object: gdjs.RuntimeObject) => {
|
||||
return (value: float) => object.setWidth(value);
|
||||
};
|
||||
const getTweenObjectHeightSetter = (object: gdjs.RuntimeObject) => {
|
||||
return (value: float) => object.setHeight(value);
|
||||
};
|
||||
const getTweenObjectRotationXSetter = (
|
||||
object: gdjs.RuntimeObject & gdjs.Base3DHandler
|
||||
) => {
|
||||
return (value: float) => object.setRotationX(value);
|
||||
};
|
||||
const getTweenObjectRotationYSetter = (
|
||||
object: gdjs.RuntimeObject & gdjs.Base3DHandler
|
||||
) => {
|
||||
return (value: float) => object.setRotationY(value);
|
||||
};
|
||||
const getTweenObjectDepthSetter = (
|
||||
object: gdjs.RuntimeObject & gdjs.Base3DHandler
|
||||
) => {
|
||||
return (value: float) => object.setDepth(value);
|
||||
};
|
||||
const getTweenObjectScaleXYSetter = (
|
||||
object: gdjs.RuntimeObject & gdjs.Scalable,
|
||||
scaleFromCenterOfObject: boolean
|
||||
) => {
|
||||
return scaleFromCenterOfObject
|
||||
? ([scaleX, scaleY]: float[]) => {
|
||||
const oldX = object.getCenterXInScene();
|
||||
const oldY = object.getCenterYInScene();
|
||||
object.setScaleX(scaleX);
|
||||
object.setScaleY(scaleY);
|
||||
object.setCenterPositionInScene(oldX, oldY);
|
||||
}
|
||||
: ([scaleX, scaleY]: float[]) => {
|
||||
object.setScaleX(scaleX);
|
||||
object.setScaleY(scaleY);
|
||||
};
|
||||
};
|
||||
const getTweenObjectScaleSetter = (
|
||||
object: gdjs.RuntimeObject & gdjs.Scalable,
|
||||
object3d: (gdjs.RuntimeObject & gdjs.Base3DHandler) | null,
|
||||
scaleFromCenterOfObject: boolean
|
||||
) => {
|
||||
return scaleFromCenterOfObject
|
||||
? (scale: float) => {
|
||||
const oldX = object.getCenterXInScene();
|
||||
const oldY = object.getCenterYInScene();
|
||||
const oldZ = object3d ? object3d.getCenterZInScene() : 0;
|
||||
object.setScale(scale);
|
||||
object.setCenterXInScene(oldX);
|
||||
object.setCenterYInScene(oldY);
|
||||
if (object3d) {
|
||||
object3d.setCenterZInScene(oldZ);
|
||||
}
|
||||
}
|
||||
: (scale: float) => object.setScale(scale);
|
||||
};
|
||||
const getTweenObjectScaleXSetter = (
|
||||
object: gdjs.RuntimeObject & gdjs.Scalable,
|
||||
scaleFromCenterOfObject: boolean
|
||||
) => {
|
||||
return scaleFromCenterOfObject
|
||||
? (scaleX: float) => {
|
||||
const oldX = object.getCenterXInScene();
|
||||
object.setScaleX(scaleX);
|
||||
object.setCenterXInScene(oldX);
|
||||
}
|
||||
: (scaleX: float) => object.setScaleX(scaleX);
|
||||
};
|
||||
const getTweenObjectScaleYSetter = (
|
||||
object: gdjs.RuntimeObject & gdjs.Scalable,
|
||||
scaleFromCenterOfObject: boolean
|
||||
) => {
|
||||
return scaleFromCenterOfObject
|
||||
? (scaleY: float) => {
|
||||
const oldY = object.getCenterYInScene();
|
||||
object.setScaleY(scaleY);
|
||||
object.setCenterYInScene(oldY);
|
||||
}
|
||||
: (scaleY: float) => object.setScaleY(scaleY);
|
||||
};
|
||||
const getTweenObjectOpacitySetter = (
|
||||
object: gdjs.RuntimeObject & gdjs.OpacityHandler
|
||||
) => {
|
||||
return (value: float) => object.setOpacity(value);
|
||||
};
|
||||
const getTweenObjectCharacterSizeSetter = (object: ICharacterScalable) => {
|
||||
return (value: float) => object.setCharacterSize(value);
|
||||
};
|
||||
const getTweenObjectNumberEffectPropertySetter = (
|
||||
effect: PixiFiltersTools.Filter,
|
||||
propertyName: string
|
||||
) => {
|
||||
return (value: float) => {
|
||||
effect.updateDoubleParameter(propertyName, value);
|
||||
};
|
||||
};
|
||||
const getTweenObjectColorEffectPropertySetter = (
|
||||
effect: PixiFiltersTools.Filter,
|
||||
propertyName: string
|
||||
) => {
|
||||
return ([hue, saturation, lightness]: number[]) => {
|
||||
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
|
||||
hue,
|
||||
saturation,
|
||||
lightness
|
||||
);
|
||||
effect.updateColorParameter(
|
||||
propertyName,
|
||||
gdjs.rgbToHexNumber(
|
||||
rgbFromHslColor[0],
|
||||
rgbFromHslColor[1],
|
||||
rgbFromHslColor[2]
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
const getTweenObjectColorSetter = (
|
||||
object: IColorable,
|
||||
useHSLColorTransition: boolean
|
||||
) => {
|
||||
if (useHSLColorTransition) {
|
||||
return ([hue, saturation, lightness]: number[]) => {
|
||||
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
|
||||
hue,
|
||||
saturation,
|
||||
lightness
|
||||
);
|
||||
object.setColor(
|
||||
Math.floor(rgbFromHslColor[0]) +
|
||||
';' +
|
||||
Math.floor(rgbFromHslColor[1]) +
|
||||
';' +
|
||||
Math.floor(rgbFromHslColor[2])
|
||||
);
|
||||
};
|
||||
} else {
|
||||
return ([red, green, blue]: number[]) => {
|
||||
object.setColor(
|
||||
Math.floor(red) + ';' + Math.floor(green) + ';' + Math.floor(blue)
|
||||
);
|
||||
};
|
||||
}
|
||||
};
|
||||
const getTweenObjectColorHSLSetter = (object: IColorable) => {
|
||||
return ([hue, saturation, lightness]: number[]) => {
|
||||
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
|
||||
hue,
|
||||
saturation,
|
||||
lightness
|
||||
);
|
||||
|
||||
object.setColor(
|
||||
Math.floor(rgbFromHslColor[0]) +
|
||||
';' +
|
||||
Math.floor(rgbFromHslColor[1]) +
|
||||
';' +
|
||||
Math.floor(rgbFromHslColor[2])
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const tweenSetterFactory =
|
||||
(object: RuntimeObject) =>
|
||||
(tweenInformation: TweenInformationNetworkSyncData) => {
|
||||
const type = tweenInformation.type;
|
||||
const variablePath = tweenInformation.variablePath;
|
||||
const effectName = tweenInformation.effectName;
|
||||
const propertyName = tweenInformation.propertyName;
|
||||
const scaleFromCenterOfObject =
|
||||
!!tweenInformation.scaleFromCenterOfObject;
|
||||
const useHSLColorTransition = !!tweenInformation.useHSLColorTransition;
|
||||
if (type === 'objectValue') {
|
||||
return tweenObjectValueSetter;
|
||||
}
|
||||
if (type === 'variable' && variablePath) {
|
||||
const variable = object
|
||||
.getVariables()
|
||||
.getVariableFromPath(variablePath);
|
||||
if (!variable) return () => {};
|
||||
return getTweenVariableSetter(variable);
|
||||
}
|
||||
if (type === 'positionX') {
|
||||
return getTweenObjectPositionXSetter(object);
|
||||
}
|
||||
if (type === 'positionY') {
|
||||
return getTweenObjectPositionYSetter(object);
|
||||
}
|
||||
if (type === 'position') {
|
||||
return getTweenObjectPositionSetter(object);
|
||||
}
|
||||
if (type === 'positionZ') {
|
||||
return getTweenObjectPositionZSetter(object);
|
||||
}
|
||||
if (type === 'width') {
|
||||
return getTweenObjectWidthSetter(object);
|
||||
}
|
||||
if (type === 'height') {
|
||||
return getTweenObjectHeightSetter(object);
|
||||
}
|
||||
if (type === 'depth') {
|
||||
if (!is3D(object)) return () => {};
|
||||
return getTweenObjectDepthSetter(object);
|
||||
}
|
||||
if (type === 'angle') {
|
||||
return getTweenObjectAngleSetter(object);
|
||||
}
|
||||
if (type === 'rotationX') {
|
||||
if (!is3D(object)) return () => {};
|
||||
return getTweenObjectRotationXSetter(object);
|
||||
}
|
||||
if (type === 'rotationY') {
|
||||
if (!is3D(object)) return () => {};
|
||||
return getTweenObjectRotationYSetter(object);
|
||||
}
|
||||
if (type === 'scale') {
|
||||
if (!isScalable(object)) return () => {};
|
||||
|
||||
const object3d = is3D(object) ? object : null;
|
||||
return getTweenObjectScaleSetter(
|
||||
object,
|
||||
object3d,
|
||||
scaleFromCenterOfObject
|
||||
);
|
||||
}
|
||||
if (type === 'scaleXY') {
|
||||
if (!isScalable(object)) return () => {};
|
||||
return getTweenObjectScaleXYSetter(object, scaleFromCenterOfObject);
|
||||
}
|
||||
if (type === 'scaleX') {
|
||||
if (!isScalable(object)) return () => {};
|
||||
return getTweenObjectScaleXSetter(object, scaleFromCenterOfObject);
|
||||
}
|
||||
if (type === 'scaleY') {
|
||||
if (!isScalable(object)) return () => {};
|
||||
return getTweenObjectScaleYSetter(object, scaleFromCenterOfObject);
|
||||
}
|
||||
if (type === 'opacity') {
|
||||
if (!isOpaque(object)) return () => {};
|
||||
return getTweenObjectOpacitySetter(object);
|
||||
}
|
||||
if (type === 'characterSize') {
|
||||
if (!isCharacterScalable(object)) return () => {};
|
||||
return getTweenObjectCharacterSizeSetter(object);
|
||||
}
|
||||
if (type === 'numberEffectProperty' && effectName && propertyName) {
|
||||
const effect = object.getRendererEffects()[effectName];
|
||||
if (!effect) {
|
||||
logger.error(
|
||||
`The object "${object.name}" doesn't have any effect called "${effectName}"`
|
||||
);
|
||||
}
|
||||
return getTweenObjectNumberEffectPropertySetter(effect, propertyName);
|
||||
}
|
||||
if (type === 'colorEffectProperty' && effectName && propertyName) {
|
||||
const effect = object.getRendererEffects()[effectName];
|
||||
if (!effect) {
|
||||
logger.error(
|
||||
`The object "${object.name}" doesn't have any effect called "${effectName}"`
|
||||
);
|
||||
}
|
||||
|
||||
return getTweenObjectColorEffectPropertySetter(effect, propertyName);
|
||||
}
|
||||
|
||||
if (type === 'objectColor') {
|
||||
if (!isColorable(object)) return () => {};
|
||||
|
||||
return getTweenObjectColorSetter(object, useHSLColorTransition);
|
||||
}
|
||||
if (type === 'objectColorHSL') {
|
||||
if (!isColorable(object)) return () => {};
|
||||
|
||||
return getTweenObjectColorHSLSetter(object);
|
||||
}
|
||||
|
||||
return () => {};
|
||||
};
|
||||
|
||||
export class TweenRuntimeBehavior extends gdjs.RuntimeBehavior {
|
||||
private _tweens = new gdjs.evtTools.tween.TweenManager();
|
||||
private _isActive: boolean = true;
|
||||
@@ -84,6 +392,43 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): TweenBehaviorNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
props: {
|
||||
tweenManager: this._tweens.getNetworkSyncData(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: TweenBehaviorNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
|
||||
if (networkSyncData.props.tweenManager) {
|
||||
this._tweens.updateFromNetworkSyncData(
|
||||
networkSyncData.props.tweenManager,
|
||||
(tweenInformationNetworkSyncData) => {
|
||||
return this.owner;
|
||||
},
|
||||
(tweenInformationNetworkSyncData) => {
|
||||
return tweenSetterFactory(this.owner)(
|
||||
tweenInformationNetworkSyncData
|
||||
);
|
||||
},
|
||||
(tweenInformationNetworkSyncData) => {
|
||||
return tweenInformationNetworkSyncData.destroyObjectWhenFinished
|
||||
? () => this._deleteFromScene()
|
||||
: null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
this._tweens.step();
|
||||
}
|
||||
@@ -126,7 +471,12 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
fromValue,
|
||||
toValue,
|
||||
(value: float) => variable.setNumber(value),
|
||||
getTweenVariableSetter(variable),
|
||||
{
|
||||
type: 'variable',
|
||||
variable,
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -208,7 +558,12 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
variable.getValue() as number,
|
||||
toValue,
|
||||
(value: float) => variable.setNumber(value),
|
||||
getTweenVariableSetter(variable),
|
||||
{
|
||||
type: 'variable',
|
||||
variable,
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -243,7 +598,11 @@ namespace gdjs {
|
||||
: linearInterpolation,
|
||||
fromValue,
|
||||
toValue,
|
||||
(value: float) => {},
|
||||
tweenObjectValueSetter,
|
||||
{
|
||||
type: 'objectValue',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -322,7 +681,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
[this.owner.getX(), this.owner.getY()],
|
||||
[toX, toY],
|
||||
([x, y]) => this.owner.setPosition(x, y),
|
||||
getTweenObjectPositionSetter(this.owner),
|
||||
{
|
||||
type: 'position',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -394,7 +757,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
this.owner.getX(),
|
||||
toX,
|
||||
(value: float) => this.owner.setX(value),
|
||||
getTweenObjectPositionXSetter(this.owner),
|
||||
{
|
||||
type: 'positionX',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -466,7 +833,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
this.owner.getY(),
|
||||
toY,
|
||||
(value: float) => this.owner.setY(value),
|
||||
getTweenObjectPositionYSetter(this.owner),
|
||||
{
|
||||
type: 'positionY',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -543,7 +914,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
owner.getZ(),
|
||||
toZ,
|
||||
(value: float) => owner.setZ(value),
|
||||
getTweenObjectPositionZSetter(owner),
|
||||
{
|
||||
type: 'positionZ',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -615,7 +990,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
this.owner.getAngle(),
|
||||
toAngle,
|
||||
(value: float) => this.owner.setAngle(value),
|
||||
getTweenObjectAngleSetter(this.owner),
|
||||
{
|
||||
type: 'angle',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -648,7 +1027,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
owner.getRotationX(),
|
||||
toAngle,
|
||||
(value: float) => owner.setRotationX(value),
|
||||
getTweenObjectRotationXSetter(owner),
|
||||
{
|
||||
type: 'rotationX',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -681,7 +1064,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
owner.getRotationY(),
|
||||
toAngle,
|
||||
(value: float) => owner.setRotationY(value),
|
||||
getTweenObjectRotationYSetter(owner),
|
||||
{
|
||||
type: 'rotationY',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -769,19 +1156,6 @@ namespace gdjs {
|
||||
if (toScaleX < 0) toScaleX = 0;
|
||||
if (toScaleY < 0) toScaleY = 0;
|
||||
|
||||
const setValue = scaleFromCenterOfObject
|
||||
? ([scaleX, scaleY]: float[]) => {
|
||||
const oldX = owner.getCenterXInScene();
|
||||
const oldY = owner.getCenterYInScene();
|
||||
owner.setScaleX(scaleX);
|
||||
owner.setScaleY(scaleY);
|
||||
owner.setCenterPositionInScene(oldX, oldY);
|
||||
}
|
||||
: ([scaleX, scaleY]: float[]) => {
|
||||
owner.setScaleX(scaleX);
|
||||
owner.setScaleY(scaleY);
|
||||
};
|
||||
|
||||
this._tweens.addMultiTween(
|
||||
identifier,
|
||||
timeSource,
|
||||
@@ -790,7 +1164,11 @@ namespace gdjs {
|
||||
interpolation,
|
||||
[owner.getScaleX(), owner.getScaleY()],
|
||||
[toScaleX, toScaleY],
|
||||
setValue,
|
||||
getTweenObjectScaleXYSetter(owner, scaleFromCenterOfObject),
|
||||
{
|
||||
type: 'scaleXY',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -830,20 +1208,6 @@ namespace gdjs {
|
||||
// when the 3D extension is not used.
|
||||
const owner3d = is3D(owner) ? owner : null;
|
||||
|
||||
const setValue = scaleFromCenterOfObject
|
||||
? (scale: float) => {
|
||||
const oldX = owner.getCenterXInScene();
|
||||
const oldY = owner.getCenterYInScene();
|
||||
const oldZ = owner3d ? owner3d.getCenterZInScene() : 0;
|
||||
owner.setScale(scale);
|
||||
owner.setCenterXInScene(oldX);
|
||||
owner.setCenterYInScene(oldY);
|
||||
if (owner3d) {
|
||||
owner3d.setCenterZInScene(oldZ);
|
||||
}
|
||||
}
|
||||
: (scale: float) => owner.setScale(scale);
|
||||
|
||||
this._tweens.addSimpleTween(
|
||||
identifier,
|
||||
this.owner,
|
||||
@@ -852,7 +1216,12 @@ namespace gdjs {
|
||||
exponentialInterpolation,
|
||||
owner.getScale(),
|
||||
toScale,
|
||||
setValue,
|
||||
getTweenObjectScaleSetter(owner, owner3d, scaleFromCenterOfObject),
|
||||
{
|
||||
type: 'scale',
|
||||
scaleFromCenterOfObject,
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -929,14 +1298,6 @@ namespace gdjs {
|
||||
const owner = this.owner;
|
||||
if (!isScalable(owner)) return;
|
||||
|
||||
const setValue = scaleFromCenterOfObject
|
||||
? (scaleX: float) => {
|
||||
const oldX = owner.getCenterXInScene();
|
||||
owner.setScaleX(scaleX);
|
||||
owner.setCenterXInScene(oldX);
|
||||
}
|
||||
: (scaleX: float) => owner.setScaleX(scaleX);
|
||||
|
||||
this._tweens.addSimpleTween(
|
||||
identifier,
|
||||
timeSource,
|
||||
@@ -945,7 +1306,12 @@ namespace gdjs {
|
||||
interpolation,
|
||||
owner.getScaleX(),
|
||||
toScaleX,
|
||||
setValue,
|
||||
getTweenObjectScaleXSetter(owner, scaleFromCenterOfObject),
|
||||
{
|
||||
type: 'scaleX',
|
||||
scaleFromCenterOfObject,
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -1022,14 +1388,6 @@ namespace gdjs {
|
||||
const owner = this.owner;
|
||||
if (!isScalable(owner)) return;
|
||||
|
||||
const setValue = scaleFromCenterOfObject
|
||||
? (scaleY: float) => {
|
||||
const oldY = owner.getCenterYInScene();
|
||||
owner.setScaleY(scaleY);
|
||||
owner.setCenterYInScene(oldY);
|
||||
}
|
||||
: (scaleY: float) => owner.setScaleY(scaleY);
|
||||
|
||||
this._tweens.addSimpleTween(
|
||||
identifier,
|
||||
timeSource,
|
||||
@@ -1038,7 +1396,12 @@ namespace gdjs {
|
||||
interpolation,
|
||||
owner.getScaleY(),
|
||||
toScaleY,
|
||||
setValue,
|
||||
getTweenObjectScaleYSetter(owner, scaleFromCenterOfObject),
|
||||
{
|
||||
type: 'scaleY',
|
||||
scaleFromCenterOfObject,
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -1113,7 +1476,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
owner.getOpacity(),
|
||||
toOpacity,
|
||||
(value: float) => owner.setOpacity(value),
|
||||
getTweenObjectOpacitySetter(owner),
|
||||
{
|
||||
type: 'opacity',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -1153,10 +1520,12 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
effect ? effect.getDoubleParameter(propertyName) : 0,
|
||||
toValue,
|
||||
(value: float) => {
|
||||
if (effect) {
|
||||
effect.updateDoubleParameter(propertyName, value);
|
||||
}
|
||||
getTweenObjectNumberEffectPropertySetter(effect, propertyName),
|
||||
{
|
||||
type: 'numberEffectProperty',
|
||||
effectName,
|
||||
propertyName,
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
@@ -1210,22 +1579,12 @@ namespace gdjs {
|
||||
rgbToColor[1],
|
||||
rgbToColor[2]
|
||||
),
|
||||
([hue, saturation, lightness]) => {
|
||||
if (effect) {
|
||||
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
|
||||
hue,
|
||||
saturation,
|
||||
lightness
|
||||
);
|
||||
effect.updateColorParameter(
|
||||
propertyName,
|
||||
gdjs.rgbToHexNumber(
|
||||
rgbFromHslColor[0],
|
||||
rgbFromHslColor[1],
|
||||
rgbFromHslColor[2]
|
||||
)
|
||||
);
|
||||
}
|
||||
getTweenObjectColorEffectPropertySetter(effect, propertyName),
|
||||
{
|
||||
type: 'colorEffectProperty',
|
||||
effectName,
|
||||
propertyName,
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
@@ -1305,43 +1664,20 @@ namespace gdjs {
|
||||
const rgbFromColor: float[] = gdjs.rgbOrHexToRGBColor(owner.getColor());
|
||||
const rgbToColor: float[] = gdjs.rgbOrHexToRGBColor(toColorStr);
|
||||
|
||||
let initialValue;
|
||||
let targetedValue;
|
||||
let setValue;
|
||||
if (useHSLColorTransition) {
|
||||
initialValue = gdjs.evtTools.tween.rgbToHsl(
|
||||
rgbFromColor[0],
|
||||
rgbFromColor[1],
|
||||
rgbFromColor[2]
|
||||
);
|
||||
targetedValue = gdjs.evtTools.tween.rgbToHsl(
|
||||
rgbToColor[0],
|
||||
rgbToColor[1],
|
||||
rgbToColor[2]
|
||||
);
|
||||
setValue = ([hue, saturation, lightness]) => {
|
||||
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
|
||||
hue,
|
||||
saturation,
|
||||
lightness
|
||||
);
|
||||
owner.setColor(
|
||||
Math.round(rgbFromHslColor[0]) +
|
||||
';' +
|
||||
Math.round(rgbFromHslColor[1]) +
|
||||
';' +
|
||||
Math.round(rgbFromHslColor[2])
|
||||
);
|
||||
};
|
||||
} else {
|
||||
initialValue = rgbFromColor;
|
||||
targetedValue = rgbToColor;
|
||||
setValue = ([red, green, blue]) => {
|
||||
owner.setColor(
|
||||
Math.floor(red) + ';' + Math.floor(green) + ';' + Math.floor(blue)
|
||||
);
|
||||
};
|
||||
}
|
||||
const initialValue = useHSLColorTransition
|
||||
? gdjs.evtTools.tween.rgbToHsl(
|
||||
rgbFromColor[0],
|
||||
rgbFromColor[1],
|
||||
rgbFromColor[2]
|
||||
)
|
||||
: rgbFromColor;
|
||||
const targetedValue = useHSLColorTransition
|
||||
? gdjs.evtTools.tween.rgbToHsl(
|
||||
rgbToColor[0],
|
||||
rgbToColor[1],
|
||||
rgbToColor[2]
|
||||
)
|
||||
: rgbToColor;
|
||||
|
||||
this._tweens.addMultiTween(
|
||||
identifier,
|
||||
@@ -1351,7 +1687,12 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
initialValue,
|
||||
targetedValue,
|
||||
setValue,
|
||||
getTweenObjectColorSetter(owner, useHSLColorTransition),
|
||||
{
|
||||
type: 'objectColor',
|
||||
useHSLColorTransition,
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -1462,25 +1803,13 @@ namespace gdjs {
|
||||
duration,
|
||||
easing,
|
||||
linearInterpolation,
|
||||
|
||||
hslFromColor,
|
||||
[toH, toS, toL],
|
||||
([hue, saturation, lightness]) => {
|
||||
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
|
||||
hue,
|
||||
saturation,
|
||||
lightness
|
||||
);
|
||||
|
||||
owner.setColor(
|
||||
Math.round(rgbFromHslColor[0]) +
|
||||
';' +
|
||||
Math.round(rgbFromHslColor[1]) +
|
||||
';' +
|
||||
Math.round(rgbFromHslColor[2])
|
||||
);
|
||||
getTweenObjectColorHSLSetter(owner),
|
||||
{
|
||||
type: 'objectColorHSL',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -1558,7 +1887,11 @@ namespace gdjs {
|
||||
interpolation,
|
||||
owner.getCharacterSize(),
|
||||
toSize,
|
||||
(value: float) => owner.setCharacterSize(value),
|
||||
getTweenObjectCharacterSizeSetter(owner),
|
||||
{
|
||||
type: 'characterSize',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -1630,7 +1963,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
this.owner.getWidth(),
|
||||
toWidth,
|
||||
(value: float) => this.owner.setWidth(value),
|
||||
getTweenObjectWidthSetter(this.owner),
|
||||
{
|
||||
type: 'width',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -1702,7 +2039,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
this.owner.getHeight(),
|
||||
toHeight,
|
||||
(value: float) => this.owner.setHeight(value),
|
||||
getTweenObjectHeightSetter(this.owner),
|
||||
{
|
||||
type: 'height',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
@@ -1779,7 +2120,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
owner.getDepth(),
|
||||
toDepth,
|
||||
(value: float) => owner.setDepth(value),
|
||||
getTweenObjectDepthSetter(owner),
|
||||
{
|
||||
type: 'depth',
|
||||
destroyObjectWhenFinished,
|
||||
},
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
|
@@ -10,6 +10,127 @@ namespace gdjs {
|
||||
export namespace tween {
|
||||
const logger = new gdjs.Logger('Tween');
|
||||
|
||||
const getTweenVariableSetter = (variable: gdjs.Variable) => {
|
||||
return (value: float) => variable.setNumber(value);
|
||||
};
|
||||
const tweenLayoutValueSetter = (value: float) => {};
|
||||
const tweenLayerValueSetter = (value: float) => {};
|
||||
const getTweenLayerCameraPositionSetter = (layer: gdjs.RuntimeLayer) => {
|
||||
return ([x, y]: Array<float>) => {
|
||||
layer.setCameraX(x);
|
||||
layer.setCameraY(y);
|
||||
};
|
||||
};
|
||||
const getTweenLayerCameraRotationSetter = (layer: gdjs.RuntimeLayer) => {
|
||||
return (value: float) => layer.setCameraRotation(value);
|
||||
};
|
||||
const getTweenLayerCameraZoomSetter = (layer: gdjs.RuntimeLayer) => {
|
||||
return (value: float) => layer.setCameraZoom(value);
|
||||
};
|
||||
const getTweenNumberEffectPropertySetter = (
|
||||
effect: PixiFiltersTools.Filter,
|
||||
propertyName: string
|
||||
) => {
|
||||
return (value: float) => {
|
||||
if (effect) {
|
||||
effect.updateDoubleParameter(propertyName, value);
|
||||
}
|
||||
};
|
||||
};
|
||||
const getTweenColorEffectPropertySetter = (
|
||||
effect: PixiFiltersTools.Filter,
|
||||
propertyName: string
|
||||
) => {
|
||||
return ([hue, saturation, lightness]: Array<float>) => {
|
||||
if (effect) {
|
||||
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
|
||||
hue,
|
||||
saturation,
|
||||
lightness
|
||||
);
|
||||
effect.updateColorParameter(
|
||||
propertyName,
|
||||
gdjs.rgbToHexNumber(
|
||||
rgbFromHslColor[0],
|
||||
rgbFromHslColor[1],
|
||||
rgbFromHslColor[2]
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Factory to get the tween setter based on type and options
|
||||
export const tweenSetterFactory =
|
||||
(runtimeScene: RuntimeScene) =>
|
||||
(tweenInformation: TweenInformationNetworkSyncData) => {
|
||||
const type = tweenInformation.type;
|
||||
const layerName = tweenInformation.layerName;
|
||||
const variablePath = tweenInformation.variablePath;
|
||||
const effectName = tweenInformation.effectName;
|
||||
const propertyName = tweenInformation.propertyName;
|
||||
|
||||
if (type === 'variable' && variablePath) {
|
||||
const variable = runtimeScene
|
||||
.getVariables()
|
||||
.getVariableFromPath(variablePath);
|
||||
if (!variable) {
|
||||
return () => {};
|
||||
}
|
||||
return getTweenVariableSetter(variable);
|
||||
}
|
||||
if (type === 'cameraZoom' && layerName !== undefined) {
|
||||
const layer = runtimeScene.getLayer(layerName);
|
||||
return getTweenLayerCameraZoomSetter(layer);
|
||||
}
|
||||
if (type === 'cameraRotation' && layerName !== undefined) {
|
||||
const layer = runtimeScene.getLayer(layerName);
|
||||
return getTweenLayerCameraRotationSetter(layer);
|
||||
}
|
||||
if (type === 'cameraPosition' && layerName !== undefined) {
|
||||
const layer = runtimeScene.getLayer(layerName);
|
||||
return getTweenLayerCameraPositionSetter(layer);
|
||||
}
|
||||
if (
|
||||
type === 'colorEffectProperty' &&
|
||||
layerName !== undefined &&
|
||||
effectName &&
|
||||
propertyName
|
||||
) {
|
||||
const layer = runtimeScene.getLayer(layerName);
|
||||
const effect = layer.getRendererEffects()[effectName];
|
||||
if (!effect) {
|
||||
logger.error(
|
||||
`The layer "${layerName}" doesn't have any effect called "${effectName}"`
|
||||
);
|
||||
}
|
||||
|
||||
return getTweenColorEffectPropertySetter(effect, propertyName);
|
||||
}
|
||||
if (
|
||||
type === 'numberEffectProperty' &&
|
||||
layerName !== undefined &&
|
||||
effectName &&
|
||||
propertyName
|
||||
) {
|
||||
const layer = runtimeScene.getLayer(layerName);
|
||||
const effect = layer.getRendererEffects()[effectName];
|
||||
if (!effect) {
|
||||
logger.error(
|
||||
`The layer "${layerName}" doesn't have any effect called "${effectName}"`
|
||||
);
|
||||
}
|
||||
return getTweenNumberEffectPropertySetter(effect, propertyName);
|
||||
}
|
||||
if (type === 'layoutValue') {
|
||||
return tweenLayoutValueSetter;
|
||||
}
|
||||
if (type === 'layerValue') {
|
||||
return tweenLayerValueSetter;
|
||||
}
|
||||
return () => {};
|
||||
};
|
||||
|
||||
export const getTweensMap = (runtimeScene: RuntimeScene) =>
|
||||
runtimeScene._tweens ||
|
||||
(runtimeScene._tweens = new gdjs.evtTools.tween.TweenManager());
|
||||
@@ -20,6 +141,44 @@ namespace gdjs {
|
||||
gdjs.evtTools.tween.getTweensMap(runtimeScene).step();
|
||||
});
|
||||
|
||||
gdjs.registerRuntimeSceneGetSyncDataCallback(
|
||||
function (runtimeScene, currentLayoutSyncData, syncOptions) {
|
||||
if (!syncOptions.syncTweens) return;
|
||||
const tweensNetworkSyncData = gdjs.evtTools.tween
|
||||
.getTweensMap(runtimeScene)
|
||||
.getNetworkSyncData();
|
||||
|
||||
currentLayoutSyncData.tween = tweensNetworkSyncData;
|
||||
}
|
||||
);
|
||||
|
||||
gdjs.registerRuntimeSceneUpdateFromSyncDataCallback(
|
||||
function (runtimeScene, receivedSyncData, syncOptions) {
|
||||
if (!receivedSyncData.tween) return;
|
||||
|
||||
gdjs.evtTools.tween
|
||||
.getTweensMap(runtimeScene)
|
||||
.updateFromNetworkSyncData(
|
||||
receivedSyncData.tween,
|
||||
(tweenInformationNetworkSyncData) => {
|
||||
if (tweenInformationNetworkSyncData.layerName !== undefined) {
|
||||
return runtimeScene.getLayer(
|
||||
tweenInformationNetworkSyncData.layerName
|
||||
);
|
||||
}
|
||||
return runtimeScene;
|
||||
},
|
||||
(tweenInformationNetworkSyncData) => {
|
||||
return gdjs.evtTools.tween.tweenSetterFactory(runtimeScene)(
|
||||
tweenInformationNetworkSyncData
|
||||
);
|
||||
},
|
||||
// No onFinish for scene tweens.
|
||||
() => null
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const sceneTweenExists = (
|
||||
runtimeScene: RuntimeScene,
|
||||
id: string
|
||||
@@ -130,7 +289,10 @@ namespace gdjs {
|
||||
: linearInterpolation,
|
||||
fromValue,
|
||||
toValue,
|
||||
(value: float) => {}
|
||||
tweenLayoutValueSetter,
|
||||
{
|
||||
type: 'layoutValue',
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -167,7 +329,11 @@ namespace gdjs {
|
||||
: linearInterpolation,
|
||||
fromValue,
|
||||
toValue,
|
||||
(value: float) => {}
|
||||
tweenLayerValueSetter,
|
||||
{
|
||||
type: 'layerValue',
|
||||
layerName,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -197,7 +363,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
from,
|
||||
to,
|
||||
(value: float) => variable.setNumber(value)
|
||||
getTweenVariableSetter(variable),
|
||||
{
|
||||
type: 'variable',
|
||||
variable,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -250,7 +420,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
variable.getValue() as number,
|
||||
toValue,
|
||||
(value: float) => variable.setNumber(value)
|
||||
getTweenVariableSetter(variable),
|
||||
{
|
||||
type: 'variable',
|
||||
variable,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -329,9 +503,10 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
[layer.getCameraX(), layer.getCameraY()],
|
||||
[toX, toY],
|
||||
([x, y]) => {
|
||||
layer.setCameraX(x);
|
||||
layer.setCameraY(y);
|
||||
getTweenLayerCameraPositionSetter(layer),
|
||||
{
|
||||
type: 'cameraPosition',
|
||||
layerName,
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -408,7 +583,11 @@ namespace gdjs {
|
||||
interpolation,
|
||||
layer.getCameraZoom(),
|
||||
toZoom,
|
||||
(value: float) => layer.setCameraZoom(value)
|
||||
getTweenLayerCameraZoomSetter(layer),
|
||||
{
|
||||
type: 'cameraZoom',
|
||||
layerName,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -478,7 +657,11 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
layer.getCameraRotation(),
|
||||
toRotation,
|
||||
(value: float) => layer.setCameraRotation(value)
|
||||
getTweenLayerCameraRotationSetter(layer),
|
||||
{
|
||||
type: 'cameraRotation',
|
||||
layerName,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -518,10 +701,12 @@ namespace gdjs {
|
||||
linearInterpolation,
|
||||
effect ? effect.getDoubleParameter(propertyName) : 0,
|
||||
toValue,
|
||||
(value: float) => {
|
||||
if (effect) {
|
||||
effect.updateDoubleParameter(propertyName, value);
|
||||
}
|
||||
getTweenNumberEffectPropertySetter(effect, propertyName),
|
||||
{
|
||||
type: 'numberEffectProperty',
|
||||
layerName,
|
||||
effectName,
|
||||
propertyName,
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -575,22 +760,12 @@ namespace gdjs {
|
||||
rgbToColor[1],
|
||||
rgbToColor[2]
|
||||
),
|
||||
([hue, saturation, lightness]) => {
|
||||
if (effect) {
|
||||
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
|
||||
hue,
|
||||
saturation,
|
||||
lightness
|
||||
);
|
||||
effect.updateColorParameter(
|
||||
propertyName,
|
||||
gdjs.rgbToHexNumber(
|
||||
rgbFromHslColor[0],
|
||||
rgbFromHslColor[1],
|
||||
rgbFromHslColor[2]
|
||||
)
|
||||
);
|
||||
}
|
||||
getTweenColorEffectPropertySetter(effect, propertyName),
|
||||
{
|
||||
type: 'colorEffectProperty',
|
||||
layerName,
|
||||
effectName,
|
||||
propertyName,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@@ -16,7 +16,7 @@ namespace gdjs {
|
||||
|
||||
export type VideoObjectData = ObjectData & VideoObjectDataType;
|
||||
|
||||
export type VideoNetworkSyncDataType = {
|
||||
export type VideoObjectNetworkSyncDataType = {
|
||||
op: float;
|
||||
// We don't sync volume, as it's probably a user setting?
|
||||
pla: boolean;
|
||||
@@ -25,8 +25,8 @@ namespace gdjs {
|
||||
ps: number;
|
||||
};
|
||||
|
||||
export type VideoNetworkSyncData = ObjectNetworkSyncData &
|
||||
VideoNetworkSyncDataType;
|
||||
export type VideoObjectNetworkSyncData = ObjectNetworkSyncData &
|
||||
VideoObjectNetworkSyncDataType;
|
||||
|
||||
/**
|
||||
* An object displaying a video on screen.
|
||||
@@ -99,9 +99,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): VideoNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): VideoObjectNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
op: this._opacity,
|
||||
pla: this.isPlayed(),
|
||||
loop: this.isLooped(),
|
||||
@@ -110,8 +112,11 @@ namespace gdjs {
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(syncData: VideoNetworkSyncData): void {
|
||||
super.updateFromNetworkSyncData(syncData);
|
||||
updateFromNetworkSyncData(
|
||||
syncData: VideoObjectNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(syncData, options);
|
||||
|
||||
if (this._opacity !== undefined && this._opacity && syncData.op) {
|
||||
this.setOpacity(syncData.op);
|
||||
|
@@ -74,10 +74,15 @@ gd::String EventsCodeGenerator::GenerateEventsListCompleteFunctionCode(
|
||||
codeGenerator.GetCodeNamespace() + ".localVariables = [];\n";
|
||||
}
|
||||
|
||||
gd::String idToCallbackMapCode;
|
||||
idToCallbackMapCode +=
|
||||
codeGenerator.GetCodeNamespace() + ".idToCallbackMap = new Map();\n";
|
||||
|
||||
gd::String output =
|
||||
// clang-format off
|
||||
codeGenerator.GetCodeNamespace() + " = {};\n" +
|
||||
localVariablesInitializationCode +
|
||||
idToCallbackMapCode +
|
||||
globalDeclarations +
|
||||
globalObjectLists + "\n\n" +
|
||||
codeGenerator.GetCustomCodeOutsideMain() + "\n\n" +
|
||||
@@ -135,8 +140,11 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode(
|
||||
gd::VariablesContainer::SourceType::Parameters);
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::
|
||||
MakeNewProjectScopedContainersForFreeEventsFunction(
|
||||
project, eventsFunctionsExtension, eventsFunction,
|
||||
parameterObjectsAndGroups, parameterVariablesContainer);
|
||||
project,
|
||||
eventsFunctionsExtension,
|
||||
eventsFunction,
|
||||
parameterObjectsAndGroups,
|
||||
parameterVariablesContainer);
|
||||
|
||||
EventsCodeGenerator codeGenerator(projectScopedContainers);
|
||||
codeGenerator.SetCodeNamespace(codeNamespace);
|
||||
@@ -148,16 +156,21 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode(
|
||||
// Generate the code setting up the context of the function.
|
||||
gd::String fullPreludeCode = "let scopeInstanceContainer = null;\n" +
|
||||
codeGenerator.GenerateFreeEventsFunctionContext(
|
||||
eventsFunctionsExtension, eventsFunction,
|
||||
eventsFunctionsExtension,
|
||||
eventsFunction,
|
||||
"runtimeScene.getOnceTriggers()");
|
||||
|
||||
gd::String output = GenerateEventsListCompleteFunctionCode(
|
||||
codeGenerator, codeGenerator.GetCodeNamespaceAccessor() + "func",
|
||||
codeGenerator,
|
||||
codeGenerator.GetCodeNamespaceAccessor() + "func",
|
||||
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
|
||||
eventsFunction.GetParametersForEvents(
|
||||
eventsFunctionsExtension.GetEventsFunctions()),
|
||||
0, true),
|
||||
fullPreludeCode, eventsFunction.GetEvents(), "",
|
||||
0,
|
||||
true),
|
||||
fullPreludeCode,
|
||||
eventsFunction.GetEvents(),
|
||||
"",
|
||||
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
|
||||
|
||||
// TODO: the editor should pass the diagnostic report and display it to the
|
||||
@@ -193,9 +206,13 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
|
||||
gd::VariablesContainer::SourceType::Properties);
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::
|
||||
MakeNewProjectScopedContainersForBehaviorEventsFunction(
|
||||
project, eventsFunctionsExtension, eventsBasedBehavior,
|
||||
eventsFunction, parameterObjectsContainers,
|
||||
parameterVariablesContainer, propertyVariablesContainer);
|
||||
project,
|
||||
eventsFunctionsExtension,
|
||||
eventsBasedBehavior,
|
||||
eventsFunction,
|
||||
parameterObjectsContainers,
|
||||
parameterVariablesContainer,
|
||||
propertyVariablesContainer);
|
||||
|
||||
EventsCodeGenerator codeGenerator(projectScopedContainers);
|
||||
codeGenerator.SetCodeNamespace(codeNamespace);
|
||||
@@ -209,8 +226,9 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
|
||||
preludeCode + "\n" + "var that = this;\n" +
|
||||
// runtimeScene is supposed to be always accessible, read
|
||||
// it from the behavior.
|
||||
// TODO: this should be renamed to "instanceContainer" and have the code generation
|
||||
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
|
||||
// TODO: this should be renamed to "instanceContainer" and have the code
|
||||
// generation adapted for this (rely less on `gdjs.RuntimeScene`, and more
|
||||
// on `RuntimeInstanceContainer`).
|
||||
"var runtimeScene = this._runtimeScene;\n" +
|
||||
"let scopeInstanceContainer = null;\n" +
|
||||
// By convention of Behavior Events Function, the object is accessible
|
||||
@@ -279,8 +297,12 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode(
|
||||
gd::VariablesContainer::SourceType::Properties);
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::
|
||||
MakeNewProjectScopedContainersForObjectEventsFunction(
|
||||
project, eventsFunctionsExtension, eventsBasedObject, eventsFunction,
|
||||
parameterObjectsContainers, parameterVariablesContainer,
|
||||
project,
|
||||
eventsFunctionsExtension,
|
||||
eventsBasedObject,
|
||||
eventsFunction,
|
||||
parameterObjectsContainers,
|
||||
parameterVariablesContainer,
|
||||
propertyVariablesContainer);
|
||||
|
||||
EventsCodeGenerator codeGenerator(projectScopedContainers);
|
||||
@@ -295,8 +317,9 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode(
|
||||
preludeCode + "\n" + "var that = this;\n" +
|
||||
// runtimeScene is supposed to be always accessible, read
|
||||
// it from the object.
|
||||
// TODO: this should be renamed to "instanceContainer" and have the code generation
|
||||
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
|
||||
// TODO: this should be renamed to "instanceContainer" and have the code
|
||||
// generation adapted for this (rely less on `gdjs.RuntimeScene`, and more
|
||||
// on `RuntimeInstanceContainer`).
|
||||
"var runtimeScene = this._instanceContainer;\n" +
|
||||
"let scopeInstanceContainer = this._instanceContainer;\n" +
|
||||
// By convention of Object Events Function, the object is accessible
|
||||
@@ -388,13 +411,14 @@ gd::String EventsCodeGenerator::GenerateFreeEventsFunctionContext(
|
||||
gd::String objectsGettersMap;
|
||||
gd::String objectArraysMap;
|
||||
gd::String behaviorNamesMap;
|
||||
return GenerateEventsFunctionContext(eventsFunctionsExtension,
|
||||
eventsFunctionsExtension.GetEventsFunctions(),
|
||||
eventsFunction,
|
||||
onceTriggersVariable,
|
||||
objectsGettersMap,
|
||||
objectArraysMap,
|
||||
behaviorNamesMap);
|
||||
return GenerateEventsFunctionContext(
|
||||
eventsFunctionsExtension,
|
||||
eventsFunctionsExtension.GetEventsFunctions(),
|
||||
eventsFunction,
|
||||
onceTriggersVariable,
|
||||
objectsGettersMap,
|
||||
objectArraysMap,
|
||||
behaviorNamesMap);
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionContext(
|
||||
@@ -929,7 +953,8 @@ gd::String EventsCodeGenerator::GenerateObjectAction(
|
||||
const std::vector<gd::String>& arguments,
|
||||
const gd::InstructionMetadata& instrInfos,
|
||||
gd::EventsCodeGenerationContext& context,
|
||||
const gd::String& optionalAsyncCallbackName) {
|
||||
const gd::String& optionalAsyncCallbackName,
|
||||
const gd::String& optionalAsyncCallbackId) {
|
||||
gd::String actionCode;
|
||||
|
||||
// Prepare call
|
||||
@@ -978,10 +1003,11 @@ gd::String EventsCodeGenerator::GenerateObjectAction(
|
||||
actionCode += " " + call + ";\n";
|
||||
actionCode += "}\n";
|
||||
|
||||
if (!optionalAsyncCallbackName.empty()) {
|
||||
if (!optionalAsyncCallbackName.empty() && !optionalAsyncCallbackId.empty()) {
|
||||
actionCode +=
|
||||
"runtimeScene.getAsyncTasksManager().addTask(asyncTaskGroup, " +
|
||||
optionalAsyncCallbackName + ")\n}";
|
||||
optionalAsyncCallbackName + ", " + optionalAsyncCallbackId +
|
||||
", asyncObjectsList)\n}";
|
||||
}
|
||||
|
||||
return actionCode;
|
||||
@@ -995,7 +1021,8 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
|
||||
const std::vector<gd::String>& arguments,
|
||||
const gd::InstructionMetadata& instrInfos,
|
||||
gd::EventsCodeGenerationContext& context,
|
||||
const gd::String& optionalAsyncCallbackName) {
|
||||
const gd::String& optionalAsyncCallbackName,
|
||||
const gd::String& optionalAsyncCallbackId) {
|
||||
gd::String actionCode;
|
||||
|
||||
// Prepare call
|
||||
@@ -1055,10 +1082,12 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
|
||||
actionCode += " " + call + ";\n";
|
||||
actionCode += "}\n";
|
||||
|
||||
if (!optionalAsyncCallbackName.empty()) {
|
||||
if (!optionalAsyncCallbackName.empty() &&
|
||||
!optionalAsyncCallbackId.empty()) {
|
||||
actionCode +=
|
||||
"runtimeScene.getAsyncTasksManager().addTask(asyncTaskGroup, " +
|
||||
optionalAsyncCallbackName + ");\n };";
|
||||
optionalAsyncCallbackName + ", " + optionalAsyncCallbackId +
|
||||
", asyncObjectsList);\n };";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1377,17 +1406,16 @@ gd::String EventsCodeGenerator::GenerateGetVariable(
|
||||
scope == VARIABLE_OR_PROPERTY_OR_PARAMETER) {
|
||||
const auto variablesContainersList =
|
||||
GetProjectScopedContainers().GetVariablesContainersList();
|
||||
const auto &variablesContainer =
|
||||
const auto& variablesContainer =
|
||||
scope == VARIABLE_OR_PROPERTY_OR_PARAMETER
|
||||
? variablesContainersList.GetVariablesContainerFromVariableOrPropertyOrParameterName(
|
||||
variableName)
|
||||
? variablesContainersList
|
||||
.GetVariablesContainerFromVariableOrPropertyOrParameterName(
|
||||
variableName)
|
||||
: scope == VARIABLE_OR_PROPERTY
|
||||
? variablesContainersList
|
||||
.GetVariablesContainerFromVariableOrPropertyName(
|
||||
variableName)
|
||||
: variablesContainersList
|
||||
.GetVariablesContainerFromVariableNameOnly(
|
||||
variableName);
|
||||
.GetVariablesContainerFromVariableOrPropertyName(variableName)
|
||||
: variablesContainersList.GetVariablesContainerFromVariableNameOnly(
|
||||
variableName);
|
||||
const auto sourceType = variablesContainer.GetSourceType();
|
||||
if (sourceType == gd::VariablesContainer::SourceType::Scene) {
|
||||
variables = &variablesContainer;
|
||||
@@ -1410,48 +1438,50 @@ gd::String EventsCodeGenerator::GenerateGetVariable(
|
||||
gd::VariablesContainer::SourceType::ExtensionScene) {
|
||||
variables = &variablesContainer;
|
||||
output = "eventsFunctionContext.sceneVariablesForExtension";
|
||||
} else if (sourceType ==
|
||||
gd::VariablesContainer::SourceType::Properties) {
|
||||
} else if (sourceType == gd::VariablesContainer::SourceType::Properties) {
|
||||
if (hasChild) {
|
||||
// Properties with children are not supported.
|
||||
return "gdjs.VariablesContainer.badVariablesContainer";
|
||||
}
|
||||
const auto &propertiesContainersList =
|
||||
const auto& propertiesContainersList =
|
||||
GetProjectScopedContainers().GetPropertiesContainersList();
|
||||
const auto &propertiesContainerAndProperty =
|
||||
const auto& propertiesContainerAndProperty =
|
||||
propertiesContainersList.Get(variableName);
|
||||
return GeneratePropertyGetterWithoutCasting(
|
||||
propertiesContainerAndProperty.first,
|
||||
propertiesContainerAndProperty.second);
|
||||
} else if (sourceType ==
|
||||
gd::VariablesContainer::SourceType::Parameters) {
|
||||
} else if (sourceType == gd::VariablesContainer::SourceType::Parameters) {
|
||||
if (hasChild) {
|
||||
// Parameters with children are not supported.
|
||||
return "gdjs.VariablesContainer.badVariablesContainer";
|
||||
}
|
||||
const auto ¶metersVectorsList =
|
||||
const auto& parametersVectorsList =
|
||||
GetProjectScopedContainers().GetParametersVectorsList();
|
||||
const auto ¶meter =
|
||||
const auto& parameter =
|
||||
gd::ParameterMetadataTools::Get(parametersVectorsList, variableName);
|
||||
return GenerateParameterGetterWithoutCasting(parameter);
|
||||
}
|
||||
} else if (scope == LAYOUT_VARIABLE) {
|
||||
output = "runtimeScene.getScene().getVariables()";
|
||||
|
||||
const auto *legacySceneVariables = GetProjectScopedContainers().GetLegacySceneVariables();
|
||||
const auto* legacySceneVariables =
|
||||
GetProjectScopedContainers().GetLegacySceneVariables();
|
||||
if (HasProjectAndLayout()) {
|
||||
variables = &GetLayout().GetVariables();
|
||||
} else if (legacySceneVariables && legacySceneVariables->Has(variableName)) {
|
||||
} else if (legacySceneVariables &&
|
||||
legacySceneVariables->Has(variableName)) {
|
||||
variables = legacySceneVariables;
|
||||
output = "eventsFunctionContext.sceneVariablesForExtension";
|
||||
}
|
||||
} else if (scope == PROJECT_VARIABLE) {
|
||||
output = "runtimeScene.getGame().getVariables()";
|
||||
|
||||
const auto *legacyGlobalVariables = GetProjectScopedContainers().GetLegacyGlobalVariables();
|
||||
const auto* legacyGlobalVariables =
|
||||
GetProjectScopedContainers().GetLegacyGlobalVariables();
|
||||
if (HasProjectAndLayout()) {
|
||||
variables = &GetProject().GetVariables();
|
||||
} else if (legacyGlobalVariables && legacyGlobalVariables->Has(variableName)) {
|
||||
} else if (legacyGlobalVariables &&
|
||||
legacyGlobalVariables->Has(variableName)) {
|
||||
variables = legacyGlobalVariables;
|
||||
output = "eventsFunctionContext.globalVariablesForExtension";
|
||||
}
|
||||
@@ -1543,9 +1573,9 @@ gd::String EventsCodeGenerator::GenerateProfilerSectionEnd(
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GeneratePropertySetterWithoutCasting(
|
||||
const gd::PropertiesContainer &propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor &property,
|
||||
const gd::String &operandCode) {
|
||||
const gd::PropertiesContainer& propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor& property,
|
||||
const gd::String& operandCode) {
|
||||
bool isLocalProperty =
|
||||
projectScopedContainers.GetPropertiesContainersList()
|
||||
.GetBottomMostPropertiesContainer() == &propertiesContainer;
|
||||
@@ -1571,8 +1601,8 @@ gd::String EventsCodeGenerator::GeneratePropertySetterWithoutCasting(
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GeneratePropertyGetterWithoutCasting(
|
||||
const gd::PropertiesContainer &propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor &property) {
|
||||
const gd::PropertiesContainer& propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor& property) {
|
||||
bool isLocalProperty =
|
||||
projectScopedContainers.GetPropertiesContainersList()
|
||||
.GetBottomMostPropertiesContainer() == &propertiesContainer;
|
||||
@@ -1639,7 +1669,7 @@ gd::String EventsCodeGenerator::GeneratePropertyGetter(
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GenerateParameterGetterWithoutCasting(
|
||||
const gd::ParameterMetadata ¶meter) {
|
||||
const gd::ParameterMetadata& parameter) {
|
||||
return "eventsFunctionContext.getArgument(" +
|
||||
ConvertToStringExplicit(parameter.GetName()) + ")";
|
||||
}
|
||||
|
@@ -59,7 +59,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
* Generate JavaScript for executing events of an events based function.
|
||||
*
|
||||
* \param project Project used.
|
||||
* \param eventsFunctionsExtension The container of the compiled event function.
|
||||
* \param eventsFunctionsExtension The container of the compiled event
|
||||
* function.
|
||||
* \param eventsFunction The events function to be compiled.
|
||||
* \param codeNamespace Where to store the context used by the function.
|
||||
* \param includeFiles Will be filled with the necessary include files.
|
||||
@@ -96,7 +97,7 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
*/
|
||||
static gd::String GenerateBehaviorEventsFunctionCode(
|
||||
gd::Project& project,
|
||||
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
|
||||
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
|
||||
const gd::EventsBasedBehavior& eventsBasedBehavior,
|
||||
const gd::EventsFunction& eventsFunction,
|
||||
const gd::String& codeNamespace,
|
||||
@@ -111,7 +112,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
* function.
|
||||
*
|
||||
* \param project Project used.
|
||||
* \param eventsBasedObject The object that contains the function to be compiled.
|
||||
* \param eventsBasedObject The object that contains the function to be
|
||||
* compiled.
|
||||
* \param eventsFunction The events function to be compiled.
|
||||
* \param codeNamespace Where to store the context used by the function.
|
||||
* \param fullyQualifiedFunctionName The function name with its namespace.
|
||||
@@ -129,7 +131,7 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
*/
|
||||
static gd::String GenerateObjectEventsFunctionCode(
|
||||
gd::Project& project,
|
||||
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
|
||||
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
|
||||
const gd::EventsBasedObject& eventsBasedObject,
|
||||
const gd::EventsFunction& eventsFunction,
|
||||
const gd::String& codeNamespace,
|
||||
@@ -152,7 +154,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
* \return Code
|
||||
*/
|
||||
virtual gd::String GenerateEventsListCode(
|
||||
gd::EventsList& events, gd::EventsCodeGenerationContext& context) override;
|
||||
gd::EventsList& events,
|
||||
gd::EventsCodeGenerationContext& context) override;
|
||||
|
||||
/**
|
||||
* Generate code for executing a condition list
|
||||
@@ -194,7 +197,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
* \brief Get the full name for accessing to a list of objects
|
||||
*/
|
||||
virtual gd::String GetObjectListName(
|
||||
const gd::String& name, const gd::EventsCodeGenerationContext& context) override;
|
||||
const gd::String& name,
|
||||
const gd::EventsCodeGenerationContext& context) override;
|
||||
|
||||
/**
|
||||
* \brief Get the namespace to be used to store code generated
|
||||
@@ -226,9 +230,9 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
};
|
||||
|
||||
virtual gd::String GeneratePropertySetterWithoutCasting(
|
||||
const gd::PropertiesContainer &propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor &property,
|
||||
const gd::String &operandCode) override;
|
||||
const gd::PropertiesContainer& propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor& property,
|
||||
const gd::String& operandCode) override;
|
||||
|
||||
protected:
|
||||
virtual gd::String GenerateParameterCodes(
|
||||
@@ -289,7 +293,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
const std::vector<gd::String>& arguments,
|
||||
const gd::InstructionMetadata& instrInfos,
|
||||
gd::EventsCodeGenerationContext& context,
|
||||
const gd::String& optionalAsyncCallbackName = "") override;
|
||||
const gd::String& optionalAsyncCallbackName = "",
|
||||
const gd::String& optionalAsyncCallbackId = "") override;
|
||||
|
||||
virtual gd::String GenerateBehaviorAction(
|
||||
const gd::String& objectName,
|
||||
@@ -299,7 +304,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
const std::vector<gd::String>& arguments,
|
||||
const gd::InstructionMetadata& instrInfos,
|
||||
gd::EventsCodeGenerationContext& context,
|
||||
const gd::String& optionalAsyncCallbackName = "") override;
|
||||
const gd::String& optionalAsyncCallbackName = "",
|
||||
const gd::String& optionalAsyncCallbackId = "") override;
|
||||
|
||||
virtual gd::String GenerateGetBehaviorNameCode(
|
||||
const gd::String& behaviorName) override;
|
||||
@@ -320,9 +326,9 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
gd::String expressionCode) override {
|
||||
// This uses `getChild` which allows to access a child
|
||||
// with a number (an index, for an array) or a string (for a structure).
|
||||
// This could be optimised, if the type of the accessed variable AND the type of the index is known,
|
||||
// so that `getChildAt` (for an array, with an index) or `getChildNamed` (for a structure, with a name)
|
||||
// is used instead.
|
||||
// This could be optimised, if the type of the accessed variable AND the
|
||||
// type of the index is known, so that `getChildAt` (for an array, with an
|
||||
// index) or `getChildNamed` (for a structure, with a name) is used instead.
|
||||
return ".getChild(" + expressionCode + ")";
|
||||
};
|
||||
|
||||
@@ -330,29 +336,33 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
return "gdjs.VariablesContainer.badVariable";
|
||||
}
|
||||
|
||||
virtual gd::String GeneratePropertyGetter(const gd::PropertiesContainer& propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor& property,
|
||||
const gd::String& type,
|
||||
gd::EventsCodeGenerationContext& context) override;
|
||||
virtual gd::String GeneratePropertyGetter(
|
||||
const gd::PropertiesContainer& propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor& property,
|
||||
const gd::String& type,
|
||||
gd::EventsCodeGenerationContext& context) override;
|
||||
|
||||
virtual gd::String GeneratePropertyGetterWithoutCasting(
|
||||
const gd::PropertiesContainer &propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor &property) override;
|
||||
const gd::PropertiesContainer& propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor& property) override;
|
||||
|
||||
virtual gd::String GenerateParameterGetter(const gd::ParameterMetadata& parameter,
|
||||
const gd::String& type,
|
||||
gd::EventsCodeGenerationContext& context) override;
|
||||
virtual gd::String GenerateParameterGetter(
|
||||
const gd::ParameterMetadata& parameter,
|
||||
const gd::String& type,
|
||||
gd::EventsCodeGenerationContext& context) override;
|
||||
|
||||
virtual gd::String GenerateParameterGetterWithoutCasting(
|
||||
const gd::ParameterMetadata ¶meter) override;
|
||||
const gd::ParameterMetadata& parameter) override;
|
||||
|
||||
virtual gd::String GenerateBadObject() override { return "null"; }
|
||||
|
||||
virtual gd::String GenerateObject(const gd::String& objectName,
|
||||
const gd::String& type,
|
||||
gd::EventsCodeGenerationContext& context) override;
|
||||
virtual gd::String GenerateObject(
|
||||
const gd::String& objectName,
|
||||
const gd::String& type,
|
||||
gd::EventsCodeGenerationContext& context) override;
|
||||
|
||||
virtual gd::String GenerateNegatedPredicate(const gd::String& predicate) const override {
|
||||
virtual gd::String GenerateNegatedPredicate(
|
||||
const gd::String& predicate) const override {
|
||||
return "!(" + predicate + ")";
|
||||
};
|
||||
|
||||
@@ -362,13 +372,15 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
virtual gd::String GenerateAllInstancesGetterCode(
|
||||
const gd::String& objectName, gd::EventsCodeGenerationContext& context);
|
||||
|
||||
virtual gd::String GenerateProfilerSectionBegin(const gd::String& section) override;
|
||||
virtual gd::String GenerateProfilerSectionEnd(const gd::String& section) override;
|
||||
virtual gd::String GenerateProfilerSectionBegin(
|
||||
const gd::String& section) override;
|
||||
virtual gd::String GenerateProfilerSectionEnd(
|
||||
const gd::String& section) override;
|
||||
|
||||
virtual gd::String GenerateRelationalOperation(
|
||||
const gd::String& relationalOperator,
|
||||
const gd::String& lhs,
|
||||
const gd::String& rhs) override;
|
||||
const gd::String& relationalOperator,
|
||||
const gd::String& lhs,
|
||||
const gd::String& rhs) override;
|
||||
|
||||
private:
|
||||
static gd::String GenerateEventsListCompleteFunctionCode(
|
||||
@@ -415,8 +427,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
* arguments from the rest of the events.
|
||||
*/
|
||||
gd::String GenerateFreeEventsFunctionContext(
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::EventsFunction &eventsFunction,
|
||||
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
|
||||
const gd::EventsFunction& eventsFunction,
|
||||
const gd::String& onceTriggersVariable);
|
||||
|
||||
/**
|
||||
@@ -425,9 +437,9 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
* arguments from the rest of the events.
|
||||
*/
|
||||
gd::String GenerateBehaviorEventsFunctionContext(
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
|
||||
const gd::EventsBasedBehavior& eventsBasedBehavior,
|
||||
const gd::EventsFunction &eventsFunction,
|
||||
const gd::EventsFunction& eventsFunction,
|
||||
const gd::String& onceTriggersVariable,
|
||||
const gd::String& thisObjectName,
|
||||
const gd::String& thisBehaviorName);
|
||||
@@ -438,9 +450,9 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
* arguments from the rest of the events.
|
||||
*/
|
||||
gd::String GenerateObjectEventsFunctionContext(
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
|
||||
const gd::EventsBasedObject& eventsBasedObject,
|
||||
const gd::EventsFunction &eventsFunction,
|
||||
const gd::EventsFunction& eventsFunction,
|
||||
const gd::String& onceTriggersVariable,
|
||||
const gd::String& thisObjectName);
|
||||
|
||||
@@ -455,7 +467,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
/**
|
||||
* \brief Construct a code generator for the specified containers.
|
||||
*/
|
||||
EventsCodeGenerator(const gd::ProjectScopedContainers& projectScopedContainers);
|
||||
EventsCodeGenerator(
|
||||
const gd::ProjectScopedContainers& projectScopedContainers);
|
||||
virtual ~EventsCodeGenerator();
|
||||
|
||||
gd::String codeNamespace; ///< Optional namespace for the generated code,
|
||||
@@ -467,16 +480,16 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
* to provides access objects, object creation and access to arguments from
|
||||
* the rest of the events.
|
||||
*/
|
||||
gd::String GenerateEventsFunctionContext(
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::EventsFunctionsContainer &eventsFunctionsContainer,
|
||||
const gd::EventsFunction &eventsFunction,
|
||||
const gd::String &onceTriggersVariable,
|
||||
gd::String &objectsGettersMap,
|
||||
gd::String &objectArraysMap,
|
||||
gd::String &behaviorNamesMap,
|
||||
const gd::String &thisObjectName = "",
|
||||
const gd::String &thisBehaviorName = "");
|
||||
gd::String GenerateEventsFunctionContext(
|
||||
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
|
||||
const gd::EventsFunctionsContainer& eventsFunctionsContainer,
|
||||
const gd::EventsFunction& eventsFunction,
|
||||
const gd::String& onceTriggersVariable,
|
||||
gd::String& objectsGettersMap,
|
||||
gd::String& objectArraysMap,
|
||||
gd::String& behaviorNamesMap,
|
||||
const gd::String& thisObjectName = "",
|
||||
const gd::String& thisBehaviorName = "");
|
||||
};
|
||||
|
||||
} // namespace gdjs
|
||||
|
@@ -22,9 +22,11 @@ AsyncExtension::AsyncExtension() {
|
||||
gd::AsyncEvent &event = dynamic_cast<gd::AsyncEvent &>(event_);
|
||||
|
||||
// Generate callback code
|
||||
const auto callbackDescriptor = codeGenerator.GenerateCallback(
|
||||
const auto callbackId =
|
||||
gd::String::From(codeGenerator.GenerateSingleUsageUniqueIdFor(
|
||||
event.GetInstruction().GetOriginalInstruction().lock().get())),
|
||||
event.GetInstruction().GetOriginalInstruction().lock().get()));
|
||||
const auto callbackDescriptor = codeGenerator.GenerateCallback(
|
||||
callbackId,
|
||||
parentContext,
|
||||
event.GetActions(),
|
||||
event.HasSubEvents() ? &event.GetSubEvents() : nullptr);
|
||||
@@ -33,10 +35,6 @@ AsyncExtension::AsyncExtension() {
|
||||
"(runtimeScene) => (" + callbackDescriptor.functionName + "(" +
|
||||
callbackDescriptor.argumentsList + "))";
|
||||
|
||||
// Generate the action and store the generated task.
|
||||
const gd::String asyncActionCode = codeGenerator.GenerateActionCode(
|
||||
event.GetInstruction(), parentContext, callbackCallCode);
|
||||
|
||||
// Generate code to backup the objects lists.
|
||||
// Do it after generating the code of the action so that it uses the
|
||||
// same object list as used in the action.
|
||||
@@ -70,6 +68,13 @@ AsyncExtension::AsyncExtension() {
|
||||
", obj);\n";
|
||||
}
|
||||
|
||||
// Generate the action and store the generated task.
|
||||
const gd::String asyncActionCode =
|
||||
codeGenerator.GenerateActionCode(event.GetInstruction(),
|
||||
parentContext,
|
||||
callbackCallCode,
|
||||
callbackId);
|
||||
|
||||
return "{\n" + parentAsyncObjectsListGetter + "{\n" +
|
||||
asyncContextBuilder + asyncActionCode + "}\n" + "}\n";
|
||||
});
|
||||
|
@@ -844,6 +844,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
InsertUnique(includesFiles, "CustomRuntimeObjectInstanceContainer.js");
|
||||
InsertUnique(includesFiles, "CustomRuntimeObject.js");
|
||||
InsertUnique(includesFiles, "CustomRuntimeObject2D.js");
|
||||
InsertUnique(includesFiles, "indexeddb.js");
|
||||
|
||||
// Common includes for events only.
|
||||
InsertUnique(includesFiles, "events-tools/commontools.js");
|
||||
|
@@ -4,6 +4,33 @@
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
namespace gdjs {
|
||||
export type WaitTaskNetworkSyncData = {
|
||||
type: 'wait';
|
||||
duration: float;
|
||||
timeElapsedOnScene: float;
|
||||
};
|
||||
export type ResolveTaskNetworkSyncData = null;
|
||||
export type PromiseTaskNetworkSyncData = null;
|
||||
export type ManuallyResolvableTaskNetworkSyncData = null;
|
||||
export type TaskGroupNetworkSyncData = {
|
||||
type: 'group';
|
||||
tasks: AsyncTaskNetworkSyncData[];
|
||||
};
|
||||
export type AsyncTaskNetworkSyncData =
|
||||
| WaitTaskNetworkSyncData
|
||||
| TaskGroupNetworkSyncData
|
||||
| PromiseTaskNetworkSyncData
|
||||
| ManuallyResolvableTaskNetworkSyncData
|
||||
| ResolveTaskNetworkSyncData;
|
||||
|
||||
export type AsyncTasksManagerNetworkSyncData = {
|
||||
tasks: Array<{
|
||||
callbackId: string;
|
||||
asyncTask: AsyncTaskNetworkSyncData;
|
||||
objectsList: gdjs.LongLivedObjectsListNetworkSyncData;
|
||||
}>;
|
||||
};
|
||||
|
||||
/**
|
||||
* This stores all asynchronous tasks waiting to be completed,
|
||||
* for a given scene.
|
||||
@@ -15,7 +42,12 @@ namespace gdjs {
|
||||
*/
|
||||
private tasksWithCallback = new Array<{
|
||||
asyncTask: AsyncTask;
|
||||
callback: (runtimeScene: gdjs.RuntimeScene) => void;
|
||||
callback: (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
longLivedObjectsList: gdjs.LongLivedObjectsList
|
||||
) => void;
|
||||
callbackId: string;
|
||||
longLivedObjectsList: gdjs.LongLivedObjectsList;
|
||||
}>();
|
||||
|
||||
/**
|
||||
@@ -26,7 +58,10 @@ namespace gdjs {
|
||||
const taskWithCallback = this.tasksWithCallback[i];
|
||||
if (taskWithCallback.asyncTask.update(runtimeScene)) {
|
||||
// The task has finished, run the callback and remove it.
|
||||
taskWithCallback.callback(runtimeScene);
|
||||
taskWithCallback.callback(
|
||||
runtimeScene,
|
||||
taskWithCallback.longLivedObjectsList
|
||||
);
|
||||
this.tasksWithCallback.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
@@ -39,9 +74,19 @@ namespace gdjs {
|
||||
*/
|
||||
addTask(
|
||||
asyncTask: AsyncTask,
|
||||
callback: (runtimeScene: RuntimeScene) => void
|
||||
callback: (
|
||||
runtimeScene: RuntimeScene,
|
||||
longLivedObjectsList: gdjs.LongLivedObjectsList
|
||||
) => void,
|
||||
callbackId: string,
|
||||
longLivedObjectsList: gdjs.LongLivedObjectsList
|
||||
): void {
|
||||
this.tasksWithCallback.push({ asyncTask, callback });
|
||||
this.tasksWithCallback.push({
|
||||
asyncTask,
|
||||
callback,
|
||||
callbackId,
|
||||
longLivedObjectsList,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,6 +96,77 @@ namespace gdjs {
|
||||
clearTasks() {
|
||||
this.tasksWithCallback.length = 0;
|
||||
}
|
||||
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): AsyncTasksManagerNetworkSyncData {
|
||||
const tasksData = this.tasksWithCallback.map(
|
||||
({ asyncTask, callbackId, longLivedObjectsList }) => {
|
||||
return {
|
||||
callbackId,
|
||||
asyncTask: asyncTask.getNetworkSyncData(),
|
||||
objectsList: longLivedObjectsList.getNetworkSyncData(syncOptions),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
tasks: tasksData,
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
syncData: AsyncTasksManagerNetworkSyncData,
|
||||
idToCallbackMap: Map<
|
||||
string,
|
||||
(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
asyncObjectsList: gdjs.LongLivedObjectsList
|
||||
) => void
|
||||
>,
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
syncOptions: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
this.clearTasks();
|
||||
|
||||
const unknownTaskTypes: string[] = [];
|
||||
|
||||
syncData.tasks.forEach(({ callbackId, asyncTask, objectsList }) => {
|
||||
if (!asyncTask) return;
|
||||
|
||||
const callback = idToCallbackMap.get(callbackId);
|
||||
if (callback) {
|
||||
const longLivedObjectsList = new gdjs.LongLivedObjectsList();
|
||||
longLivedObjectsList.updateFromNetworkSyncData(
|
||||
objectsList,
|
||||
runtimeScene,
|
||||
syncOptions
|
||||
);
|
||||
|
||||
if (asyncTask.type === 'group') {
|
||||
const task = new TaskGroup();
|
||||
task.updateFromNetworkSyncData(asyncTask);
|
||||
this.addTask(task, callback, callbackId, longLivedObjectsList);
|
||||
} else if (asyncTask.type === 'wait') {
|
||||
const task = new gdjs.evtTools.runtimeScene.WaitTask(
|
||||
asyncTask.duration
|
||||
);
|
||||
task.updateFromNetworkSyncData(asyncTask);
|
||||
this.addTask(task, callback, callbackId, longLivedObjectsList);
|
||||
} else {
|
||||
// Unknown task type.
|
||||
// @ts-ignore
|
||||
unknownTaskTypes.push(asyncTask.type);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (unknownTaskTypes.length) {
|
||||
console.warn(
|
||||
`${unknownTaskTypes.length} asynchronous task(s) could not be restored from network sync data. ${unknownTaskTypes.join(', ')}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,6 +179,12 @@ namespace gdjs {
|
||||
* @return True if the task is finished, false if it needs to continue running.
|
||||
*/
|
||||
abstract update(runtimeScene: RuntimeScene): boolean;
|
||||
|
||||
abstract getNetworkSyncData(): AsyncTaskNetworkSyncData;
|
||||
|
||||
abstract updateFromNetworkSyncData(
|
||||
syncData: AsyncTaskNetworkSyncData
|
||||
): void;
|
||||
}
|
||||
|
||||
export class TaskGroup extends AsyncTask {
|
||||
@@ -80,12 +202,54 @@ namespace gdjs {
|
||||
|
||||
return this.tasks.length === 0;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): TaskGroupNetworkSyncData {
|
||||
return {
|
||||
type: 'group',
|
||||
tasks: this.tasks.map((task) => task.getNetworkSyncData()),
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(syncData: TaskGroupNetworkSyncData) {
|
||||
const unknownTaskTypes: string[] = [];
|
||||
syncData.tasks.forEach((asyncTask) => {
|
||||
if (!asyncTask) return;
|
||||
|
||||
if (asyncTask.type === 'group') {
|
||||
const task = new TaskGroup();
|
||||
task.updateFromNetworkSyncData(asyncTask);
|
||||
this.addTask(task);
|
||||
} else if (asyncTask.type === 'wait') {
|
||||
const task = new gdjs.evtTools.runtimeScene.WaitTask(
|
||||
asyncTask.duration
|
||||
);
|
||||
task.updateFromNetworkSyncData(asyncTask);
|
||||
this.addTask(task);
|
||||
} else {
|
||||
// Unknown task type.
|
||||
// @ts-ignore
|
||||
unknownTaskTypes.push(asyncTask.type);
|
||||
}
|
||||
});
|
||||
|
||||
if (unknownTaskTypes.length) {
|
||||
console.warn(
|
||||
`${unknownTaskTypes.length} asynchronous task(s) could not be restored from network sync data. ${unknownTaskTypes.join(', ')}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ResolveTask extends AsyncTask {
|
||||
update() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): AsyncTaskNetworkSyncData {
|
||||
return null;
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(syncData: AsyncTaskNetworkSyncData): void {}
|
||||
}
|
||||
|
||||
const logger = new gdjs.Logger('Internal PromiseTask');
|
||||
@@ -121,6 +285,12 @@ ${error ? 'The following error was thrown: ' + error : ''}`
|
||||
update() {
|
||||
return this.isResolved;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): AsyncTaskNetworkSyncData {
|
||||
return null;
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(syncData: AsyncTaskNetworkSyncData): void {}
|
||||
}
|
||||
|
||||
export class ManuallyResolvableTask extends AsyncTask {
|
||||
@@ -133,5 +303,11 @@ ${error ? 'The following error was thrown: ' + error : ''}`
|
||||
update(): boolean {
|
||||
return this.isResolved;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): AsyncTaskNetworkSyncData {
|
||||
return null;
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(syncData: AsyncTaskNetworkSyncData): void {}
|
||||
}
|
||||
}
|
||||
|
@@ -17,11 +17,19 @@ namespace gdjs {
|
||||
isInnerAreaFollowingParentSize: boolean;
|
||||
};
|
||||
|
||||
export type CustomObjectNetworkSyncDataType = ObjectNetworkSyncData & {
|
||||
export type CustomObjectNetworkSyncDataType = {
|
||||
anim?: SpriteAnimatorNetworkSyncData;
|
||||
ifx: boolean;
|
||||
ify: boolean;
|
||||
sx: float;
|
||||
sy: float;
|
||||
op: float;
|
||||
cc?: [float, float];
|
||||
};
|
||||
|
||||
export type CustomObjectNetworkSyncData = ObjectNetworkSyncData &
|
||||
CustomObjectNetworkSyncDataType;
|
||||
|
||||
/**
|
||||
* An object that contains other object.
|
||||
*
|
||||
@@ -221,24 +229,66 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): CustomObjectNetworkSyncDataType {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): CustomObjectNetworkSyncData {
|
||||
const animator = this.getAnimator();
|
||||
const networkSyncData: CustomObjectNetworkSyncData = {
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
ifx: this.isFlippedX(),
|
||||
ify: this.isFlippedY(),
|
||||
sx: this._scaleX,
|
||||
sy: this._scaleY,
|
||||
op: this.opacity,
|
||||
};
|
||||
if (animator) {
|
||||
networkSyncData.anim = animator.getNetworkSyncData();
|
||||
}
|
||||
if (this._customCenter) {
|
||||
networkSyncData.cc = this._customCenter;
|
||||
}
|
||||
return networkSyncData;
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: CustomObjectNetworkSyncDataType
|
||||
networkSyncData: CustomObjectNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
super.updateFromNetworkSyncData(networkSyncData, options);
|
||||
if (networkSyncData.ifx !== undefined) {
|
||||
this.flipX(networkSyncData.ifx);
|
||||
}
|
||||
if (networkSyncData.ify !== undefined) {
|
||||
this.flipY(networkSyncData.ify);
|
||||
}
|
||||
if (networkSyncData.sx !== undefined) {
|
||||
this.setScaleX(Math.abs(networkSyncData.sx));
|
||||
}
|
||||
if (networkSyncData.sy !== undefined) {
|
||||
this.setScaleY(Math.abs(networkSyncData.sy));
|
||||
}
|
||||
if (networkSyncData.op !== undefined) {
|
||||
this.setOpacity(networkSyncData.op);
|
||||
}
|
||||
if (networkSyncData.anim) {
|
||||
const animator = this.getAnimator();
|
||||
if (animator) {
|
||||
animator.updateFromNetworkSyncData(networkSyncData.anim);
|
||||
}
|
||||
}
|
||||
if (networkSyncData.cc) {
|
||||
this.setRotationCenter(networkSyncData.cc[0], networkSyncData.cc[1]);
|
||||
}
|
||||
if (
|
||||
networkSyncData.ifx !== undefined ||
|
||||
networkSyncData.ify !== undefined ||
|
||||
networkSyncData.sx !== undefined ||
|
||||
networkSyncData.sy !== undefined ||
|
||||
networkSyncData.anim !== undefined ||
|
||||
networkSyncData.cc !== undefined
|
||||
) {
|
||||
this.onChildrenLocationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
override extraInitializationFromInitialInstance(
|
||||
|
@@ -623,7 +623,7 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Create a new object from its name. The object is also added to the instances
|
||||
* living in the container (No need to call {@link gdjs.RuntimeScene.addObject})
|
||||
* living in the container (No need to call {@link addObject})
|
||||
* @param objectName The name of the object to be created
|
||||
* @return The created object
|
||||
*/
|
||||
|
@@ -116,6 +116,101 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
getNetworkSyncData(): LayerNetworkSyncData {
|
||||
const effectsNetworkSyncData = {};
|
||||
for (const effectName in this._rendererEffects) {
|
||||
effectsNetworkSyncData[effectName] =
|
||||
this._rendererEffects[effectName].getNetworkSyncData();
|
||||
}
|
||||
|
||||
return {
|
||||
timeScale: this._timeScale,
|
||||
defaultZOrder: this._defaultZOrder,
|
||||
hidden: this._hidden,
|
||||
effects: effectsNetworkSyncData,
|
||||
followBaseLayerCamera: this._followBaseLayerCamera,
|
||||
clearColor: this._clearColor,
|
||||
cameraX: this.getCameraX(),
|
||||
cameraY: this.getCameraY(),
|
||||
cameraZ: this.getCameraZ(null),
|
||||
cameraRotation: this.getCameraRotation(),
|
||||
cameraZoom: this.getCameraZoom(),
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(networkSyncData: LayerNetworkSyncData): void {
|
||||
if (
|
||||
networkSyncData.timeScale !== undefined &&
|
||||
networkSyncData.timeScale !== this._timeScale
|
||||
) {
|
||||
this.setTimeScale(networkSyncData.timeScale);
|
||||
}
|
||||
if (
|
||||
networkSyncData.defaultZOrder !== undefined &&
|
||||
networkSyncData.defaultZOrder !== this._defaultZOrder
|
||||
) {
|
||||
this.setDefaultZOrder(networkSyncData.defaultZOrder);
|
||||
}
|
||||
if (
|
||||
this._hidden != undefined &&
|
||||
this._hidden !== networkSyncData.hidden
|
||||
) {
|
||||
this.show(!networkSyncData.hidden);
|
||||
}
|
||||
if (
|
||||
networkSyncData.followBaseLayerCamera !== undefined &&
|
||||
networkSyncData.followBaseLayerCamera !== this._followBaseLayerCamera
|
||||
) {
|
||||
this.setFollowBaseLayerCamera(networkSyncData.followBaseLayerCamera);
|
||||
}
|
||||
if (
|
||||
networkSyncData.clearColor !== undefined &&
|
||||
networkSyncData.clearColor !== this._clearColor
|
||||
) {
|
||||
this.setClearColor(
|
||||
networkSyncData.clearColor[0] * 255,
|
||||
networkSyncData.clearColor[1] * 255,
|
||||
networkSyncData.clearColor[2] * 255
|
||||
);
|
||||
}
|
||||
if (
|
||||
networkSyncData.cameraX !== undefined &&
|
||||
networkSyncData.cameraX !== this.getCameraX()
|
||||
) {
|
||||
this.setCameraX(networkSyncData.cameraX);
|
||||
}
|
||||
if (
|
||||
networkSyncData.cameraY !== undefined &&
|
||||
networkSyncData.cameraY !== this.getCameraY()
|
||||
) {
|
||||
this.setCameraY(networkSyncData.cameraY);
|
||||
}
|
||||
if (
|
||||
networkSyncData.cameraZ !== undefined &&
|
||||
networkSyncData.cameraZ !== this.getCameraZ(null)
|
||||
) {
|
||||
this.setCameraZ(networkSyncData.cameraZ, null);
|
||||
}
|
||||
if (
|
||||
networkSyncData.cameraRotation !== undefined &&
|
||||
networkSyncData.cameraRotation !== this.getCameraRotation()
|
||||
) {
|
||||
this.setCameraRotation(networkSyncData.cameraRotation);
|
||||
}
|
||||
if (
|
||||
networkSyncData.cameraZoom !== undefined &&
|
||||
networkSyncData.cameraZoom !== this.getCameraZoom()
|
||||
) {
|
||||
this.setCameraZoom(networkSyncData.cameraZoom);
|
||||
}
|
||||
|
||||
for (const effectName in networkSyncData.effects) {
|
||||
this._rendererEffects[effectName].updateFromNetworkSyncData(
|
||||
networkSyncData.effects[effectName]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getRuntimeLayer(): gdjs.RuntimeLayer {
|
||||
return this;
|
||||
}
|
||||
|
@@ -622,6 +622,14 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
const logger = new gdjs.Logger('LongLivedObjectsLists');
|
||||
export type LongLivedObjectsListNetworkSyncData = {
|
||||
objectsLists: {
|
||||
[objectName: string]: Array<string>;
|
||||
};
|
||||
localVariablesContainers: Array<Array<VariableNetworkSyncData>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* A container for objects lists that should last more than the current frame.
|
||||
* It automatically removes objects that were destroyed from the objects lists.
|
||||
@@ -695,5 +703,85 @@ namespace gdjs {
|
||||
): void {
|
||||
gdjs.copyArray(variablesContainers, this.localVariablesContainers);
|
||||
}
|
||||
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): LongLivedObjectsListNetworkSyncData {
|
||||
const objectsLists: {
|
||||
[objectName: string]: Array<string>;
|
||||
} = {};
|
||||
for (const [objectName, runtimeObjects] of this.objectsLists.entries()) {
|
||||
const objectNetworkIds: Array<string> = [];
|
||||
for (const runtimeObject of runtimeObjects) {
|
||||
const objectNetworkId = runtimeObject.getNetworkId();
|
||||
if (!objectNetworkId) {
|
||||
logger.warn(
|
||||
'Tried to get sync data of a LongLivedObjectsList and found an object without a network ID'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
objectNetworkIds.push(objectNetworkId);
|
||||
}
|
||||
objectsLists[objectName] = objectNetworkIds;
|
||||
}
|
||||
return {
|
||||
objectsLists,
|
||||
localVariablesContainers: this.localVariablesContainers.map(
|
||||
(container) => container.getNetworkSyncData(syncOptions)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
syncData: LongLivedObjectsListNetworkSyncData,
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
syncOptions: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
const { objectsLists, localVariablesContainers } = syncData;
|
||||
|
||||
// Clear the current state.
|
||||
this.objectsLists.clear();
|
||||
this.localVariablesContainers.length = 0;
|
||||
|
||||
// Restore the list of objects.
|
||||
for (const [objectName, objectNetworkIds] of Object.entries(
|
||||
objectsLists
|
||||
)) {
|
||||
const runtimeObjects = runtimeScene.getObjects(objectName);
|
||||
if (!runtimeObjects) {
|
||||
logger.warn(
|
||||
'Tried to update sync data of a LongLivedObjectsList but cannot find objects with name: ' +
|
||||
objectName
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const runtimeObjectsFromSyncData = runtimeObjects.filter(
|
||||
(runtimeObject) => {
|
||||
const runtimeObjectNetworkId = runtimeObject.getNetworkId();
|
||||
return (
|
||||
!!runtimeObjectNetworkId &&
|
||||
objectNetworkIds.includes(runtimeObjectNetworkId)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
for (const runtimeObject of runtimeObjectsFromSyncData) {
|
||||
this.addObject(objectName, runtimeObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the local variables containers.
|
||||
this.localVariablesContainers = localVariablesContainers.map(
|
||||
(localVariablesContainer) => {
|
||||
const newContainer = new gdjs.VariablesContainer();
|
||||
newContainer.updateFromNetworkSyncData(
|
||||
localVariablesContainer,
|
||||
syncOptions
|
||||
);
|
||||
return newContainer;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -143,6 +143,19 @@ namespace gdjs {
|
||||
.getElapsedTime();
|
||||
return this.timeElapsedOnScene >= this.duration;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): WaitTaskNetworkSyncData {
|
||||
return {
|
||||
type: 'wait',
|
||||
duration: this.duration,
|
||||
timeElapsedOnScene: this.timeElapsedOnScene,
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(syncData: WaitTaskNetworkSyncData): void {
|
||||
this.duration = syncData.duration;
|
||||
this.timeElapsedOnScene = syncData.timeElapsedOnScene;
|
||||
}
|
||||
}
|
||||
|
||||
export const wait = (durationInSeconds: float): AsyncTask =>
|
||||
|
@@ -32,6 +32,16 @@ namespace gdjs {
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
runtimeObject: gdjs.RuntimeObject
|
||||
) => void;
|
||||
type RuntimeSceneGetSyncDataCallback = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
currentSyncData: LayoutNetworkSyncData,
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
) => void;
|
||||
type RuntimeSceneUpdateFromSyncData = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
receivedSyncData: LayoutNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) => void;
|
||||
|
||||
export const callbacksFirstRuntimeSceneLoaded: Array<RuntimeSceneCallback> =
|
||||
[];
|
||||
@@ -45,6 +55,10 @@ namespace gdjs {
|
||||
export const callbacksRuntimeSceneUnloaded: Array<RuntimeSceneCallback> = [];
|
||||
export const callbacksObjectDeletedFromScene: Array<RuntimeSceneRuntimeObjectCallback> =
|
||||
[];
|
||||
export const callbacksRuntimeSceneGetSyncData: Array<RuntimeSceneGetSyncDataCallback> =
|
||||
[];
|
||||
export const callbacksRuntimeSceneUpdateFromSyncData: Array<RuntimeSceneUpdateFromSyncData> =
|
||||
[];
|
||||
|
||||
/** Base64 encoded logo of GDevelop for the splash screen. */
|
||||
export let gdevelopLogo: string = '';
|
||||
@@ -397,6 +411,28 @@ namespace gdjs {
|
||||
gdjs.callbacksObjectDeletedFromScene.push(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a function to be called each time a scene is getting its sync
|
||||
* data retrieved (via getNetworkSyncData).
|
||||
* @param callback The function to be called.
|
||||
*/
|
||||
export const registerRuntimeSceneGetSyncDataCallback = function (
|
||||
callback: RuntimeSceneGetSyncDataCallback
|
||||
): void {
|
||||
gdjs.callbacksRuntimeSceneGetSyncData.push(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a function to be called each time a scene is getting its sync
|
||||
* data updated (via updateFromNetworkSyncData).
|
||||
* @param callback The function to be called.
|
||||
*/
|
||||
export const registerRuntimeSceneUpdateFromSyncDataCallback = function (
|
||||
callback: RuntimeSceneUpdateFromSyncData
|
||||
): void {
|
||||
gdjs.callbacksRuntimeSceneUpdateFromSyncData.push(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unregister a callback.
|
||||
* This should not be used apart from the code generated from extensions
|
||||
|
@@ -84,11 +84,23 @@ namespace gdjs {
|
||||
*/
|
||||
private _onPlay: Array<HowlCallback> = [];
|
||||
|
||||
constructor(howl: Howl, volume: float, loop: boolean, rate: float) {
|
||||
/**
|
||||
* The filepath to the resource
|
||||
*/
|
||||
private _audioResourceName: string;
|
||||
|
||||
constructor(
|
||||
howl: Howl,
|
||||
volume: float,
|
||||
loop: boolean,
|
||||
rate: float,
|
||||
audioResourceName: string
|
||||
) {
|
||||
this._howl = howl;
|
||||
this._initialVolume = clampVolume(volume);
|
||||
this._loop = loop;
|
||||
this._rate = rate;
|
||||
this._audioResourceName = audioResourceName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,10 +369,27 @@ namespace gdjs {
|
||||
if (this._id !== null) this._howl.off(event, handler, this._id);
|
||||
return this;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): SoundSyncData | undefined {
|
||||
if (this.stopped()) return undefined;
|
||||
// Seek can sometimes return the Howl object in case it isn't loaded yet, in this case we default to 0.
|
||||
const seek = this.getSeek();
|
||||
const numberSeek = typeof seek !== 'number' ? 0 : seek;
|
||||
// If the Howl is still loading, we use the initialVolume, as the Howl
|
||||
// has been initialized with volume 0.
|
||||
const volume = this.isLoaded() ? this.getVolume() : this._initialVolume;
|
||||
return {
|
||||
resourceName: this._audioResourceName,
|
||||
loop: this._loop,
|
||||
volume,
|
||||
rate: this._rate,
|
||||
seek: numberSeek,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HowlerSoundManager is used to manage the sounds and musics of a RuntimeScene.
|
||||
* HowlerSoundManager is used to manage the sounds and musics of a RuntimeGame.
|
||||
*
|
||||
* It is basically a container to associate channels to sounds and keep a list
|
||||
* of all sounds being played.
|
||||
@@ -609,8 +638,7 @@ namespace gdjs {
|
||||
);
|
||||
cacheContainer.set(resource, howl);
|
||||
}
|
||||
|
||||
return new gdjs.HowlerSound(howl, volume, loop, rate);
|
||||
return new gdjs.HowlerSound(howl, volume, loop, rate, soundName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -708,7 +736,13 @@ namespace gdjs {
|
||||
this._loadedSounds.clear();
|
||||
}
|
||||
|
||||
playSound(soundName: string, loop: boolean, volume: float, pitch: float) {
|
||||
playSound(
|
||||
soundName: string,
|
||||
loop: boolean,
|
||||
volume: float,
|
||||
pitch: float,
|
||||
seek?: float
|
||||
) {
|
||||
const sound = this.createHowlerSound(
|
||||
soundName,
|
||||
/* isMusic= */ false,
|
||||
@@ -724,6 +758,9 @@ namespace gdjs {
|
||||
}
|
||||
});
|
||||
sound.play();
|
||||
if (seek) {
|
||||
sound.setSeek(seek);
|
||||
}
|
||||
}
|
||||
|
||||
playSoundOnChannel(
|
||||
@@ -731,7 +768,8 @@ namespace gdjs {
|
||||
channel: integer,
|
||||
loop: boolean,
|
||||
volume: float,
|
||||
pitch: float
|
||||
pitch: float,
|
||||
seek?: float
|
||||
) {
|
||||
if (this._sounds[channel]) this._sounds[channel].stop();
|
||||
|
||||
@@ -756,13 +794,22 @@ namespace gdjs {
|
||||
}
|
||||
});
|
||||
sound.play();
|
||||
if (seek) {
|
||||
sound.setSeek(seek);
|
||||
}
|
||||
}
|
||||
|
||||
getSoundOnChannel(channel: integer): HowlerSound | null {
|
||||
return this._sounds[channel] || null;
|
||||
}
|
||||
|
||||
playMusic(soundName: string, loop: boolean, volume: float, pitch: float) {
|
||||
playMusic(
|
||||
soundName: string,
|
||||
loop: boolean,
|
||||
volume: float,
|
||||
pitch: float,
|
||||
seek?: float
|
||||
) {
|
||||
const music = this.createHowlerSound(
|
||||
soundName,
|
||||
/* isMusic= */ true,
|
||||
@@ -778,6 +825,9 @@ namespace gdjs {
|
||||
}
|
||||
});
|
||||
music.play();
|
||||
if (seek) {
|
||||
music.setSeek(seek);
|
||||
}
|
||||
}
|
||||
|
||||
playMusicOnChannel(
|
||||
@@ -785,7 +835,8 @@ namespace gdjs {
|
||||
channel: integer,
|
||||
loop: boolean,
|
||||
volume: float,
|
||||
pitch: float
|
||||
pitch: float,
|
||||
seek?: float
|
||||
) {
|
||||
if (this._musics[channel]) this._musics[channel].stop();
|
||||
|
||||
@@ -805,6 +856,9 @@ namespace gdjs {
|
||||
}
|
||||
});
|
||||
music.play();
|
||||
if (seek) {
|
||||
music.setSeek(seek);
|
||||
}
|
||||
}
|
||||
|
||||
getMusicOnChannel(channel: integer): HowlerSound | null {
|
||||
@@ -932,6 +986,97 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
getNetworkSyncData(): SoundManagerSyncData {
|
||||
const freeMusicsNetworkSyncData: SoundSyncData[] = [];
|
||||
this._freeMusics.forEach((freeMusic) => {
|
||||
const musicSyncData = freeMusic.getNetworkSyncData();
|
||||
if (musicSyncData) freeMusicsNetworkSyncData.push(musicSyncData);
|
||||
});
|
||||
const freeSoundsNetworkSyncData: SoundSyncData[] = [];
|
||||
this._freeSounds.forEach((freeSound) => {
|
||||
const soundSyncData = freeSound.getNetworkSyncData();
|
||||
if (soundSyncData) freeSoundsNetworkSyncData.push(soundSyncData);
|
||||
});
|
||||
const musicsNetworkSyncData: ChannelsSoundSyncData = {};
|
||||
Object.entries(this._musics).forEach(([channel, music]) => {
|
||||
const musicSyncData = music.getNetworkSyncData();
|
||||
if (musicSyncData) {
|
||||
const channelNumber = parseInt(channel, 10);
|
||||
musicsNetworkSyncData[channelNumber] = musicSyncData;
|
||||
}
|
||||
});
|
||||
const soundsNetworkSyncData: ChannelsSoundSyncData = {};
|
||||
Object.entries(this._sounds).forEach(([channel, sound]) => {
|
||||
const soundSyncData = sound.getNetworkSyncData();
|
||||
if (soundSyncData) {
|
||||
const channelNumber = parseInt(channel, 10);
|
||||
soundsNetworkSyncData[channelNumber] = soundSyncData;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
globalVolume: this._globalVolume,
|
||||
cachedSpatialPosition: this._cachedSpatialPosition,
|
||||
freeMusics: freeMusicsNetworkSyncData,
|
||||
freeSounds: freeSoundsNetworkSyncData,
|
||||
musics: musicsNetworkSyncData,
|
||||
sounds: soundsNetworkSyncData,
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(syncData: SoundManagerSyncData): void {
|
||||
this.clearAll();
|
||||
this._globalVolume = syncData.globalVolume;
|
||||
this._cachedSpatialPosition = syncData.cachedSpatialPosition;
|
||||
|
||||
for (let i = 0; i < syncData.freeSounds.length; i++) {
|
||||
const freeSoundsSyncData: SoundSyncData = syncData.freeSounds[i];
|
||||
|
||||
this.playSound(
|
||||
freeSoundsSyncData.resourceName,
|
||||
freeSoundsSyncData.loop,
|
||||
freeSoundsSyncData.volume * 100,
|
||||
freeSoundsSyncData.rate,
|
||||
freeSoundsSyncData.seek
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < syncData.freeMusics.length; i++) {
|
||||
const freeMusicsSyncData: SoundSyncData = syncData.freeMusics[i];
|
||||
this.playMusic(
|
||||
freeMusicsSyncData.resourceName,
|
||||
freeMusicsSyncData.loop,
|
||||
freeMusicsSyncData.volume * 100,
|
||||
freeMusicsSyncData.rate,
|
||||
freeMusicsSyncData.seek
|
||||
);
|
||||
}
|
||||
|
||||
for (const [channel, soundSyncData] of Object.entries(syncData.sounds)) {
|
||||
const channelNumber = parseInt(channel, 10);
|
||||
this.playSoundOnChannel(
|
||||
soundSyncData.resourceName,
|
||||
channelNumber,
|
||||
soundSyncData.loop,
|
||||
soundSyncData.volume * 100,
|
||||
soundSyncData.rate,
|
||||
soundSyncData.seek
|
||||
);
|
||||
}
|
||||
|
||||
for (const [channel, musicSyncData] of Object.entries(syncData.musics)) {
|
||||
const channelNumber = parseInt(channel, 10);
|
||||
this.playMusicOnChannel(
|
||||
musicSyncData.resourceName,
|
||||
channelNumber,
|
||||
musicSyncData.loop,
|
||||
musicSyncData.volume * 100,
|
||||
musicSyncData.rate,
|
||||
musicSyncData.seek
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when the game is disposed.
|
||||
* Unloads all audio from memory, clear Howl cache and stop all audio.
|
||||
|
105
GDJS/Runtime/indexeddb.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* GDevelop JS Platform
|
||||
* Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
namespace gdjs {
|
||||
export namespace indexedDb {
|
||||
export const loadFromIndexedDB = async function (
|
||||
dbName: string,
|
||||
objectStoreName: string,
|
||||
key: string
|
||||
): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const request = indexedDB.open(dbName, 1);
|
||||
request.onupgradeneeded = function () {
|
||||
const db = request.result;
|
||||
if (!db.objectStoreNames.contains(objectStoreName)) {
|
||||
db.createObjectStore(objectStoreName);
|
||||
}
|
||||
};
|
||||
|
||||
request.onsuccess = function () {
|
||||
const db = request.result;
|
||||
|
||||
const tx = db.transaction(objectStoreName, 'readonly');
|
||||
const store = tx.objectStore(objectStoreName);
|
||||
const getRequest = store.get(key);
|
||||
|
||||
getRequest.onsuccess = function () {
|
||||
if (getRequest.result !== undefined) {
|
||||
resolve(getRequest.result);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
};
|
||||
|
||||
getRequest.onerror = function () {
|
||||
console.error(
|
||||
'Error loading data from IndexedDB:',
|
||||
getRequest.error
|
||||
);
|
||||
reject(getRequest.error);
|
||||
};
|
||||
};
|
||||
|
||||
request.onerror = function () {
|
||||
console.error('Error opening IndexedDB:', request.error);
|
||||
reject(request.error);
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Exception thrown while opening IndexedDB:', err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const saveToIndexedDB = async function (
|
||||
dbName: string,
|
||||
objectStoreName: string,
|
||||
key: string,
|
||||
data: any
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const request = indexedDB.open(dbName, 1);
|
||||
request.onupgradeneeded = function (event) {
|
||||
const db = request.result;
|
||||
if (!db.objectStoreNames.contains(objectStoreName)) {
|
||||
db.createObjectStore(objectStoreName);
|
||||
}
|
||||
};
|
||||
request.onsuccess = function () {
|
||||
const db = request.result;
|
||||
const tx = db.transaction(objectStoreName, 'readwrite');
|
||||
const store = tx.objectStore(objectStoreName);
|
||||
const putRequest = store.put(data, key);
|
||||
|
||||
putRequest.onsuccess = function () {
|
||||
resolve();
|
||||
};
|
||||
|
||||
putRequest.onerror = function () {
|
||||
console.error(
|
||||
'Error saving data to IndexedDB:',
|
||||
putRequest.error
|
||||
);
|
||||
reject(putRequest.error);
|
||||
};
|
||||
};
|
||||
|
||||
request.onerror = function () {
|
||||
console.error('Error opening IndexedDB:', request.error);
|
||||
reject(request.error);
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Exception thrown while opening IndexedDB:', err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
@@ -8,6 +8,11 @@ namespace gdjs {
|
||||
* OnceTriggers is used to store the status of the conditions "Trigger once",
|
||||
* that are used in events to have conditions that are only valid for one frame in a row.
|
||||
*/
|
||||
|
||||
type OnceTriggersSyncData = {
|
||||
onceTriggers: Record<integer, boolean>;
|
||||
lastFrameOnceTriggers: Record<integer, boolean>;
|
||||
};
|
||||
export class OnceTriggers {
|
||||
_onceTriggers: Record<integer, boolean> = {};
|
||||
_lastFrameOnceTrigger: Record<integer, boolean> = {};
|
||||
@@ -40,5 +45,17 @@ namespace gdjs {
|
||||
this._onceTriggers[triggerId] = true;
|
||||
return !this._lastFrameOnceTrigger.hasOwnProperty(triggerId);
|
||||
}
|
||||
|
||||
getNetworkSyncData(): OnceTriggersSyncData {
|
||||
return {
|
||||
onceTriggers: this._onceTriggers,
|
||||
lastFrameOnceTriggers: this._lastFrameOnceTrigger,
|
||||
};
|
||||
}
|
||||
|
||||
updateNetworkSyncData(data: OnceTriggersSyncData): void {
|
||||
this._onceTriggers = data.onceTriggers;
|
||||
this._lastFrameOnceTrigger = data.lastFrameOnceTriggers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -77,7 +77,9 @@ namespace gdjs {
|
||||
return false;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): BehaviorNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): BehaviorNetworkSyncData {
|
||||
// To be redefined by behaviors that need to synchronize properties
|
||||
// while calling super() to get the common properties.
|
||||
return {
|
||||
@@ -90,7 +92,10 @@ namespace gdjs {
|
||||
* Update the behavior properties using the provided data.
|
||||
* @param networkSyncData The new properties of the behavior.
|
||||
*/
|
||||
updateFromNetworkSyncData(networkSyncData: BehaviorNetworkSyncData): void {
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: BehaviorNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
// Must be redefined by behaviors that need to synchronize properties
|
||||
// while calling super() to get the common properties.
|
||||
if (networkSyncData.act !== this._activated) {
|
||||
|
@@ -1385,6 +1385,9 @@ namespace gdjs {
|
||||
): GameNetworkSyncData | null {
|
||||
const syncData: GameNetworkSyncData = {
|
||||
var: this._variables.getNetworkSyncData(syncOptions),
|
||||
sm: syncOptions.syncSounds
|
||||
? this.getSoundManager().getNetworkSyncData()
|
||||
: undefined,
|
||||
ss: this._sceneStack.getNetworkSyncData(syncOptions) || undefined,
|
||||
};
|
||||
|
||||
@@ -1412,10 +1415,16 @@ namespace gdjs {
|
||||
return syncData;
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(syncData: GameNetworkSyncData) {
|
||||
updateFromNetworkSyncData(
|
||||
syncData: GameNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
this._throwIfDisposed();
|
||||
if (syncData.var) {
|
||||
this._variables.updateFromNetworkSyncData(syncData.var);
|
||||
this._variables.updateFromNetworkSyncData(syncData.var, options);
|
||||
}
|
||||
if (syncData.sm) {
|
||||
this.getSoundManager().updateFromNetworkSyncData(syncData.sm);
|
||||
}
|
||||
if (syncData.ss) {
|
||||
this._sceneStack.updateFromNetworkSyncData(syncData.ss);
|
||||
@@ -1430,7 +1439,8 @@ namespace gdjs {
|
||||
this.getVariablesForExtension(extensionName);
|
||||
if (extensionVariables) {
|
||||
extensionVariables.updateFromNetworkSyncData(
|
||||
extensionVariablesData
|
||||
extensionVariablesData,
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -422,7 +422,8 @@ namespace gdjs {
|
||||
updatePreRender(instanceContainer: gdjs.RuntimeInstanceContainer): void {}
|
||||
|
||||
/**
|
||||
* Called when the object is created from an initial instance at the startup of the scene.<br>
|
||||
* Called when the object is created from an initial instance at the startup of the scene.
|
||||
*
|
||||
* Note that common properties (position, angle, z order...) have already been setup.
|
||||
*
|
||||
* @param initialInstanceData The data of the initial instance.
|
||||
@@ -452,17 +453,17 @@ namespace gdjs {
|
||||
* This can be redefined by objects to send more information.
|
||||
* @returns The full network sync data.
|
||||
*/
|
||||
getNetworkSyncData(): ObjectNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): ObjectNetworkSyncData {
|
||||
const behaviorNetworkSyncData = {};
|
||||
this._behaviors.forEach((behavior) => {
|
||||
if (!behavior.isSyncedOverNetwork()) {
|
||||
if (!behavior.isSyncedOverNetwork() && !syncOptions.syncAllBehaviors) {
|
||||
return;
|
||||
}
|
||||
|
||||
const networkSyncData = behavior.getNetworkSyncData();
|
||||
if (networkSyncData) {
|
||||
behaviorNetworkSyncData[behavior.getName()] = networkSyncData;
|
||||
}
|
||||
const networkSyncData = behavior.getNetworkSyncData(syncOptions);
|
||||
behaviorNetworkSyncData[behavior.getName()] = networkSyncData;
|
||||
});
|
||||
|
||||
const variablesNetworkSyncData = this._variables.getNetworkSyncData({
|
||||
@@ -481,7 +482,7 @@ namespace gdjs {
|
||||
this._timers.items[timerName].getNetworkSyncData();
|
||||
}
|
||||
|
||||
return {
|
||||
const networkSyncData: ObjectNetworkSyncData = {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
w: this.getWidth(),
|
||||
@@ -498,6 +499,19 @@ namespace gdjs {
|
||||
eff: effectsNetworkSyncData,
|
||||
tim: timersNetworkSyncData,
|
||||
};
|
||||
|
||||
if (syncOptions.syncObjectIdentifiers) {
|
||||
networkSyncData.n = this.name;
|
||||
if (!this.networkId) {
|
||||
// If this is the first time the object is synced
|
||||
// with identifier, then generate a networkId,
|
||||
// so it can be re-used for future syncs.
|
||||
this.networkId = gdjs.makeUuid().substring(0, 8);
|
||||
}
|
||||
networkSyncData.networkId = this.networkId;
|
||||
}
|
||||
|
||||
return networkSyncData;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -507,7 +521,10 @@ namespace gdjs {
|
||||
* @param networkSyncData The new data for the object.
|
||||
* @returns true if the object was updated, false if it could not (i.e: network sync is not supported).
|
||||
*/
|
||||
updateFromNetworkSyncData(networkSyncData: ObjectNetworkSyncData) {
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: ObjectNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
if (networkSyncData.x !== undefined) {
|
||||
this.setX(networkSyncData.x);
|
||||
}
|
||||
@@ -567,13 +584,13 @@ namespace gdjs {
|
||||
const behaviorNetworkSyncData = networkSyncData.beh[behaviorName];
|
||||
const behavior = this.getBehavior(behaviorName);
|
||||
if (behavior) {
|
||||
behavior.updateFromNetworkSyncData(behaviorNetworkSyncData);
|
||||
behavior.updateFromNetworkSyncData(behaviorNetworkSyncData, options);
|
||||
}
|
||||
}
|
||||
|
||||
// If variables are synchronized, update them.
|
||||
if (networkSyncData.var) {
|
||||
this._variables.updateFromNetworkSyncData(networkSyncData.var);
|
||||
this._variables.updateFromNetworkSyncData(networkSyncData.var, options);
|
||||
}
|
||||
|
||||
// If effects are synchronized, update them.
|
||||
@@ -599,6 +616,10 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (networkSyncData.networkId !== undefined) {
|
||||
this.networkId = networkSyncData.networkId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -701,8 +722,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique identifier of the object.<br>
|
||||
* The identifier is set by the runtimeScene owning the object.<br>
|
||||
* Get the unique identifier of the object.
|
||||
*
|
||||
* The identifier is set by the runtimeScene owning the object.
|
||||
*
|
||||
* You can also use the id property (this._object.id) for increased efficiency instead of
|
||||
* calling this method.
|
||||
*
|
||||
@@ -712,6 +735,18 @@ namespace gdjs {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the network ID of the object.
|
||||
*
|
||||
* The network ID is used to identify the object in a networked game.
|
||||
* Or, for Save/Load purposes.
|
||||
*
|
||||
* @return The network ID of the object.
|
||||
*/
|
||||
getNetworkId(): string | null {
|
||||
return this.networkId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the position of the object.
|
||||
*
|
||||
@@ -1472,7 +1507,8 @@ namespace gdjs {
|
||||
|
||||
//Forces :
|
||||
/**
|
||||
* Get a force from the garbage, or create a new force is garbage is empty.<br>
|
||||
* Get a force from the garbage, or create a new force is garbage is empty.
|
||||
*
|
||||
* To be used each time a force is created so as to avoid temporaries objects.
|
||||
*
|
||||
* @param x The x coordinates of the force
|
||||
@@ -1557,7 +1593,8 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a force oriented toward another object.<br>
|
||||
* Add a force oriented toward another object.
|
||||
*
|
||||
* (Shortcut for addForceTowardPosition)
|
||||
* @param object The target object
|
||||
* @param len The force length, in pixels.
|
||||
|
@@ -12,7 +12,13 @@ namespace gdjs {
|
||||
*/
|
||||
export class RuntimeScene extends gdjs.RuntimeInstanceContainer {
|
||||
_eventsFunction: null | ((runtimeScene: RuntimeScene) => void) = null;
|
||||
|
||||
_idToCallbackMap: null | Map<
|
||||
string,
|
||||
(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
asyncObjectsList: gdjs.LongLivedObjectsList
|
||||
) => void
|
||||
> = null;
|
||||
_renderer: RuntimeSceneRenderer;
|
||||
_debuggerRenderer: gdjs.DebuggerRenderer;
|
||||
_variables: gdjs.VariablesContainer;
|
||||
@@ -125,7 +131,13 @@ namespace gdjs {
|
||||
* @param sceneAndExtensionsData An object containing the scene data.
|
||||
* @see gdjs.RuntimeGame#getSceneAndExtensionsData
|
||||
*/
|
||||
loadFromScene(sceneAndExtensionsData: SceneAndExtensionsData | null) {
|
||||
loadFromScene(
|
||||
sceneAndExtensionsData: SceneAndExtensionsData | null,
|
||||
options?: {
|
||||
skipCreatingInstances?: boolean;
|
||||
skipStoppingSoundsOnStartup?: boolean;
|
||||
}
|
||||
) {
|
||||
if (!sceneAndExtensionsData) {
|
||||
logger.error('loadFromScene was called without a scene');
|
||||
return;
|
||||
@@ -183,15 +195,16 @@ namespace gdjs {
|
||||
this.registerObject(sceneData.objects[i]);
|
||||
}
|
||||
|
||||
//Create initial instances of objects
|
||||
this.createObjectsFrom(
|
||||
sceneData.instances,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
/*trackByPersistentUuid=*/
|
||||
true
|
||||
);
|
||||
// Create initial instances of objects
|
||||
if (!options || !options.skipCreatingInstances)
|
||||
this.createObjectsFrom(
|
||||
sceneData.instances,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
/*trackByPersistentUuid=*/
|
||||
true
|
||||
);
|
||||
|
||||
// Set up the default z order (for objects created from events)
|
||||
this._setLayerDefaultZOrders();
|
||||
@@ -209,7 +222,11 @@ namespace gdjs {
|
||||
for (let i = 0; i < gdjs.callbacksRuntimeSceneLoaded.length; ++i) {
|
||||
gdjs.callbacksRuntimeSceneLoaded[i](this);
|
||||
}
|
||||
if (sceneData.stopSoundsOnStartup && this._runtimeGame) {
|
||||
if (
|
||||
sceneData.stopSoundsOnStartup &&
|
||||
(!options || !options.skipStoppingSoundsOnStartup) &&
|
||||
this._runtimeGame
|
||||
) {
|
||||
this._runtimeGame.getSoundManager().clearAll();
|
||||
}
|
||||
this._isLoaded = true;
|
||||
@@ -336,6 +353,8 @@ namespace gdjs {
|
||||
const module = gdjs[sceneData.mangledName + 'Code'];
|
||||
if (module && module.func) {
|
||||
this._eventsFunction = module.func;
|
||||
this._idToCallbackMap =
|
||||
gdjs[sceneData.mangledName + 'Code'].idToCallbackMap;
|
||||
} else {
|
||||
setupWarningLogger.warn(
|
||||
'No function found for running logic of scene ' + this._name
|
||||
@@ -830,27 +849,67 @@ namespace gdjs {
|
||||
if (
|
||||
syncedPlayerNumber !== undefined &&
|
||||
syncedPlayerNumber !== 1 &&
|
||||
(!this.networkId ||
|
||||
(variablesNetworkSyncData.length === 0 &&
|
||||
!Object.keys(extensionsVariablesSyncData).length))
|
||||
!this.networkId
|
||||
) {
|
||||
// If we are getting sync data for a specific player,
|
||||
// and they are not the host, there is no sync data to send if:
|
||||
// - The scene has no networkId (it's either not a multiplayer scene or the scene is not yet networked).
|
||||
// - There are no variables to sync in the scene or extensions.
|
||||
// and they are not the host, there is no sync data to send if
|
||||
// the scene has no networkId (it's either not a multiplayer scene or the scene is not yet networked).
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
const networkSyncData: LayoutNetworkSyncData = {
|
||||
var: variablesNetworkSyncData,
|
||||
extVar: extensionsVariablesSyncData,
|
||||
id: this.getOrCreateNetworkId(),
|
||||
};
|
||||
if (syncOptions.syncSceneVisualProps) {
|
||||
networkSyncData.color = this._backgroundColor;
|
||||
}
|
||||
if (syncOptions.syncLayers) {
|
||||
const layersSyncData = {};
|
||||
for (const layerName in this._layers.items) {
|
||||
layersSyncData[layerName] =
|
||||
this._layers.items[layerName].getNetworkSyncData();
|
||||
}
|
||||
networkSyncData.layers = layersSyncData;
|
||||
}
|
||||
if (syncOptions.syncSceneTimers) {
|
||||
networkSyncData.time = this._timeManager.getNetworkSyncData();
|
||||
}
|
||||
if (syncOptions.syncOnceTriggers) {
|
||||
networkSyncData.once = this._onceTriggers.getNetworkSyncData();
|
||||
}
|
||||
|
||||
gdjs.callbacksRuntimeSceneGetSyncData.forEach((callback) => {
|
||||
callback(this, networkSyncData, syncOptions);
|
||||
});
|
||||
|
||||
if (syncOptions.syncAsyncTasks) {
|
||||
networkSyncData.async =
|
||||
this._asyncTasksManager.getNetworkSyncData(syncOptions);
|
||||
}
|
||||
|
||||
return networkSyncData;
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(syncData: LayoutNetworkSyncData) {
|
||||
updateFromNetworkSyncData(
|
||||
syncData: LayoutNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
if (syncData.color !== undefined) {
|
||||
this._backgroundColor = syncData.color;
|
||||
}
|
||||
if (syncData.layers) {
|
||||
for (const layerName in syncData.layers) {
|
||||
const layerData = syncData.layers[layerName];
|
||||
if (this.hasLayer(layerName)) {
|
||||
const layer = this.getLayer(layerName);
|
||||
layer.updateFromNetworkSyncData(layerData);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (syncData.var) {
|
||||
this._variables.updateFromNetworkSyncData(syncData.var);
|
||||
this._variables.updateFromNetworkSyncData(syncData.var, options);
|
||||
}
|
||||
if (syncData.extVar) {
|
||||
for (const extensionName in syncData.extVar) {
|
||||
@@ -862,11 +921,32 @@ namespace gdjs {
|
||||
this._variablesByExtensionName.get(extensionName);
|
||||
if (extensionVariables) {
|
||||
extensionVariables.updateFromNetworkSyncData(
|
||||
extensionVariablesData
|
||||
extensionVariablesData,
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (syncData.time) {
|
||||
this._timeManager.updateFromNetworkSyncData(syncData.time);
|
||||
}
|
||||
if (syncData.once) {
|
||||
this._onceTriggers.updateNetworkSyncData(syncData.once);
|
||||
}
|
||||
|
||||
gdjs.callbacksRuntimeSceneUpdateFromSyncData.forEach((callback) => {
|
||||
callback(this, syncData, options);
|
||||
});
|
||||
|
||||
// Sync Async last, as it might depend on other data.
|
||||
if (syncData.async && this._idToCallbackMap) {
|
||||
this._asyncTasksManager.updateFromNetworkSyncData(
|
||||
syncData.async,
|
||||
this._idToCallbackMap,
|
||||
this,
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getOrCreateNetworkId(): string {
|
||||
|
@@ -2,6 +2,17 @@ namespace gdjs {
|
||||
const logger = new gdjs.Logger('Scene stack');
|
||||
const debugLogger = new gdjs.Logger('Multiplayer - Debug');
|
||||
|
||||
interface PushSceneOptions {
|
||||
sceneName: string;
|
||||
externalLayoutName?: string;
|
||||
skipCreatingInstancesFromScene?: boolean;
|
||||
skipStoppingSoundsOnStartup?: boolean;
|
||||
}
|
||||
|
||||
interface ReplaceSceneOptions extends PushSceneOptions {
|
||||
clear: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hold the stack of scenes ({@link gdjs.RuntimeScene}) being played.
|
||||
*/
|
||||
@@ -121,15 +132,32 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the scene currently being played and start the new scene that is specified.
|
||||
* If `externalLayoutName` is set, also instantiate the objects from this external layout.
|
||||
* Pause the scene currently being played and start the new scene that is specified in `options.sceneName`.
|
||||
* If `options.externalLayoutName` is set, also instantiate the objects from this external layout.
|
||||
*
|
||||
* @param options Contains the scene name and optional external layout name to instantiate.
|
||||
* @param deprecatedExternalLayoutName Deprecated, use `options.externalLayoutName` instead.
|
||||
*/
|
||||
push(
|
||||
newSceneName: string,
|
||||
externalLayoutName?: string
|
||||
options: PushSceneOptions | string,
|
||||
deprecatedExternalLayoutName?: string
|
||||
): gdjs.RuntimeScene | null {
|
||||
this._throwIfDisposed();
|
||||
|
||||
const sceneName =
|
||||
typeof options === 'string' ? options : options.sceneName;
|
||||
const skipCreatingInstancesFromScene =
|
||||
typeof options === 'string'
|
||||
? false
|
||||
: options.skipCreatingInstancesFromScene;
|
||||
const skipStoppingSoundsOnStartup =
|
||||
typeof options === 'string'
|
||||
? false
|
||||
: options.skipStoppingSoundsOnStartup;
|
||||
const externalLayoutName =
|
||||
deprecatedExternalLayoutName ||
|
||||
(typeof options === 'string' ? undefined : options.externalLayoutName);
|
||||
|
||||
// Tell the scene it's being paused
|
||||
const currentScene = this._stack[this._stack.length - 1];
|
||||
if (currentScene) {
|
||||
@@ -138,36 +166,48 @@ namespace gdjs {
|
||||
|
||||
// Avoid a risk of displaying an intermediate loading screen
|
||||
// during 1 frame.
|
||||
if (this._runtimeGame.areSceneAssetsReady(newSceneName)) {
|
||||
return this._loadNewScene(newSceneName, externalLayoutName);
|
||||
if (this._runtimeGame.areSceneAssetsReady(sceneName)) {
|
||||
return this._loadNewScene({
|
||||
sceneName,
|
||||
externalLayoutName,
|
||||
skipCreatingInstancesFromScene,
|
||||
skipStoppingSoundsOnStartup,
|
||||
});
|
||||
}
|
||||
|
||||
this._isNextLayoutLoading = true;
|
||||
this._runtimeGame.loadSceneAssets(newSceneName).then(() => {
|
||||
this._loadNewScene(newSceneName);
|
||||
this._runtimeGame.loadSceneAssets(sceneName).then(() => {
|
||||
this._loadNewScene({
|
||||
sceneName,
|
||||
externalLayoutName,
|
||||
skipCreatingInstancesFromScene,
|
||||
skipStoppingSoundsOnStartup,
|
||||
});
|
||||
this._isNextLayoutLoading = false;
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private _loadNewScene(
|
||||
newSceneName: string,
|
||||
externalLayoutName?: string
|
||||
): gdjs.RuntimeScene {
|
||||
private _loadNewScene(options: PushSceneOptions): gdjs.RuntimeScene {
|
||||
this._throwIfDisposed();
|
||||
|
||||
// Load the new one
|
||||
const newScene = new gdjs.RuntimeScene(this._runtimeGame);
|
||||
newScene.loadFromScene(
|
||||
this._runtimeGame.getSceneAndExtensionsData(newSceneName)
|
||||
this._runtimeGame.getSceneAndExtensionsData(options.sceneName),
|
||||
{
|
||||
skipCreatingInstances: options.skipCreatingInstancesFromScene,
|
||||
skipStoppingSoundsOnStartup: options.skipStoppingSoundsOnStartup,
|
||||
}
|
||||
);
|
||||
this._wasFirstSceneLoaded = true;
|
||||
|
||||
// Optionally create the objects from an external layout.
|
||||
if (externalLayoutName) {
|
||||
const externalLayoutData =
|
||||
this._runtimeGame.getExternalLayoutData(externalLayoutName);
|
||||
if (options.externalLayoutName) {
|
||||
const externalLayoutData = this._runtimeGame.getExternalLayoutData(
|
||||
options.externalLayoutName
|
||||
);
|
||||
if (externalLayoutData) {
|
||||
newScene.createObjectsFrom(
|
||||
externalLayoutData.instances,
|
||||
@@ -184,10 +224,21 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the specified scene, replacing the one currently being played.
|
||||
* If `clear` is set to true, all running scenes are also removed from the stack of scenes.
|
||||
* Start the scene in `options.sceneName`, replacing the one currently being played.
|
||||
* If `options.clear` is set to true, all running scenes are also removed from the stack of scenes.
|
||||
*
|
||||
* @param options Contains the scene name and optional external layout name to instantiate.
|
||||
* @param deprecatedClear Deprecated, use `options.clear` instead.
|
||||
*/
|
||||
replace(newSceneName: string, clear?: boolean): gdjs.RuntimeScene | null {
|
||||
replace(
|
||||
options: ReplaceSceneOptions | string,
|
||||
deprecatedClear?: boolean
|
||||
): gdjs.RuntimeScene | null {
|
||||
const clear =
|
||||
deprecatedClear || typeof options === 'string' ? false : options.clear;
|
||||
const newSceneName =
|
||||
typeof options === 'string' ? options : options.sceneName;
|
||||
|
||||
this._throwIfDisposed();
|
||||
if (!!clear) {
|
||||
// Unload all the scenes
|
||||
@@ -206,7 +257,7 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.push(newSceneName);
|
||||
return this.push(options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,6 +278,11 @@ namespace gdjs {
|
||||
return this._wasFirstSceneLoaded;
|
||||
}
|
||||
|
||||
getAllScenes(): Array<gdjs.RuntimeScene> {
|
||||
this._throwIfDisposed();
|
||||
return this._stack;
|
||||
}
|
||||
|
||||
getAllSceneNames(): Array<string> {
|
||||
this._throwIfDisposed();
|
||||
return this._stack.map((scene) => scene.getName());
|
||||
@@ -267,7 +323,9 @@ namespace gdjs {
|
||||
this._sceneStackSyncDataToApply = sceneStackSyncData;
|
||||
}
|
||||
|
||||
applyUpdateFromNetworkSyncDataIfAny(): boolean {
|
||||
applyUpdateFromNetworkSyncDataIfAny(
|
||||
options?: UpdateFromNetworkSyncDataOptions
|
||||
): boolean {
|
||||
this._throwIfDisposed();
|
||||
const sceneStackSyncData = this._sceneStackSyncDataToApply;
|
||||
let hasMadeChangeToStack = false;
|
||||
@@ -275,6 +333,32 @@ namespace gdjs {
|
||||
|
||||
this._sceneStackSyncDataToApply = null;
|
||||
|
||||
const skipCreatingInstancesFromScene =
|
||||
!!options && !!options.preventInitialInstancesCreation;
|
||||
const skipStoppingSoundsOnStartup =
|
||||
!!options && !!options.preventSoundsStoppingOnStartup;
|
||||
|
||||
if (options && options.clearSceneStack) {
|
||||
while (this._stack.length !== 0) {
|
||||
let scene = this._stack.pop();
|
||||
if (scene) {
|
||||
scene.unloadScene();
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < sceneStackSyncData.length; ++i) {
|
||||
const sceneSyncData = sceneStackSyncData[i];
|
||||
const newScene = this.push({
|
||||
sceneName: sceneSyncData.name,
|
||||
skipCreatingInstancesFromScene,
|
||||
skipStoppingSoundsOnStartup,
|
||||
});
|
||||
if (newScene) {
|
||||
newScene.networkId = sceneSyncData.networkId;
|
||||
}
|
||||
}
|
||||
hasMadeChangeToStack = true;
|
||||
return hasMadeChangeToStack;
|
||||
}
|
||||
// If this method is called, we are a client.
|
||||
// We trust the host to be the source of truth for the scene stack.
|
||||
// So we loop through the scenes in the stack given by the host and either:
|
||||
@@ -284,12 +368,16 @@ namespace gdjs {
|
||||
for (let i = 0; i < sceneStackSyncData.length; ++i) {
|
||||
const sceneSyncData = sceneStackSyncData[i];
|
||||
const sceneAtThisPositionInOurStack = this._stack[i];
|
||||
|
||||
if (!sceneAtThisPositionInOurStack) {
|
||||
debugLogger.info(
|
||||
`Scene at position ${i} with name ${sceneSyncData.name} is missing from the stack, adding it.`
|
||||
);
|
||||
// We have fewer scenes in the stack than the host, let's add the scene.
|
||||
const newScene = this.push(sceneSyncData.name);
|
||||
const newScene = this.push({
|
||||
sceneName: sceneSyncData.name,
|
||||
skipCreatingInstancesFromScene,
|
||||
});
|
||||
if (newScene) {
|
||||
newScene.networkId = sceneSyncData.networkId;
|
||||
}
|
||||
@@ -306,10 +394,12 @@ namespace gdjs {
|
||||
);
|
||||
// The scene does not correspond to the scene at this position in our stack
|
||||
// Let's unload everything after this position to recreate the stack.
|
||||
const newScene = this.replace(
|
||||
sceneSyncData.name,
|
||||
true // Clear the stack
|
||||
);
|
||||
|
||||
const newScene = this.replace({
|
||||
sceneName: sceneSyncData.name,
|
||||
clear: true,
|
||||
skipCreatingInstancesFromScene,
|
||||
});
|
||||
if (newScene) {
|
||||
newScene.networkId = sceneSyncData.networkId;
|
||||
}
|
||||
@@ -349,10 +439,11 @@ namespace gdjs {
|
||||
// This can happen if the host has restarted the scene
|
||||
// We can't just update the networkId of the scene in the stack
|
||||
// We need to replace it with a new scene
|
||||
const newScene = this.replace(
|
||||
sceneSyncData.name,
|
||||
false // Don't clear the stack
|
||||
);
|
||||
const newScene = this.replace({
|
||||
sceneName: sceneSyncData.name,
|
||||
clear: false,
|
||||
skipCreatingInstancesFromScene,
|
||||
});
|
||||
if (newScene) {
|
||||
newScene.networkId = sceneSyncData.networkId;
|
||||
}
|
||||
|
@@ -115,9 +115,11 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): SpriteNetworkSyncData {
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): SpriteNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
...super.getNetworkSyncData(syncOptions),
|
||||
anim: this._animator.getNetworkSyncData(),
|
||||
ifx: this.isFlippedX(),
|
||||
ify: this.isFlippedY(),
|
||||
@@ -128,8 +130,11 @@ namespace gdjs {
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(newNetworkSyncData: SpriteNetworkSyncData) {
|
||||
super.updateFromNetworkSyncData(newNetworkSyncData);
|
||||
updateFromNetworkSyncData(
|
||||
newNetworkSyncData: SpriteNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
super.updateFromNetworkSyncData(newNetworkSyncData, options);
|
||||
if (newNetworkSyncData.ifx !== undefined) {
|
||||
this.flipX(newNetworkSyncData.ifx);
|
||||
}
|
||||
|
@@ -9,6 +9,15 @@ namespace gdjs {
|
||||
* frame, since the beginning of the scene and other time related values.
|
||||
* All durations are expressed in milliseconds.
|
||||
*/
|
||||
|
||||
declare interface TimeManagerSyncData {
|
||||
elapsedTime: float;
|
||||
timeScale: float;
|
||||
timeFromStart: float;
|
||||
firstFrame: boolean;
|
||||
timers: Hashtable<TimerNetworkSyncData>;
|
||||
firstUpdateDone: boolean;
|
||||
}
|
||||
export class TimeManager {
|
||||
_elapsedTime: float = 0;
|
||||
_timeScale: float = 1;
|
||||
@@ -59,6 +68,48 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
getNetworkSyncData(): TimeManagerSyncData {
|
||||
const timerNetworkSyncDatas = new Hashtable<TimerNetworkSyncData>();
|
||||
Object.entries(this._timers.items).forEach(([key, timer]) => {
|
||||
timerNetworkSyncDatas.put(key, timer.getNetworkSyncData());
|
||||
});
|
||||
|
||||
return {
|
||||
elapsedTime: this._elapsedTime,
|
||||
timeScale: this._timeScale,
|
||||
timeFromStart: this._timeFromStart,
|
||||
firstFrame: this._firstFrame,
|
||||
timers: timerNetworkSyncDatas,
|
||||
firstUpdateDone: this._firstUpdateDone,
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(syncData: TimeManagerSyncData): void {
|
||||
if (syncData.elapsedTime !== undefined) {
|
||||
this._elapsedTime = syncData.elapsedTime;
|
||||
}
|
||||
if (syncData.timeScale !== undefined) {
|
||||
this._timeScale = syncData.timeScale;
|
||||
}
|
||||
if (syncData.timeFromStart !== undefined) {
|
||||
this._timeFromStart = syncData.timeFromStart;
|
||||
}
|
||||
if (syncData.firstFrame !== undefined) {
|
||||
this._firstFrame = syncData.firstFrame;
|
||||
}
|
||||
if (syncData.timers !== undefined) {
|
||||
this._timers.clear();
|
||||
Object.entries(syncData.timers.items).forEach(([key, timerData]) => {
|
||||
const newTimer = new gdjs.Timer(timerData.name);
|
||||
newTimer.updateFromNetworkSyncData(timerData);
|
||||
this._timers.put(key, newTimer);
|
||||
});
|
||||
}
|
||||
|
||||
if (syncData.firstUpdateDone !== undefined) {
|
||||
this._firstUpdateDone = syncData.firstUpdateDone;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the time scale.
|
||||
* @return The time scale (positive, 1 is normal speed).
|
||||
|
@@ -77,6 +77,7 @@ namespace gdjs {
|
||||
|
||||
getNetworkSyncData(): TimerNetworkSyncData {
|
||||
return {
|
||||
name: this._name,
|
||||
time: this._time,
|
||||
paused: this._paused,
|
||||
};
|
||||
|
145
GDJS/Runtime/types/project-data.d.ts
vendored
@@ -42,6 +42,26 @@ declare type ObjectData = {
|
||||
declare type GetNetworkSyncDataOptions = {
|
||||
playerNumber?: number;
|
||||
isHost?: boolean;
|
||||
syncObjectIdentifiers?: boolean;
|
||||
syncAllVariables?: boolean;
|
||||
syncAllBehaviors?: boolean;
|
||||
syncSceneTimers?: boolean;
|
||||
syncOnceTriggers?: boolean;
|
||||
syncSounds?: boolean;
|
||||
syncTweens?: boolean;
|
||||
syncLayers?: boolean;
|
||||
syncAsyncTasks?: boolean;
|
||||
syncSceneVisualProps?: boolean;
|
||||
syncFullTileMaps?: boolean;
|
||||
};
|
||||
|
||||
declare type UpdateFromNetworkSyncDataOptions = {
|
||||
clearSceneStack?: boolean;
|
||||
preventInitialInstancesCreation?: boolean;
|
||||
preventSoundsStoppingOnStartup?: boolean;
|
||||
clearInputs?: boolean;
|
||||
keepControl?: boolean;
|
||||
ignoreVariableOwnership?: boolean;
|
||||
};
|
||||
|
||||
/** Object containing basic properties for all objects synchronizing over the network. */
|
||||
@@ -70,6 +90,10 @@ declare type BasicObjectNetworkSyncData = {
|
||||
pfx: number;
|
||||
/** Permanent force on Y */
|
||||
pfy: number;
|
||||
/** Name of the object */
|
||||
n?: string;
|
||||
/** The network ID of the instance. */
|
||||
networkId?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -91,6 +115,8 @@ declare interface ObjectNetworkSyncData extends BasicObjectNetworkSyncData {
|
||||
tim?: {
|
||||
[timerName: string]: TimerNetworkSyncData;
|
||||
};
|
||||
/** Tweens */
|
||||
tween?: TweenManagerNetworkSyncData;
|
||||
}
|
||||
|
||||
declare type ForceNetworkSyncData = {
|
||||
@@ -102,6 +128,7 @@ declare type ForceNetworkSyncData = {
|
||||
};
|
||||
|
||||
declare type TimerNetworkSyncData = {
|
||||
name: string;
|
||||
time: float;
|
||||
paused: boolean;
|
||||
};
|
||||
@@ -128,12 +155,30 @@ declare type VariableData = Readonly<{
|
||||
/** A variable child of a container. Those always have a name. */
|
||||
declare type RootVariableData = Omit<VariableData, 'name'> & { name: string };
|
||||
|
||||
declare type VariableNetworkSyncData = {
|
||||
name: string;
|
||||
declare type UnnamedVariableNetworkSyncData = {
|
||||
value: string | float | boolean;
|
||||
children?: VariableNetworkSyncData[];
|
||||
type: VariableType;
|
||||
owner: number;
|
||||
owner: number | null;
|
||||
};
|
||||
declare type VariableNetworkSyncData = UnnamedVariableNetworkSyncData & {
|
||||
name: string;
|
||||
};
|
||||
|
||||
declare type LayerNetworkSyncData = {
|
||||
timeScale: float;
|
||||
defaultZOrder: integer;
|
||||
hidden: boolean;
|
||||
effects: {
|
||||
[effectName: string]: EffectNetworkSyncData;
|
||||
};
|
||||
followBaseLayerCamera: boolean;
|
||||
clearColor: Array<integer>;
|
||||
cameraX: float;
|
||||
cameraY: float;
|
||||
cameraZ: float;
|
||||
cameraRotation: float;
|
||||
cameraZoom: float;
|
||||
};
|
||||
|
||||
/** Properties to set up a behavior. */
|
||||
@@ -149,6 +194,74 @@ declare type BehaviorNetworkSyncData = {
|
||||
props: any;
|
||||
};
|
||||
|
||||
declare type SceneTweenType =
|
||||
| 'layoutValue'
|
||||
| 'layerValue'
|
||||
| 'variable'
|
||||
| 'cameraZoom'
|
||||
| 'cameraRotation'
|
||||
| 'cameraPosition'
|
||||
| 'colorEffectProperty'
|
||||
| 'numberEffectProperty';
|
||||
declare type ObjectTweenType =
|
||||
| 'variable'
|
||||
| 'position'
|
||||
| 'positionX'
|
||||
| 'positionY'
|
||||
| 'positionZ'
|
||||
| 'width'
|
||||
| 'height'
|
||||
| 'depth'
|
||||
| 'angle'
|
||||
| 'rotationX'
|
||||
| 'rotationY'
|
||||
| 'scale'
|
||||
| 'scaleXY'
|
||||
| 'scaleX'
|
||||
| 'scaleY'
|
||||
| 'opacity'
|
||||
| 'characterSize'
|
||||
| 'numberEffectProperty'
|
||||
| 'colorEffectProperty'
|
||||
| 'objectColor'
|
||||
| 'objectColorHSL'
|
||||
| 'objectValue';
|
||||
|
||||
declare type TweenInformation = {
|
||||
type: SceneTweenType | ObjectTweenType;
|
||||
layerName?: string;
|
||||
variable?: Variable;
|
||||
effectName?: string;
|
||||
propertyName?: string;
|
||||
scaleFromCenterOfObject?: boolean;
|
||||
useHSLColorTransition?: boolean;
|
||||
destroyObjectWhenFinished?: boolean;
|
||||
};
|
||||
|
||||
declare type TweenInformationNetworkSyncData = Omit<
|
||||
TweenInformation,
|
||||
'variable' // When synced, a variable is replaced by its path
|
||||
> & { variablePath?: string[] };
|
||||
|
||||
declare type TweenInstanceNetworkSyncData<T> = {
|
||||
initialValue: T;
|
||||
targetedValue: T;
|
||||
elapsedTime: float;
|
||||
totalDuration: float;
|
||||
easingIdentifier: string;
|
||||
interpolationString: 'linear' | 'exponential';
|
||||
isPaused: boolean;
|
||||
tweenInformation: TweenInformationNetworkSyncData;
|
||||
};
|
||||
|
||||
declare type TweenManagerNetworkSyncData = {
|
||||
tweens: Record<
|
||||
string,
|
||||
| TweenInstanceNetworkSyncData<float>
|
||||
| TweenInstanceNetworkSyncData<Array<float>>
|
||||
>;
|
||||
};
|
||||
|
||||
declare interface GdVersionData {
|
||||
build: number;
|
||||
major: number;
|
||||
@@ -183,6 +296,14 @@ declare interface LayoutNetworkSyncData {
|
||||
extVar?: {
|
||||
[extensionName: string]: VariableNetworkSyncData[];
|
||||
};
|
||||
time?: TimeManagerSyncData;
|
||||
tween?: TweenManagerNetworkSyncData;
|
||||
once?: OnceTriggersSyncData;
|
||||
layers?: {
|
||||
[layerName: string]: LayerNetworkSyncData;
|
||||
};
|
||||
async?: AsyncTasksManagerNetworkSyncData;
|
||||
color?: integer;
|
||||
}
|
||||
|
||||
declare interface SceneStackSceneNetworkSyncData {
|
||||
@@ -192,12 +313,30 @@ declare interface SceneStackSceneNetworkSyncData {
|
||||
|
||||
declare type SceneStackNetworkSyncData = SceneStackSceneNetworkSyncData[];
|
||||
|
||||
declare type SoundSyncData = {
|
||||
loop: boolean;
|
||||
volume: float;
|
||||
rate: float;
|
||||
resourceName: string;
|
||||
seek: float;
|
||||
};
|
||||
declare type ChannelsSoundSyncData = Record<integer, SoundSyncData>;
|
||||
declare type SoundManagerSyncData = {
|
||||
globalVolume: float;
|
||||
cachedSpatialPosition: Record<number, [number, number, number]>;
|
||||
freeSounds: SoundSyncData[];
|
||||
freeMusics: SoundSyncData[];
|
||||
musics: ChannelsSoundSyncData;
|
||||
sounds: ChannelsSoundSyncData;
|
||||
};
|
||||
|
||||
declare interface GameNetworkSyncData {
|
||||
var?: VariableNetworkSyncData[];
|
||||
ss?: SceneStackNetworkSyncData;
|
||||
extVar?: {
|
||||
[extensionName: string]: VariableNetworkSyncData[];
|
||||
};
|
||||
sm?: SoundManagerSyncData;
|
||||
}
|
||||
|
||||
declare interface EventsFunctionsExtensionData {
|
||||
|
9
GDJS/Runtime/types/save-state.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
declare type SceneSaveState = {
|
||||
sceneData: LayoutNetworkSyncData;
|
||||
objectDatas: { [objectId: integer]: ObjectNetworkSyncData };
|
||||
};
|
||||
|
||||
declare type GameSaveState = {
|
||||
gameNetworkSyncData: GameNetworkSyncData;
|
||||
layoutNetworkSyncDatas: SceneSaveState[];
|
||||
};
|
@@ -9,7 +9,7 @@ namespace gdjs {
|
||||
/**
|
||||
* Children of a structure.
|
||||
*/
|
||||
type Children = Record<string, gdjs.Variable>;
|
||||
export type Children = Record<string, gdjs.Variable>;
|
||||
|
||||
/**
|
||||
* A Variable is an object storing a value (number or a string) or children variables.
|
||||
@@ -108,6 +108,171 @@ namespace gdjs {
|
||||
return target;
|
||||
}
|
||||
|
||||
static getVariableDataFromNetworkSyncData = (
|
||||
syncData: VariableNetworkSyncData
|
||||
): VariableData => {
|
||||
return {
|
||||
name: syncData.name,
|
||||
value: syncData.value,
|
||||
type: syncData.type,
|
||||
children: syncData.children
|
||||
? syncData.children.map((childSyncData) =>
|
||||
gdjs.Variable.getVariableDataFromNetworkSyncData(childSyncData)
|
||||
)
|
||||
: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): UnnamedVariableNetworkSyncData | undefined {
|
||||
const syncedPlayerNumber = syncOptions.playerNumber;
|
||||
const isHost = syncOptions.isHost;
|
||||
const variableOwner = this.getPlayerOwnership();
|
||||
if (
|
||||
// Variable undefined.
|
||||
this.isUndefinedInContainer() ||
|
||||
// If we force sync everything, we don't look at the ownership.
|
||||
(!syncOptions.syncAllVariables &&
|
||||
// Variable marked as not to be synchronized.
|
||||
(variableOwner === null ||
|
||||
// Getting sync data for a specific player:
|
||||
(syncedPlayerNumber !== undefined &&
|
||||
// Variable is owned by host but this player number is not the host.
|
||||
variableOwner === 0 &&
|
||||
!isHost) ||
|
||||
// Variable is owned by a player but not getting sync data for this player number.
|
||||
(variableOwner !== 0 && syncedPlayerNumber !== variableOwner)))
|
||||
) {
|
||||
// In those cases, the variable should not be synchronized.
|
||||
return;
|
||||
}
|
||||
|
||||
const variableType = this.getType();
|
||||
const variableValue =
|
||||
variableType === 'structure' || variableType === 'array'
|
||||
? ''
|
||||
: this.getValue();
|
||||
|
||||
return {
|
||||
value: variableValue,
|
||||
type: variableType,
|
||||
children: this.getStructureNetworkSyncData(this),
|
||||
owner: variableOwner,
|
||||
};
|
||||
}
|
||||
|
||||
// Structure variables can contain other variables, so we need to recursively
|
||||
// get the sync data for each child variable.
|
||||
getStructureNetworkSyncData(
|
||||
variable: gdjs.Variable
|
||||
): VariableNetworkSyncData[] | undefined {
|
||||
if (variable.getType() === 'array') {
|
||||
const allVariableNetworkSyncData: VariableNetworkSyncData[] = [];
|
||||
variable.getAllChildrenArray().forEach((childVariable) => {
|
||||
const childVariableType = childVariable.getType();
|
||||
const childVariableValue =
|
||||
childVariableType === 'structure' || childVariableType === 'array'
|
||||
? ''
|
||||
: childVariable.getValue();
|
||||
|
||||
const childVariableOwner = childVariable.getPlayerOwnership();
|
||||
if (
|
||||
// Variable undefined.
|
||||
childVariable.isUndefinedInContainer() ||
|
||||
// Variable marked as not to be synchronized.
|
||||
childVariableOwner === null
|
||||
) {
|
||||
// In those cases, the variable should not be synchronized.
|
||||
return;
|
||||
}
|
||||
|
||||
allVariableNetworkSyncData.push({
|
||||
name: '',
|
||||
value: childVariableValue,
|
||||
type: childVariableType,
|
||||
children: this.getStructureNetworkSyncData(childVariable),
|
||||
owner: childVariableOwner,
|
||||
});
|
||||
});
|
||||
|
||||
return allVariableNetworkSyncData;
|
||||
}
|
||||
|
||||
if (variable.getType() === 'structure') {
|
||||
const variableChildren = variable.getAllChildren();
|
||||
if (!variableChildren) return undefined;
|
||||
const allVariableNetworkSyncData: VariableNetworkSyncData[] = [];
|
||||
|
||||
Object.entries(variableChildren).forEach(
|
||||
([childVariableName, childVariable]) => {
|
||||
const childVariableType = childVariable.getType();
|
||||
const childVariableValue =
|
||||
childVariableType === 'structure' || childVariableType === 'array'
|
||||
? ''
|
||||
: childVariable.getValue();
|
||||
const childVariableOwner = childVariable.getPlayerOwnership();
|
||||
if (
|
||||
// Variable undefined.
|
||||
childVariable.isUndefinedInContainer() ||
|
||||
// Variable marked as not to be synchronized.
|
||||
childVariableOwner === null
|
||||
) {
|
||||
// In those cases, the variable should not be synchronized.
|
||||
return;
|
||||
}
|
||||
|
||||
allVariableNetworkSyncData.push({
|
||||
name: childVariableName,
|
||||
value: childVariableValue,
|
||||
type: childVariableType,
|
||||
children: this.getStructureNetworkSyncData(childVariable),
|
||||
owner: childVariableOwner,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return allVariableNetworkSyncData;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: VariableNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
// // If we receive an update for this variable for a different owner than the one we know about,
|
||||
// then 2 cases:
|
||||
// - If we are the owner of the variable, then ignore the message, we assume it's a late update message or a wrong one,
|
||||
// we are confident that we own this variable. (it may be reverted if we don't receive an acknowledgment in time)
|
||||
// - If we are not the owner of the variable, then assume that we missed the ownership change message, so update the variable's
|
||||
// ownership and then update the variable.
|
||||
const syncedVariableOwner = networkSyncData.owner;
|
||||
const variableData =
|
||||
gdjs.Variable.getVariableDataFromNetworkSyncData(networkSyncData);
|
||||
|
||||
if (!options.ignoreVariableOwnership) {
|
||||
const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();
|
||||
|
||||
const currentVariableOwner = this.getPlayerOwnership();
|
||||
if (currentPlayerNumber === currentVariableOwner) {
|
||||
// Variable owned by us, ignoring update message.
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
syncedVariableOwner &&
|
||||
syncedVariableOwner !== currentVariableOwner
|
||||
) {
|
||||
/// Variable owned by someone else on our game, changing ownership as part of the update event.
|
||||
this.setPlayerOwnership(syncedVariableOwner);
|
||||
}
|
||||
}
|
||||
|
||||
this.reinitialize(variableData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a JavaScript object into a value compatible
|
||||
* with GDevelop variables and store it inside this variable.
|
||||
@@ -148,7 +313,7 @@ namespace gdjs {
|
||||
" aren't supported by GDevelop variables, it will be reduced to that size."
|
||||
);
|
||||
// @ts-ignore
|
||||
variable.setNumber(parseInt(obj, 10));
|
||||
this.setNumber(parseInt(obj, 10));
|
||||
} else if (typeof obj === 'function') {
|
||||
logger.error(
|
||||
'Error while converting JS variable to GDevelop variable: Impossible to set variable value to a function.'
|
||||
|
@@ -228,183 +228,89 @@ namespace gdjs {
|
||||
return null;
|
||||
}
|
||||
|
||||
getVariablePathInContainerByLoopingThroughAllVariables(
|
||||
variable: gdjs.Variable,
|
||||
childrenToLookIn: Children | null = null
|
||||
): string[] | null {
|
||||
const variables = childrenToLookIn || this._variables.items;
|
||||
for (const variableName in variables) {
|
||||
if (variables.hasOwnProperty(variableName)) {
|
||||
const variableItem = variables[variableName];
|
||||
if (variableItem === variable) {
|
||||
return [variableName];
|
||||
} else if (variableItem.getType() === 'structure') {
|
||||
const variableItemChildren = variableItem.getAllChildren();
|
||||
const childPath =
|
||||
this.getVariablePathInContainerByLoopingThroughAllVariables(
|
||||
variable,
|
||||
variableItemChildren
|
||||
);
|
||||
if (childPath) {
|
||||
return [variableName, ...childPath];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
getVariableFromPath(variablePath: string[]): gdjs.Variable | null {
|
||||
let variableItems = this._variables.items;
|
||||
for (let i = 0; i < variablePath.length; i++) {
|
||||
const part = variablePath[i];
|
||||
const nextVariable = variableItems[part];
|
||||
if (!nextVariable) {
|
||||
return null;
|
||||
}
|
||||
if (i === variablePath.length - 1) {
|
||||
return nextVariable;
|
||||
}
|
||||
variableItems = nextVariable.getAllChildren();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static _deletedVars: Array<string | undefined> = [];
|
||||
|
||||
getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): VariableNetworkSyncData[] {
|
||||
const syncedPlayerNumber = syncOptions.playerNumber;
|
||||
const isHost = syncOptions.isHost;
|
||||
const networkSyncData: VariableNetworkSyncData[] = [];
|
||||
const variableNames = [];
|
||||
this._variables.keys(variableNames);
|
||||
variableNames.forEach((variableName) => {
|
||||
const variable = this._variables.get(variableName);
|
||||
const variableOwner = variable.getPlayerOwnership();
|
||||
if (
|
||||
// Variable undefined.
|
||||
variable.isUndefinedInContainer() ||
|
||||
// Variable marked as not to be synchronized.
|
||||
variableOwner === null ||
|
||||
// Getting sync data for a specific player:
|
||||
(syncedPlayerNumber !== undefined &&
|
||||
// Variable is owned by host but this player number is not the host.
|
||||
variableOwner === 0 &&
|
||||
!isHost) ||
|
||||
// Variable is owned by a player but not getting sync data for this player number.
|
||||
(variableOwner !== 0 && syncedPlayerNumber !== variableOwner)
|
||||
) {
|
||||
// In those cases, the variable should not be synchronized.
|
||||
return;
|
||||
const variableSyncData = variable.getNetworkSyncData(syncOptions);
|
||||
if (variableSyncData) {
|
||||
networkSyncData.push({
|
||||
name: variableName,
|
||||
...variableSyncData,
|
||||
});
|
||||
}
|
||||
|
||||
const variableType = variable.getType();
|
||||
const variableValue =
|
||||
variableType === 'structure' || variableType === 'array'
|
||||
? ''
|
||||
: variable.getValue();
|
||||
|
||||
networkSyncData.push({
|
||||
name: variableName,
|
||||
value: variableValue,
|
||||
type: variableType,
|
||||
children: this.getStructureNetworkSyncData(variable),
|
||||
owner: variableOwner,
|
||||
});
|
||||
});
|
||||
|
||||
return networkSyncData;
|
||||
}
|
||||
|
||||
// Structure variables can contain other variables, so we need to recursively
|
||||
// get the sync data for each child variable.
|
||||
getStructureNetworkSyncData(
|
||||
variable: gdjs.Variable
|
||||
): VariableNetworkSyncData[] | undefined {
|
||||
if (variable.getType() === 'array') {
|
||||
const allVariableNetworkSyncData: VariableNetworkSyncData[] = [];
|
||||
variable.getAllChildrenArray().forEach((childVariable) => {
|
||||
const childVariableType = childVariable.getType();
|
||||
const childVariableValue =
|
||||
childVariableType === 'structure' || childVariableType === 'array'
|
||||
? ''
|
||||
: childVariable.getValue();
|
||||
|
||||
const childVariableOwner = childVariable.getPlayerOwnership();
|
||||
if (
|
||||
// Variable undefined.
|
||||
childVariable.isUndefinedInContainer() ||
|
||||
// Variable marked as not to be synchronized.
|
||||
childVariableOwner === null
|
||||
) {
|
||||
// In those cases, the variable should not be synchronized.
|
||||
return;
|
||||
}
|
||||
|
||||
allVariableNetworkSyncData.push({
|
||||
name: '',
|
||||
value: childVariableValue,
|
||||
type: childVariableType,
|
||||
children: this.getStructureNetworkSyncData(childVariable),
|
||||
owner: childVariableOwner,
|
||||
});
|
||||
});
|
||||
|
||||
return allVariableNetworkSyncData;
|
||||
}
|
||||
|
||||
if (variable.getType() === 'structure') {
|
||||
const variableChildren = variable.getAllChildren();
|
||||
if (!variableChildren) return undefined;
|
||||
const allVariableNetworkSyncData: VariableNetworkSyncData[] = [];
|
||||
|
||||
Object.entries(variableChildren).forEach(
|
||||
([childVariableName, childVariable]) => {
|
||||
const childVariableType = childVariable.getType();
|
||||
const childVariableValue =
|
||||
childVariableType === 'structure' || childVariableType === 'array'
|
||||
? ''
|
||||
: childVariable.getValue();
|
||||
const childVariableOwner = childVariable.getPlayerOwnership();
|
||||
if (
|
||||
// Variable undefined.
|
||||
childVariable.isUndefinedInContainer() ||
|
||||
// Variable marked as not to be synchronized.
|
||||
childVariableOwner === null
|
||||
) {
|
||||
// In those cases, the variable should not be synchronized.
|
||||
return;
|
||||
}
|
||||
|
||||
allVariableNetworkSyncData.push({
|
||||
name: childVariableName,
|
||||
value: childVariableValue,
|
||||
type: childVariableType,
|
||||
children: this.getStructureNetworkSyncData(childVariable),
|
||||
owner: childVariableOwner,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return allVariableNetworkSyncData;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(networkSyncData: VariableNetworkSyncData[]) {
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: VariableNetworkSyncData[],
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
) {
|
||||
const that = this;
|
||||
for (let j = 0; j < networkSyncData.length; ++j) {
|
||||
const variableSyncData = networkSyncData[j];
|
||||
const variableData =
|
||||
that._getVariableDataFromNetworkSyncData(variableSyncData);
|
||||
gdjs.Variable.getVariableDataFromNetworkSyncData(variableSyncData);
|
||||
const variableName = variableData.name;
|
||||
if (!variableName) continue;
|
||||
|
||||
const variable = that.get(variableName);
|
||||
|
||||
// // If we receive an update for this variable for a different owner than the one we know about,
|
||||
// then 2 cases:
|
||||
// - If we are the owner of the variable, then ignore the message, we assume it's a late update message or a wrong one,
|
||||
// we are confident that we own this variable. (it may be reverted if we don't receive an acknowledgment in time)
|
||||
// - If we are not the owner of the variable, then assume that we missed the ownership change message, so update the variable's
|
||||
// ownership and then update the variable.
|
||||
const syncedVariableOwner = variableSyncData.owner;
|
||||
const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();
|
||||
const currentVariableOwner = variable.getPlayerOwnership();
|
||||
if (currentPlayerNumber === currentVariableOwner) {
|
||||
console.info(
|
||||
`Variable ${variableName} is owned by us ${gdjs.multiplayer.playerNumber}, ignoring update message from ${syncedVariableOwner}.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (syncedVariableOwner !== currentVariableOwner) {
|
||||
console.info(
|
||||
`Variable ${variableName} is owned by ${currentVariableOwner} on our game, changing ownership to ${syncedVariableOwner} as part of the update event.`
|
||||
);
|
||||
variable.setPlayerOwnership(syncedVariableOwner);
|
||||
}
|
||||
|
||||
variable.reinitialize(variableData);
|
||||
variable.updateFromNetworkSyncData(variableSyncData, options);
|
||||
}
|
||||
}
|
||||
|
||||
_getVariableDataFromNetworkSyncData(
|
||||
syncData: VariableNetworkSyncData
|
||||
): VariableData {
|
||||
return {
|
||||
name: syncData.name,
|
||||
value: syncData.value,
|
||||
type: syncData.type,
|
||||
children: syncData.children
|
||||
? syncData.children.map((childSyncData) =>
|
||||
this._getVariableDataFromNetworkSyncData(childSyncData)
|
||||
)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* "Bad" variable container, used by events when no other valid container can be found.
|
||||
* This container has no state and always returns the bad variable ( see VariablesContainer.badVariable ).
|
||||
@@ -440,18 +346,18 @@ namespace gdjs {
|
||||
updateFromNetworkSyncData: function () {
|
||||
return;
|
||||
},
|
||||
getStructureNetworkSyncData: function () {
|
||||
return undefined;
|
||||
},
|
||||
_getVariableDataFromNetworkSyncData: function () {
|
||||
return {};
|
||||
},
|
||||
hasVariable: function () {
|
||||
return false;
|
||||
},
|
||||
getVariableNameInContainerByLoopingThroughAllVariables: function () {
|
||||
return '';
|
||||
},
|
||||
getVariablePathInContainerByLoopingThroughAllVariables: function () {
|
||||
return [];
|
||||
},
|
||||
getVariableFromPath: function () {
|
||||
return null;
|
||||
},
|
||||
rebuildIndexFrom: function () {
|
||||
return;
|
||||
},
|
||||
@@ -572,6 +478,15 @@ namespace gdjs {
|
||||
disableSynchronization: function () {
|
||||
return;
|
||||
},
|
||||
getNetworkSyncData: function () {
|
||||
return undefined;
|
||||
},
|
||||
getStructureNetworkSyncData: function () {
|
||||
return [];
|
||||
},
|
||||
updateFromNetworkSyncData: function () {
|
||||
return;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
19546
GDJS/tests/games/SaveLoadExample/Save & load example.json
Normal file
BIN
GDJS/tests/games/SaveLoadExample/assets/Background.png
Normal file
After Width: | Height: | Size: 277 B |
After Width: | Height: | Size: 322 B |
After Width: | Height: | Size: 331 B |
After Width: | Height: | Size: 314 B |
After Width: | Height: | Size: 309 B |
After Width: | Height: | Size: 318 B |
After Width: | Height: | Size: 355 B |
After Width: | Height: | Size: 310 B |
BIN
GDJS/tests/games/SaveLoadExample/assets/Coins 8.aac
Normal file
BIN
GDJS/tests/games/SaveLoadExample/assets/Flag Blue.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.0 KiB |
BIN
GDJS/tests/games/SaveLoadExample/assets/France.png
Normal file
After Width: | Height: | Size: 367 B |
BIN
GDJS/tests/games/SaveLoadExample/assets/Large sign.png
Normal file
After Width: | Height: | Size: 232 B |
BIN
GDJS/tests/games/SaveLoadExample/assets/LightGlow.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
GDJS/tests/games/SaveLoadExample/assets/NewTiledSprite.png
Normal file
After Width: | Height: | Size: 259 B |
BIN
GDJS/tests/games/SaveLoadExample/assets/PickupCoin.wav
Normal file
BIN
GDJS/tests/games/SaveLoadExample/assets/StartingCoin.png
Normal file
After Width: | Height: | Size: 654 B |
BIN
GDJS/tests/games/SaveLoadExample/assets/StartingGround.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
GDJS/tests/games/SaveLoadExample/assets/StartingPlayer.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
GDJS/tests/games/SaveLoadExample/assets/Top arrow button.png
Normal file
After Width: | Height: | Size: 926 B |
BIN
GDJS/tests/games/SaveLoadExample/assets/stick.png
Normal file
After Width: | Height: | Size: 268 B |
@@ -128,7 +128,7 @@ export class EditableTileMap {
|
||||
return tileMap;
|
||||
}
|
||||
|
||||
toJSObject(): Object {
|
||||
toJSObject(): EditableTileMapAsJsObject {
|
||||
return {
|
||||
tileWidth: this.tileWidth,
|
||||
tileHeight: this.tileHeight,
|
||||
@@ -493,8 +493,12 @@ abstract class AbstractEditableLayer {
|
||||
this.visible = visible;
|
||||
}
|
||||
|
||||
toJSObject(): Object {
|
||||
return {};
|
||||
toJSObject(): EditableTileMapLayerAsJsObject {
|
||||
return {
|
||||
id: this.id,
|
||||
alpha: 0,
|
||||
tiles: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -660,7 +664,7 @@ export class EditableTileMapLayer extends AbstractEditableLayer {
|
||||
return layer;
|
||||
}
|
||||
|
||||
toJSObject(): Object {
|
||||
toJSObject(): EditableTileMapLayerAsJsObject {
|
||||
return {
|
||||
id: this.id,
|
||||
alpha: this._alpha,
|
||||
|
19
newIDE/app/public/res/actions/saveDown.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_492_72)">
|
||||
<path d="M0 24H24V5.14286L18.8571 0H7.3871H4.5H0V24Z" fill="#3DB3E4" />
|
||||
<path d="M20.5714 13.7143H3.42857V22.2857H20.5714V13.7143Z" fill="#404D9B" />
|
||||
<path d="M18.8571 10.2857V1.71429H11.1428V4.28572H14.2857L9.85714 10.2857H18.8571Z" fill="white" />
|
||||
<path
|
||||
d="M10.2857 -3.42857L10.2857 5.14286H12L7.71429 10.2857L3.42857 5.14286H5.14286V-3.42857H10.2857Z"
|
||||
fill="#404D9B" />
|
||||
<g opacity="0.1">
|
||||
<path d="M24 24L0 0V24H24Z" fill="black" />
|
||||
<path d="M24 5.14286L18.8571 0H0L24 24V5.14286Z" fill="white" />
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_492_72">
|
||||
<rect width="24" height="24" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 818 B |
19
newIDE/app/public/res/actions/saveUp.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_492_78)">
|
||||
<path d="M0 24H24V5.14286L18.8571 0H7.3871H4.5H0V24Z" fill="#3DB3E4" />
|
||||
<path
|
||||
d="M3.42857 15.4286L3.42857 6.85714H1.71429L6 1.71429L10.2857 6.85714H8.57143V15.4286H3.42857Z"
|
||||
fill="#404D9B" />
|
||||
<path d="M20.5714 13.7143H3.42857V22.2857H20.5714V13.7143Z" fill="#404D9B" />
|
||||
<path d="M18.8571 10.2857V1.71429H7.71429L12.8571 7.71429H10.2857V10.2857H18.8571Z" fill="white" />
|
||||
<g opacity="0.1">
|
||||
<path d="M24 24L0 0V24H24Z" fill="black" />
|
||||
<path d="M24 5.14286L18.8571 0H0L24 24V5.14286Z" fill="white" />
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_492_78">
|
||||
<rect width="24" height="24" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 815 B |
@@ -41,6 +41,12 @@ const jsExtensions = [
|
||||
extensionModule: require('GDJS-for-web-app-only/Runtime/Extensions/DeviceVibration/JsExtension.js'),
|
||||
objectsRenderingServiceModules: {},
|
||||
},
|
||||
{
|
||||
name: 'SaveState',
|
||||
// $FlowExpectedError - this path is ignored for Flow.
|
||||
extensionModule: require('GDJS-for-web-app-only/Runtime/Extensions/SaveState/JsExtension.js'),
|
||||
objectsRenderingServiceModules: {},
|
||||
},
|
||||
{
|
||||
name: 'DebuggerTools',
|
||||
// $FlowExpectedError - this path is ignored for Flow.
|
||||
|
@@ -31,7 +31,7 @@ type GetExpectedNumberOfJSExtensionModulesArguments = {|
|
||||
function getExpectedNumberOfJSExtensionModules(
|
||||
{ filterExamples } /*: GetExpectedNumberOfJSExtensionModulesArguments*/
|
||||
) /*:number*/ {
|
||||
return 28 + (filterExamples ? 0 : 1);
|
||||
return 29 + (filterExamples ? 0 : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -84,13 +84,13 @@ export const ExtensionLoadErrorDialog = ({
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderColumn>
|
||||
<TableHeaderColumn style={{ width: '25%' }}>
|
||||
<Trans>Extension name</Trans>
|
||||
</TableHeaderColumn>
|
||||
<TableHeaderColumn>
|
||||
<TableHeaderColumn style={{ width: '35%' }}>
|
||||
<Trans>Message</Trans>
|
||||
</TableHeaderColumn>
|
||||
<TableHeaderColumn>
|
||||
<TableHeaderColumn style={{ width: '40%' }}>
|
||||
<Trans>Raw error</Trans>
|
||||
</TableHeaderColumn>
|
||||
</TableRow>
|
||||
@@ -99,9 +99,19 @@ export const ExtensionLoadErrorDialog = ({
|
||||
{erroredExtensionLoadingResults.map(
|
||||
({ extensionModulePath, result: { message, rawError } }) => (
|
||||
<TableRow key={extensionModulePath}>
|
||||
<TableRowColumn>{extensionModulePath}</TableRowColumn>
|
||||
<TableRowColumn>{message}</TableRowColumn>
|
||||
<TableRowColumn>
|
||||
<TableRowColumn
|
||||
style={{ width: '25%', wordBreak: 'break-word' }}
|
||||
>
|
||||
{extensionModulePath}
|
||||
</TableRowColumn>
|
||||
<TableRowColumn
|
||||
style={{ width: '35%', wordBreak: 'break-word' }}
|
||||
>
|
||||
{message}
|
||||
</TableRowColumn>
|
||||
<TableRowColumn
|
||||
style={{ width: '40%', wordBreak: 'break-word' }}
|
||||
>
|
||||
{rawError && (
|
||||
<Text>
|
||||
{rawError.toString()}
|
||||
@@ -115,9 +125,13 @@ export const ExtensionLoadErrorDialog = ({
|
||||
)}
|
||||
{genericError ? (
|
||||
<TableRow>
|
||||
<TableRowColumn>-</TableRowColumn>
|
||||
<TableRowColumn>{genericError.toString()}</TableRowColumn>
|
||||
<TableRowColumn>-</TableRowColumn>
|
||||
<TableRowColumn style={{ width: '25%' }}>-</TableRowColumn>
|
||||
<TableRowColumn
|
||||
style={{ width: '35%', wordBreak: 'break-word' }}
|
||||
>
|
||||
{genericError.toString()}
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={{ width: '40%' }}>-</TableRowColumn>
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableBody>
|
||||
|