Compare commits

..

21 Commits

Author SHA1 Message Date
David Markowitz
e76d70c9c4 fix: get entity from manager vs from character
fixes a possible crash due to null entity
2025-06-26 00:35:45 -07:00
David Markowitz
1f580491c7 feat: add speed behavior (#1831) 2025-06-25 05:04:25 -04:00
David Markowitz
2618e9a864 fix: specifiy width of integer being written (#1830) 2025-06-25 00:58:11 -04:00
David Markowitz
0f0d0a6dee optimizations (#1829) 2025-06-24 22:13:48 -05:00
David Markowitz
f63a9a6bea fix: don't construct zone control twice on player loadin (#1828)
checked that the logs no longer have an error about zone control mis matched pointers

Update EntityManager.cpp
2025-06-24 22:03:13 -05:00
David Markowitz
f0f98a6108 fix: consuming items not decrementing mission progress (#1827)
tested that consuming water no longer leaves a mission unable to be completed
2025-06-24 22:01:59 -05:00
David Markowitz
4ed7bd6767 fix: some mail features (#1826) 2025-06-23 23:58:55 -04:00
David Markowitz
9f92f48a0f fix: models with multiple parts not being normalized properly (#1825)
Tested that models are migrated to the new format a-ok
Tested that the new logic works as expected.
Old code needs to be kept so that models in both states can be brought to modern standards
2025-06-23 03:08:16 -04:00
David Markowitz
48e3471831 fix: imaginite not being taken when starting shooting gallery (#1823) 2025-06-23 03:07:52 -04:00
David Markowitz
3c244cce27 fix: large inventories and inspect not printing objectID (#1824) 2025-06-23 03:07:34 -04:00
David Markowitz
8ba35be64d feat: add saving behaviors to the inventory (#1822)
* change behavior id to LWOOBJID

Convert behavior ID to LWOOBJID length

missed header

fix sqlite field names

sqlite brother

* feat: add saving behaviors to the inventory

consolidate copied code

consolidate copied code

Update ModelComponent.cpp

remove ability to save loot behaviors
2025-06-22 20:45:49 -05:00
David Markowitz
f7c9267ba4 change behavior id to LWOOBJID (#1821)
Convert behavior ID to LWOOBJID length

missed header

fix sqlite field names

sqlite brother
2025-06-22 15:05:09 -05:00
David Markowitz
b6e9d6872d fix: not checking OnChat block node (#1820) 2025-06-19 18:39:36 -07:00
David Markowitz
c83797984a check for null on property management instance (#1819) 2025-06-18 00:02:53 -05:00
David Markowitz
04487efa25 feat: add chat behaviors (#1818)
* Move in all directions is functional

* feat: add movement behaviors

the following behaviors will function
MoveRight
MoveLeft
FlyUp
FlyDown
MoveForward
MoveBackward

The behavior of the behaviors is once a move in an axis is active, that behavior must finish its movement before another one on that axis can do another movement on it.

* feat: add chat behaviors

Tested that models can correctly send chat messages, silently and publically.  Tested as well that the filter is used by the client for behaviors and added a security check to not broadcast messages that fail the check if words are removed.
2025-06-17 17:34:52 -05:00
David Markowitz
2f315d9288 feat: Movement behaviors (#1815)
* Move in all directions is functional

* feat: add movement behaviors

the following behaviors will function
MoveRight
MoveLeft
FlyUp
FlyDown
MoveForward
MoveBackward

The behavior of the behaviors is once a move in an axis is active, that behavior must finish its movement before another one on that axis can do another movement on it.
2025-06-11 12:52:15 -07:00
David Markowitz
6ae1c7a376 Add null check for character (#1814) 2025-06-10 12:41:08 -05:00
David Markowitz
c19ee04c8a fix: property behavior crashes (#1813) 2025-06-08 21:41:43 -05:00
David Markowitz
2858345269 fix: destroy enemies on entering build mode (#1812) 2025-06-08 21:41:19 -05:00
David Markowitz
37e14979a4 fix: winter race orbs (#1810)
Tested that script is loaded
2025-06-08 14:14:35 -05:00
David Markowitz
b509fd4f10 fix: Constructing player to themself (#1808)
tested that I can see other players leave and join a world and that i no longer see a white screen when loading between worlds
2025-06-07 18:30:22 -05:00
76 changed files with 1202 additions and 523 deletions

View File

@@ -20,6 +20,7 @@ set(DCOMMON_SOURCES
"TinyXmlUtils.cpp" "TinyXmlUtils.cpp"
"Sd0.cpp" "Sd0.cpp"
"Lxfml.cpp" "Lxfml.cpp"
"LxfmlBugged.cpp"
) )
# Workaround for compiler bug where the optimized code could result in a memcpy of 0 bytes, even though that isnt possible. # Workaround for compiler bug where the optimized code could result in a memcpy of 0 bytes, even though that isnt possible.

View File

@@ -4,7 +4,7 @@
#include <assert.h> #include <assert.h>
#ifdef _DEBUG #ifdef _DEBUG
# define DluAssert(expression) do { assert(expression) } while(0) # define DluAssert(expression) do { assert(expression); } while(0)
#else #else
# define DluAssert(expression) # define DluAssert(expression)
#endif #endif

View File

@@ -27,7 +27,7 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
// First get all the positions of bricks // First get all the positions of bricks
for (const auto& brick : lxfml["Bricks"]) { for (const auto& brick : lxfml["Bricks"]) {
const auto* part = brick.FirstChildElement("Part"); const auto* part = brick.FirstChildElement("Part");
if (part) { while (part) {
const auto* bone = part->FirstChildElement("Bone"); const auto* bone = part->FirstChildElement("Bone");
if (bone) { if (bone) {
auto* transformation = bone->Attribute("transformation"); auto* transformation = bone->Attribute("transformation");
@@ -36,6 +36,7 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
if (refID) transformations[refID] = transformation; if (refID) transformations[refID] = transformation;
} }
} }
part = part->NextSiblingElement("Part");
} }
} }
@@ -92,7 +93,7 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
// Finally write the new transformation back into the lxfml // Finally write the new transformation back into the lxfml
for (auto& brick : lxfml["Bricks"]) { for (auto& brick : lxfml["Bricks"]) {
auto* part = brick.FirstChildElement("Part"); auto* part = brick.FirstChildElement("Part");
if (part) { while (part) {
auto* bone = part->FirstChildElement("Bone"); auto* bone = part->FirstChildElement("Bone");
if (bone) { if (bone) {
auto* transformation = bone->Attribute("transformation"); auto* transformation = bone->Attribute("transformation");
@@ -103,6 +104,7 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
} }
} }
} }
part = part->NextSiblingElement("Part");
} }
} }

View File

@@ -18,6 +18,10 @@ namespace Lxfml {
// Normalizes a LXFML model to be positioned relative to its local 0, 0, 0 rather than a game worlds 0, 0, 0. // Normalizes a LXFML model to be positioned relative to its local 0, 0, 0 rather than a game worlds 0, 0, 0.
// Returns a struct of its new center and the updated LXFML containing these edits. // Returns a struct of its new center and the updated LXFML containing these edits.
[[nodiscard]] Result NormalizePosition(const std::string_view data); [[nodiscard]] Result NormalizePosition(const std::string_view data);
// these are only for the migrations due to a bug in one of the implementations.
[[nodiscard]] Result NormalizePositionOnlyFirstPart(const std::string_view data);
[[nodiscard]] Result NormalizePositionAfterFirstPart(const std::string_view data, const NiPoint3& position);
}; };
#endif //!LXFML_H #endif //!LXFML_H

210
dCommon/LxfmlBugged.cpp Normal file
View File

@@ -0,0 +1,210 @@
#include "Lxfml.h"
#include "GeneralUtils.h"
#include "StringifiedEnum.h"
#include "TinyXmlUtils.h"
#include <ranges>
// this file should not be touched
Lxfml::Result Lxfml::NormalizePositionOnlyFirstPart(const std::string_view data) {
Result toReturn;
tinyxml2::XMLDocument doc;
const auto err = doc.Parse(data.data());
if (err != tinyxml2::XML_SUCCESS) {
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
return toReturn;
}
TinyXmlUtils::DocumentReader reader(doc);
std::map<std::string/* refID */, std::string> transformations;
auto lxfml = reader["LXFML"];
if (!lxfml) {
LOG("Failed to find LXFML element.");
return toReturn;
}
// First get all the positions of bricks
for (const auto& brick : lxfml["Bricks"]) {
const auto* part = brick.FirstChildElement("Part");
if (part) {
const auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) transformations[refID] = transformation;
}
}
}
}
// These points are well out of bounds for an actual player
NiPoint3 lowest{ 10'000.0f, 10'000.0f, 10'000.0f };
NiPoint3 highest{ -10'000.0f, -10'000.0f, -10'000.0f };
// Calculate the lowest and highest points on the entire model
for (const auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) {
LOG("Not enough in the split?");
continue;
}
auto x = GeneralUtils::TryParse<float>(split[9]).value();
auto y = GeneralUtils::TryParse<float>(split[10]).value();
auto z = GeneralUtils::TryParse<float>(split[11]).value();
if (x < lowest.x) lowest.x = x;
if (y < lowest.y) lowest.y = y;
if (z < lowest.z) lowest.z = z;
if (highest.x < x) highest.x = x;
if (highest.y < y) highest.y = y;
if (highest.z < z) highest.z = z;
}
auto delta = (highest - lowest) / 2.0f;
auto newRootPos = lowest + delta;
// Clamp the Y to the lowest point on the model
newRootPos.y = lowest.y;
// Adjust all positions to account for the new origin
for (auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) {
LOG("Not enough in the split?");
continue;
}
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x;
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y;
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z;
std::stringstream stream;
for (int i = 0; i < 9; i++) {
stream << split[i];
stream << ',';
}
stream << x << ',' << y << ',' << z;
transformation = stream.str();
}
// Finally write the new transformation back into the lxfml
for (auto& brick : lxfml["Bricks"]) {
auto* part = brick.FirstChildElement("Part");
if (part) {
auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) {
bone->SetAttribute("transformation", transformations[refID].c_str());
}
}
}
}
}
tinyxml2::XMLPrinter printer;
doc.Print(&printer);
toReturn.lxfml = printer.CStr();
toReturn.center = newRootPos;
return toReturn;
}
Lxfml::Result Lxfml::NormalizePositionAfterFirstPart(const std::string_view data, const NiPoint3& position) {
Result toReturn;
tinyxml2::XMLDocument doc;
const auto err = doc.Parse(data.data());
if (err != tinyxml2::XML_SUCCESS) {
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
return toReturn;
}
TinyXmlUtils::DocumentReader reader(doc);
std::map<std::string/* refID */, std::string> transformations;
auto lxfml = reader["LXFML"];
if (!lxfml) {
LOG("Failed to find LXFML element.");
return toReturn;
}
// First get all the positions of bricks
for (const auto& brick : lxfml["Bricks"]) {
const auto* part = brick.FirstChildElement("Part");
bool firstPart = true;
while (part) {
if (firstPart) {
firstPart = false;
} else {
LOG("Found extra bricks");
const auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) transformations[refID] = transformation;
}
}
}
part = part->NextSiblingElement("Part");
}
}
auto newRootPos = position;
// Adjust all positions to account for the new origin
for (auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) {
LOG("Not enough in the split?");
continue;
}
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x;
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y;
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z;
std::stringstream stream;
for (int i = 0; i < 9; i++) {
stream << split[i];
stream << ',';
}
stream << x << ',' << y << ',' << z;
transformation = stream.str();
}
// Finally write the new transformation back into the lxfml
for (auto& brick : lxfml["Bricks"]) {
auto* part = brick.FirstChildElement("Part");
bool firstPart = true;
while (part) {
if (firstPart) {
firstPart = false;
} else {
auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) {
bone->SetAttribute("transformation", transformations[refID].c_str());
}
}
}
}
part = part->NextSiblingElement("Part");
}
}
tinyxml2::XMLPrinter printer;
doc.Print(&printer);
toReturn.lxfml = printer.CStr();
toReturn.center = newRootPos;
return toReturn;
}

View File

@@ -8,15 +8,15 @@
class IBehaviors { class IBehaviors {
public: public:
struct Info { struct Info {
int32_t behaviorId{}; LWOOBJID behaviorId{};
uint32_t characterId{}; uint32_t characterId{};
std::string behaviorInfo; std::string behaviorInfo;
}; };
// This Add also takes care of updating if it exists. // This Add also takes care of updating if it exists.
virtual void AddBehavior(const Info& info) = 0; virtual void AddBehavior(const Info& info) = 0;
virtual std::string GetBehavior(const int32_t behaviorId) = 0; virtual std::string GetBehavior(const LWOOBJID behaviorId) = 0;
virtual void RemoveBehavior(const int32_t behaviorId) = 0; virtual void RemoveBehavior(const LWOOBJID behaviorId) = 0;
}; };
#endif //!IBEHAVIORS_H #endif //!IBEHAVIORS_H

View File

@@ -17,7 +17,7 @@ public:
LWOOBJID id{}; LWOOBJID id{};
LOT lot{}; LOT lot{};
uint32_t ugcId{}; uint32_t ugcId{};
std::array<int32_t, 5> behaviors{}; std::array<LWOOBJID, 5> behaviors{};
}; };
// Inserts a new UGC model into the database. // Inserts a new UGC model into the database.
@@ -34,9 +34,9 @@ public:
virtual void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) = 0; virtual void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) = 0;
// Update the model position and rotation for the given property id. // Update the model position and rotation for the given property id.
virtual void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) = 0; virtual void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) = 0;
virtual void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<int32_t, 5> behaviorIDs) { virtual void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<LWOOBJID, 5> behaviorIDs) {
std::array<std::pair<int32_t, std::string>, 5> behaviors; std::array<std::pair<LWOOBJID, std::string>, 5> behaviors;
for (int32_t i = 0; i < behaviors.size(); i++) behaviors[i].first = behaviorIDs[i]; for (int32_t i = 0; i < behaviors.size(); i++) behaviors[i].first = behaviorIDs[i];
UpdateModel(modelID, position, rotation, behaviors); UpdateModel(modelID, position, rotation, behaviors);
} }

View File

@@ -75,7 +75,7 @@ public:
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override; std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override; void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override; void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override; void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) override;
void RemoveModel(const LWOOBJID& modelId) override; void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override; void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override; void InsertNewBugReport(const IBugReports::Info& info) override;
@@ -110,8 +110,8 @@ public:
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override; void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override; std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
void AddBehavior(const IBehaviors::Info& info) override; void AddBehavior(const IBehaviors::Info& info) override;
std::string GetBehavior(const int32_t behaviorId) override; std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const int32_t characterId) override; void RemoveBehavior(const LWOOBJID characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override; std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override; std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;

View File

@@ -9,11 +9,11 @@ void MySQLDatabase::AddBehavior(const IBehaviors::Info& info) {
); );
} }
void MySQLDatabase::RemoveBehavior(const int32_t behaviorId) { void MySQLDatabase::RemoveBehavior(const LWOOBJID behaviorId) {
ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId); ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId);
} }
std::string MySQLDatabase::GetBehavior(const int32_t behaviorId) { std::string MySQLDatabase::GetBehavior(const LWOOBJID behaviorId) {
auto result = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId); auto result = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId);
return result->next() ? result->getString("behavior_info").c_str() : ""; return result->next() ? result->getString("behavior_info").c_str() : "";
} }

View File

