Compare commits

...

18 Commits

Author SHA1 Message Date
David Markowitz
edda646856 feat: Add Restart Behavior
Resets the model to the default state at the end of the models frame.  Will see if in the future designers want this to be more strict on the resetting timing.
2025-06-28 21:18:44 -07:00
David Markowitz
55d181ea4b fix: lxfml normalization (#1835) 2025-06-27 22:31:48 -07:00
David Markowitz
ecbb465020 fix: get entity from manager vs from character (#1833)
fixes a possible crash due to null entity
2025-06-26 07:04:05 -04:00
David Markowitz
ec9927acbb fix: multiplied speeds with Speed Behaviors (#1832) 2025-06-26 07:03:25 -04: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
75 changed files with 1076 additions and 523 deletions

View File

@@ -20,6 +20,7 @@ set(DCOMMON_SOURCES
"TinyXmlUtils.cpp"
"Sd0.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.

View File

@@ -3,6 +3,7 @@
// C++
#include <charconv>
#include <cstdint>
#include <cmath>
#include <ctime>
#include <functional>
#include <optional>
@@ -145,7 +146,7 @@ namespace GeneralUtils {
template <typename... Bases>
struct overload : Bases... {
using is_transparent = void;
using Bases::operator() ... ;
using Bases::operator() ...;
};
struct char_pointer_hash {
@@ -202,7 +203,7 @@ namespace GeneralUtils {
}
template<typename T>
requires(!Numeric<T>)
requires(!Numeric<T>)
[[nodiscard]] std::optional<T> TryParse(std::string_view str);
#if !(__GNUC__ >= 11 || _MSC_VER >= 1924)
@@ -221,7 +222,7 @@ namespace GeneralUtils {
*/
template <std::floating_point T>
[[nodiscard]] std::optional<T> TryParse(std::string_view str) noexcept
try {
try {
while (!str.empty() && std::isspace(str.front())) str.remove_prefix(1);
size_t parseNum;
@@ -323,4 +324,28 @@ namespace GeneralUtils {
return GenerateRandomNumber<T>(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
}
// https://www.quora.com/How-do-you-round-to-specific-increments-like-0-5-in-C
// Rounds to the nearest floating point value specified.
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
T RountToNearestEven(const T value, const T modulus) {
const auto modulo = std::fmod(value, modulus);
const auto abs_modulo_2 = std::abs(modulo * 2);
const auto abs_modulus = std::abs(modulus);
bool round_away_from_zero = false;
if (abs_modulo_2 > abs_modulus) {
round_away_from_zero = true;
} else if (abs_modulo_2 == abs_modulus) {
const auto trunc_quot = std::floor(std::abs(value / modulus));
const auto odd = std::fmod(trunc_quot, T{ 2 }) != 0;
round_away_from_zero = odd;
}
if (round_away_from_zero) {
return value + (std::copysign(modulus, value) - modulo);
} else {
return value - modulo;
}
}
}

View File

@@ -6,7 +6,7 @@
#include <ranges>
Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoint3& curPosition) {
Result toReturn;
tinyxml2::XMLDocument doc;
const auto err = doc.Parse(data.data());
@@ -27,7 +27,7 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
// First get all the positions of bricks
for (const auto& brick : lxfml["Bricks"]) {
const auto* part = brick.FirstChildElement("Part");
if (part) {
while (part) {
const auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
@@ -36,6 +36,7 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
if (refID) transformations[refID] = transformation;
}
}
part = part->NextSiblingElement("Part");
}
}
@@ -43,29 +44,42 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
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;
NiPoint3 delta = NiPoint3Constant::ZERO;
if (curPosition == NiPoint3Constant::ZERO) {
// 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 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;
delta = (highest - lowest) / 2.0f;
} else {
lowest = curPosition;
highest = curPosition;
delta = NiPoint3Constant::ZERO;
}
auto delta = (highest - lowest) / 2.0f;
auto newRootPos = lowest + delta;
// Need to snap this chosen position to the nearest valid spot
// on the LEGO grid
newRootPos.x = GeneralUtils::RountToNearestEven(newRootPos.x, 0.8f);
newRootPos.z = GeneralUtils::RountToNearestEven(newRootPos.z, 0.8f);
// Clamp the Y to the lowest point on the model
newRootPos.y = lowest.y;
@@ -77,9 +91,9 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
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;
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x + curPosition.x;
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y + curPosition.y;
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z + curPosition.z;
std::stringstream stream;
for (int i = 0; i < 9; i++) {
stream << split[i];
@@ -92,7 +106,7 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
// Finally write the new transformation back into the lxfml
for (auto& brick : lxfml["Bricks"]) {
auto* part = brick.FirstChildElement("Part");
if (part) {
while (part) {
auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
@@ -103,6 +117,7 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
}
}
}
part = part->NextSiblingElement("Part");
}
}

View File

@@ -17,7 +17,11 @@ namespace Lxfml {
// 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.
[[nodiscard]] Result NormalizePosition(const std::string_view data);
[[nodiscard]] Result NormalizePosition(const std::string_view data, const NiPoint3& curPosition = NiPoint3Constant::ZERO);
// 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

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 {
public:
struct Info {
int32_t behaviorId{};
LWOOBJID behaviorId{};
uint32_t characterId{};
std::string behaviorInfo;
};
// This Add also takes care of updating if it exists.
virtual void AddBehavior(const Info& info) = 0;
virtual std::string GetBehavior(const int32_t behaviorId) = 0;
virtual void RemoveBehavior(const int32_t behaviorId) = 0;
virtual std::string GetBehavior(const LWOOBJID behaviorId) = 0;
virtual void RemoveBehavior(const LWOOBJID behaviorId) = 0;
};
#endif //!IBEHAVIORS_H

View File

@@ -17,7 +17,7 @@ public:
LWOOBJID id{};
LOT lot{};
uint32_t ugcId{};
std::array<int32_t, 5> behaviors{};
std::array<LWOOBJID, 5> behaviors{};
};
// 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;
// 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<int32_t, 5> behaviorIDs) {
std::array<std::pair<int32_t, std::string>, 5> behaviors;
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<LWOOBJID, 5> behaviorIDs) {
std::array<std::pair<LWOOBJID, std::string>, 5> behaviors;
for (int32_t i = 0; i < behaviors.size(); i++) behaviors[i].first = behaviorIDs[i];
UpdateModel(modelID, position, rotation, behaviors);
}

View File

@@ -75,7 +75,7 @@ public:
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() 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 UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) 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;
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
void AddBehavior(const IBehaviors::Info& info) override;
std::string GetBehavior(const int32_t behaviorId) override;
void RemoveBehavior(const int32_t characterId) override;
std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const LWOOBJID characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) 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);
}
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);
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.z = result->getFloat("rz");
model.ugcId = result->getUInt64("ugc_id");
model.behaviors[0] = result->getInt("behavior_1");
model.behaviors[1] = result->getInt("behavior_2");
model.behaviors[2] = result->getInt("behavior_3");
model.behaviors[3] = result->getInt("behavior_4");
model.behaviors[4] = result->getInt("behavior_5");
model.behaviors[0] = result->getUInt64("behavior_1");
model.behaviors[1] = result->getUInt64("behavior_2");
model.behaviors[2] = result->getUInt64("behavior_3");
model.behaviors[3] = result->getUInt64("behavior_4");
model.behaviors[4] = result->getUInt64("behavior_5");
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(
"UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, "
"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.z = result->getFloat("rz");
model.ugcId = result->getUInt64("ugc_id");
model.behaviors[0] = result->getInt("behavior_1");
model.behaviors[1] = result->getInt("behavior_2");
model.behaviors[2] = result->getInt("behavior_3");
model.behaviors[3] = result->getInt("behavior_4");
model.behaviors[4] = result->getInt("behavior_5");
model.behaviors[0] = result->getUInt64("behavior_1");
model.behaviors[1] = result->getUInt64("behavior_2");
model.behaviors[2] = result->getUInt64("behavior_3");
model.behaviors[3] = result->getUInt64("behavior_4");
model.behaviors[4] = result->getUInt64("behavior_5");
}
return model;

View File

@@ -73,7 +73,7 @@ public:
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() 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 UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) 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;
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
void AddBehavior(const IBehaviors::Info& info) override;
std::string GetBehavior(const int32_t behaviorId) override;
void RemoveBehavior(const int32_t characterId) override;
std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const LWOOBJID characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) 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);
}
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);
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.z = result.getFloatField("rz");
model.ugcId = result.getInt64Field("ugc_id");
model.behaviors[0] = result.getIntField("behavior_1");
model.behaviors[1] = result.getIntField("behavior_2");
model.behaviors[2] = result.getIntField("behavior_3");
model.behaviors[3] = result.getIntField("behavior_4");
model.behaviors[4] = result.getIntField("behavior_5");
model.behaviors[0] = result.getInt64Field("behavior_1");
model.behaviors[1] = result.getInt64Field("behavior_2");
model.behaviors[2] = result.getInt64Field("behavior_3");
model.behaviors[3] = result.getInt64Field("behavior_4");
model.behaviors[4] = result.getInt64Field("behavior_5");
toReturn.push_back(std::move(model));
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(
"UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, "
"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.z = result.getFloatField("rz");
model.ugcId = result.getInt64Field("ugc_id");
model.behaviors[0] = result.getIntField("behavior_1");
model.behaviors[1] = result.getIntField("behavior_2");
model.behaviors[2] = result.getIntField("behavior_3");
model.behaviors[3] = result.getIntField("behavior_4");
model.behaviors[4] = result.getIntField("behavior_5");
model.behaviors[0] = result.getInt64Field("behavior_1");
model.behaviors[1] = result.getInt64Field("behavior_2");
model.behaviors[2] = result.getInt64Field("behavior_3");
model.behaviors[3] = result.getInt64Field("behavior_4");
model.behaviors[4] = result.getInt64Field("behavior_5");
} 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 {};
}
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;
void RemoveUnreferencedUgcModels() 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 UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) 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;
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
void AddBehavior(const IBehaviors::Info& info) override;
std::string GetBehavior(const int32_t behaviorId) override;
void RemoveBehavior(const int32_t behaviorId) override;
std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const LWOOBJID behaviorId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override { return {}; };
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override { return {}; };

