mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-12-25 23:13:35 +00:00
c7c84c21ef
* Add addstrip handling add SendBehaviorBlocksToClient serialization add id generation and auto updating add behaviorlisttoclient serialization * fix crash happened if you added state 0 and 6 and nothing in between * Section off code Use proper encapsulation to hide code away and only let specific objects do certain jobs. * Organize serialization Section off into operational chunks Write data at the level most appropriate * Remove and simplify BlockDefinitions Remove pointer usage for BlockDefinitions and move to optional. * ControlBehaviors: Add addaction handling * re-organization remove const from return value change to int from uint use generic methods to reduce code clutter * add strip ui position handling * add split strip functionality * fix issues fix an issue where if you were on an empty state, the server would allow you to remain on that state fix an issue where the ui would not open on the previously opened state fix an issue where deleting strips in order caused the wrong strips to be deleted * update how you remove behaviors from models * Add remove actions and rename * migrate actions * update action and rearrange strip * merge strips * add and move to inventory * Remove dead code * simplify code * nits and move finish MoveToInventory constify serialize further include path fixes use const, comments fix amf message Update ModelComponent.cpp replace operator subscript with at * Update ModelComponent.cpp * Update MigrateActionsMessage.h * const * Move to separate translation units * include amf3 its precompiled, but just in case
287 lines
12 KiB
C++
287 lines
12 KiB
C++
#include "ControlBehaviors.h"
|
|
|
|
#include "Amf3.h"
|
|
#include "Entity.h"
|
|
#include "Game.h"
|
|
#include "GameMessages.h"
|
|
#include "ModelComponent.h"
|
|
#include "../../dWorldServer/ObjectIDManager.h"
|
|
#include "Logger.h"
|
|
#include "BehaviorStates.h"
|
|
#include "AssetManager.h"
|
|
#include "BlockDefinition.h"
|
|
#include "User.h"
|
|
#include "tinyxml2.h"
|
|
#include "CDClientDatabase.h"
|
|
|
|
// Message includes
|
|
#include "Action.h"
|
|
#include "AddActionMessage.h"
|
|
#include "AddStripMessage.h"
|
|
#include "AddMessage.h"
|
|
#include "MigrateActionsMessage.h"
|
|
#include "MoveToInventoryMessage.h"
|
|
#include "MergeStripsMessage.h"
|
|
#include "RearrangeStripMessage.h"
|
|
#include "RemoveActionsMessage.h"
|
|
#include "RemoveStripMessage.h"
|
|
#include "RenameMessage.h"
|
|
#include "SplitStripMessage.h"
|
|
#include "UpdateActionMessage.h"
|
|
#include "UpdateStripUiMessage.h"
|
|
|
|
void ControlBehaviors::RequestUpdatedID(ControlBehaviorContext& context) {
|
|
ObjectIDManager::Instance()->RequestPersistentID(
|
|
[context](uint32_t persistentId) {
|
|
if (!context) {
|
|
LOG("Model to update behavior ID for is null. Cannot update ID.");
|
|
return;
|
|
}
|
|
// This updates the behavior ID of the behavior should this be a new behavior
|
|
AMFArrayValue args;
|
|
|
|
args.Insert("behaviorID", std::to_string(persistentId));
|
|
args.Insert("objectID", std::to_string(context.modelComponent->GetParent()->GetObjectID()));
|
|
|
|
GameMessages::SendUIMessageServerToSingleClient(context.modelOwner, context.modelOwner->GetSystemAddress(), "UpdateBehaviorID", args);
|
|
context.modelComponent->UpdatePendingBehaviorId(persistentId);
|
|
|
|
ControlBehaviors::Instance().SendBehaviorListToClient(context);
|
|
});
|
|
}
|
|
|
|
void ControlBehaviors::SendBehaviorListToClient(const ControlBehaviorContext& context) {
|
|
if (!context) return;
|
|
|
|
AMFArrayValue behaviorsToSerialize;
|
|
context.modelComponent->SendBehaviorListToClient(behaviorsToSerialize);
|
|
|
|
GameMessages::SendUIMessageServerToSingleClient(context.modelOwner, context.modelOwner->GetSystemAddress(), "UpdateBehaviorList", behaviorsToSerialize);
|
|
}
|
|
|
|
// TODO This is also supposed to serialize the state of the behaviors in progress but those aren't implemented yet
|
|
void ControlBehaviors::SendBehaviorBlocksToClient(ControlBehaviorContext& context) {
|
|
if (!context) return;
|
|
BehaviorMessageBase behaviorMsg(context.arguments);
|
|
|
|
context.modelComponent->VerifyBehaviors();
|
|
AMFArrayValue behavior;
|
|
context.modelComponent->SendBehaviorBlocksToClient(behaviorMsg.GetBehaviorId(), behavior);
|
|
GameMessages::SendUIMessageServerToSingleClient(context.modelOwner, context.modelOwner->GetSystemAddress(), "UpdateBehaviorBlocks", behavior);
|
|
}
|
|
|
|
void ControlBehaviors::UpdateAction(AMFArrayValue* arguments) {
|
|
UpdateActionMessage updateActionMessage(arguments);
|
|
auto blockDefinition = GetBlockInfo(updateActionMessage.GetAction().GetType());
|
|
|
|
if (!blockDefinition) {
|
|
LOG("Received undefined block type %s. Ignoring.", updateActionMessage.GetAction().GetType().c_str());
|
|
return;
|
|
}
|
|
|
|
if (updateActionMessage.GetAction().GetValueParameterString().size() > 0) {
|
|
if (updateActionMessage.GetAction().GetValueParameterString().size() < blockDefinition->GetMinimumValue() ||
|
|
updateActionMessage.GetAction().GetValueParameterString().size() > blockDefinition->GetMaximumValue()) {
|
|
LOG("Updated block %s is out of range. Ignoring update", updateActionMessage.GetAction().GetType().c_str());
|
|
return;
|
|
}
|
|
} else {
|
|
if (updateActionMessage.GetAction().GetValueParameterDouble() < blockDefinition->GetMinimumValue() ||
|
|
updateActionMessage.GetAction().GetValueParameterDouble() > blockDefinition->GetMaximumValue()) {
|
|
LOG("Updated block %s is out of range. Ignoring update", updateActionMessage.GetAction().GetType().c_str());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ControlBehaviors::ProcessCommand(Entity* modelEntity, const SystemAddress& sysAddr, AMFArrayValue* arguments, std::string command, Entity* modelOwner) {
|
|
if (!isInitialized || !modelEntity || !modelOwner || !arguments) return;
|
|
auto* modelComponent = modelEntity->GetComponent<ModelComponent>();
|
|
|
|
if (!modelComponent) return;
|
|
|
|
ControlBehaviorContext context(arguments, modelComponent, modelOwner);
|
|
|
|
if (command == "sendBehaviorListToClient") {
|
|
SendBehaviorListToClient(context);
|
|
} else if (command == "modelTypeChanged") {
|
|
auto* modelType = arguments->Get<double>("ModelType");
|
|
if (!modelType) return;
|
|
|
|
modelEntity->SetVar<int>(u"modelType", modelType->GetValue());
|
|
} else if (command == "toggleExecutionUpdates") {
|
|
// TODO
|
|
} else if (command == "addStrip") {
|
|
if (BehaviorMessageBase(context.arguments).IsDefaultBehaviorId()) RequestUpdatedID(context);
|
|
|
|
context.modelComponent->HandleControlBehaviorsMsg<AddStripMessage>(context.arguments);
|
|
} else if (command == "removeStrip") {
|
|
context.modelComponent->HandleControlBehaviorsMsg<RemoveStripMessage>(arguments);
|
|
} else if (command == "mergeStrips") {
|
|
context.modelComponent->HandleControlBehaviorsMsg<MergeStripsMessage>(arguments);
|
|
} else if (command == "splitStrip") {
|
|
context.modelComponent->HandleControlBehaviorsMsg<SplitStripMessage>(arguments);
|
|
} else if (command == "updateStripUI") {
|
|
context.modelComponent->HandleControlBehaviorsMsg<UpdateStripUiMessage>(arguments);
|
|
} else if (command == "addAction") {
|
|
context.modelComponent->HandleControlBehaviorsMsg<AddActionMessage>(arguments);
|
|
} else if (command == "migrateActions") {
|
|
context.modelComponent->HandleControlBehaviorsMsg<MigrateActionsMessage>(arguments);
|
|
} else if (command == "rearrangeStrip") {
|
|
context.modelComponent->HandleControlBehaviorsMsg<RearrangeStripMessage>(arguments);
|
|
} else if (command == "add") {
|
|
AddMessage msg(context.arguments);
|
|
context.modelComponent->AddBehavior(msg);
|
|
SendBehaviorListToClient(context);
|
|
} else if (command == "removeActions") {
|
|
context.modelComponent->HandleControlBehaviorsMsg<RemoveActionsMessage>(arguments);
|
|
} else if (command == "rename") {
|
|
context.modelComponent->HandleControlBehaviorsMsg<RenameMessage>(arguments);
|
|
|
|
// Send the list back to the client so the name is updated.
|
|
SendBehaviorListToClient(context);
|
|
} else if (command == "sendBehaviorBlocksToClient") {
|
|
SendBehaviorBlocksToClient(context);
|
|
} else if (command == "moveToInventory") {
|
|
MoveToInventoryMessage msg(arguments);
|
|
context.modelComponent->MoveToInventory(msg);
|
|
|
|
AMFArrayValue args;
|
|
args.Insert("BehaviorID", std::to_string(msg.GetBehaviorId()));
|
|
GameMessages::SendUIMessageServerToSingleClient(modelOwner, modelOwner->GetParentUser()->GetSystemAddress(), "BehaviorRemoved", args);
|
|
|
|
SendBehaviorListToClient(context);
|
|
} else if (command == "updateAction") {
|
|
context.modelComponent->HandleControlBehaviorsMsg<UpdateActionMessage>(arguments);
|
|
} else {
|
|
LOG("Unknown behavior command (%s)", command.c_str());
|
|
}
|
|
}
|
|
|
|
ControlBehaviors::ControlBehaviors() {
|
|
auto blocksBuffer = Game::assetManager->GetFile("ui\\ingame\\blocksdef.xml");
|
|
if (!blocksBuffer) {
|
|
LOG("Failed to open blocksdef.xml, property behaviors will be disabled for this zone! "
|
|
"(This is a necessary file for cheat detection and ensuring we do not send unexpected values to the client)");
|
|
return;
|
|
}
|
|
|
|
tinyxml2::XMLDocument m_Doc;
|
|
|
|
std::string read;
|
|
|
|
std::string buffer;
|
|
bool commentBlockStart = false;
|
|
while (std::getline(blocksBuffer, read)) {
|
|
// tinyxml2 should handle comment blocks but the client has one that fails the processing.
|
|
// This preprocessing just removes all comments from the read file out of an abundance of caution.
|
|
if (read.find("<!--") != std::string::npos) {
|
|
commentBlockStart = true;
|
|
}
|
|
if (read.find("-->") != std::string::npos) {
|
|
commentBlockStart = false;
|
|
continue;
|
|
}
|
|
if (!commentBlockStart) buffer += read;
|
|
}
|
|
|
|
auto ret = m_Doc.Parse(buffer.c_str());
|
|
if (ret == tinyxml2::XML_SUCCESS) {
|
|
LOG_DEBUG("Successfully parsed the blocksdef file!");
|
|
} else {
|
|
LOG("Failed to parse BlocksDef xmlData due to error %i!", ret);
|
|
return;
|
|
}
|
|
auto* blockLibrary = m_Doc.FirstChildElement();
|
|
if (!blockLibrary) {
|
|
LOG("No Block Library child element found.");
|
|
return;
|
|
}
|
|
|
|
// Now parse the blocksdef for the cheat detection server side.
|
|
// The client does these checks, but a bad actor can bypass the client checks
|
|
auto* blockSections = blockLibrary->FirstChildElement();
|
|
while (blockSections) {
|
|
auto* block = blockSections->FirstChildElement();
|
|
std::string blockName{};
|
|
while (block) {
|
|
blockName = block->Name();
|
|
|
|
auto& blockDefinition = blockTypes[blockName];
|
|
std::string name{};
|
|
std::string typeName{};
|
|
|
|
auto* argument = block->FirstChildElement("Argument");
|
|
if (argument) {
|
|
auto* defaultDefinition = argument->FirstChildElement("DefaultValue");
|
|
if (defaultDefinition) blockDefinition.SetDefaultValue(defaultDefinition->GetText());
|
|
|
|
auto* typeDefinition = argument->FirstChildElement("Type");
|
|
if (typeDefinition) typeName = typeDefinition->GetText();
|
|
|
|
auto* nameDefinition = argument->FirstChildElement("Name");
|
|
if (nameDefinition) name = nameDefinition->GetText();
|
|
|
|
// Now we parse the blocksdef file for the relevant information
|
|
if (typeName == "String") {
|
|
blockDefinition.SetMaximumValue(50); // The client has a hardcoded limit of 50 characters in a string field
|
|
} else if (typeName == "Float" || typeName == "Integer") {
|
|
auto* maximumDefinition = argument->FirstChildElement("Maximum");
|
|
if (maximumDefinition) blockDefinition.SetMaximumValue(std::stof(maximumDefinition->GetText()));
|
|
|
|
auto* minimumDefinition = argument->FirstChildElement("Minimum");
|
|
if (minimumDefinition) blockDefinition.SetMinimumValue(std::stof(minimumDefinition->GetText()));
|
|
} else if (typeName == "Enumeration") {
|
|
auto* values = argument->FirstChildElement("Values");
|
|
if (values) {
|
|
auto* value = values->FirstChildElement("Value");
|
|
while (value) {
|
|
if (value->GetText() == blockDefinition.GetDefaultValue()) blockDefinition.GetDefaultValue() = std::to_string(blockDefinition.GetMaximumValue());
|
|
blockDefinition.SetMaximumValue(blockDefinition.GetMaximumValue() + 1);
|
|
value = value->NextSiblingElement("Value");
|
|
}
|
|
blockDefinition.SetMaximumValue(blockDefinition.GetMaximumValue() - 1); // Maximum value is 0 indexed
|
|
} else {
|
|
values = argument->FirstChildElement("EnumerationSource");
|
|
if (!values) {
|
|
LOG("Failed to parse EnumerationSource from block (%s)", blockName.c_str());
|
|
continue;
|
|
}
|
|
|
|
auto* serviceNameNode = values->FirstChildElement("ServiceName");
|
|
if (!serviceNameNode) {
|
|
LOG("Failed to parse ServiceName from block (%s)", blockName.c_str());
|
|
continue;
|
|
}
|
|
|
|
std::string serviceName = serviceNameNode->GetText();
|
|
if (serviceName == "GetBehaviorSoundList") {
|
|
auto res = CDClientDatabase::ExecuteQuery("SELECT MAX(id) as countSounds FROM UGBehaviorSounds;");
|
|
blockDefinition.SetMaximumValue(res.getIntField("countSounds"));
|
|
blockDefinition.SetDefaultValue("0");
|
|
} else {
|
|
LOG("Unsupported Enumeration ServiceType (%s)", serviceName.c_str());
|
|
continue;
|
|
}
|
|
}
|
|
} else {
|
|
LOG("Unsupported block value type (%s)!", typeName.c_str());
|
|
continue;
|
|
}
|
|
}
|
|
block = block->NextSiblingElement();
|
|
}
|
|
blockSections = blockSections->NextSiblingElement();
|
|
}
|
|
isInitialized = true;
|
|
LOG_DEBUG("Created all base block classes");
|
|
for (auto& [name, block] : blockTypes) {
|
|
LOG_DEBUG("block name is %s default %s min %f max %f", name.c_str(), block.GetDefaultValue().c_str(), block.GetMinimumValue(), block.GetMaximumValue());
|
|
}
|
|
}
|
|
|
|
std::optional<BlockDefinition> ControlBehaviors::GetBlockInfo(const BlockName& blockName) {
|
|
auto blockDefinition = blockTypes.find(blockName);
|
|
return blockDefinition != blockTypes.end() ? std::optional(blockDefinition->second) : std::nullopt;
|
|
}
|