@@ -20,11 +20,11 @@ std::vector<IPropertyContents::Model> MySQLDatabase::GetPropertyModels(const LWO
model.rotation.y = result->getFloat("ry"); model.rotation.y = result->getFloat("ry");
model.rotation.z = result->getFloat("rz"); model.rotation.z = result->getFloat("rz");
model.ugcId = result->getUInt64("ugc_id"); model.ugcId = result->getUInt64("ugc_id");
model.behaviors[0] = result->getInt("behavior_1"); model.behaviors[0] = result->getUInt64("behavior_1");
model.behaviors[1] = result->getInt("behavior_2"); model.behaviors[1] = result->getUInt64("behavior_2");
model.behaviors[2] = result->getInt("behavior_3"); model.behaviors[2] = result->getUInt64("behavior_3");
model.behaviors[3] = result->getInt("behavior_4"); model.behaviors[3] = result->getUInt64("behavior_4");
model.behaviors[4] = result->getInt("behavior_5"); model.behaviors[4] = result->getUInt64("behavior_5");
toReturn.push_back(std::move(model)); toReturn.push_back(std::move(model));
} }
@@ -52,7 +52,7 @@ void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPr
} }
} }
void MySQLDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) { void MySQLDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) {
ExecuteUpdate( ExecuteUpdate(
"UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, " "UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, "
"behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;", "behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;",
@@ -79,11 +79,11 @@ IPropertyContents::Model MySQLDatabase::GetModel(const LWOOBJID modelID) {
model.rotation.y = result->getFloat("ry"); model.rotation.y = result->getFloat("ry");
model.rotation.z = result->getFloat("rz"); model.rotation.z = result->getFloat("rz");
model.ugcId = result->getUInt64("ugc_id"); model.ugcId = result->getUInt64("ugc_id");
model.behaviors[0] = result->getInt("behavior_1"); model.behaviors[0] = result->getUInt64("behavior_1");
model.behaviors[1] = result->getInt("behavior_2"); model.behaviors[1] = result->getUInt64("behavior_2");
model.behaviors[2] = result->getInt("behavior_3"); model.behaviors[2] = result->getUInt64("behavior_3");
model.behaviors[3] = result->getInt("behavior_4"); model.behaviors[3] = result->getUInt64("behavior_4");
model.behaviors[4] = result->getInt("behavior_5"); model.behaviors[4] = result->getUInt64("behavior_5");
} }
return model; return model;

View File

@@ -73,7 +73,7 @@ public:
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override; std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override; void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override; void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override; void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) override;
void RemoveModel(const LWOOBJID& modelId) override; void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override; void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override; void InsertNewBugReport(const IBugReports::Info& info) override;
@@ -108,8 +108,8 @@ public:
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override; void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override; std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
void AddBehavior(const IBehaviors::Info& info) override; void AddBehavior(const IBehaviors::Info& info) override;
std::string GetBehavior(const int32_t behaviorId) override; std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const int32_t characterId) override; void RemoveBehavior(const LWOOBJID characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override; std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override; std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;

View File

@@ -9,11 +9,11 @@ void SQLiteDatabase::AddBehavior(const IBehaviors::Info& info) {
); );
} }
void SQLiteDatabase::RemoveBehavior(const int32_t behaviorId) { void SQLiteDatabase::RemoveBehavior(const LWOOBJID behaviorId) {
ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId); ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId);
} }
std::string SQLiteDatabase::GetBehavior(const int32_t behaviorId) { std::string SQLiteDatabase::GetBehavior(const LWOOBJID behaviorId) {
auto [_, result] = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId); auto [_, result] = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId);
return !result.eof() ? result.getStringField("behavior_info") : ""; return !result.eof() ? result.getStringField("behavior_info") : "";
} }

View File

@@ -19,11 +19,11 @@ std::vector<IPropertyContents::Model> SQLiteDatabase::GetPropertyModels(const LW
model.rotation.y = result.getFloatField("ry"); model.rotation.y = result.getFloatField("ry");
model.rotation.z = result.getFloatField("rz"); model.rotation.z = result.getFloatField("rz");
model.ugcId = result.getInt64Field("ugc_id"); model.ugcId = result.getInt64Field("ugc_id");
model.behaviors[0] = result.getIntField("behavior_1"); model.behaviors[0] = result.getInt64Field("behavior_1");
model.behaviors[1] = result.getIntField("behavior_2"); model.behaviors[1] = result.getInt64Field("behavior_2");
model.behaviors[2] = result.getIntField("behavior_3"); model.behaviors[2] = result.getInt64Field("behavior_3");
model.behaviors[3] = result.getIntField("behavior_4"); model.behaviors[3] = result.getInt64Field("behavior_4");
model.behaviors[4] = result.getIntField("behavior_5"); model.behaviors[4] = result.getInt64Field("behavior_5");
toReturn.push_back(std::move(model)); toReturn.push_back(std::move(model));
result.nextRow(); result.nextRow();
@@ -52,7 +52,7 @@ void SQLiteDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IP
} }
} }
void SQLiteDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) { void SQLiteDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) {
ExecuteUpdate( ExecuteUpdate(
"UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, " "UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, "
"behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;", "behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;",
@@ -80,11 +80,11 @@ IPropertyContents::Model SQLiteDatabase::GetModel(const LWOOBJID modelID) {
model.rotation.y = result.getFloatField("ry"); model.rotation.y = result.getFloatField("ry");
model.rotation.z = result.getFloatField("rz"); model.rotation.z = result.getFloatField("rz");
model.ugcId = result.getInt64Field("ugc_id"); model.ugcId = result.getInt64Field("ugc_id");
model.behaviors[0] = result.getIntField("behavior_1"); model.behaviors[0] = result.getInt64Field("behavior_1");
model.behaviors[1] = result.getIntField("behavior_2"); model.behaviors[1] = result.getInt64Field("behavior_2");
model.behaviors[2] = result.getIntField("behavior_3"); model.behaviors[2] = result.getInt64Field("behavior_3");
model.behaviors[3] = result.getIntField("behavior_4"); model.behaviors[3] = result.getInt64Field("behavior_4");
model.behaviors[4] = result.getIntField("behavior_5"); model.behaviors[4] = result.getInt64Field("behavior_5");
} while (result.nextRow()); } while (result.nextRow());
} }

View File

@@ -168,7 +168,7 @@ void TestSQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const I
} }
void TestSQLDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) { void TestSQLDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) {
} }
@@ -292,11 +292,11 @@ void TestSQLDatabase::AddBehavior(const IBehaviors::Info& info) {
} }
std::string TestSQLDatabase::GetBehavior(const int32_t behaviorId) { std::string TestSQLDatabase::GetBehavior(const LWOOBJID behaviorId) {
return {}; return {};
} }
void TestSQLDatabase::RemoveBehavior(const int32_t behaviorId) { void TestSQLDatabase::RemoveBehavior(const LWOOBJID behaviorId) {
} }

View File

@@ -52,7 +52,7 @@ class TestSQLDatabase : public GameDatabase {
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override; std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override; void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override; void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override; void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) override;
void RemoveModel(const LWOOBJID& modelId) override; void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override; void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override; void InsertNewBugReport(const IBugReports::Info& info) override;
@@ -87,8 +87,8 @@ class TestSQLDatabase : public GameDatabase {
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override; void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override; std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
void AddBehavior(const IBehaviors::Info& info) override; void AddBehavior(const IBehaviors::Info& info) override;
std::string GetBehavior(const int32_t behaviorId) override; std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const int32_t behaviorId) override; void RemoveBehavior(const LWOOBJID behaviorId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override { return {}; }; std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override { return {}; };
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override { return {}; }; std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override { return {}; };

View File

@@ -47,6 +47,7 @@ void MigrationRunner::RunMigrations() {
std::string finalSQL = ""; std::string finalSQL = "";
bool runSd0Migrations = false; bool runSd0Migrations = false;
bool runNormalizeMigrations = false; bool runNormalizeMigrations = false;
bool runNormalizeAfterFirstPartMigrations = false;
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/" / migrationFolder).string())) { for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/" / migrationFolder).string())) {
auto migration = LoadMigration("dlu/" + migrationFolder + "/", entry); auto migration = LoadMigration("dlu/" + migrationFolder + "/", entry);
@@ -61,6 +62,8 @@ void MigrationRunner::RunMigrations() {
runSd0Migrations = true; runSd0Migrations = true;
} else if (migration.name.ends_with("_normalize_model_positions.sql")) { } else if (migration.name.ends_with("_normalize_model_positions.sql")) {
runNormalizeMigrations = true; runNormalizeMigrations = true;
} else if (migration.name.ends_with("_normalize_model_positions_after_first_part.sql")) {
runNormalizeAfterFirstPartMigrations = true;
} else { } else {
finalSQL.append(migration.data.c_str()); finalSQL.append(migration.data.c_str());
} }
@@ -68,7 +71,7 @@ void MigrationRunner::RunMigrations() {
Database::Get()->InsertMigration(migration.name); Database::Get()->InsertMigration(migration.name);
} }
if (finalSQL.empty() && !runSd0Migrations && !runNormalizeMigrations) { if (finalSQL.empty() && !runSd0Migrations && !runNormalizeMigrations && !runNormalizeAfterFirstPartMigrations) {
LOG("Server database is up to date."); LOG("Server database is up to date.");
return; return;
} }
@@ -96,6 +99,10 @@ void MigrationRunner::RunMigrations() {
if (runNormalizeMigrations) { if (runNormalizeMigrations) {
ModelNormalizeMigration::Run(); ModelNormalizeMigration::Run();
} }
if (runNormalizeAfterFirstPartMigrations) {
ModelNormalizeMigration::RunAfterFirstPart();
}
} }
void MigrationRunner::RunSQLiteMigrations() { void MigrationRunner::RunSQLiteMigrations() {

View File

@@ -14,7 +14,7 @@ void ModelNormalizeMigration::Run() {
Sd0 sd0(lxfmlData); Sd0 sd0(lxfmlData);
const auto asStr = sd0.GetAsStringUncompressed(); const auto asStr = sd0.GetAsStringUncompressed();
const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr); const auto [newLxfml, newCenter] = Lxfml::NormalizePositionOnlyFirstPart(asStr);
if (newCenter == NiPoint3Constant::ZERO) { if (newCenter == NiPoint3Constant::ZERO) {
LOG("Failed to update model %llu due to failure reading xml."); LOG("Failed to update model %llu due to failure reading xml.");
continue; continue;
@@ -28,3 +28,23 @@ void ModelNormalizeMigration::Run() {
} }
Database::Get()->SetAutoCommit(oldCommit); Database::Get()->SetAutoCommit(oldCommit);
} }
void ModelNormalizeMigration::RunAfterFirstPart() {
const auto oldCommit = Database::Get()->GetAutoCommit();
Database::Get()->SetAutoCommit(false);
for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) {
const auto model = Database::Get()->GetModel(modelID);
// only BBB models (lot 14) need to have their position fixed from the above blunder
if (model.lot != 14) continue;
Sd0 sd0(lxfmlData);
const auto asStr = sd0.GetAsStringUncompressed();
const auto [newLxfml, newCenter] = Lxfml::NormalizePositionAfterFirstPart(asStr, model.position);
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
auto asStream = sd0.GetAsStream();
Database::Get()->UpdateModel(model.id, newCenter, model.rotation, model.behaviors);
Database::Get()->UpdateUgcModelData(id, asStream);
}
Database::Get()->SetAutoCommit(oldCommit);
}

View File

@@ -6,6 +6,7 @@
namespace ModelNormalizeMigration { namespace ModelNormalizeMigration {
void Run(); void Run();
void RunAfterFirstPart();
}; };
#endif //!MODELNORMALIZEMIGRATION_H #endif //!MODELNORMALIZEMIGRATION_H

File diff suppressed because it is too large Load Diff

View File