View File

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

View File

@@ -14,7 +14,7 @@ void ModelNormalizeMigration::Run() {
Sd0 sd0(lxfmlData);
const auto asStr = sd0.GetAsStringUncompressed();
const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr);
const auto [newLxfml, newCenter] = Lxfml::NormalizePositionOnlyFirstPart(asStr);
if (newCenter == NiPoint3Constant::ZERO) {
LOG("Failed to update model %llu due to failure reading xml.");
continue;
@@ -28,3 +28,44 @@ void ModelNormalizeMigration::Run() {
}
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);
}
void ModelNormalizeMigration::RunBrickBuildGrid() {
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::NormalizePosition(asStr, model.position);
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
LOG("Updated model %llu to have a center of %f %f %f", modelID, newCenter.x, newCenter.y, newCenter.z);
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,8 @@
namespace ModelNormalizeMigration {
void Run();
void RunAfterFirstPart();
void RunBrickBuildGrid();
};
#endif //!MODELNORMALIZEMIGRATION_H

File diff suppressed because it is too large Load Diff

View File

@@ -34,7 +34,6 @@ namespace tinyxml2 {
};
class Player;
class EntityInfo;
class User;
class Spawner;
class ScriptComponent;
@@ -45,6 +44,7 @@ class Item;
class Character;
class EntityCallbackTimer;
class PositionUpdate;
struct EntityInfo;
enum class eTriggerEventType;
enum class eGameMasterLevel : uint8_t;
enum class eReplicaComponentType : uint32_t;
@@ -60,7 +60,7 @@ namespace CppScripts {
*/
class Entity {
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();
void Initialize();
@@ -113,7 +113,7 @@ public:
float GetDefaultScale() const;
const NiPoint3& GetPosition() const;
NiPoint3 GetPosition() const;
const NiQuaternion& GetRotation() const;
@@ -146,9 +146,9 @@ public:
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);
@@ -169,7 +169,7 @@ public:
void AddComponent(eReplicaComponentType componentId, Component* component);
// 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 Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationName);
@@ -182,8 +182,8 @@ public:
void RemoveParent();
// Adds a timer to start next frame with the given name and time.
void AddTimer(std::string name, float time);
void AddCallbackTimer(float time, std::function<void()> callback);
void AddTimer(const std::string& name, float time);
void AddCallbackTimer(float time, const std::function<void()> callback);
bool HasTimer(const std::string& name);
void CancelCallbackTimers();
void CancelAllTimers();
@@ -195,7 +195,7 @@ public:
std::unordered_map<eReplicaComponentType, Component*>& GetComponents() { return m_Components; } // TODO: Remove
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 Update(float deltaTime);
@@ -242,21 +242,21 @@ public:
void AddDieCallback(const std::function<void()>& callback);
void Resurrect();
void AddLootItem(const Loot::Info& info);
void PickupItem(const LWOOBJID& objectID);
void AddLootItem(const Loot::Info& info) const;
void PickupItem(const LWOOBJID& objectID) const;
bool CanPickupCoins(uint64_t count);
void RegisterCoinDrop(uint64_t count);
bool PickupCoins(uint64_t count) const;
void RegisterCoinDrop(uint64_t count) const;
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; }
const NiPoint3& GetRespawnPosition() const;
const NiQuaternion& GetRespawnRotation() const;
void Sleep();
void Wake();
void Sleep() const;
void Wake() const;
bool IsSleeping() const;
/*
@@ -266,7 +266,7 @@ public:
* 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;
int32_t GetI32(const std::u16string& name) const;
int64_t GetI64(const std::u16string& name) const;
@@ -334,7 +334,8 @@ public:
*/
static Observable<Entity*, const PositionUpdate&> OnPlayerPositionUpdate;
protected:
private:
void WriteLDFData(const std::vector<LDFBaseData*>& ldf, RakNet::BitStream& outBitStream) const;
LWOOBJID m_ObjectID;
LOT m_TemplateID;
@@ -357,7 +358,6 @@ protected:
Entity* m_ParentEntity; //For spawners and the like
std::vector<Entity*> m_ChildEntities;
eGameMasterLevel m_GMLevel;
uint16_t m_CollectibleID;
std::vector<std::string> m_Groups;
uint16_t m_NetworkID;
std::vector<std::function<void()>> m_DieCallbacks;
@@ -383,6 +383,8 @@ protected:
bool m_IsParentChildDirty = true;
bool m_IsSleeping = false;
/*
* Collision
*/
@@ -391,7 +393,7 @@ protected:
// objectID of receiver and map of notification name to script
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;
};
/**

View File

@@ -394,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) {
@@ -417,7 +417,7 @@ void EntityManager::DestructEntity(Entity* entity, const SystemAddress& sysAddr)
void EntityManager::SerializeEntity(Entity* entity) {
if (!entity) return;
EntityManager::SerializeEntity(*entity);
}
@@ -463,7 +463,7 @@ void EntityManager::UpdateGhosting() {
m_PlayersToUpdateGhosting.clear();
}
void EntityManager::UpdateGhosting(Entity* player) {
void EntityManager::UpdateGhosting(Entity* player, const bool constructAll) {
if (!player) return;
auto* missionComponent = player->GetComponent<MissionComponent>();
@@ -511,9 +511,12 @@ void EntityManager::UpdateGhosting(Entity* player) {
ghostComponent->ObserveEntity(id);
ConstructEntity(entity, player->GetSystemAddress());
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"
class Entity;
class EntityInfo;
struct EntityInfo;
class Player;
class User;
enum class eReplicaComponentType : uint32_t;
@@ -54,7 +54,7 @@ public:
void SetGhostDistanceMin(float value);
void QueueGhostUpdate(LWOOBJID playerID);
void UpdateGhosting();
void UpdateGhosting(Entity* player);
void UpdateGhosting(Entity* player, const bool constructAll = false);
void CheckGhosting(Entity* entity);
Entity* GetGhostCandidate(LWOOBJID id) 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) {
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) {
auto* entity = character->GetEntity();
auto* entity = Game::entityManager->GetEntity(character->GetObjectID());
if (entity) {
auto* characterComponent = entity->GetComponent<CharacterComponent>();
if (characterComponent) {

View File

@@ -7,7 +7,9 @@
#include "BehaviorStates.h"
#include "ControlBehaviorMsgs.h"
#include "tinyxml2.h"
#include "InventoryComponent.h"
#include "SimplePhysicsComponent.h"
#include "eObjectBits.h"
#include "Database.h"
#include "DluAssert.h"
@@ -35,6 +37,7 @@ bool ModelComponent::OnResetModelToDefaults(GameMessages::GameMsg& msg) {
m_Parent->SetRotation(m_OriginalRotation);
m_Parent->SetVelocity(NiPoint3Constant::ZERO);
m_Speed = 3.0f;
m_NumListeningInteract = 0;
m_NumActiveUnSmash = 0;
m_Dirty = true;
@@ -60,6 +63,12 @@ void ModelComponent::Update(float deltaTime) {
for (auto& behavior : m_Behaviors) {
behavior.Update(deltaTime, *this);
}
if (!m_RestartAtEndOfFrame) return;
GameMessages::ResetModelToDefaults reset{};
OnResetModelToDefaults(reset);
m_RestartAtEndOfFrame = false;
}
void ModelComponent::LoadBehaviors() {
@@ -67,25 +76,30 @@ void ModelComponent::LoadBehaviors() {
for (const auto& behavior : behaviors) {
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;
LOG_DEBUG("Loading behavior %d", behaviorId.value());
auto& inserted = m_Behaviors.emplace_back();
inserted.SetBehaviorId(*behaviorId);
// add behavior at the back
LoadBehavior(behaviorId.value(), m_Behaviors.size(), false);
}
}
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;
auto res = behaviorXml.Parse(behaviorStr.c_str(), behaviorStr.size());
LOG_DEBUG("Behavior %i %d: %s", res, behaviorId.value(), behaviorStr.c_str());
const auto behaviorStr = Database::Get()->GetBehavior(behaviorID);
const auto* const behaviorRoot = behaviorXml.FirstChildElement("Behavior");
if (!behaviorRoot) {
LOG("Failed to load behavior %d due to missing behavior root", behaviorId.value());
continue;
}
tinyxml2::XMLDocument behaviorXml;
auto res = behaviorXml.Parse(behaviorStr.c_str(), behaviorStr.size());
LOG_DEBUG("Behavior %i %llu: %s", res, behaviorID, behaviorStr.c_str());
const auto* const behaviorRoot = behaviorXml.FirstChildElement("Behavior");
if (behaviorRoot) {
inserted.Deserialize(*behaviorRoot);
} else {
LOG("Failed to load behavior %d due to missing behavior root", behaviorID);
}
}
@@ -116,8 +130,12 @@ void ModelComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialU
if (bIsInitialUpdate) outBitStream.Write0(); // We are not writing model editing info
}
void ModelComponent::UpdatePendingBehaviorId(const int32_t newId) {
for (auto& behavior : m_Behaviors) if (behavior.GetBehaviorId() == -1) behavior.SetBehaviorId(newId);
void ModelComponent::UpdatePendingBehaviorId(const LWOOBJID newId, const LWOOBJID oldId) {
for (auto& behavior : m_Behaviors) {
if (behavior.GetBehaviorId() != oldId) continue;
behavior.SetBehaviorId(newId);
behavior.SetIsLoot(false);
}
}
void ModelComponent::SendBehaviorListToClient(AMFArrayValue& args) const {
@@ -134,7 +152,7 @@ void ModelComponent::VerifyBehaviors() {
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("objectID", std::to_string(m_Parent->GetObjectID()));
for (auto& behavior : m_Behaviors) if (behavior.GetBehaviorId() == behaviorToSend) behavior.SendBehaviorBlocksToClient(args);
@@ -143,8 +161,21 @@ void ModelComponent::SendBehaviorBlocksToClient(int32_t behaviorToSend, AMFArray
void ModelComponent::AddBehavior(AddMessage& msg) {
// Can only have 1 of the loot behaviors
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>();
if (simplePhysComponent) {
simplePhysComponent->SetPhysicsMotionState(1);
@@ -152,8 +183,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;
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());
// TODO move to the inventory
if (m_Behaviors.empty()) {
@@ -165,22 +229,14 @@ void ModelComponent::MoveToInventory(MoveToInventoryMessage& msg) {
}
}
std::array<std::pair<int32_t, std::string>, 5> ModelComponent::GetBehaviorsForSave() const {
std::array<std::pair<int32_t, std::string>, 5> toReturn{};
std::array<std::pair<LWOOBJID, std::string>, 5> ModelComponent::GetBehaviorsForSave() const {
std::array<std::pair<LWOOBJID, std::string>, 5> toReturn{};
for (auto i = 0; i < m_Behaviors.size(); i++) {
const auto& behavior = m_Behaviors.at(i);
if (behavior.GetBehaviorId() == -1) continue;
auto& [id, behaviorData] = toReturn[i];
id = behavior.GetBehaviorId();
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();
behaviorData = SaveBehavior(behavior);
}
return toReturn;
}
@@ -215,7 +271,7 @@ bool ModelComponent::TrySetVelocity(const NiPoint3& velocity) const {
// 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;
const auto [x, y, z] = velocity * m_Speed;
if (x != 0.0f) {
if (currentVelocity.x != 0.0f) return false;
currentVelocity.x = x;
@@ -237,3 +293,7 @@ bool ModelComponent::TrySetVelocity(const NiPoint3& velocity) const {
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

@@ -66,15 +66,18 @@ public:
*
* @tparam Msg The message type to pass
* @param args the arguments of the message to be deserialized
*
* @return returns true if a new behaviorID is needed.
*/
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");
Msg msg{ args };
for (auto&& behavior : m_Behaviors) {
if (behavior.GetBehaviorId() == msg.GetBehaviorId()) {
behavior.CheckModifyState(msg);
behavior.HandleMsg(msg);
return;
return msg.GetNeedsNewBehaviorID();
}
}
@@ -82,22 +85,24 @@ public:
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!
if (m_Behaviors.size() == 5) return;
if (m_Behaviors.size() == 5) return false;
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.
// 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.
newBehavior->SetBehaviorId(msg.GetBehaviorId());
newBehavior->CheckModifyState(msg);
newBehavior->HandleMsg(msg);
return msg.GetNeedsNewBehaviorID();
};
void AddBehavior(AddMessage& msg);
void MoveToInventory(MoveToInventoryMessage& msg);
void RemoveBehavior(MoveToInventoryMessage& msg, const bool keepItem);
// 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.
@@ -112,11 +117,11 @@ public:
*/
void SendBehaviorListToClient(AMFArrayValue& args) const;
void SendBehaviorBlocksToClient(int32_t behaviorToSend, AMFArrayValue& args) const;
void SendBehaviorBlocksToClient(const LWOOBJID behaviorToSend, AMFArrayValue& args) const;
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; };
@@ -138,7 +143,22 @@ public:
// Force sets the velocity to a value.
void SetVelocity(const NiPoint3& velocity) const;
void OnChatMessageReceived(const std::string& sMessage);
// Sets the speed of the model
void SetSpeed(const float newSpeed) { m_Speed = newSpeed; }
// Whether or not to restart at the end of the frame
void RestartAtEndOfFrame() { m_RestartAtEndOfFrame = true; }
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.
uint32_t m_NumActiveUnSmash{};
@@ -171,4 +191,10 @@ private:
* The ID of the user that made the model
*/
LWOOBJID m_userModelID;
// The speed at which this model moves
float m_Speed{ 3.0f };
// Whether or not to restart at the end of the frame.
bool m_RestartAtEndOfFrame{ false };
};

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");
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) {

View File

@@ -20,7 +20,7 @@ public:
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; }
const NiQuaternion& GetRotation() const { return m_Rotation; }
@@ -35,6 +35,8 @@ protected:
void SpawnVertices(dpEntity* entity) const;
bool OnGetPosition(GameMessages::GameMsg& msg);
NiPoint3 m_Position;
NiQuaternion m_Rotation;