@@ -34,7 +34,6 @@ namespace tinyxml2 {
}; };
class Player; class Player;
class EntityInfo;
class User; class User;
class Spawner; class Spawner;
class ScriptComponent; class ScriptComponent;
@@ -45,6 +44,7 @@ class Item;
class Character; class Character;
class EntityCallbackTimer; class EntityCallbackTimer;
class PositionUpdate; class PositionUpdate;
struct EntityInfo;
enum class eTriggerEventType; enum class eTriggerEventType;
enum class eGameMasterLevel : uint8_t; enum class eGameMasterLevel : uint8_t;
enum class eReplicaComponentType : uint32_t; enum class eReplicaComponentType : uint32_t;
@@ -60,7 +60,7 @@ namespace CppScripts {
*/ */
class Entity { class Entity {
public: public:
explicit Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser = nullptr, Entity* parentEntity = nullptr); Entity(const LWOOBJID& objectID, const EntityInfo& info, User* parentUser = nullptr, Entity* parentEntity = nullptr);
~Entity(); ~Entity();
void Initialize(); void Initialize();
@@ -113,7 +113,7 @@ public:
float GetDefaultScale() const; float GetDefaultScale() const;
const NiPoint3& GetPosition() const; NiPoint3 GetPosition() const;
const NiQuaternion& GetRotation() const; const NiQuaternion& GetRotation() const;
@@ -124,6 +124,8 @@ public:
// then return the collision group from that. // then return the collision group from that.
int32_t GetCollisionGroup() const; int32_t GetCollisionGroup() const;
const NiPoint3& GetVelocity() const;
/** /**
* Setters * Setters
*/ */
@@ -144,9 +146,11 @@ public:
void SetRotation(const NiQuaternion& rotation); void SetRotation(const NiQuaternion& rotation);
void SetRespawnPos(const NiPoint3& position); void SetRespawnPos(const NiPoint3& position) const;
void SetRespawnRot(const NiQuaternion& rotation); void SetRespawnRot(const NiQuaternion& rotation) const;
void SetVelocity(const NiPoint3& velocity);
/** /**
* Component management * Component management
@@ -165,7 +169,7 @@ public:
void AddComponent(eReplicaComponentType componentId, Component* component); void AddComponent(eReplicaComponentType componentId, Component* component);
// This is expceted to never return nullptr, an assert checks this. // This is expceted to never return nullptr, an assert checks this.
CppScripts::Script* const GetScript(); CppScripts::Script* const GetScript() const;
void Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd, const std::string& notificationName); void Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd, const std::string& notificationName);
void Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationName); void Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationName);
@@ -178,8 +182,8 @@ public:
void RemoveParent(); void RemoveParent();
// Adds a timer to start next frame with the given name and time. // Adds a timer to start next frame with the given name and time.
void AddTimer(std::string name, float time); void AddTimer(const std::string& name, float time);
void AddCallbackTimer(float time, std::function<void()> callback); void AddCallbackTimer(float time, const std::function<void()> callback);
bool HasTimer(const std::string& name); bool HasTimer(const std::string& name);
void CancelCallbackTimers(); void CancelCallbackTimers();
void CancelAllTimers(); void CancelAllTimers();
@@ -191,7 +195,7 @@ public:
std::unordered_map<eReplicaComponentType, Component*>& GetComponents() { return m_Components; } // TODO: Remove std::unordered_map<eReplicaComponentType, Component*>& GetComponents() { return m_Components; } // TODO: Remove
void WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacketType packetType); void WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacketType packetType);
void WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType packetType); void WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType packetType) const;
void UpdateXMLDoc(tinyxml2::XMLDocument& doc); void UpdateXMLDoc(tinyxml2::XMLDocument& doc);
void Update(float deltaTime); void Update(float deltaTime);
@@ -238,21 +242,21 @@ public:
void AddDieCallback(const std::function<void()>& callback); void AddDieCallback(const std::function<void()>& callback);
void Resurrect(); void Resurrect();
void AddLootItem(const Loot::Info& info); void AddLootItem(const Loot::Info& info) const;
void PickupItem(const LWOOBJID& objectID); void PickupItem(const LWOOBJID& objectID) const;
bool CanPickupCoins(uint64_t count); bool PickupCoins(uint64_t count) const;
void RegisterCoinDrop(uint64_t count); void RegisterCoinDrop(uint64_t count) const;
void ScheduleKillAfterUpdate(Entity* murderer = nullptr); void ScheduleKillAfterUpdate(Entity* murderer = nullptr);
void TriggerEvent(eTriggerEventType event, Entity* optionalTarget = nullptr); void TriggerEvent(eTriggerEventType event, Entity* optionalTarget = nullptr) const;
void ScheduleDestructionAfterUpdate() { m_ShouldDestroyAfterUpdate = true; } void ScheduleDestructionAfterUpdate() { m_ShouldDestroyAfterUpdate = true; }
const NiPoint3& GetRespawnPosition() const; const NiPoint3& GetRespawnPosition() const;
const NiQuaternion& GetRespawnRotation() const; const NiQuaternion& GetRespawnRotation() const;
void Sleep(); void Sleep() const;
void Wake(); void Wake() const;
bool IsSleeping() const; bool IsSleeping() const;
/* /*
@@ -262,7 +266,7 @@ public:
* Retroactively corrects the model vault size due to incorrect initialization in a previous patch. * Retroactively corrects the model vault size due to incorrect initialization in a previous patch.
* *
*/ */
void RetroactiveVaultSize(); void RetroactiveVaultSize() const;
bool GetBoolean(const std::u16string& name) const; bool GetBoolean(const std::u16string& name) const;
int32_t GetI32(const std::u16string& name) const; int32_t GetI32(const std::u16string& name) const;
int64_t GetI64(const std::u16string& name) const; int64_t GetI64(const std::u16string& name) const;
@@ -329,8 +333,9 @@ public:
* @brief The observable for player entity position updates. * @brief The observable for player entity position updates.
*/ */
static Observable<Entity*, const PositionUpdate&> OnPlayerPositionUpdate; static Observable<Entity*, const PositionUpdate&> OnPlayerPositionUpdate;
protected: private:
void WriteLDFData(const std::vector<LDFBaseData*>& ldf, RakNet::BitStream& outBitStream) const;
LWOOBJID m_ObjectID; LWOOBJID m_ObjectID;
LOT m_TemplateID; LOT m_TemplateID;
@@ -353,7 +358,6 @@ protected:
Entity* m_ParentEntity; //For spawners and the like Entity* m_ParentEntity; //For spawners and the like
std::vector<Entity*> m_ChildEntities; std::vector<Entity*> m_ChildEntities;
eGameMasterLevel m_GMLevel; eGameMasterLevel m_GMLevel;
uint16_t m_CollectibleID;
std::vector<std::string> m_Groups; std::vector<std::string> m_Groups;
uint16_t m_NetworkID; uint16_t m_NetworkID;
std::vector<std::function<void()>> m_DieCallbacks; std::vector<std::function<void()>> m_DieCallbacks;
@@ -379,6 +383,8 @@ protected:
bool m_IsParentChildDirty = true; bool m_IsParentChildDirty = true;
bool m_IsSleeping = false;
/* /*
* Collision * Collision
*/ */
@@ -387,7 +393,7 @@ protected:
// objectID of receiver and map of notification name to script // objectID of receiver and map of notification name to script
std::map<LWOOBJID, std::map<std::string, CppScripts::Script*>> m_Subscriptions; std::map<LWOOBJID, std::map<std::string, CppScripts::Script*>> m_Subscriptions;
std::multimap<MessageType::Game, std::function<bool(GameMessages::GameMsg&)>> m_MsgHandlers; std::unordered_multimap<MessageType::Game, std::function<bool(GameMessages::GameMsg&)>> m_MsgHandlers;
}; };
/** /**
@@ -435,7 +441,7 @@ const T& Entity::GetVar(const std::u16string& name) const {
template<typename T> template<typename T>
T Entity::GetVarAs(const std::u16string& name) const { T Entity::GetVarAs(const std::u16string& name) const {
const auto data = GetVarAsString(name); const auto data = GetVarAsString(name);
return GeneralUtils::TryParse<T>(data).value_or(LDFData<T>::Default); return GeneralUtils::TryParse<T>(data).value_or(LDFData<T>::Default);
} }

View File

@@ -364,6 +364,8 @@ void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) { if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) {
for (auto* player : PlayerManager::GetAllPlayers()) { for (auto* player : PlayerManager::GetAllPlayers()) {
// Don't need to construct the player to themselves
if (entity->GetObjectID() == player->GetObjectID()) continue;
if (player->GetPlayerReadyForUpdates()) { if (player->GetPlayerReadyForUpdates()) {
Game::server->Send(stream, player->GetSystemAddress(), false); Game::server->Send(stream, player->GetSystemAddress(), false);
} else { } else {
@@ -392,7 +394,7 @@ void EntityManager::ConstructAllEntities(const SystemAddress& sysAddr) {
} }
} }
UpdateGhosting(PlayerManager::GetPlayer(sysAddr)); UpdateGhosting(PlayerManager::GetPlayer(sysAddr), true);
} }
void EntityManager::DestructEntity(Entity* entity, const SystemAddress& sysAddr) { void EntityManager::DestructEntity(Entity* entity, const SystemAddress& sysAddr) {
@@ -415,7 +417,7 @@ void EntityManager::DestructEntity(Entity* entity, const SystemAddress& sysAddr)
void EntityManager::SerializeEntity(Entity* entity) { void EntityManager::SerializeEntity(Entity* entity) {
if (!entity) return; if (!entity) return;
EntityManager::SerializeEntity(*entity); EntityManager::SerializeEntity(*entity);
} }
@@ -461,7 +463,7 @@ void EntityManager::UpdateGhosting() {
m_PlayersToUpdateGhosting.clear(); m_PlayersToUpdateGhosting.clear();
} }
void EntityManager::UpdateGhosting(Entity* player) { void EntityManager::UpdateGhosting(Entity* player, const bool constructAll) {
if (!player) return; if (!player) return;
auto* missionComponent = player->GetComponent<MissionComponent>(); auto* missionComponent = player->GetComponent<MissionComponent>();
@@ -509,9 +511,12 @@ void EntityManager::UpdateGhosting(Entity* player) {
ghostComponent->ObserveEntity(id); ghostComponent->ObserveEntity(id);
ConstructEntity(entity, player->GetSystemAddress());
entity->SetObservers(entity->GetObservers() + 1); entity->SetObservers(entity->GetObservers() + 1);
// TODO: figure out if zone control should be ghosted at all
if (constructAll && entity->GetObjectID() == GetZoneControlEntity()->GetObjectID()) continue;
ConstructEntity(entity, player->GetSystemAddress());
} }
} }
} }

View File

@@ -9,7 +9,7 @@
#include "dCommonVars.h" #include "dCommonVars.h"
class Entity; class Entity;
class EntityInfo; struct EntityInfo;
class Player; class Player;
class User; class User;
enum class eReplicaComponentType : uint32_t; enum class eReplicaComponentType : uint32_t;
@@ -54,7 +54,7 @@ public:
void SetGhostDistanceMin(float value); void SetGhostDistanceMin(float value);
void QueueGhostUpdate(LWOOBJID playerID); void QueueGhostUpdate(LWOOBJID playerID);
void UpdateGhosting(); void UpdateGhosting();
void UpdateGhosting(Entity* player); void UpdateGhosting(Entity* player, const bool constructAll = false);
void CheckGhosting(Entity* entity); void CheckGhosting(Entity* entity);
Entity* GetGhostCandidate(LWOOBJID id) const; Entity* GetGhostCandidate(LWOOBJID id) const;
bool GetGhostingEnabled() const; bool GetGhostingEnabled() const;

View File

@@ -526,7 +526,7 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, zoneID, character->GetZoneClone(), false, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, zoneID, character->GetZoneClone(), false, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", character->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", character->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
if (character) { if (character) {
auto* entity = character->GetEntity(); auto* entity = Game::entityManager->GetEntity(character->GetObjectID());
if (entity) { if (entity) {
auto* characterComponent = entity->GetComponent<CharacterComponent>(); auto* characterComponent = entity->GetComponent<CharacterComponent>();
if (characterComponent) { if (characterComponent) {

View File

@@ -7,7 +7,9 @@
#include "BehaviorStates.h" #include "BehaviorStates.h"
#include "ControlBehaviorMsgs.h" #include "ControlBehaviorMsgs.h"
#include "tinyxml2.h" #include "tinyxml2.h"
#include "InventoryComponent.h"
#include "SimplePhysicsComponent.h" #include "SimplePhysicsComponent.h"
#include "eObjectBits.h"
#include "Database.h" #include "Database.h"
#include "DluAssert.h" #include "DluAssert.h"
@@ -30,10 +32,17 @@ bool ModelComponent::OnResetModelToDefaults(GameMessages::GameMsg& msg) {
unsmash.target = GetParent()->GetObjectID(); unsmash.target = GetParent()->GetObjectID();
unsmash.duration = 0.0f; unsmash.duration = 0.0f;
unsmash.Send(UNASSIGNED_SYSTEM_ADDRESS); unsmash.Send(UNASSIGNED_SYSTEM_ADDRESS);
m_Parent->SetPosition(m_OriginalPosition);
m_Parent->SetRotation(m_OriginalRotation);
m_Parent->SetVelocity(NiPoint3Constant::ZERO);
m_Speed = 3.0f;
m_NumListeningInteract = 0; m_NumListeningInteract = 0;
m_NumActiveUnSmash = 0; m_NumActiveUnSmash = 0;
m_Dirty = true; m_Dirty = true;
Game::entityManager->SerializeEntity(GetParent()); Game::entityManager->SerializeEntity(GetParent());
return true; return true;
} }
@@ -61,25 +70,30 @@ void ModelComponent::LoadBehaviors() {
for (const auto& behavior : behaviors) { for (const auto& behavior : behaviors) {
if (behavior.empty()) continue; if (behavior.empty()) continue;
const auto behaviorId = GeneralUtils::TryParse<int32_t>(behavior); const auto behaviorId = GeneralUtils::TryParse<LWOOBJID>(behavior);
if (!behaviorId.has_value() || behaviorId.value() == 0) continue; if (!behaviorId.has_value() || behaviorId.value() == 0) continue;
LOG_DEBUG("Loading behavior %d", behaviorId.value()); // add behavior at the back
auto& inserted = m_Behaviors.emplace_back(); LoadBehavior(behaviorId.value(), m_Behaviors.size(), false);
inserted.SetBehaviorId(*behaviorId); }
}
const auto behaviorStr = Database::Get()->GetBehavior(behaviorId.value()); void ModelComponent::LoadBehavior(const LWOOBJID behaviorID, const size_t index, const bool isIndexed) {
LOG_DEBUG("Loading behavior %d", behaviorID);
auto& inserted = *m_Behaviors.emplace(m_Behaviors.begin() + index, PropertyBehavior(isIndexed));
inserted.SetBehaviorId(behaviorID);
tinyxml2::XMLDocument behaviorXml; const auto behaviorStr = Database::Get()->GetBehavior(behaviorID);
auto res = behaviorXml.Parse(behaviorStr.c_str(), behaviorStr.size());
LOG_DEBUG("Behavior %i %d: %s", res, behaviorId.value(), behaviorStr.c_str());
const auto* const behaviorRoot = behaviorXml.FirstChildElement("Behavior"); tinyxml2::XMLDocument behaviorXml;
if (!behaviorRoot) { auto res = behaviorXml.Parse(behaviorStr.c_str(), behaviorStr.size());
LOG("Failed to load behavior %d due to missing behavior root", behaviorId.value()); LOG_DEBUG("Behavior %i %llu: %s", res, behaviorID, behaviorStr.c_str());
continue;
} const auto* const behaviorRoot = behaviorXml.FirstChildElement("Behavior");
if (behaviorRoot) {
inserted.Deserialize(*behaviorRoot); inserted.Deserialize(*behaviorRoot);
} else {
LOG("Failed to load behavior %d due to missing behavior root", behaviorID);
} }
} }
@@ -110,8 +124,12 @@ void ModelComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialU
if (bIsInitialUpdate) outBitStream.Write0(); // We are not writing model editing info if (bIsInitialUpdate) outBitStream.Write0(); // We are not writing model editing info
} }
void ModelComponent::UpdatePendingBehaviorId(const int32_t newId) { void ModelComponent::UpdatePendingBehaviorId(const LWOOBJID newId, const LWOOBJID oldId) {
for (auto& behavior : m_Behaviors) if (behavior.GetBehaviorId() == -1) behavior.SetBehaviorId(newId); for (auto& behavior : m_Behaviors) {
if (behavior.GetBehaviorId() != oldId) continue;
behavior.SetBehaviorId(newId);
behavior.SetIsLoot(false);
}
} }
void ModelComponent::SendBehaviorListToClient(AMFArrayValue& args) const { void ModelComponent::SendBehaviorListToClient(AMFArrayValue& args) const {
@@ -128,7 +146,7 @@ void ModelComponent::VerifyBehaviors() {
for (auto& behavior : m_Behaviors) behavior.VerifyLastEditedState(); for (auto& behavior : m_Behaviors) behavior.VerifyLastEditedState();
} }
void ModelComponent::SendBehaviorBlocksToClient(int32_t behaviorToSend, AMFArrayValue& args) const { void ModelComponent::SendBehaviorBlocksToClient(const LWOOBJID behaviorToSend, AMFArrayValue& args) const {
args.Insert("BehaviorID", std::to_string(behaviorToSend)); args.Insert("BehaviorID", std::to_string(behaviorToSend));
args.Insert("objectID", std::to_string(m_Parent->GetObjectID())); args.Insert("objectID", std::to_string(m_Parent->GetObjectID()));
for (auto& behavior : m_Behaviors) if (behavior.GetBehaviorId() == behaviorToSend) behavior.SendBehaviorBlocksToClient(args); for (auto& behavior : m_Behaviors) if (behavior.GetBehaviorId() == behaviorToSend) behavior.SendBehaviorBlocksToClient(args);
@@ -137,8 +155,21 @@ void ModelComponent::SendBehaviorBlocksToClient(int32_t behaviorToSend, AMFArray
void ModelComponent::AddBehavior(AddMessage& msg) { void ModelComponent::AddBehavior(AddMessage& msg) {
// Can only have 1 of the loot behaviors // Can only have 1 of the loot behaviors
for (auto& behavior : m_Behaviors) if (behavior.GetBehaviorId() == msg.GetBehaviorId()) return; for (auto& behavior : m_Behaviors) if (behavior.GetBehaviorId() == msg.GetBehaviorId()) return;
m_Behaviors.insert(m_Behaviors.begin() + msg.GetBehaviorIndex(), PropertyBehavior());
m_Behaviors.at(msg.GetBehaviorIndex()).HandleMsg(msg); // If we're loading a behavior from an ADD, it is from the database.
// Mark it as not modified by default to prevent wasting persistentIDs.
LoadBehavior(msg.GetBehaviorId(), msg.GetBehaviorIndex(), true);
auto& insertedBehavior = m_Behaviors[msg.GetBehaviorIndex()];
auto* const playerEntity = Game::entityManager->GetEntity(msg.GetOwningPlayerID());
if (playerEntity) {
auto* inventoryComponent = playerEntity->GetComponent<InventoryComponent>();
if (inventoryComponent) {
// Check if this behavior is able to be found via lot (if so, its a loot behavior).
insertedBehavior.SetIsLoot(inventoryComponent->FindItemByLot(msg.GetBehaviorId(), eInventoryType::BEHAVIORS));
}
}
auto* const simplePhysComponent = m_Parent->GetComponent<SimplePhysicsComponent>(); auto* const simplePhysComponent = m_Parent->GetComponent<SimplePhysicsComponent>();
if (simplePhysComponent) { if (simplePhysComponent) {
simplePhysComponent->SetPhysicsMotionState(1); simplePhysComponent->SetPhysicsMotionState(1);
@@ -146,8 +177,41 @@ void ModelComponent::AddBehavior(AddMessage& msg) {
} }
} }
void ModelComponent::MoveToInventory(MoveToInventoryMessage& msg) { std::string ModelComponent::SaveBehavior(const PropertyBehavior& behavior) const {
tinyxml2::XMLDocument doc;
auto* root = doc.NewElement("Behavior");
behavior.Serialize(*root);
doc.InsertFirstChild(root);
tinyxml2::XMLPrinter printer(0, true, 0);
doc.Print(&printer);
return printer.CStr();
}
void ModelComponent::RemoveBehavior(MoveToInventoryMessage& msg, const bool keepItem) {
if (msg.GetBehaviorIndex() >= m_Behaviors.size() || m_Behaviors.at(msg.GetBehaviorIndex()).GetBehaviorId() != msg.GetBehaviorId()) return; if (msg.GetBehaviorIndex() >= m_Behaviors.size() || m_Behaviors.at(msg.GetBehaviorIndex()).GetBehaviorId() != msg.GetBehaviorId()) return;
const auto behavior = m_Behaviors[msg.GetBehaviorIndex()];
if (keepItem) {
auto* const playerEntity = Game::entityManager->GetEntity(msg.GetOwningPlayerID());
if (playerEntity) {
auto* const inventoryComponent = playerEntity->GetComponent<InventoryComponent>();
if (inventoryComponent && !behavior.GetIsLoot()) {
// config is owned by the item
std::vector<LDFBaseData*> config;
config.push_back(new LDFData<std::string>(u"userModelName", behavior.GetName()));
inventoryComponent->AddItem(7965, 1, eLootSourceType::PROPERTY, eInventoryType::BEHAVIORS, config, LWOOBJID_EMPTY, true, false, msg.GetBehaviorId());
}
}
}
// save the behavior before deleting it so players can re-add them
IBehaviors::Info info{};
info.behaviorId = msg.GetBehaviorId();
info.behaviorInfo = SaveBehavior(behavior);
info.characterId = msg.GetOwningPlayerID();
Database::Get()->AddBehavior(info);
m_Behaviors.erase(m_Behaviors.begin() + msg.GetBehaviorIndex()); m_Behaviors.erase(m_Behaviors.begin() + msg.GetBehaviorIndex());
// TODO move to the inventory // TODO move to the inventory
if (m_Behaviors.empty()) { if (m_Behaviors.empty()) {
@@ -159,22 +223,14 @@ void ModelComponent::MoveToInventory(MoveToInventoryMessage& msg) {
} }
} }
std::array<std::pair<int32_t, std::string>, 5> ModelComponent::GetBehaviorsForSave() const { std::array<std::pair<LWOOBJID, std::string>, 5> ModelComponent::GetBehaviorsForSave() const {
std::array<std::pair<int32_t, std::string>, 5> toReturn{}; std::array<std::pair<LWOOBJID, std::string>, 5> toReturn{};
for (auto i = 0; i < m_Behaviors.size(); i++) { for (auto i = 0; i < m_Behaviors.size(); i++) {
const auto& behavior = m_Behaviors.at(i); const auto& behavior = m_Behaviors.at(i);
if (behavior.GetBehaviorId() == -1) continue; if (behavior.GetBehaviorId() == -1) continue;
auto& [id, behaviorData] = toReturn[i]; auto& [id, behaviorData] = toReturn[i];
id = behavior.GetBehaviorId(); id = behavior.GetBehaviorId();
behaviorData = SaveBehavior(behavior);
tinyxml2::XMLDocument doc;
auto* root = doc.NewElement("Behavior");
behavior.Serialize(*root);
doc.InsertFirstChild(root);
tinyxml2::XMLPrinter printer(0, true, 0);
doc.Print(&printer);
behaviorData = printer.CStr();
} }
return toReturn; return toReturn;
} }
@@ -203,3 +259,36 @@ void ModelComponent::RemoveUnSmash() {
LOG_DEBUG("Removing UnSmash %i", m_NumActiveUnSmash); LOG_DEBUG("Removing UnSmash %i", m_NumActiveUnSmash);
m_NumActiveUnSmash--; m_NumActiveUnSmash--;
} }
bool ModelComponent::TrySetVelocity(const NiPoint3& velocity) const {
auto currentVelocity = m_Parent->GetVelocity();
// If we're currently moving on an axis, prevent the move so only 1 behavior can have control over an axis
if (velocity != NiPoint3Constant::ZERO) {
const auto [x, y, z] = velocity;
if (x != 0.0f) {
if (currentVelocity.x != 0.0f) return false;
currentVelocity.x = x;
} else if (y != 0.0f) {
if (currentVelocity.y != 0.0f) return false;
currentVelocity.y = y;
} else if (z != 0.0f) {
if (currentVelocity.z != 0.0f) return false;
currentVelocity.z = z;
}
} else {
currentVelocity = velocity;
}
currentVelocity *= m_Speed;
m_Parent->SetVelocity(currentVelocity);
return true;
}
void ModelComponent::SetVelocity(const NiPoint3& velocity) const {
m_Parent->SetVelocity(velocity);
}
void ModelComponent::OnChatMessageReceived(const std::string& sMessage) {
for (auto& behavior : m_Behaviors) behavior.OnChatMessageReceived(sMessage);
}

View File

@@ -41,7 +41,7 @@ public:
* Returns the original position of the model * Returns the original position of the model
* @return the original position of the model * @return the original position of the model
*/ */
const NiPoint3& GetPosition() { return m_OriginalPosition; } const NiPoint3& GetOriginalPosition() { return m_OriginalPosition; }
/** /**
* Sets the original position of the model * Sets the original position of the model
@@ -53,7 +53,7 @@ public:
* Returns the original rotation of the model * Returns the original rotation of the model
* @return the original rotation of the model * @return the original rotation of the model
*/ */
const NiQuaternion& GetRotation() { return m_OriginalRotation; } const NiQuaternion& GetOriginalRotation() { return m_OriginalRotation; }
/** /**
* Sets the original rotation of the model * Sets the original rotation of the model
@@ -66,15 +66,18 @@ public:
* *
* @tparam Msg The message type to pass * @tparam Msg The message type to pass
* @param args the arguments of the message to be deserialized * @param args the arguments of the message to be deserialized
*
* @return returns true if a new behaviorID is needed.
*/ */
template<typename Msg> template<typename Msg>
void HandleControlBehaviorsMsg(const AMFArrayValue& args) { bool HandleControlBehaviorsMsg(const AMFArrayValue& args) {
static_assert(std::is_base_of_v<BehaviorMessageBase, Msg>, "Msg must be a BehaviorMessageBase"); static_assert(std::is_base_of_v<BehaviorMessageBase, Msg>, "Msg must be a BehaviorMessageBase");
Msg msg{ args }; Msg msg{ args };
for (auto&& behavior : m_Behaviors) { for (auto&& behavior : m_Behaviors) {
if (behavior.GetBehaviorId() == msg.GetBehaviorId()) { if (behavior.GetBehaviorId() == msg.GetBehaviorId()) {
behavior.CheckModifyState(msg);
behavior.HandleMsg(msg); behavior.HandleMsg(msg);
return; return msg.GetNeedsNewBehaviorID();
} }
} }
@@ -82,22 +85,24 @@ public:
if (m_Behaviors.size() > 5) m_Behaviors.resize(5); if (m_Behaviors.size() > 5) m_Behaviors.resize(5);
// Do not allow more than 5 to be added. The client UI will break if you do! // Do not allow more than 5 to be added. The client UI will break if you do!
if (m_Behaviors.size() == 5) return; if (m_Behaviors.size() == 5) return false;
auto newBehavior = m_Behaviors.insert(m_Behaviors.begin(), PropertyBehavior()); auto newBehavior = m_Behaviors.insert(m_Behaviors.begin(), PropertyBehavior());
// Generally if we are inserting a new behavior, it is because the client is creating a new behavior. // Generally if we are inserting a new behavior, it is because the client is creating a new behavior.
// However if we are testing behaviors the behavior will not exist on the initial pass, so we set the ID here to that of the msg. // However if we are testing behaviors the behavior will not exist on the initial pass, so we set the ID here to that of the msg.
// This will either set the ID to -1 (no change in the current default) or set the ID to the ID of the behavior we are testing. // This will either set the ID to -1 (no change in the current default) or set the ID to the ID of the behavior we are testing.
newBehavior->SetBehaviorId(msg.GetBehaviorId()); newBehavior->SetBehaviorId(msg.GetBehaviorId());
newBehavior->CheckModifyState(msg);
newBehavior->HandleMsg(msg); newBehavior->HandleMsg(msg);
return msg.GetNeedsNewBehaviorID();
}; };
void AddBehavior(AddMessage& msg); void AddBehavior(AddMessage& msg);
void MoveToInventory(MoveToInventoryMessage& msg); void RemoveBehavior(MoveToInventoryMessage& msg, const bool keepItem);
// Updates the pending behavior ID to the new ID. // Updates the pending behavior ID to the new ID.
void UpdatePendingBehaviorId(const int32_t newId); void UpdatePendingBehaviorId(const LWOOBJID newId, const LWOOBJID oldId);
// Sends the behavior list to the client. // Sends the behavior list to the client.
@@ -112,11 +117,11 @@ public:
*/ */
void SendBehaviorListToClient(AMFArrayValue& args) const; void SendBehaviorListToClient(AMFArrayValue& args) const;
void SendBehaviorBlocksToClient(int32_t behaviorToSend, AMFArrayValue& args) const; void SendBehaviorBlocksToClient(const LWOOBJID behaviorToSend, AMFArrayValue& args) const;
void VerifyBehaviors(); void VerifyBehaviors();
std::array<std::pair<int32_t, std::string>, 5> GetBehaviorsForSave() const; std::array<std::pair<LWOOBJID, std::string>, 5> GetBehaviorsForSave() const;
const std::vector<PropertyBehavior>& GetBehaviors() const { return m_Behaviors; }; const std::vector<PropertyBehavior>& GetBehaviors() const { return m_Behaviors; };
@@ -130,7 +135,26 @@ public:
bool IsUnSmashing() const { return m_NumActiveUnSmash != 0; } bool IsUnSmashing() const { return m_NumActiveUnSmash != 0; }
void Resume(); void Resume();
// Attempts to set the velocity of an axis for movement.
// If the axis currently has a velocity of zero, returns true.
// If the axis is currently controlled by a behavior, returns false.
bool TrySetVelocity(const NiPoint3& velocity) const;
// Force sets the velocity to a value.
void SetVelocity(const NiPoint3& velocity) const;
void OnChatMessageReceived(const std::string& sMessage);
void SetSpeed(const float newSpeed) { m_Speed = newSpeed; }
private: private:
// Loads a behavior from the database.
void LoadBehavior(const LWOOBJID behaviorID, const size_t index, const bool isIndexed);
// Writes a behavior to a string so it can be saved.
std::string SaveBehavior(const PropertyBehavior& behavior) const;
// Number of Actions that are awaiting an UnSmash to finish. // Number of Actions that are awaiting an UnSmash to finish.
uint32_t m_NumActiveUnSmash{}; uint32_t m_NumActiveUnSmash{};
@@ -163,4 +187,7 @@ private:
* The ID of the user that made the model * The ID of the user that made the model
*/ */
LWOOBJID m_userModelID; LWOOBJID m_userModelID;
// The speed at which this model moves
float m_Speed{ 3.0f };
}; };

View File

@@ -29,6 +29,13 @@ PhysicsComponent::PhysicsComponent(Entity* parent, int32_t componentId) : Compon
} }
if (m_Parent->HasVar(u"CollisionGroupID")) m_CollisionGroup = m_Parent->GetVar<int32_t>(u"CollisionGroupID"); if (m_Parent->HasVar(u"CollisionGroupID")) m_CollisionGroup = m_Parent->GetVar<int32_t>(u"CollisionGroupID");
RegisterMsg(MessageType::Game::GET_POSITION, this, &PhysicsComponent::OnGetPosition);
}
bool PhysicsComponent::OnGetPosition(GameMessages::GameMsg& msg) {
static_cast<GameMessages::GetPosition&>(msg).pos = GetPosition();
return true;
} }
void PhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { void PhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {

View File

@@ -20,7 +20,7 @@ public:
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
const NiPoint3& GetPosition() const { return m_Position; } const NiPoint3& GetPosition() const noexcept { return m_Position; }
virtual void SetPosition(const NiPoint3& pos) { if (m_Position == pos) return; m_Position = pos; m_DirtyPosition = true; } virtual void SetPosition(const NiPoint3& pos) { if (m_Position == pos) return; m_Position = pos; m_DirtyPosition = true; }
const NiQuaternion& GetRotation() const { return m_Rotation; } const NiQuaternion& GetRotation() const { return m_Rotation; }
@@ -35,6 +35,8 @@ protected:
void SpawnVertices(dpEntity* entity) const; void SpawnVertices(dpEntity* entity) const;
bool OnGetPosition(GameMessages::GameMsg& msg);
NiPoint3 m_Position; NiPoint3 m_Position;
NiQuaternion m_Rotation; NiQuaternion m_Rotation;

View File

@@ -272,6 +272,10 @@ void PropertyManagementComponent::OnStartBuilding() {
model->HandleMsg(reset); model->HandleMsg(reset);
} }
} }
for (auto* const entity : Game::entityManager->GetEntitiesInGroup("SpawnedPropertyEnemies")) {
if (entity) entity->Smash();
}
} }
void PropertyManagementComponent::OnFinishBuilding() { void PropertyManagementComponent::OnFinishBuilding() {
@@ -296,6 +300,10 @@ void PropertyManagementComponent::OnFinishBuilding() {
model->HandleMsg(reset); model->HandleMsg(reset);
} }
} }
for (auto* const entity : Game::entityManager->GetEntitiesInGroup("SpawnedPropertyEnemies")) {
if (entity) entity->Smash();
}
} }
void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const NiPoint3 position, NiQuaternion rotation) { void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const NiPoint3 position, NiQuaternion rotation) {
@@ -696,8 +704,9 @@ void PropertyManagementComponent::Save() {
Database::Get()->AddBehavior(info); Database::Get()->AddBehavior(info);
} }
const auto position = entity->GetPosition(); // Always save the original position so we can move the model freely
const auto rotation = entity->GetRotation(); const auto& position = modelComponent->GetOriginalPosition();
const auto& rotation = modelComponent->GetOriginalRotation();
if (std::find(present.begin(), present.end(), id) == present.end()) { if (std::find(present.begin(), present.end(), id) == present.end()) {
IPropertyContents::Model model; IPropertyContents::Model model;
@@ -808,3 +817,14 @@ void PropertyManagementComponent::SetOwnerId(const LWOOBJID value) {
const std::map<LWOOBJID, LWOOBJID>& PropertyManagementComponent::GetModels() const { const std::map<LWOOBJID, LWOOBJID>& PropertyManagementComponent::GetModels() const {
return models; return models;
} }
void PropertyManagementComponent::OnChatMessageReceived(const std::string& sMessage) const {
for (const auto& modelID : models | std::views::keys) {
auto* const model = Game::entityManager->GetEntity(modelID);
if (!model) continue;
auto* const modelComponent = model->GetComponent<ModelComponent>();
if (!modelComponent) continue;
modelComponent->OnChatMessageReceived(sMessage);
}
}

View File

@@ -164,6 +164,8 @@ public:
LWOOBJID GetId() const noexcept { return propertyId; } LWOOBJID GetId() const noexcept { return propertyId; }
void OnChatMessageReceived(const std::string& sMessage) const;
private: private:
/** /**
* This * This
@@ -193,7 +195,7 @@ private:
/** /**
* The models that are placed on this property * The models that are placed on this property
*/ */
std::map<LWOOBJID, LWOOBJID> models = {}; std::map<LWOOBJID /* ObjectID */, LWOOBJID /* SpawnerID */> models = {};
/** /**
* The name of this property * The name of this property

View File

@@ -2,11 +2,9 @@
#include "EntityManager.h" #include "EntityManager.h"
#include "ScriptedActivityComponent.h" #include "ScriptedActivityComponent.h"
ShootingGalleryComponent::ShootingGalleryComponent(Entity* parent) : Component(parent) { ShootingGalleryComponent::ShootingGalleryComponent(Entity* parent, int32_t activityID) : ActivityComponent(parent, activityID) {
} }
ShootingGalleryComponent::~ShootingGalleryComponent() = default;
void ShootingGalleryComponent::SetStaticParams(const StaticShootingGalleryParams& params) { void ShootingGalleryComponent::SetStaticParams(const StaticShootingGalleryParams& params) {
m_StaticParams = params; m_StaticParams = params;
} }

View File

@@ -4,6 +4,7 @@
#include "Entity.h" #include "Entity.h"
#include "Component.h" #include "Component.h"
#include "eReplicaComponentType.h" #include "eReplicaComponentType.h"
#include "ActivityComponent.h"
/** /**
* Parameters for the shooting gallery that change during playtime * Parameters for the shooting gallery that change during playtime
@@ -71,12 +72,11 @@ struct StaticShootingGalleryParams {
* A very ancient component that was used to guide shooting galleries, it's still kind of used but a lot of logic is * A very ancient component that was used to guide shooting galleries, it's still kind of used but a lot of logic is
* also in the related scripts. * also in the related scripts.
*/ */
class ShootingGalleryComponent final : public Component { class ShootingGalleryComponent final : public ActivityComponent {
public: public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SHOOTING_GALLERY; static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SHOOTING_GALLERY;
explicit ShootingGalleryComponent(Entity* parent); explicit ShootingGalleryComponent(Entity* parent, int32_t activityID);
~ShootingGalleryComponent();
void Serialize(RakNet::BitStream& outBitStream, bool isInitialUpdate) override; void Serialize(RakNet::BitStream& outBitStream, bool isInitialUpdate) override;
/** /**

View File

@@ -33,6 +33,13 @@ SimplePhysicsComponent::SimplePhysicsComponent(Entity* parent, int32_t component
SimplePhysicsComponent::~SimplePhysicsComponent() { SimplePhysicsComponent::~SimplePhysicsComponent() {
} }
void SimplePhysicsComponent::Update(const float deltaTime) {
if (m_Velocity == NiPoint3Constant::ZERO) return;
m_Position += m_Velocity * deltaTime;
m_DirtyPosition = true;
Game::entityManager->SerializeEntity(m_Parent);
}
void SimplePhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { void SimplePhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
if (bIsInitialUpdate) { if (bIsInitialUpdate) {
outBitStream.Write(m_ClimbableType != eClimbableType::CLIMBABLE_TYPE_NOT); outBitStream.Write(m_ClimbableType != eClimbableType::CLIMBABLE_TYPE_NOT);

View File

@@ -33,6 +33,8 @@ public:
SimplePhysicsComponent(Entity* parent, int32_t componentID); SimplePhysicsComponent(Entity* parent, int32_t componentID);
~SimplePhysicsComponent() override; ~SimplePhysicsComponent() override;
void Update(const float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
/** /**

View File

@@ -158,6 +158,10 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
inv->AddItemSkills(item.lot); inv->AddItemSkills(item.lot);
} }
// Fixes a bug where testmapping too fast causes large item inventories to become invisible.
// Only affects item inventory
GameMessages::SendSetInventorySize(entity, eInventoryType::ITEMS, inv->GetInventory(eInventoryType::ITEMS)->GetSize());
} }
GameMessages::SendRestoreToPostLoadStats(entity, sysAddr); GameMessages::SendRestoreToPostLoadStats(entity, sysAddr);

View File

@@ -5207,7 +5207,7 @@ void GameMessages::HandlePickupCurrency(RakNet::BitStream& inStream, Entity* ent
if (currency == 0) return; if (currency == 0) return;
auto* ch = entity->GetCharacter(); auto* ch = entity->GetCharacter();
if (entity->CanPickupCoins(currency)) { if (ch && entity->PickupCoins(currency)) {
ch->SetCoins(ch->GetCoins() + currency, eLootSourceType::PICKUP); ch->SetCoins(ch->GetCoins() + currency, eLootSourceType::PICKUP);
} }
} }

View File

@@ -843,5 +843,10 @@ namespace GameMessages {
LWOOBJID targetID; LWOOBJID targetID;
}; };
struct GetPosition : public GameMsg {
GetPosition() : GameMsg(MessageType::Game::GET_POSITION) {}
NiPoint3 pos{};
};
}; };
#endif // GAMEMESSAGES_H #endif // GAMEMESSAGES_H

View File

@@ -21,6 +21,8 @@
#include "eUseItemResponse.h" #include "eUseItemResponse.h"
#include "dZoneManager.h" #include "dZoneManager.h"
#include "ChatPackets.h" #include "ChatPackets.h"
#include "MissionComponent.h"
#include "eMissionTaskType.h"
#include "CDBrickIDTableTable.h" #include "CDBrickIDTableTable.h"
#include "CDObjectSkillsTable.h" #include "CDObjectSkillsTable.h"
@@ -268,9 +270,9 @@ bool Item::IsEquipped() const {
} }
bool Item::Consume() { bool Item::Consume() {
auto* skillsTable = CDClientManager::GetTable<CDObjectSkillsTable>(); auto* const skillsTable = CDClientManager::GetTable<CDObjectSkillsTable>();
auto skills = skillsTable->Query([this](const CDObjectSkills entry) { const auto skills = skillsTable->Query([this](const CDObjectSkills& entry) {
return entry.objectTemplate == static_cast<uint32_t>(lot); return entry.objectTemplate == static_cast<uint32_t>(lot);
}); });
@@ -288,7 +290,12 @@ bool Item::Consume() {
GameMessages::SendUseItemResult(inventory->GetComponent()->GetParent(), lot, success); GameMessages::SendUseItemResult(inventory->GetComponent()->GetParent(), lot, success);
if (success) { if (success) {
// Save this because if this is the last item in the inventory
// we may delete ourself (lol)
const auto myLot = this->lot;
inventory->GetComponent()->RemoveItem(lot, 1); inventory->GetComponent()->RemoveItem(lot, 1);
auto* missionComponent = inventory->GetComponent()->GetParent()->GetComponent<MissionComponent>();
if (missionComponent) missionComponent->Progress(eMissionTaskType::GATHER, myLot, LWOOBJID_EMPTY, "", -1);
} }
return success; return success;

View File

@@ -20,6 +20,7 @@ target_include_directories(dPropertyBehaviors PUBLIC "." "ControlBehaviorMessage
"${PROJECT_SOURCE_DIR}/dGame/dUtilities" # ObjectIdManager.h "${PROJECT_SOURCE_DIR}/dGame/dUtilities" # ObjectIdManager.h
"${PROJECT_SOURCE_DIR}/dGame/dGameMessages" # GameMessages.h "${PROJECT_SOURCE_DIR}/dGame/dGameMessages" # GameMessages.h
"${PROJECT_SOURCE_DIR}/dGame/dComponents" # ModelComponent.h "${PROJECT_SOURCE_DIR}/dGame/dComponents" # ModelComponent.h
"${PROJECT_SOURCE_DIR}/dChatFilter" # dChatFilter.h
) )
target_precompile_headers(dPropertyBehaviors REUSE_FROM dGameBase) target_precompile_headers(dPropertyBehaviors REUSE_FROM dGameBase)

View File

@@ -10,5 +10,5 @@ AddActionMessage::AddActionMessage(const AMFArrayValue& arguments)
m_Action = Action{ *actionValue }; m_Action = Action{ *actionValue };
LOG_DEBUG("actionIndex %i stripId %i stateId %i type %s valueParameterName %s valueParameterString %s valueParameterDouble %f m_BehaviorId %i", m_ActionIndex, m_ActionContext.GetStripId(), m_ActionContext.GetStateId(), m_Action.GetType().data(), m_Action.GetValueParameterName().data(), m_Action.GetValueParameterString().data(), m_Action.GetValueParameterDouble(), m_BehaviorId); LOG_DEBUG("actionIndex %i stripId %i stateId %i type %s valueParameterName %s valueParameterString %s valueParameterDouble %f m_BehaviorId %llu", m_ActionIndex, m_ActionContext.GetStripId(), m_ActionContext.GetStateId(), m_Action.GetType().data(), m_Action.GetValueParameterName().data(), m_Action.GetValueParameterString().data(), m_Action.GetValueParameterDouble(), m_BehaviorId);
} }

View File

@@ -1,9 +1,9 @@
#include "AddMessage.h" #include "AddMessage.h"
AddMessage::AddMessage(const AMFArrayValue& arguments) : BehaviorMessageBase{ arguments } { AddMessage::AddMessage(const AMFArrayValue& arguments, const LWOOBJID _owningPlayerID) : m_OwningPlayerID{ _owningPlayerID }, BehaviorMessageBase { arguments } {
const auto* const behaviorIndexValue = arguments.Get<double>("BehaviorIndex"); const auto* const behaviorIndexValue = arguments.Get<double>("BehaviorIndex");
if (!behaviorIndexValue) return; if (!behaviorIndexValue) return;
m_BehaviorIndex = static_cast<uint32_t>(behaviorIndexValue->GetValue()); m_BehaviorIndex = static_cast<uint32_t>(behaviorIndexValue->GetValue());
LOG_DEBUG("behaviorId %i index %i", m_BehaviorId, m_BehaviorIndex); LOG_DEBUG("behaviorId %llu index %i", m_BehaviorId, m_BehaviorIndex);
} }

View File

@@ -9,11 +9,13 @@
*/ */
class AddMessage : public BehaviorMessageBase { class AddMessage : public BehaviorMessageBase {
public: public:
AddMessage(const AMFArrayValue& arguments); AddMessage(const AMFArrayValue& arguments, const LWOOBJID _owningPlayerID);
[[nodiscard]] uint32_t GetBehaviorIndex() const noexcept { return m_BehaviorIndex; }; [[nodiscard]] uint32_t GetBehaviorIndex() const noexcept { return m_BehaviorIndex; };
[[nodiscard]] LWOOBJID GetOwningPlayerID() const noexcept { return m_OwningPlayerID; };
private: private:
uint32_t m_BehaviorIndex{ 0 }; uint32_t m_BehaviorIndex{ 0 };
LWOOBJID m_OwningPlayerID{};
}; };
#endif //!__ADDMESSAGE__H__ #endif //!__ADDMESSAGE__H__

View File

@@ -19,7 +19,7 @@ AddStripMessage::AddStripMessage(const AMFArrayValue& arguments)
m_ActionsToAdd.emplace_back(*actionValue); m_ActionsToAdd.emplace_back(*actionValue);
LOG_DEBUG("xPosition %f yPosition %f stripId %i stateId %i behaviorId %i t %s valueParameterName %s valueParameterString %s valueParameterDouble %f", m_Position.GetX(), m_Position.GetY(), m_ActionContext.GetStripId(), m_ActionContext.GetStateId(), m_BehaviorId, m_ActionsToAdd.back().GetType().data(), m_ActionsToAdd.back().GetValueParameterName().data(), m_ActionsToAdd.back().GetValueParameterString().data(), m_ActionsToAdd.back().GetValueParameterDouble()); LOG_DEBUG("xPosition %f yPosition %f stripId %i stateId %i behaviorId %llu t %s valueParameterName %s valueParameterString %s valueParameterDouble %f", m_Position.GetX(), m_Position.GetY(), m_ActionContext.GetStripId(), m_ActionContext.GetStateId(), m_BehaviorId, m_ActionsToAdd.back().GetType().data(), m_ActionsToAdd.back().GetValueParameterName().data(), m_ActionsToAdd.back().GetValueParameterString().data(), m_ActionsToAdd.back().GetValueParameterDouble());
} }
LOG_DEBUG("number of actions %i", m_ActionsToAdd.size()); LOG_DEBUG("number of actions %i", m_ActionsToAdd.size());
} }

View File

@@ -4,14 +4,14 @@
#include "BehaviorStates.h" #include "BehaviorStates.h"
#include "dCommonVars.h" #include "dCommonVars.h"
int32_t BehaviorMessageBase::GetBehaviorIdFromArgument(const AMFArrayValue& arguments) { LWOOBJID BehaviorMessageBase::GetBehaviorIdFromArgument(const AMFArrayValue& arguments) {
static constexpr std::string_view key = "BehaviorID"; static constexpr std::string_view key = "BehaviorID";
const auto* const behaviorIDValue = arguments.Get<std::string>(key); const auto* const behaviorIDValue = arguments.Get<std::string>(key);
int32_t behaviorId = DefaultBehaviorId; LWOOBJID behaviorId = DefaultBehaviorId;
if (behaviorIDValue && behaviorIDValue->GetValueType() == eAmf::String) { if (behaviorIDValue && behaviorIDValue->GetValueType() == eAmf::String) {
behaviorId = behaviorId =
GeneralUtils::TryParse<int32_t>(behaviorIDValue->GetValue()).value_or(behaviorId); GeneralUtils::TryParse<LWOOBJID>(behaviorIDValue->GetValue()).value_or(behaviorId);
} else if (arguments.Get(key) && arguments.Get(key)->GetValueType() != eAmf::Undefined) { } else if (arguments.Get(key) && arguments.Get(key)->GetValueType() != eAmf::Undefined) {
throw std::invalid_argument("Unable to find behavior ID"); throw std::invalid_argument("Unable to find behavior ID");
} }

View File

@@ -11,19 +11,22 @@ enum class BehaviorState : uint32_t;
/** /**
* @brief The behaviorID target of this ControlBehaviors message * @brief The behaviorID target of this ControlBehaviors message
* *
*/ */
class BehaviorMessageBase { class BehaviorMessageBase {
public: public:
static constexpr int32_t DefaultBehaviorId{ -1 }; static constexpr LWOOBJID DefaultBehaviorId{ -1 };
BehaviorMessageBase(const AMFArrayValue& arguments) : m_BehaviorId{ GetBehaviorIdFromArgument(arguments) } {} BehaviorMessageBase(const AMFArrayValue& arguments) : m_BehaviorId{ GetBehaviorIdFromArgument(arguments) } {}
[[nodiscard]] int32_t GetBehaviorId() const noexcept { return m_BehaviorId; } [[nodiscard]] LWOOBJID GetBehaviorId() const noexcept { return m_BehaviorId; }
[[nodiscard]] bool IsDefaultBehaviorId() const noexcept { return m_BehaviorId == DefaultBehaviorId; } [[nodiscard]] bool IsDefaultBehaviorId() const noexcept { return m_BehaviorId == DefaultBehaviorId; }
[[nodiscard]] bool GetNeedsNewBehaviorID() const noexcept { return m_NeedsNewBehaviorID; }
void SetNeedsNewBehaviorID(const bool val) noexcept { m_NeedsNewBehaviorID = val; }
protected: protected:
[[nodiscard]] int32_t GetBehaviorIdFromArgument(const AMFArrayValue& arguments); [[nodiscard]] LWOOBJID GetBehaviorIdFromArgument(const AMFArrayValue& arguments);
[[nodiscard]] int32_t GetActionIndexFromArgument(const AMFArrayValue& arguments, const std::string_view keyName = "actionIndex") const; [[nodiscard]] int32_t GetActionIndexFromArgument(const AMFArrayValue& arguments, const std::string_view keyName = "actionIndex") const;
int32_t m_BehaviorId{ DefaultBehaviorId }; LWOOBJID m_BehaviorId{ DefaultBehaviorId };
bool m_NeedsNewBehaviorID{ false };
}; };
#endif //!__BEHAVIORMESSAGEBASE__H__ #endif //!__BEHAVIORMESSAGEBASE__H__

View File

@@ -6,6 +6,6 @@ MergeStripsMessage::MergeStripsMessage(const AMFArrayValue& arguments)
, m_SourceActionContext{ arguments, "srcStateID", "srcStripID" } , m_SourceActionContext{ arguments, "srcStateID", "srcStripID" }
, m_DestinationActionContext{ arguments, "dstStateID", "dstStripID" } { , m_DestinationActionContext{ arguments, "dstStateID", "dstStripID" } {
LOG_DEBUG("srcstripId %i dststripId %i srcstateId %i dststateId %i dstactionIndex %i behaviorId %i", m_SourceActionContext.GetStripId(), m_DestinationActionContext.GetStripId(), m_SourceActionContext.GetStateId(), m_DestinationActionContext.GetStateId(), m_DstActionIndex, m_BehaviorId); LOG_DEBUG("srcstripId %i dststripId %i srcstateId %i dststateId %i dstactionIndex %i behaviorId %llu", m_SourceActionContext.GetStripId(), m_DestinationActionContext.GetStripId(), m_SourceActionContext.GetStateId(), m_DestinationActionContext.GetStateId(), m_DstActionIndex, m_BehaviorId);
} }

View File

@@ -7,5 +7,5 @@ MigrateActionsMessage::MigrateActionsMessage(const AMFArrayValue& arguments)
, m_SourceActionContext{ arguments, "srcStateID", "srcStripID" } , m_SourceActionContext{ arguments, "srcStateID", "srcStripID" }
, m_DestinationActionContext{ arguments, "dstStateID", "dstStripID" } { , m_DestinationActionContext{ arguments, "dstStateID", "dstStripID" } {
LOG_DEBUG("srcactionIndex %i dstactionIndex %i srcstripId %i dststripId %i srcstateId %i dststateId %i behaviorId %i", m_SrcActionIndex, m_DstActionIndex, m_SourceActionContext.GetStripId(), m_DestinationActionContext.GetStripId(), m_SourceActionContext.GetStateId(), m_DestinationActionContext.GetStateId(), m_BehaviorId); LOG_DEBUG("srcactionIndex %i dstactionIndex %i srcstripId %i dststripId %i srcstateId %i dststateId %i behaviorId %llu", m_SrcActionIndex, m_DstActionIndex, m_SourceActionContext.GetStripId(), m_DestinationActionContext.GetStripId(), m_SourceActionContext.GetStateId(), m_DestinationActionContext.GetStateId(), m_BehaviorId);
} }

View File

@@ -1,9 +1,9 @@
#include "MoveToInventoryMessage.h" #include "MoveToInventoryMessage.h"
MoveToInventoryMessage::MoveToInventoryMessage(const AMFArrayValue& arguments) : BehaviorMessageBase{ arguments } { MoveToInventoryMessage::MoveToInventoryMessage(const AMFArrayValue& arguments, const LWOOBJID _owningPlayerID) : m_OwningPlayerID{ _owningPlayerID }, BehaviorMessageBase{ arguments } {
const auto* const behaviorIndexValue = arguments.Get<double>("BehaviorIndex"); const auto* const behaviorIndexValue = arguments.Get<double>("BehaviorIndex");
if (!behaviorIndexValue) return; if (!behaviorIndexValue) return;
m_BehaviorIndex = static_cast<uint32_t>(behaviorIndexValue->GetValue()); m_BehaviorIndex = static_cast<uint32_t>(behaviorIndexValue->GetValue());
LOG_DEBUG("behaviorId %i behaviorIndex %i", m_BehaviorId, m_BehaviorIndex); LOG_DEBUG("behaviorId %llu behaviorIndex %i", m_BehaviorId, m_BehaviorIndex);
} }

View File

@@ -10,11 +10,13 @@ class AMFArrayValue;
*/ */
class MoveToInventoryMessage : public BehaviorMessageBase { class MoveToInventoryMessage : public BehaviorMessageBase {
public: public:
MoveToInventoryMessage(const AMFArrayValue& arguments); MoveToInventoryMessage(const AMFArrayValue& arguments, const LWOOBJID owningPlayerID);
[[nodiscard]] uint32_t GetBehaviorIndex() const noexcept { return m_BehaviorIndex; }; [[nodiscard]] uint32_t GetBehaviorIndex() const noexcept { return m_BehaviorIndex; };
[[nodiscard]] LWOOBJID GetOwningPlayerID() const noexcept { return m_OwningPlayerID; };
private: private:
uint32_t m_BehaviorIndex; uint32_t m_BehaviorIndex;
LWOOBJID m_OwningPlayerID{};
}; };
#endif //!__MOVETOINVENTORYMESSAGE__H__ #endif //!__MOVETOINVENTORYMESSAGE__H__

View File

@@ -6,5 +6,5 @@ RearrangeStripMessage::RearrangeStripMessage(const AMFArrayValue& arguments)
, m_DstActionIndex{ GetActionIndexFromArgument(arguments, "dstActionIndex") } , m_DstActionIndex{ GetActionIndexFromArgument(arguments, "dstActionIndex") }
, m_ActionContext{ arguments } { , m_ActionContext{ arguments } {
LOG_DEBUG("srcactionIndex %i dstactionIndex %i stripId %i behaviorId %i stateId %i", m_SrcActionIndex, m_DstActionIndex, m_ActionContext.GetStripId(), m_BehaviorId, m_ActionContext.GetStateId()); LOG_DEBUG("srcactionIndex %i dstactionIndex %i stripId %i behaviorId %llu stateId %i", m_SrcActionIndex, m_DstActionIndex, m_ActionContext.GetStripId(), m_BehaviorId, m_ActionContext.GetStateId());
} }

View File

@@ -5,5 +5,5 @@ RemoveActionsMessage::RemoveActionsMessage(const AMFArrayValue& arguments)
, m_ActionIndex{ GetActionIndexFromArgument(arguments) } , m_ActionIndex{ GetActionIndexFromArgument(arguments) }
, m_ActionContext{ arguments } { , m_ActionContext{ arguments } {
LOG_DEBUG("behaviorId %i actionIndex %i stripId %i stateId %i", m_BehaviorId, m_ActionIndex, m_ActionContext.GetStripId(), m_ActionContext.GetStateId()); LOG_DEBUG("behaviorId %llu actionIndex %i stripId %i stateId %i", m_BehaviorId, m_ActionIndex, m_ActionContext.GetStripId(), m_ActionContext.GetStateId());
} }

View File

@@ -4,5 +4,5 @@ RemoveStripMessage::RemoveStripMessage(const AMFArrayValue& arguments)
: BehaviorMessageBase{ arguments } : BehaviorMessageBase{ arguments }
, m_ActionContext{ arguments } { , m_ActionContext{ arguments } {
LOG_DEBUG("stripId %i stateId %i behaviorId %i", m_ActionContext.GetStripId(), m_ActionContext.GetStateId(), m_BehaviorId); LOG_DEBUG("stripId %i stateId %i behaviorId %llu", m_ActionContext.GetStripId(), m_ActionContext.GetStateId(), m_BehaviorId);
} }

View File

@@ -5,5 +5,5 @@ RenameMessage::RenameMessage(const AMFArrayValue& arguments) : BehaviorMessageBa
if (!nameAmf) return; if (!nameAmf) return;
m_Name = nameAmf->GetValue(); m_Name = nameAmf->GetValue();
LOG_DEBUG("behaviorId %i n %s", m_BehaviorId, m_Name.c_str()); LOG_DEBUG("behaviorId %llu n %s", m_BehaviorId, m_Name.c_str());
} }

View File

@@ -7,5 +7,5 @@ SplitStripMessage::SplitStripMessage(const AMFArrayValue& arguments)
, m_DestinationActionContext{ arguments, "dstStateID", "dstStripID" } , m_DestinationActionContext{ arguments, "dstStateID", "dstStripID" }
, m_DestinationPosition{ arguments, "dstStripUI" } { , m_DestinationPosition{ arguments, "dstStripUI" } {
LOG_DEBUG("behaviorId %i xPosition %f yPosition %f sourceStrip %i destinationStrip %i sourceState %i destinationState %i srcActindex %i", m_BehaviorId, m_DestinationPosition.GetX(), m_DestinationPosition.GetY(), m_SourceActionContext.GetStripId(), m_DestinationActionContext.GetStripId(), m_SourceActionContext.GetStateId(), m_DestinationActionContext.GetStateId(), m_SrcActionIndex); LOG_DEBUG("behaviorId %llu xPosition %f yPosition %f sourceStrip %i destinationStrip %i sourceState %i destinationState %i srcActindex %i", m_BehaviorId, m_DestinationPosition.GetX(), m_DestinationPosition.GetY(), m_SourceActionContext.GetStripId(), m_DestinationActionContext.GetStripId(), m_SourceActionContext.GetStateId(), m_DestinationActionContext.GetStateId(), m_SrcActionIndex);
} }

View File

@@ -12,5 +12,5 @@ UpdateActionMessage::UpdateActionMessage(const AMFArrayValue& arguments)
m_Action = Action{ *actionValue }; m_Action = Action{ *actionValue };
LOG_DEBUG("type %s valueParameterName %s valueParameterString %s valueParameterDouble %f behaviorId %i actionIndex %i stripId %i stateId %i", m_Action.GetType().data(), m_Action.GetValueParameterName().data(), m_Action.GetValueParameterString().data(), m_Action.GetValueParameterDouble(), m_BehaviorId, m_ActionIndex, m_ActionContext.GetStripId(), m_ActionContext.GetStateId()); LOG_DEBUG("type %s valueParameterName %s valueParameterString %s valueParameterDouble %f behaviorId %llu actionIndex %i stripId %i stateId %i", m_Action.GetType().data(), m_Action.GetValueParameterName().data(), m_Action.GetValueParameterString().data(), m_Action.GetValueParameterDouble(), m_BehaviorId, m_ActionIndex, m_ActionContext.GetStripId(), m_ActionContext.GetStateId());
} }