View File

@@ -817,3 +817,14 @@ void PropertyManagementComponent::SetOwnerId(const LWOOBJID value) {
const std::map<LWOOBJID, LWOOBJID>& PropertyManagementComponent::GetModels() const {
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; }
void OnChatMessageReceived(const std::string& sMessage) const;
private:
/**
* This
@@ -193,7 +195,7 @@ private:
/**
* 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

View File

@@ -2,11 +2,9 @@
#include "EntityManager.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) {
m_StaticParams = params;
}

View File

@@ -4,6 +4,7 @@
#include "Entity.h"
#include "Component.h"
#include "eReplicaComponentType.h"
#include "ActivityComponent.h"
/**
* 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
* also in the related scripts.
*/
class ShootingGalleryComponent final : public Component {
class ShootingGalleryComponent final : public ActivityComponent {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SHOOTING_GALLERY;
explicit ShootingGalleryComponent(Entity* parent);
~ShootingGalleryComponent();
explicit ShootingGalleryComponent(Entity* parent, int32_t activityID);
void Serialize(RakNet::BitStream& outBitStream, bool isInitialUpdate) override;
/**

View File

@@ -158,6 +158,10 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
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);

View File

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

View File

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

View File

@@ -21,6 +21,8 @@
#include "eUseItemResponse.h"
#include "dZoneManager.h"
#include "ChatPackets.h"
#include "MissionComponent.h"
#include "eMissionTaskType.h"
#include "CDBrickIDTableTable.h"
#include "CDObjectSkillsTable.h"
@@ -268,9 +270,9 @@ bool Item::IsEquipped() const {
}
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);
});
@@ -288,7 +290,12 @@ bool Item::Consume() {
GameMessages::SendUseItemResult(inventory->GetComponent()->GetParent(), lot, 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);
auto* missionComponent = inventory->GetComponent()->GetParent()->GetComponent<MissionComponent>();
if (missionComponent) missionComponent->Progress(eMissionTaskType::GATHER, myLot, LWOOBJID_EMPTY, "", -1);
}
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/dGameMessages" # GameMessages.h
"${PROJECT_SOURCE_DIR}/dGame/dComponents" # ModelComponent.h
"${PROJECT_SOURCE_DIR}/dChatFilter" # dChatFilter.h
)
target_precompile_headers(dPropertyBehaviors REUSE_FROM dGameBase)

View File

@@ -10,5 +10,5 @@ AddActionMessage::AddActionMessage(const AMFArrayValue& arguments)
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"
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");
if (!behaviorIndexValue) return;
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 {
public:
AddMessage(const AMFArrayValue& arguments);
AddMessage(const AMFArrayValue& arguments, const LWOOBJID _owningPlayerID);
[[nodiscard]] uint32_t GetBehaviorIndex() const noexcept { return m_BehaviorIndex; };
[[nodiscard]] LWOOBJID GetOwningPlayerID() const noexcept { return m_OwningPlayerID; };
private:
uint32_t m_BehaviorIndex{ 0 };
LWOOBJID m_OwningPlayerID{};
};
#endif //!__ADDMESSAGE__H__

View File

@@ -19,7 +19,7 @@ AddStripMessage::AddStripMessage(const AMFArrayValue& arguments)
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());
}

View File

@@ -4,14 +4,14 @@
#include "BehaviorStates.h"
#include "dCommonVars.h"
int32_t BehaviorMessageBase::GetBehaviorIdFromArgument(const AMFArrayValue& arguments) {
LWOOBJID BehaviorMessageBase::GetBehaviorIdFromArgument(const AMFArrayValue& arguments) {
static constexpr std::string_view key = "BehaviorID";
const auto* const behaviorIDValue = arguments.Get<std::string>(key);
int32_t behaviorId = DefaultBehaviorId;
LWOOBJID behaviorId = DefaultBehaviorId;
if (behaviorIDValue && behaviorIDValue->GetValueType() == eAmf::String) {
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) {
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
*
*
*/
class BehaviorMessageBase {
public:
static constexpr int32_t DefaultBehaviorId{ -1 };
static constexpr LWOOBJID DefaultBehaviorId{ -1 };
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 GetNeedsNewBehaviorID() const noexcept { return m_NeedsNewBehaviorID; }
void SetNeedsNewBehaviorID(const bool val) noexcept { m_NeedsNewBehaviorID = val; }
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;
int32_t m_BehaviorId{ DefaultBehaviorId };
LWOOBJID m_BehaviorId{ DefaultBehaviorId };
bool m_NeedsNewBehaviorID{ false };
};
#endif //!__BEHAVIORMESSAGEBASE__H__

View File

@@ -6,6 +6,6 @@ MergeStripsMessage::MergeStripsMessage(const AMFArrayValue& arguments)
, m_SourceActionContext{ arguments, "srcStateID", "srcStripID" }
, 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_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"
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");
if (!behaviorIndexValue) return;
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 {
public:
MoveToInventoryMessage(const AMFArrayValue& arguments);
MoveToInventoryMessage(const AMFArrayValue& arguments, const LWOOBJID owningPlayerID);
[[nodiscard]] uint32_t GetBehaviorIndex() const noexcept { return m_BehaviorIndex; };
[[nodiscard]] LWOOBJID GetOwningPlayerID() const noexcept { return m_OwningPlayerID; };
private:
uint32_t m_BehaviorIndex;
LWOOBJID m_OwningPlayerID{};
};
#endif //!__MOVETOINVENTORYMESSAGE__H__

View File

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

View File

@@ -8,9 +8,12 @@
#include <ranges>
PropertyBehavior::PropertyBehavior() {
PropertyBehavior::PropertyBehavior(bool _isTemplated) {
m_LastEditedState = 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<>
@@ -81,13 +84,6 @@ void PropertyBehavior::HandleMsg(RenameMessage& msg) {
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<>
void PropertyBehavior::HandleMsg(GameMessages::RequestUse& 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);
}
void PropertyBehavior::CheckModifyState(BehaviorMessageBase& msg) {
if (!isTemplated && m_BehaviorId != BehaviorMessageBase::DefaultBehaviorId) return;
isTemplated = false;
msg.SetNeedsNewBehaviorID(true);
}
void PropertyBehavior::SendBehaviorListToClient(AMFArrayValue& args) const {
args.Insert("id", std::to_string(m_BehaviorId));
args.Insert("name", m_Name);
@@ -147,6 +149,9 @@ void PropertyBehavior::Serialize(tinyxml2::XMLElement& behavior) const {
behavior.SetAttribute("isLocked", isLocked);
behavior.SetAttribute("isLoot", isLoot);
// CUSTOM XML ATTRIBUTE
behavior.SetAttribute("isTemplated", isTemplated);
for (const auto& [stateId, state] : m_States) {
if (state.IsEmpty()) continue;
auto* const stateElement = behavior.InsertNewChildElement("State");
@@ -161,6 +166,9 @@ void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) {
behavior.QueryBoolAttribute("isLocked", &isLocked);
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")) {
int32_t stateId = -1;
stateElement->QueryIntAttribute("id", &stateId);
@@ -172,3 +180,7 @@ void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) {
void PropertyBehavior::Update(float deltaTime, ModelComponent& 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;
class AMFArrayValue;
class BehaviorMessageBase;
class ModelComponent;
/**
@@ -17,7 +18,7 @@ class ModelComponent;
*/
class PropertyBehavior {
public:
PropertyBehavior();
PropertyBehavior(bool _isTemplated = false);
template <typename Msg>
void HandleMsg(Msg& msg);
@@ -26,14 +27,21 @@ public:
void VerifyLastEditedState();
void SendBehaviorListToClient(AMFArrayValue& args) const;
void SendBehaviorBlocksToClient(AMFArrayValue& args) const;
void CheckModifyState(BehaviorMessageBase& msg);
[[nodiscard]] int32_t GetBehaviorId() const noexcept { return m_BehaviorId; }
void SetBehaviorId(int32_t id) noexcept { m_BehaviorId = id; }
[[nodiscard]] LWOOBJID GetBehaviorId() const noexcept { return m_BehaviorId; }
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 Deserialize(const tinyxml2::XMLElement& behavior);
void Update(float deltaTime, ModelComponent& modelComponent);
void OnChatMessageReceived(const std::string& sMessage);
private:
// 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.
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.
// If the last edited state has no strips, it will open to the first state that has strips.
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
// and needs to be assigned an id.
int32_t m_BehaviorId = -1;
LWOOBJID m_BehaviorId = -1;
};
#endif //!__PROPERTYBEHAVIOR__H__

View File

@@ -166,3 +166,7 @@ void State::Deserialize(const tinyxml2::XMLElement& state) {
void State::Update(float deltaTime, ModelComponent& 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 Update(float deltaTime, ModelComponent& modelComponent);
void OnChatMessageReceived(const std::string& sMessage);
private:
// The strips contained within this state.

View File

@@ -5,7 +5,12 @@
#include "tinyxml2.h"
#include "dEntity/EntityInfo.h"
#include "ModelComponent.h"
#include "ChatPackets.h"
#include "PropertyManagementComponent.h"
#include "PlayerManager.h"
#include "SimplePhysicsComponent.h"
#include "dChatFilter.h"
#include "DluAssert.h"
@@ -103,6 +108,16 @@ void Strip::HandleMsg(GameMessages::ResetModelToDefaults& msg) {
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() {
if (m_Actions.empty()) return;
m_NextActionIndex++;
@@ -131,6 +146,7 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
auto& entity = *modelComponent.GetParent();
auto& nextAction = GetNextAction();
auto number = nextAction.GetValueParameterDouble();
auto valueStr = nextAction.GetValueParameterString();
auto numberAsInt = static_cast<int32_t>(number);
auto nextActionType = GetNextAction().GetType();
@@ -139,16 +155,18 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
if (nextActionType == "MoveRight" || nextActionType == "MoveLeft") {
// X axis
bool isMoveLeft = nextActionType == "MoveLeft";
int negative = isMoveLeft ? -1 : 1;
// Default velocity is 3 units per second.
if (modelComponent.TrySetVelocity(NiPoint3{ isMoveLeft ? -3.0f : 3.0f, 0.0f, 0.0f })) {
if (modelComponent.TrySetVelocity(NiPoint3Constant::UNIT_X * negative)) {
m_PreviousFramePosition = entity.GetPosition();
m_InActionMove.x = isMoveLeft ? -number : number;
}
} 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(NiPoint3{ 0.0f, isFlyDown ? -3.0f : 3.0f, 0.0f })) {
if (modelComponent.TrySetVelocity(NiPoint3Constant::UNIT_Y * negative)) {
m_PreviousFramePosition = entity.GetPosition();
m_InActionMove.y = isFlyDown ? -number : number;
}
@@ -156,14 +174,21 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
} 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(NiPoint3{ 0.0f, 0.0f, isMoveBackward ? -3.0f : 3.0f })) {
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()) {
@@ -183,11 +208,21 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
m_PausedTime = number;
} else if (nextActionType == "Wait") {
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") {
GameMessages::PlayBehaviorSound sound;
sound.target = modelComponent.GetParent()->GetObjectID();
sound.soundID = numberAsInt;
sound.Send(UNASSIGNED_SYSTEM_ADDRESS);
} else if (nextActionType == "Restart") {
modelComponent.RestartAtEndOfFrame();
}
/* END Action */
/* BEGIN Gameplay */
@@ -304,6 +339,9 @@ void Strip::Update(float deltaTime, ModelComponent& modelComponent) {
Game::entityManager->SerializeEntity(entity);
m_WaitingForAction = true;
} else if (nextAction.GetType() == "OnChat") {
Game::entityManager->SerializeEntity(entity);
m_WaitingForAction = true;
}
} else { // should be a normal block
ProcNormalAction(deltaTime, modelComponent);

View File

@@ -40,6 +40,8 @@ public:
// 2 actions are required for strips to work
bool HasMinimumActions() const { return m_Actions.size() >= 2; }
void OnChatMessageReceived(const std::string& sMessage);
private:
// Indicates this Strip is waiting for an action to be taken upon it to progress to its actions
bool m_WaitingForAction{ false };
@@ -63,6 +65,8 @@ private:
// The position of the parent model on the previous frame
NiPoint3 m_PreviousFramePosition{};
NiPoint3 m_SavedVelocity{};
};
#endif //!__STRIP__H__

View File

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

View File

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

View File

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

View File

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

View File

@@ -58,6 +58,7 @@ bool MailInfo::Deserialize(RakNet::BitStream& bitStream) {
bitStream.IgnoreBytes(4); // padding
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;
}

View File

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

View File

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

View File

@@ -1367,6 +1367,7 @@ void HandlePacket(Packet* packet) {
std::string sMessage = GeneralUtils::UTF16ToWTF8(chatMessage.message);
LOG("%s: %s", playerName.c_str(), sMessage.c_str());
ChatPackets::SendChatMessage(packet->systemAddress, chatMessage.chatChannel, playerName, user->GetLoggedInChar(), isMythran, chatMessage.message);
if (PropertyManagementComponent::Instance()) PropertyManagementComponent::Instance()->OnChatMessageReceived(sMessage);
}
break;

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 @@
/* 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

@@ -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);
const auto arr = ReadArrayFromBitStream(inStream);
AddMessage add(*arr);
AddMessage add(*arr, LWOOBJID_EMPTY);
ASSERT_EQ(add.GetBehaviorId(), 10446);
ASSERT_EQ(add.GetBehaviorIndex(), 0);