View File

@@ -5,5 +5,5 @@ UpdateStripUiMessage::UpdateStripUiMessage(const AMFArrayValue& arguments)
, m_Position{ arguments } , m_Position{ arguments }
, m_ActionContext{ arguments } { , m_ActionContext{ arguments } {
LOG_DEBUG("xPosition %f yPosition %f stripId %i stateId %i behaviorId %i", m_Position.GetX(), m_Position.GetY(), m_ActionContext.GetStripId(), m_ActionContext.GetStateId(), m_BehaviorId); LOG_DEBUG("xPosition %f yPosition %f stripId %i stateId %i behaviorId %llu", m_Position.GetX(), m_Position.GetY(), m_ActionContext.GetStripId(), m_ActionContext.GetStateId(), m_BehaviorId);
} }

View File

@@ -30,22 +30,27 @@
#include "SplitStripMessage.h" #include "SplitStripMessage.h"
#include "UpdateActionMessage.h" #include "UpdateActionMessage.h"
#include "UpdateStripUiMessage.h" #include "UpdateStripUiMessage.h"
#include "eObjectBits.h"
void ControlBehaviors::RequestUpdatedID(ControlBehaviorContext& context) { void ControlBehaviors::RequestUpdatedID(ControlBehaviorContext& context) {
BehaviorMessageBase msgBase{ context.arguments };
const auto oldBehaviorID = msgBase.GetBehaviorId();
ObjectIDManager::RequestPersistentID( ObjectIDManager::RequestPersistentID(
[context](uint32_t persistentId) { [context, oldBehaviorID](uint32_t persistentId) {
if (!context) { if (!context) {
LOG("Model to update behavior ID for is null. Cannot update ID."); LOG("Model to update behavior ID for is null. Cannot update ID.");
return; return;
} }
LWOOBJID persistentIdBig = persistentId;
GeneralUtils::SetBit(persistentIdBig, eObjectBits::CHARACTER);
// This updates the behavior ID of the behavior should this be a new behavior // This updates the behavior ID of the behavior should this be a new behavior
AMFArrayValue args; AMFArrayValue args;
args.Insert("behaviorID", std::to_string(persistentId)); args.Insert("behaviorID", std::to_string(persistentIdBig));
args.Insert("objectID", std::to_string(context.modelComponent->GetParent()->GetObjectID())); args.Insert("objectID", std::to_string(context.modelComponent->GetParent()->GetObjectID()));
GameMessages::SendUIMessageServerToSingleClient(context.modelOwner, context.modelOwner->GetSystemAddress(), "UpdateBehaviorID", args); GameMessages::SendUIMessageServerToSingleClient(context.modelOwner, context.modelOwner->GetSystemAddress(), "UpdateBehaviorID", args);
context.modelComponent->UpdatePendingBehaviorId(persistentId); context.modelComponent->UpdatePendingBehaviorId(persistentIdBig, oldBehaviorID);
ControlBehaviors::Instance().SendBehaviorListToClient(context); ControlBehaviors::Instance().SendBehaviorListToClient(context);
}); });
@@ -102,9 +107,12 @@ void ControlBehaviors::ProcessCommand(Entity* const modelEntity, const AMFArrayV
if (!modelComponent) return; if (!modelComponent) return;
ControlBehaviorContext context{ arguments, modelComponent, modelOwner }; ControlBehaviorContext context{ arguments, modelComponent, modelOwner };
bool needsNewBehaviorID = false;
if (command == "sendBehaviorListToClient") { if (command == "sendBehaviorListToClient") {
SendBehaviorListToClient(context); SendBehaviorListToClient(context);
} else if (command == "sendBehaviorBlocksToClient") {
SendBehaviorBlocksToClient(context);
} else if (command == "modelTypeChanged") { } else if (command == "modelTypeChanged") {
const auto* const modelType = arguments.Get<double>("ModelType"); const auto* const modelType = arguments.Get<double>("ModelType");
if (!modelType) return; if (!modelType) return;
@@ -113,52 +121,54 @@ void ControlBehaviors::ProcessCommand(Entity* const modelEntity, const AMFArrayV
} else if (command == "toggleExecutionUpdates") { } else if (command == "toggleExecutionUpdates") {
// TODO // TODO
} else if (command == "addStrip") { } else if (command == "addStrip") {
if (BehaviorMessageBase(context.arguments).IsDefaultBehaviorId()) RequestUpdatedID(context); needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<AddStripMessage>(context.arguments);
context.modelComponent->HandleControlBehaviorsMsg<AddStripMessage>(context.arguments);
} else if (command == "removeStrip") { } else if (command == "removeStrip") {
context.modelComponent->HandleControlBehaviorsMsg<RemoveStripMessage>(arguments); needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<RemoveStripMessage>(arguments);
} else if (command == "mergeStrips") { } else if (command == "mergeStrips") {
context.modelComponent->HandleControlBehaviorsMsg<MergeStripsMessage>(arguments); needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<MergeStripsMessage>(arguments);
} else if (command == "splitStrip") { } else if (command == "splitStrip") {
context.modelComponent->HandleControlBehaviorsMsg<SplitStripMessage>(arguments); needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<SplitStripMessage>(arguments);
} else if (command == "updateStripUI") { } else if (command == "updateStripUI") {
context.modelComponent->HandleControlBehaviorsMsg<UpdateStripUiMessage>(arguments); needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<UpdateStripUiMessage>(arguments);
} else if (command == "addAction") { } else if (command == "addAction") {
context.modelComponent->HandleControlBehaviorsMsg<AddActionMessage>(arguments); needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<AddActionMessage>(arguments);
} else if (command == "migrateActions") { } else if (command == "migrateActions") {
context.modelComponent->HandleControlBehaviorsMsg<MigrateActionsMessage>(arguments); needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<MigrateActionsMessage>(arguments);
} else if (command == "rearrangeStrip") { } else if (command == "rearrangeStrip") {
context.modelComponent->HandleControlBehaviorsMsg<RearrangeStripMessage>(arguments); needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<RearrangeStripMessage>(arguments);
} else if (command == "add") {
AddMessage msg{ context.arguments };
context.modelComponent->AddBehavior(msg);
SendBehaviorListToClient(context);
} else if (command == "removeActions") { } else if (command == "removeActions") {
context.modelComponent->HandleControlBehaviorsMsg<RemoveActionsMessage>(arguments); needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<RemoveActionsMessage>(arguments);
} else if (command == "updateAction") {
needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<UpdateActionMessage>(arguments);
} else if (command == "rename") { } else if (command == "rename") {
context.modelComponent->HandleControlBehaviorsMsg<RenameMessage>(arguments); needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<RenameMessage>(arguments);
// Send the list back to the client so the name is updated. // Send the list back to the client so the name is updated.
SendBehaviorListToClient(context); SendBehaviorListToClient(context);
} else if (command == "sendBehaviorBlocksToClient") { } else if (command == "add") {
SendBehaviorBlocksToClient(context); AddMessage msg{ context.arguments, context.modelOwner->GetObjectID() };
} else if (command == "moveToInventory") { context.modelComponent->AddBehavior(msg);
MoveToInventoryMessage msg{ arguments }; SendBehaviorListToClient(context);
context.modelComponent->MoveToInventory(msg); } else if (command == "moveToInventory" || command == "remove") {
// both moveToInventory and remove use the same args
const bool isRemove = command != "remove";
MoveToInventoryMessage msg{ arguments, modelOwner->GetObjectID() };
context.modelComponent->RemoveBehavior(msg, isRemove);
auto* characterComponent = modelOwner->GetComponent<CharacterComponent>(); auto* characterComponent = modelOwner->GetComponent<CharacterComponent>();
if (!characterComponent) return; if (!characterComponent) return;
AMFArrayValue args; if (!isRemove) {
args.Insert("BehaviorID", std::to_string(msg.GetBehaviorId())); AMFArrayValue args;
GameMessages::SendUIMessageServerToSingleClient(modelOwner, characterComponent->GetSystemAddress(), "BehaviorRemoved", args); args.Insert("BehaviorID", std::to_string(msg.GetBehaviorId()));
GameMessages::SendUIMessageServerToSingleClient(modelOwner, characterComponent->GetSystemAddress(), "BehaviorRemoved", args);
}
SendBehaviorListToClient(context); SendBehaviorListToClient(context);
} else if (command == "updateAction") {
context.modelComponent->HandleControlBehaviorsMsg<UpdateActionMessage>(arguments);
} else { } else {
LOG("Unknown behavior command (%s)", command.data()); LOG("Unknown behavior command (%s)", command.data());
} }
if (needsNewBehaviorID) RequestUpdatedID(context);
} }
ControlBehaviors::ControlBehaviors() { ControlBehaviors::ControlBehaviors() {

View File

@@ -8,9 +8,12 @@
#include <ranges> #include <ranges>
PropertyBehavior::PropertyBehavior() { PropertyBehavior::PropertyBehavior(bool _isTemplated) {
m_LastEditedState = BehaviorState::HOME_STATE; m_LastEditedState = BehaviorState::HOME_STATE;
m_ActiveState = BehaviorState::HOME_STATE; m_ActiveState = BehaviorState::HOME_STATE;
// Starts off as true so that only specific ways of adding behaviors allow a new id to be requested.
isTemplated = _isTemplated;
} }
template<> template<>
@@ -81,13 +84,6 @@ void PropertyBehavior::HandleMsg(RenameMessage& msg) {
m_Name = msg.GetName(); m_Name = msg.GetName();
}; };
template<>
void PropertyBehavior::HandleMsg(AddMessage& msg) {
// TODO Parse the corresponding behavior xml file.
m_BehaviorId = msg.GetBehaviorId();
isLoot = m_BehaviorId != 7965;
};
template<> template<>
void PropertyBehavior::HandleMsg(GameMessages::RequestUse& msg) { void PropertyBehavior::HandleMsg(GameMessages::RequestUse& msg) {
m_States[m_ActiveState].HandleMsg(msg); m_States[m_ActiveState].HandleMsg(msg);
@@ -99,6 +95,12 @@ void PropertyBehavior::HandleMsg(GameMessages::ResetModelToDefaults& msg) {
for (auto& state : m_States | std::views::values) state.HandleMsg(msg); for (auto& state : m_States | std::views::values) state.HandleMsg(msg);
} }
void PropertyBehavior::CheckModifyState(BehaviorMessageBase& msg) {
if (!isTemplated && m_BehaviorId != BehaviorMessageBase::DefaultBehaviorId) return;
isTemplated = false;
msg.SetNeedsNewBehaviorID(true);
}
void PropertyBehavior::SendBehaviorListToClient(AMFArrayValue& args) const { void PropertyBehavior::SendBehaviorListToClient(AMFArrayValue& args) const {
args.Insert("id", std::to_string(m_BehaviorId)); args.Insert("id", std::to_string(m_BehaviorId));
args.Insert("name", m_Name); args.Insert("name", m_Name);
@@ -147,6 +149,9 @@ void PropertyBehavior::Serialize(tinyxml2::XMLElement& behavior) const {
behavior.SetAttribute("isLocked", isLocked); behavior.SetAttribute("isLocked", isLocked);
behavior.SetAttribute("isLoot", isLoot); behavior.SetAttribute("isLoot", isLoot);
// CUSTOM XML ATTRIBUTE
behavior.SetAttribute("isTemplated", isTemplated);
for (const auto& [stateId, state] : m_States) { for (const auto& [stateId, state] : m_States) {
if (state.IsEmpty()) continue; if (state.IsEmpty()) continue;
auto* const stateElement = behavior.InsertNewChildElement("State"); auto* const stateElement = behavior.InsertNewChildElement("State");
@@ -161,6 +166,9 @@ void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) {
behavior.QueryBoolAttribute("isLocked", &isLocked); behavior.QueryBoolAttribute("isLocked", &isLocked);
behavior.QueryBoolAttribute("isLoot", &isLoot); behavior.QueryBoolAttribute("isLoot", &isLoot);
// CUSTOM XML ATTRIBUTE
if (!isTemplated) behavior.QueryBoolAttribute("isTemplated", &isTemplated);
for (const auto* stateElement = behavior.FirstChildElement("State"); stateElement; stateElement = stateElement->NextSiblingElement("State")) { for (const auto* stateElement = behavior.FirstChildElement("State"); stateElement; stateElement = stateElement->NextSiblingElement("State")) {
int32_t stateId = -1; int32_t stateId = -1;
stateElement->QueryIntAttribute("id", &stateId); stateElement->QueryIntAttribute("id", &stateId);
@@ -172,3 +180,7 @@ void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) {
void PropertyBehavior::Update(float deltaTime, ModelComponent& modelComponent) { void PropertyBehavior::Update(float deltaTime, ModelComponent& modelComponent) {
for (auto& state : m_States | std::views::values) state.Update(deltaTime, modelComponent); for (auto& state : m_States | std::views::values) state.Update(deltaTime, modelComponent);
} }
void PropertyBehavior::OnChatMessageReceived(const std::string& sMessage) {
for (auto& state : m_States | std::views::values) state.OnChatMessageReceived(sMessage);
}

View File

@@ -10,6 +10,7 @@ namespace tinyxml2 {
enum class BehaviorState : uint32_t; enum class BehaviorState : uint32_t;
class AMFArrayValue; class AMFArrayValue;
class BehaviorMessageBase;
class ModelComponent; class ModelComponent;
/** /**
@@ -17,7 +18,7 @@ class ModelComponent;
*/ */
class PropertyBehavior { class PropertyBehavior {
public: public:
PropertyBehavior(); PropertyBehavior(bool _isTemplated = false);
template <typename Msg> template <typename Msg>
void HandleMsg(Msg& msg); void HandleMsg(Msg& msg);
@@ -26,14 +27,21 @@ public:
void VerifyLastEditedState(); void VerifyLastEditedState();
void SendBehaviorListToClient(AMFArrayValue& args) const; void SendBehaviorListToClient(AMFArrayValue& args) const;
void SendBehaviorBlocksToClient(AMFArrayValue& args) const; void SendBehaviorBlocksToClient(AMFArrayValue& args) const;
void CheckModifyState(BehaviorMessageBase& msg);
[[nodiscard]] int32_t GetBehaviorId() const noexcept { return m_BehaviorId; } [[nodiscard]] LWOOBJID GetBehaviorId() const noexcept { return m_BehaviorId; }
void SetBehaviorId(int32_t id) noexcept { m_BehaviorId = id; } void SetBehaviorId(LWOOBJID id) noexcept { m_BehaviorId = id; }
bool GetIsLoot() const noexcept { return isLoot; }
void SetIsLoot(const bool val) noexcept { isLoot = val; }
const std::string& GetName() const noexcept { return m_Name; }
void Serialize(tinyxml2::XMLElement& behavior) const; void Serialize(tinyxml2::XMLElement& behavior) const;
void Deserialize(const tinyxml2::XMLElement& behavior); void Deserialize(const tinyxml2::XMLElement& behavior);
void Update(float deltaTime, ModelComponent& modelComponent); void Update(float deltaTime, ModelComponent& modelComponent);
void OnChatMessageReceived(const std::string& sMessage);
private: private:
// The current active behavior state. Behaviors can only be in ONE state at a time. // The current active behavior state. Behaviors can only be in ONE state at a time.
@@ -51,13 +59,16 @@ private:
// Whether this behavior is custom or pre-fab. // Whether this behavior is custom or pre-fab.
bool isLoot = false; bool isLoot = false;
// Whether or not the behavior has been modified from its original state.
bool isTemplated;
// The last state that was edited. This is used so when the client re-opens the behavior editor, it will open to the last edited state. // The last state that was edited. This is used so when the client re-opens the behavior editor, it will open to the last edited state.
// If the last edited state has no strips, it will open to the first state that has strips. // If the last edited state has no strips, it will open to the first state that has strips.
BehaviorState m_LastEditedState; BehaviorState m_LastEditedState;
// The behavior id for this behavior. This is expected to be fully unique, however an id of -1 means this behavior was just created // The behavior id for this behavior. This is expected to be fully unique, however an id of -1 means this behavior was just created
// and needs to be assigned an id. // and needs to be assigned an id.
int32_t m_BehaviorId = -1; LWOOBJID m_BehaviorId = -1;
}; };
#endif //!__PROPERTYBEHAVIOR__H__ #endif //!__PROPERTYBEHAVIOR__H__

View File

@@ -166,3 +166,7 @@ void State::Deserialize(const tinyxml2::XMLElement& state) {
void State::Update(float deltaTime, ModelComponent& modelComponent) { void State::Update(float deltaTime, ModelComponent& modelComponent) {
for (auto& strip : m_Strips) strip.Update(deltaTime, modelComponent); for (auto& strip : m_Strips) strip.Update(deltaTime, modelComponent);
} }
void State::OnChatMessageReceived(const std::string& sMessage) {
for (auto& strip : m_Strips) strip.OnChatMessageReceived(sMessage);
}

View File

@@ -22,6 +22,8 @@ public:
void Deserialize(const tinyxml2::XMLElement& state); void Deserialize(const tinyxml2::XMLElement& state);
void Update(float deltaTime, ModelComponent& modelComponent); void Update(float deltaTime, ModelComponent& modelComponent);
void OnChatMessageReceived(const std::string& sMessage);
private: private:
// The strips contained within this state. // The strips contained within this state.

View File

@@ -5,7 +5,12 @@
#include "tinyxml2.h" #include "tinyxml2.h"
#include "dEntity/EntityInfo.h" #include "dEntity/EntityInfo.h"
#include "ModelComponent.h" #include "ModelComponent.h"
#include "ChatPackets.h"
#include "PropertyManagementComponent.h"
#include "PlayerManager.h" #include "PlayerManager.h"
#include "SimplePhysicsComponent.h"
#include "dChatFilter.h"
#include "DluAssert.h" #include "DluAssert.h"
@@ -84,9 +89,11 @@ void Strip::HandleMsg(MigrateActionsMessage& msg) {
template<> template<>
void Strip::HandleMsg(GameMessages::RequestUse& msg) { void Strip::HandleMsg(GameMessages::RequestUse& msg) {
if (m_PausedTime > 0.0f) return; if (m_PausedTime > 0.0f || !HasMinimumActions()) return;
if (m_Actions[m_NextActionIndex].GetType() == "OnInteract") { auto& nextAction = GetNextAction();
if (nextAction.GetType() == "OnInteract") {
IncrementAction(); IncrementAction();
m_WaitingForAction = false; m_WaitingForAction = false;
} }
@@ -97,6 +104,18 @@ void Strip::HandleMsg(GameMessages::ResetModelToDefaults& msg) {
m_WaitingForAction = false; m_WaitingForAction = false;
m_PausedTime = 0.0f; m_PausedTime = 0.0f;
m_NextActionIndex = 0; m_NextActionIndex = 0;
m_InActionMove = NiPoint3Constant::ZERO;
m_PreviousFramePosition = NiPoint3Constant::ZERO;
}
void Strip::OnChatMessageReceived(const std::string& sMessage) {
if (m_PausedTime > 0.0f || !HasMinimumActions()) return;
const auto& nextAction = GetNextAction();
if (nextAction.GetType() == "OnChat" && nextAction.GetValueParameterString() == sMessage) {
IncrementAction();
m_WaitingForAction = false;
}
} }
void Strip::IncrementAction() { void Strip::IncrementAction() {
@@ -111,7 +130,9 @@ void Strip::Spawn(LOT lot, Entity& entity) {
info.pos = entity.GetPosition(); info.pos = entity.GetPosition();
info.rot = NiQuaternionConstant::IDENTITY; info.rot = NiQuaternionConstant::IDENTITY;
info.spawnerID = entity.GetObjectID(); info.spawnerID = entity.GetObjectID();
Game::entityManager->ConstructEntity(Game::entityManager->CreateEntity(info, nullptr, &entity)); auto* const spawnedEntity = Game::entityManager->CreateEntity(info, nullptr, &entity);
spawnedEntity->AddToGroup("SpawnedPropertyEnemies");
Game::entityManager->ConstructEntity(spawnedEntity);
} }
// Spawns a specific drop for all // Spawns a specific drop for all
@@ -125,21 +146,51 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
auto& entity = *modelComponent.GetParent(); auto& entity = *modelComponent.GetParent();
auto& nextAction = GetNextAction(); auto& nextAction = GetNextAction();
auto number = nextAction.GetValueParameterDouble(); auto number = nextAction.GetValueParameterDouble();
auto valueStr = nextAction.GetValueParameterString();
auto numberAsInt = static_cast<int32_t>(number); auto numberAsInt = static_cast<int32_t>(number);
auto nextActionType = GetNextAction().GetType(); auto nextActionType = GetNextAction().GetType();
if (nextActionType == "SpawnStromling") {
Spawn(10495, entity); // Stromling property // TODO replace with switch case and nextActionType with enum
} else if (nextActionType == "SpawnPirate") { /* BEGIN Move */
Spawn(10497, entity); // Maelstrom Pirate property if (nextActionType == "MoveRight" || nextActionType == "MoveLeft") {
} else if (nextActionType == "SpawnRonin") { // X axis
Spawn(10498, entity); // Dark Ronin property bool isMoveLeft = nextActionType == "MoveLeft";
} else if (nextActionType == "DropImagination") { int negative = isMoveLeft ? -1 : 1;
for (; numberAsInt > 0; numberAsInt--) SpawnDrop(935, entity); // 1 Imagination powerup // Default velocity is 3 units per second.
} else if (nextActionType == "DropHealth") { if (modelComponent.TrySetVelocity(NiPoint3Constant::UNIT_X * negative)) {
for (; numberAsInt > 0; numberAsInt--) SpawnDrop(177, entity); // 1 Life powerup m_PreviousFramePosition = entity.GetPosition();
} else if (nextActionType == "DropArmor") { m_InActionMove.x = isMoveLeft ? -number : number;
for (; numberAsInt > 0; numberAsInt--) SpawnDrop(6431, entity); // 1 Armor powerup }
} else if (nextActionType == "Smash") { } else if (nextActionType == "FlyUp" || nextActionType == "FlyDown") {
// Y axis
bool isFlyDown = nextActionType == "FlyDown";
int negative = isFlyDown ? -1 : 1;
// Default velocity is 3 units per second.
if (modelComponent.TrySetVelocity(NiPoint3Constant::UNIT_Y * negative)) {
m_PreviousFramePosition = entity.GetPosition();
m_InActionMove.y = isFlyDown ? -number : number;
}
} else if (nextActionType == "MoveForward" || nextActionType == "MoveBackward") {
// Z axis
bool isMoveBackward = nextActionType == "MoveBackward";
int negative = isMoveBackward ? -1 : 1;
// Default velocity is 3 units per second.
if (modelComponent.TrySetVelocity(NiPoint3Constant::UNIT_Z * negative)) {
m_PreviousFramePosition = entity.GetPosition();
m_InActionMove.z = isMoveBackward ? -number : number;
}
}
/* END Move */
/* BEGIN Navigation */
else if (nextActionType == "SetSpeed") {
modelComponent.SetSpeed(number);
}
/* END Navigation */
/* BEGIN Action */
else if (nextActionType == "Smash") {
if (!modelComponent.IsUnSmashing()) { if (!modelComponent.IsUnSmashing()) {
GameMessages::Smash smash{}; GameMessages::Smash smash{};
smash.target = entity.GetObjectID(); smash.target = entity.GetObjectID();
@@ -157,18 +208,42 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
m_PausedTime = number; m_PausedTime = number;
} else if (nextActionType == "Wait") { } else if (nextActionType == "Wait") {
m_PausedTime = number; m_PausedTime = number;
} else if (nextActionType == "Chat") {
bool isOk = Game::chatFilter->IsSentenceOkay(valueStr.data(), eGameMasterLevel::CIVILIAN).empty();
// In case a word is removed from the whitelist after it was approved
const auto modelName = "%[Objects_" + std::to_string(entity.GetLOT()) + "_name]";
if (isOk) ChatPackets::SendChatMessage(UNASSIGNED_SYSTEM_ADDRESS, 12, modelName, entity.GetObjectID(), false, GeneralUtils::ASCIIToUTF16(valueStr));
PropertyManagementComponent::Instance()->OnChatMessageReceived(valueStr.data());
} else if (nextActionType == "PrivateMessage") {
PropertyManagementComponent::Instance()->OnChatMessageReceived(valueStr.data());
} else if (nextActionType == "PlaySound") { } else if (nextActionType == "PlaySound") {
GameMessages::PlayBehaviorSound sound; GameMessages::PlayBehaviorSound sound;
sound.target = modelComponent.GetParent()->GetObjectID(); sound.target = modelComponent.GetParent()->GetObjectID();
sound.soundID = numberAsInt; sound.soundID = numberAsInt;
sound.Send(UNASSIGNED_SYSTEM_ADDRESS); sound.Send(UNASSIGNED_SYSTEM_ADDRESS);
} else { }
/* END Action */
/* BEGIN Gameplay */
else if (nextActionType == "SpawnStromling") {
Spawn(10495, entity); // Stromling property
} else if (nextActionType == "SpawnPirate") {
Spawn(10497, entity); // Maelstrom Pirate property
} else if (nextActionType == "SpawnRonin") {
Spawn(10498, entity); // Dark Ronin property
} else if (nextActionType == "DropImagination") {
for (; numberAsInt > 0; numberAsInt--) SpawnDrop(935, entity); // 1 Imagination powerup
} else if (nextActionType == "DropHealth") {
for (; numberAsInt > 0; numberAsInt--) SpawnDrop(177, entity); // 1 Life powerup
} else if (nextActionType == "DropArmor") {
for (; numberAsInt > 0; numberAsInt--) SpawnDrop(6431, entity); // 1 Armor powerup
}
/* END Gameplay */
else {
static std::set<std::string> g_WarnedActions; static std::set<std::string> g_WarnedActions;
if (!g_WarnedActions.contains(nextActionType.data())) { if (!g_WarnedActions.contains(nextActionType.data())) {
LOG("Tried to play action (%s) which is not supported.", nextActionType.data()); LOG("Tried to play action (%s) which is not supported.", nextActionType.data());
g_WarnedActions.insert(nextActionType.data()); g_WarnedActions.insert(nextActionType.data());
} }
return;
} }
IncrementAction(); IncrementAction();
@@ -187,12 +262,67 @@ void Strip::RemoveStates(ModelComponent& modelComponent) const {
} }
} }
bool Strip::CheckMovement(float deltaTime, ModelComponent& modelComponent) {
auto& entity = *modelComponent.GetParent();
const auto& currentPos = entity.GetPosition();
const auto diff = currentPos - m_PreviousFramePosition;
const auto [moveX, moveY, moveZ] = m_InActionMove;
m_PreviousFramePosition = currentPos;
// Only want to subtract from the move if one is being performed.
// Starts at true because we may not be doing a move at all.
// If one is being done, then one of the move_ variables will be non-zero
bool moveFinished = true;
NiPoint3 finalPositionAdjustment = NiPoint3Constant::ZERO;
if (moveX != 0.0f) {
m_InActionMove.x -= diff.x;
// If the sign bit is different between the two numbers, then we have finished our move.
moveFinished = std::signbit(m_InActionMove.x) != std::signbit(moveX);
finalPositionAdjustment.x = m_InActionMove.x;
} else if (moveY != 0.0f) {
m_InActionMove.y -= diff.y;
// If the sign bit is different between the two numbers, then we have finished our move.
moveFinished = std::signbit(m_InActionMove.y) != std::signbit(moveY);
finalPositionAdjustment.y = m_InActionMove.y;
} else if (moveZ != 0.0f) {
m_InActionMove.z -= diff.z;
// If the sign bit is different between the two numbers, then we have finished our move.
moveFinished = std::signbit(m_InActionMove.z) != std::signbit(moveZ);
finalPositionAdjustment.z = m_InActionMove.z;
}
// Once done, set the in action move & velocity to zero
if (moveFinished && m_InActionMove != NiPoint3Constant::ZERO) {
auto entityVelocity = entity.GetVelocity();
// Zero out only the velocity that was acted on
if (moveX != 0.0f) entityVelocity.x = 0.0f;
else if (moveY != 0.0f) entityVelocity.y = 0.0f;
else if (moveZ != 0.0f) entityVelocity.z = 0.0f;
modelComponent.SetVelocity(entityVelocity);
// Do the final adjustment so we will have moved exactly the requested units
entity.SetPosition(entity.GetPosition() + finalPositionAdjustment);
m_InActionMove = NiPoint3Constant::ZERO;
}
return moveFinished;
}
void Strip::Update(float deltaTime, ModelComponent& modelComponent) { void Strip::Update(float deltaTime, ModelComponent& modelComponent) {
// No point in running a strip with only one action.
// Strips are also designed to have 2 actions or more to run.
if (!HasMinimumActions()) return;
// Return if this strip has an active movement action
if (!CheckMovement(deltaTime, modelComponent)) return;
// Don't run this strip if we're paused.
m_PausedTime -= deltaTime; m_PausedTime -= deltaTime;
if (m_PausedTime > 0.0f) return; if (m_PausedTime > 0.0f) return;
m_PausedTime = 0.0f; m_PausedTime = 0.0f;
// Return here if we're waiting for external interactions to continue.
if (m_WaitingForAction) return; if (m_WaitingForAction) return;
auto& entity = *modelComponent.GetParent(); auto& entity = *modelComponent.GetParent();
@@ -200,13 +330,16 @@ void Strip::Update(float deltaTime, ModelComponent& modelComponent) {
RemoveStates(modelComponent); RemoveStates(modelComponent);
// Check for starting blocks and if not a starting block proc this blocks action // Check for trigger blocks and if not a trigger block proc this blocks action
if (m_NextActionIndex == 0) { if (m_NextActionIndex == 0) {
if (nextAction.GetType() == "OnInteract") { if (nextAction.GetType() == "OnInteract") {
modelComponent.AddInteract(); modelComponent.AddInteract();
Game::entityManager->SerializeEntity(entity); Game::entityManager->SerializeEntity(entity);
m_WaitingForAction = true; m_WaitingForAction = true;
} else if (nextAction.GetType() == "OnChat") {
Game::entityManager->SerializeEntity(entity);
m_WaitingForAction = true;
} }
} else { // should be a normal block } else { // should be a normal block
ProcNormalAction(deltaTime, modelComponent); ProcNormalAction(deltaTime, modelComponent);

View File

@@ -29,15 +29,25 @@ public:
void IncrementAction(); void IncrementAction();
void Spawn(LOT object, Entity& entity); void Spawn(LOT object, Entity& entity);
// Checks the movement logic for whether or not to proceed
// Returns true if the movement can continue, false if it needs to wait more.
bool CheckMovement(float deltaTime, ModelComponent& modelComponent);
void Update(float deltaTime, ModelComponent& modelComponent); void Update(float deltaTime, ModelComponent& modelComponent);
void SpawnDrop(LOT dropLOT, Entity& entity); void SpawnDrop(LOT dropLOT, Entity& entity);
void ProcNormalAction(float deltaTime, ModelComponent& modelComponent); void ProcNormalAction(float deltaTime, ModelComponent& modelComponent);
void RemoveStates(ModelComponent& modelComponent) const; void RemoveStates(ModelComponent& modelComponent) const;
// 2 actions are required for strips to work
bool HasMinimumActions() const { return m_Actions.size() >= 2; }
void OnChatMessageReceived(const std::string& sMessage);
private: private:
// Indicates this Strip is waiting for an action to be taken upon it to progress to its actions // Indicates this Strip is waiting for an action to be taken upon it to progress to its actions
bool m_WaitingForAction{ false }; bool m_WaitingForAction{ false };
// The amount of time this strip is paused for. Any interactions with this strip should be bounced if this is greater than 0. // The amount of time this strip is paused for. Any interactions with this strip should be bounced if this is greater than 0.
// Actions that do not use time do not use this (ex. positions).
float m_PausedTime{ 0.0f }; float m_PausedTime{ 0.0f };
// The index of the next action to be played. This should always be within range of [0, m_Actions.size()). // The index of the next action to be played. This should always be within range of [0, m_Actions.size()).
@@ -48,6 +58,13 @@ private:
// The location of this strip on the UGBehaviorEditor UI // The location of this strip on the UGBehaviorEditor UI
StripUiPosition m_Position; StripUiPosition m_Position;
// The current actions remaining distance to the target
// Only 1 of these vertexs' will be active at once for any given strip.
NiPoint3 m_InActionMove{};
// The position of the parent model on the previous frame
NiPoint3 m_PreviousFramePosition{};
}; };
#endif //!__STRIP__H__ #endif //!__STRIP__H__

View File

@@ -24,9 +24,8 @@ namespace {
} }
void Loot::CacheMatrix(uint32_t matrixIndex) { void Loot::CacheMatrix(uint32_t matrixIndex) {
if (CachedMatrices.find(matrixIndex) != CachedMatrices.end()) { if (CachedMatrices.contains(matrixIndex)) return;
return;
}
CachedMatrices.insert(matrixIndex); CachedMatrices.insert(matrixIndex);
CDComponentsRegistryTable* componentsRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>(); CDComponentsRegistryTable* componentsRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
CDItemComponentTable* itemComponentTable = CDClientManager::GetTable<CDItemComponentTable>(); CDItemComponentTable* itemComponentTable = CDClientManager::GetTable<CDItemComponentTable>();

View File

@@ -132,6 +132,7 @@ namespace Mail {
} else { } else {
response.status = eSendResponse::SenderAccountIsMuted; response.status = eSendResponse::SenderAccountIsMuted;
} }
LOG("Finished send with status %s", StringifiedEnum::ToString(response.status).data());
response.Send(sysAddr); response.Send(sysAddr);
} }
@@ -158,6 +159,7 @@ namespace Mail {
DataResponse response; DataResponse response;
response.playerMail = playerMail; response.playerMail = playerMail;
response.Send(sysAddr); response.Send(sysAddr);
LOG("DataRequest");
} }
void DataResponse::Serialize(RakNet::BitStream& bitStream) const { void DataResponse::Serialize(RakNet::BitStream& bitStream) const {
@@ -196,6 +198,7 @@ namespace Mail {
response.status = eAttachmentCollectResponse::Success; response.status = eAttachmentCollectResponse::Success;
} }
} }
LOG("AttachmentCollectResponse %s", StringifiedEnum::ToString(response.status).data());
response.Send(sysAddr); response.Send(sysAddr);
} }
@@ -218,14 +221,15 @@ namespace Mail {
response.mailID = mailID; response.mailID = mailID;
auto mailData = Database::Get()->GetMail(mailID); auto mailData = Database::Get()->GetMail(mailID);
if (mailData && !(mailData->itemLOT != 0 && mailData->itemCount > 0)) { if (mailData && !(mailData->itemLOT > 0 && mailData->itemCount > 0)) {
Database::Get()->DeleteMail(mailID); Database::Get()->DeleteMail(mailID);
response.status = eDeleteResponse::Success; response.status = eDeleteResponse::Success;
} else if (mailData && mailData->itemLOT != 0 && mailData->itemCount > 0) { } else if (mailData && mailData->itemLOT > 0 && mailData->itemCount > 0) {
response.status = eDeleteResponse::HasAttachments; response.status = eDeleteResponse::HasAttachments;
} else { } else {
response.status = eDeleteResponse::NotFound; response.status = eDeleteResponse::NotFound;
} }
LOG("DeleteRequest status %s", StringifiedEnum::ToString(response.status).data());
response.Send(sysAddr); response.Send(sysAddr);
} }
@@ -249,7 +253,9 @@ namespace Mail {
if (Database::Get()->GetMail(mailID)) { if (Database::Get()->GetMail(mailID)) {
response.status = eReadResponse::Success; response.status = eReadResponse::Success;
Database::Get()->MarkMailRead(mailID); Database::Get()->MarkMailRead(mailID);
} }
LOG("ReadRequest %s", StringifiedEnum::ToString(response.status).data());
response.Send(sysAddr); response.Send(sysAddr);
} }
@@ -267,6 +273,8 @@ namespace Mail {
response.status = eNotificationResponse::NewMail; response.status = eNotificationResponse::NewMail;
response.mailCount = unreadMailCount; response.mailCount = unreadMailCount;
} }
LOG("NotificationRequest %s", StringifiedEnum::ToString(response.status).data());
response.Send(sysAddr); response.Send(sysAddr);
} }
} }
@@ -288,6 +296,7 @@ void Mail::HandleMail(RakNet::BitStream& inStream, const SystemAddress& sysAddr,
LOG_DEBUG("Error Reading Mail Request: %s", StringifiedEnum::ToString(data.messageID).data()); LOG_DEBUG("Error Reading Mail Request: %s", StringifiedEnum::ToString(data.messageID).data());
return; return;
} }
LOG("Received mail message %s", StringifiedEnum::ToString(data.messageID).data());
request->Handle(); request->Handle();
} else { } else {
LOG_DEBUG("Unhandled Mail Request with ID: %i", data.messageID); LOG_DEBUG("Unhandled Mail Request with ID: %i", data.messageID);

View File

@@ -119,12 +119,15 @@ namespace Mail {
struct SendRequest : public MailLUBitStream { struct SendRequest : public MailLUBitStream {
MailInfo mailInfo; MailInfo mailInfo;
SendRequest() : MailLUBitStream(eMessageID::SendRequest) {}
bool Deserialize(RakNet::BitStream& bitStream) override; bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle() override; void Handle() override;
}; };
struct SendResponse :public MailLUBitStream { struct SendResponse :public MailLUBitStream {
eSendResponse status = eSendResponse::UnknownError; eSendResponse status = eSendResponse::UnknownError;
SendResponse() : MailLUBitStream(eMessageID::SendResponse) {}
void Serialize(RakNet::BitStream& bitStream) const override; void Serialize(RakNet::BitStream& bitStream) const override;
}; };
@@ -137,6 +140,7 @@ namespace Mail {
}; };
struct DataRequest : public MailLUBitStream { struct DataRequest : public MailLUBitStream {
DataRequest() : MailLUBitStream(eMessageID::DataRequest) {}
bool Deserialize(RakNet::BitStream& bitStream) override { return true; }; bool Deserialize(RakNet::BitStream& bitStream) override { return true; };
void Handle() override; void Handle() override;
}; };

View File

@@ -1514,6 +1514,7 @@ namespace DEVGMCommands {
} }
if (!closest) return; if (!closest) return;
LOG("%llu", closest->GetObjectID());
Game::entityManager->SerializeEntity(closest); Game::entityManager->SerializeEntity(closest);

View File

@@ -58,6 +58,7 @@ bool MailInfo::Deserialize(RakNet::BitStream& bitStream) {
bitStream.IgnoreBytes(4); // padding bitStream.IgnoreBytes(4); // padding
DluAssert(bitStream.GetNumberOfUnreadBits() == 0); DluAssert(bitStream.GetNumberOfUnreadBits() == 0);
LOG_DEBUG("MailInfo: %llu, %s, %s, %s, %llu, %i, %llu, %i, %llu, %i", id, subject.GetAsString().c_str(), body.GetAsString().c_str(), recipientName.GetAsString().c_str(), itemID, itemLOT, itemSubkey, itemCount, timeSent, wasRead);
return true; return true;
} }

View File

@@ -50,6 +50,8 @@ void NjMonastryBossInstance::OnPlayerLoaded(Entity* self, Entity* player) {
// Join the player in the activity // Join the player in the activity
UpdatePlayer(self, player->GetObjectID()); UpdatePlayer(self, player->GetObjectID());
TakeActivityCost(self, player->GetObjectID());
// Buff the player // Buff the player
auto* destroyableComponent = player->GetComponent<DestroyableComponent>(); auto* destroyableComponent = player->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr) { if (destroyableComponent != nullptr) {

View File

@@ -6,6 +6,7 @@
#include <algorithm> #include <algorithm>
#include "Logger.h" #include "Logger.h"
#include "Loot.h" #include "Loot.h"
#include "ShootingGalleryComponent.h"
bool ActivityManager::IsPlayerInActivity(Entity* self, LWOOBJID playerID) { bool ActivityManager::IsPlayerInActivity(Entity* self, LWOOBJID playerID) {
const auto* sac = self->GetComponent<ScriptedActivityComponent>(); const auto* sac = self->GetComponent<ScriptedActivityComponent>();
@@ -93,15 +94,16 @@ void ActivityManager::SaveScore(Entity* self, const LWOOBJID playerID, const flo
} }
bool ActivityManager::TakeActivityCost(const Entity* self, const LWOOBJID playerID) { bool ActivityManager::TakeActivityCost(const Entity* self, const LWOOBJID playerID) {
auto* sac = self->GetComponent<ScriptedActivityComponent>(); ActivityComponent* activityComponent = self->GetComponent<ScriptedActivityComponent>();
if (sac == nullptr) if (activityComponent == nullptr) {
return false; activityComponent = self->GetComponent<ShootingGalleryComponent>();
}
auto* player = Game::entityManager->GetEntity(playerID); auto* player = Game::entityManager->GetEntity(playerID);
if (player == nullptr) if (player == nullptr)
return false; return false;
return sac->TakeCost(player); return activityComponent->TakeCost(player);
} }
uint32_t ActivityManager::CalculateActivityRating(Entity* self, const LWOOBJID playerID) { uint32_t ActivityManager::CalculateActivityRating(Entity* self, const LWOOBJID playerID) {

View File

@@ -713,12 +713,12 @@ void HandleMasterPacket(Packet* packet) {
//Create our user and send them in: //Create our user and send them in:
UserManager::Instance()->CreateUser(it->second.sysAddr, username.GetAsString(), userHash); UserManager::Instance()->CreateUser(it->second.sysAddr, username.GetAsString(), userHash);
auto zone = Game::zoneManager->GetZone(); if (Game::zoneManager->HasZone()) {
if (zone) {
float x = 0.0f; float x = 0.0f;
float y = 0.0f; float y = 0.0f;
float z = 0.0f; float z = 0.0f;
auto zone = Game::zoneManager->GetZone();
if (zone->GetZoneID().GetMapID() == 1100) { if (zone->GetZoneID().GetMapID() == 1100) {
auto pos = zone->GetSpawnPos(); auto pos = zone->GetSpawnPos();
x = pos.x; x = pos.x;
@@ -1367,6 +1367,7 @@ void HandlePacket(Packet* packet) {
std::string sMessage = GeneralUtils::UTF16ToWTF8(chatMessage.message); std::string sMessage = GeneralUtils::UTF16ToWTF8(chatMessage.message);
LOG("%s: %s", playerName.c_str(), sMessage.c_str()); LOG("%s: %s", playerName.c_str(), sMessage.c_str());
ChatPackets::SendChatMessage(packet->systemAddress, chatMessage.chatChannel, playerName, user->GetLoggedInChar(), isMythran, chatMessage.message); ChatPackets::SendChatMessage(packet->systemAddress, chatMessage.chatChannel, playerName, user->GetLoggedInChar(), isMythran, chatMessage.message);
if (PropertyManagementComponent::Instance()) PropertyManagementComponent::Instance()->OnChatMessageReceived(sMessage);
} }
break; break;

View File

@@ -29,6 +29,7 @@ public:
/* Gets a pointer to the currently loaded zone. */ /* Gets a pointer to the currently loaded zone. */
Zone* GetZoneMut() const; Zone* GetZoneMut() const;
const Zone* GetZone() const { return GetZoneMut(); }; const Zone* GetZone() const { return GetZoneMut(); };
bool HasZone() const { return m_pZone != nullptr; };
void LoadZone(const LWOZONEID& zoneID); //Discard the current zone (if any) and loads a new zone. void LoadZone(const LWOZONEID& zoneID); //Discard the current zone (if any) and loads a new zone.
/* Adds a spawner to the zone with the specified ID. */ /* Adds a spawner to the zone with the specified ID. */

View File

@@ -0,0 +1,21 @@
UPDATE behaviors SET behavior_id = behavior_id | 0x1000000000000000;
ALTER TABLE properties_contents MODIFY behavior_1 BIGINT DEFAULT 0;
ALTER TABLE properties_contents MODIFY behavior_2 BIGINT DEFAULT 0;
ALTER TABLE properties_contents MODIFY behavior_3 BIGINT DEFAULT 0;
ALTER TABLE properties_contents MODIFY behavior_4 BIGINT DEFAULT 0;
ALTER TABLE properties_contents MODIFY behavior_5 BIGINT DEFAULT 0;
UPDATE properties_contents SET
behavior_1 = behavior_1 | 0x1000000000000000
WHERE behavior_1 != 0;
UPDATE properties_contents SET
behavior_2 = behavior_2 | 0x1000000000000000
WHERE behavior_2 != 0;
UPDATE properties_contents SET
behavior_3 = behavior_3 | 0x1000000000000000
WHERE behavior_3 != 0;
UPDATE properties_contents SET
behavior_4 = behavior_4 | 0x1000000000000000
WHERE behavior_4 != 0;
UPDATE properties_contents SET
behavior_5 = behavior_5 | 0x1000000000000000
WHERE behavior_5 != 0;

View File

@@ -0,0 +1 @@
/* See ModelNormalizeMigration.cpp for details */

View File

@@ -0,0 +1,31 @@
UPDATE behaviors SET behavior_id = behavior_id | 0x1000000000000000;
ALTER TABLE properties_contents ADD COLUMN behavior_1_1 BIGINT DEFAULT 0;
ALTER TABLE properties_contents ADD COLUMN behavior_2_1 BIGINT DEFAULT 0;
ALTER TABLE properties_contents ADD COLUMN behavior_3_1 BIGINT DEFAULT 0;
ALTER TABLE properties_contents ADD COLUMN behavior_4_1 BIGINT DEFAULT 0;
ALTER TABLE properties_contents ADD COLUMN behavior_5_1 BIGINT DEFAULT 0;
UPDATE properties_contents SET
behavior_1_1 = behavior_1 | 0x1000000000000000
WHERE behavior_1 != 0;
UPDATE properties_contents SET
behavior_2_1 = behavior_2 | 0x1000000000000000
WHERE behavior_2 != 0;
UPDATE properties_contents SET
behavior_3_1 = behavior_3 | 0x1000000000000000
WHERE behavior_3 != 0;
UPDATE properties_contents SET
behavior_4_1 = behavior_4 | 0x1000000000000000
WHERE behavior_4 != 0;
UPDATE properties_contents SET
behavior_5_1 = behavior_5 | 0x1000000000000000
WHERE behavior_5 != 0;
ALTER TABLE properties_contents DROP COLUMN behavior_1;
ALTER TABLE properties_contents DROP COLUMN behavior_2;
ALTER TABLE properties_contents DROP COLUMN behavior_3;
ALTER TABLE properties_contents DROP COLUMN behavior_4;
ALTER TABLE properties_contents DROP COLUMN behavior_5;
ALTER TABLE properties_contents RENAME COLUMN behavior_1_1 TO behavior_1;
ALTER TABLE properties_contents RENAME COLUMN behavior_2_1 TO behavior_2;
ALTER TABLE properties_contents RENAME COLUMN behavior_3_1 TO behavior_3;
ALTER TABLE properties_contents RENAME COLUMN behavior_4_1 TO behavior_4;
ALTER TABLE properties_contents RENAME COLUMN behavior_5_1 TO behavior_5;

View File

@@ -0,0 +1 @@
/* See ModelNormalizeMigration.cpp for details */

View File

@@ -213,7 +213,7 @@ TEST_F(GameMessageTests, ControlBehaviorAdd) {
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true); RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream); const auto arr = ReadArrayFromBitStream(inStream);
AddMessage add(*arr); AddMessage add(*arr, LWOOBJID_EMPTY);
ASSERT_EQ(add.GetBehaviorId(), 10446); ASSERT_EQ(add.GetBehaviorId(), 10446);
ASSERT_EQ(add.GetBehaviorIndex(), 0); ASSERT_EQ(add.GetBehaviorIndex(), 0);