Compare commits

..

14 Commits

Author SHA1 Message Date
David Markowitz
7145536061 feat: add despawn command 2025-07-19 12:52:57 -07:00
David Markowitz
6b52cf67a0 feat: debug features and implement ObjectDebugger (#1846)
Move the -s and base features of inspect to the object debugger (this file is present in an unmodified, live client)
2025-07-19 05:11:32 -05:00
David Markowitz
71f708f1b5 fix: Let's not ghost zone control (#1845)
* Let's not ghost zone control

* remove zonecontrol stuff
2025-07-18 10:15:45 -07:00
David Markowitz
49aa632d42 feat: WaypointReached notification for MovementAI and don't move the AI when its not built (#1844) 2025-07-12 22:21:45 -07:00
David Markowitz
5ec4142ca1 fix: multiple collection tasks in one mission and remove sanity check on mission rewards (#1843) 2025-07-12 21:04:41 -05:00
David Markowitz
5e9fe40bec feat: Add GetComponents(Mut) functions to Entity (#1842)
Allows for a really neat way of getting components using structured binding.  Tested that powerups still function

do it again because its neat
2025-07-01 07:26:05 -05:00
David Markowitz
9524198044 Update DEVGMCommands.cpp (#1840) 2025-06-29 17:23:11 -04:00
David Markowitz
a5d0788488 feat: barfight (#1839) 2025-06-29 17:18:59 -04:00
David Markowitz
a1ba5b8f12 feat: remove instance pointer management by migrating to unique_ptr (#1838)
Tested that i can join clones, zones and private instances and that the expected zones are loaded into
2025-06-29 05:41:03 -04:00
David Markowitz
48510b7315 feat: add messaging system for manager and add example usage to Loot (#1837)
tested that loot still drops at the entities position
2025-06-29 03:22:41 -04:00
David Markowitz
c697f8ad97 feat: Add Restart Behavior (#1836)
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-29 03:22:20 -04: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
51 changed files with 512 additions and 303 deletions

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;
@@ -299,6 +300,12 @@ namespace GeneralUtils {
return T();
}
template<typename Container>
inline Container::value_type GetRandomElement(const Container& container) {
DluAssert(!container.empty());
return container[GenerateRandomNumber<typename Container::value_type>(0, container.size() - 1)];
}
/**
* Casts the value of an enum entry to its underlying type
* @param entry Enum entry to cast
@@ -323,4 +330,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());
@@ -44,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;
@@ -78,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];

View File

@@ -17,7 +17,7 @@ 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);

View File

@@ -48,6 +48,7 @@ void MigrationRunner::RunMigrations() {
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);
@@ -64,6 +65,8 @@ void MigrationRunner::RunMigrations() {
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());
}
@@ -71,7 +74,7 @@ void MigrationRunner::RunMigrations() {
Database::Get()->InsertMigration(migration.name);
}
if (finalSQL.empty() && !runSd0Migrations && !runNormalizeMigrations && !runNormalizeAfterFirstPartMigrations) {
if (finalSQL.empty() && !runSd0Migrations && !runNormalizeMigrations && !runNormalizeAfterFirstPartMigrations && !runBrickBuildsNotOnGrid) {
LOG("Server database is up to date.");
return;
}
@@ -103,6 +106,10 @@ void MigrationRunner::RunMigrations() {
if (runNormalizeAfterFirstPartMigrations) {
ModelNormalizeMigration::RunAfterFirstPart();
}
if (runBrickBuildsNotOnGrid) {
ModelNormalizeMigration::RunBrickBuildGrid();
}
}
void MigrationRunner::RunSQLiteMigrations() {

View File

@@ -48,3 +48,24 @@ void ModelNormalizeMigration::RunAfterFirstPart() {
}
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

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

View File

@@ -97,6 +97,8 @@
#include "CDSkillBehaviorTable.h"
#include "CDZoneTableTable.h"
#include "StringifiedEnum.h"
#include <ranges>
Observable<Entity*, const PositionUpdate&> Entity::OnPlayerPositionUpdate;
@@ -187,6 +189,7 @@ Entity::~Entity() {
}
void Entity::Initialize() {
RegisterMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, this, &Entity::MsgRequestServerObjectInfo);
/**
* Setup trigger
*/
@@ -920,7 +923,7 @@ void Entity::WriteLDFData(const std::vector<LDFBaseData*>& ldf, RakNet::BitStrea
numberOfValidKeys--;
}
}
// Now write it to the main bitstream
outBitStream.Write<uint32_t>(settingStream.GetNumberOfBytesUsed() + 1 + sizeof(uint32_t));
outBitStream.Write<uint8_t>(0); //no compression used
@@ -1662,11 +1665,9 @@ void Entity::PickupItem(const LWOOBJID& objectID) const {
auto* const skillsTable = CDClientManager::GetTable<CDObjectSkillsTable>();
const auto skills = skillsTable->Query([&info](CDObjectSkills entry) {return (entry.objectTemplate == info.lot); });
for (const auto& skill : skills) {
auto* skillComponent = GetComponent<SkillComponent>();
const auto [skillComponent, missionComponent] = GetComponentsMut<SkillComponent, MissionComponent>();
if (skillComponent) skillComponent->CastSkill(skill.skillID, GetObjectID(), GetObjectID(), skill.castOnType, NiQuaternion(0, 0, 0, 0));
auto* missionComponent = GetComponent<MissionComponent>();
if (missionComponent != nullptr) {
missionComponent->Progress(eMissionTaskType::POWERUP, skill.skillID);
}
@@ -2211,3 +2212,38 @@ bool Entity::HandleMsg(GameMessages::GameMsg& msg) const {
void Entity::RegisterMsg(const MessageType::Game msgId, std::function<bool(GameMessages::GameMsg&)> handler) {
m_MsgHandlers.emplace(msgId, handler);
}
bool Entity::MsgRequestServerObjectInfo(GameMessages::GameMsg& msg) {
auto& requestInfo = static_cast<GameMessages::RequestServerObjectInfo&>(msg);
AMFArrayValue response;
response.Insert("visible", true);
response.Insert("objectID", std::to_string(m_ObjectID));
response.Insert("serverInfo", true);
GameMessages::GetObjectReportInfo info{};
info.info = response.InsertArray("data");
auto& objectInfo = info.info->PushDebug("Object Details");
auto* table = CDClientManager::GetTable<CDObjectsTable>();
const auto& objTableInfo = table->GetByID(GetLOT());
objectInfo.PushDebug<AMFStringValue>("Name") = objTableInfo.name;
objectInfo.PushDebug<AMFIntValue>("Template ID(LOT)") = GetLOT();
objectInfo.PushDebug<AMFStringValue>("Object ID") = std::to_string(GetObjectID());
objectInfo.PushDebug<AMFStringValue>("Spawner's Object ID") = std::to_string(GetSpawnerID());
auto& componentDetails = objectInfo.PushDebug("Component Information");
for (const auto [id, component] : m_Components) {
componentDetails.PushDebug<AMFStringValue>(StringifiedEnum::ToString(id)) = "";
}
auto& configData = objectInfo.PushDebug("Config Data");
for (const auto config : m_Settings) {
configData.PushDebug<AMFStringValue>(GeneralUtils::UTF16ToWTF8(config->GetKey())) = config->GetValueAsString();
}
HandleMsg(info);
auto* targetForReport = Game::entityManager->GetEntity(requestInfo.targetForReport);
if (targetForReport) GameMessages::SendUIMessageServerToSingleClient("ToggleObjectDebugger", response, targetForReport->GetSystemAddress());
return true;
}

View File

@@ -2,6 +2,7 @@
#include <map>
#include <functional>
#include <tuple>
#include <typeinfo>
#include <type_traits>
#include <unordered_map>
@@ -161,6 +162,12 @@ public:
template<typename T>
T* GetComponent() const;
template<typename... T>
auto GetComponents() const;
template<typename... T>
auto GetComponentsMut() const;
template<typename T>
bool TryGetComponent(eReplicaComponentType componentId, T*& component) const;
@@ -168,6 +175,8 @@ public:
void AddComponent(eReplicaComponentType componentId, Component* component);
bool MsgRequestServerObjectInfo(GameMessages::GameMsg& msg);
// This is expceted to never return nullptr, an assert checks this.
CppScripts::Script* const GetScript() const;
@@ -329,6 +338,10 @@ public:
bool HandleMsg(GameMessages::GameMsg& msg) const;
void RegisterMsg(const MessageType::Game msgId, auto* self, const auto handler) {
RegisterMsg(msgId, std::bind(handler, self, std::placeholders::_1));
}
/**
* @brief The observable for player entity position updates.
*/
@@ -579,3 +592,13 @@ inline ComponentType* Entity::AddComponent(VaArgs... args) {
// To allow a static cast here instead of a dynamic one.
return dynamic_cast<ComponentType*>(componentToReturn);
}
template<typename... T>
auto Entity::GetComponents() const {
return GetComponentsMut<const T...>();
}
template<typename... T>
auto Entity::GetComponentsMut() const {
return std::tuple{GetComponent<T>()...};
}

View File

@@ -129,6 +129,8 @@ Entity* EntityManager::CreateEntity(EntityInfo info, User* user, Entity* parentE
// Set the zone control entity if the entity is a zone control object, this should only happen once
if (controller) {
m_ZoneControlEntity = entity;
// Proooooobably shouldn't ghost zoneControl
m_ZoneControlEntity->SetIsGhostingCandidate(false);
}
// Check if this entity is a respawn point, if so add it to the registry
@@ -394,7 +396,7 @@ void EntityManager::ConstructAllEntities(const SystemAddress& sysAddr) {
}
}
UpdateGhosting(PlayerManager::GetPlayer(sysAddr), true);
UpdateGhosting(PlayerManager::GetPlayer(sysAddr));
}
void EntityManager::DestructEntity(Entity* entity, const SystemAddress& sysAddr) {
@@ -463,7 +465,7 @@ void EntityManager::UpdateGhosting() {
m_PlayersToUpdateGhosting.clear();
}
void EntityManager::UpdateGhosting(Entity* player, const bool constructAll) {
void EntityManager::UpdateGhosting(Entity* player) {
if (!player) return;
auto* missionComponent = player->GetComponent<MissionComponent>();
@@ -513,9 +515,6 @@ void EntityManager::UpdateGhosting(Entity* player, const bool constructAll) {
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());
}
}
@@ -605,3 +604,14 @@ void EntityManager::FireEventServerSide(Entity* origin, std::string args) {
bool EntityManager::IsExcludedFromGhosting(LOT lot) {
return std::find(m_GhostingExcludedLOTs.begin(), m_GhostingExcludedLOTs.end(), lot) != m_GhostingExcludedLOTs.end();
}
bool EntityManager::SendMessage(GameMessages::GameMsg& msg) const {
bool handled = false;
const auto entityItr = m_Entities.find(msg.target);
if (entityItr != m_Entities.end()) {
auto* const entity = entityItr->second;
if (entity) handled = entity->HandleMsg(msg);
}
return handled;
}

View File

@@ -14,6 +14,10 @@ class Player;
class User;
enum class eReplicaComponentType : uint32_t;
namespace GameMessages {
struct GameMsg;
}
struct SystemAddress;
class EntityManager {
@@ -54,7 +58,7 @@ public:
void SetGhostDistanceMin(float value);
void QueueGhostUpdate(LWOOBJID playerID);
void UpdateGhosting();
void UpdateGhosting(Entity* player, const bool constructAll = false);
void UpdateGhosting(Entity* player);
void CheckGhosting(Entity* entity);
Entity* GetGhostCandidate(LWOOBJID id) const;
bool GetGhostingEnabled() const;
@@ -72,6 +76,9 @@ public:
const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; };
const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; };
// Messaging
bool SendMessage(GameMessages::GameMsg& msg) const;
private:
void SerializeEntities();
void KillEntities();

View File

@@ -575,7 +575,7 @@ void ActivityInstance::RewardParticipant(Entity* participant) {
maxCoins = currencyTable[0].maxvalue;
}
Loot::DropLoot(participant, m_Parent, activityRewards[0].LootMatrixIndex, minCoins, maxCoins);
Loot::DropLoot(participant, m_Parent->GetObjectID(), activityRewards[0].LootMatrixIndex, minCoins, maxCoins);
}
}

View File

@@ -49,19 +49,13 @@ CharacterComponent::CharacterComponent(Entity* parent, Character* character, con
m_LastUpdateTimestamp = std::time(nullptr);
m_SystemAddress = systemAddress;
RegisterMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, this, &CharacterComponent::OnRequestServerObjectInfo);
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &CharacterComponent::OnGetObjectReportInfo);
}
bool CharacterComponent::OnRequestServerObjectInfo(GameMessages::GameMsg& msg) {
auto& request = static_cast<GameMessages::RequestServerObjectInfo&>(msg);
AMFArrayValue response;
bool CharacterComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
response.Insert("visible", true);
response.Insert("objectID", std::to_string(request.targetForReport));
response.Insert("serverInfo", true);
auto& data = *response.InsertArray("data");
auto& cmptType = data.PushDebug("Character");
auto& cmptType = reportInfo.info->PushDebug("Character");
cmptType.PushDebug<AMFIntValue>("Component ID") = GeneralUtils::ToUnderlying(ComponentType);
cmptType.PushDebug<AMFIntValue>("Character's account ID") = m_Character->GetParentUser()->GetAccountID();
@@ -72,6 +66,13 @@ bool CharacterComponent::OnRequestServerObjectInfo(GameMessages::GameMsg& msg) {
cmptType.PushDebug<AMFStringValue>("Total currency") = std::to_string(m_Character->GetCoins());
cmptType.PushDebug<AMFStringValue>("Currency able to be picked up") = std::to_string(m_DroppedCoins);
cmptType.PushDebug<AMFStringValue>("Tooltip flags value") = "0";
auto& vl = cmptType.PushDebug("Visited Levels");
for (const auto zoneID : m_VisitedLevels) {
std::stringstream sstream;
sstream << "MapID: " << zoneID.GetMapID() << " CloneID: " << zoneID.GetCloneID();
vl.PushDebug<AMFStringValue>(sstream.str()) = "";
}
// visited locations
cmptType.PushDebug<AMFBoolValue>("is a GM") = m_GMLevel > eGameMasterLevel::CIVILIAN;
cmptType.PushDebug<AMFBoolValue>("Has PVP flag turned on") = m_PvpEnabled;
@@ -83,9 +84,6 @@ bool CharacterComponent::OnRequestServerObjectInfo(GameMessages::GameMsg& msg) {
cmptType.PushDebug<AMFIntValue>("Current Activity Type") = GeneralUtils::ToUnderlying(m_CurrentActivity);
cmptType.PushDebug<AMFDoubleValue>("Property Clone ID") = m_Character->GetPropertyCloneID();
GameMessages::SendUIMessageServerToSingleClient("ToggleObjectDebugger", response, m_Parent->GetSystemAddress());
LOG("Handled!");
return true;
}

View File

@@ -331,7 +331,7 @@ public:
void LoadVisitedLevelsXml(const tinyxml2::XMLElement& doc);
private:
bool OnRequestServerObjectInfo(GameMessages::GameMsg& msg);
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
/**
* The map of active venture vision effects

View File

@@ -755,18 +755,18 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
auto* member = Game::entityManager->GetEntity(specificOwner);
if (member) Loot::DropLoot(member, m_Parent, lootMatrixId, GetMinCoins(), GetMaxCoins());
if (member) Loot::DropLoot(member, m_Parent->GetObjectID(), lootMatrixId, GetMinCoins(), GetMaxCoins());
} else {
for (const auto memberId : team->members) { // Free for all
auto* member = Game::entityManager->GetEntity(memberId);
if (member == nullptr) continue;
Loot::DropLoot(member, m_Parent, lootMatrixId, GetMinCoins(), GetMaxCoins());
Loot::DropLoot(member, m_Parent->GetObjectID(), lootMatrixId, GetMinCoins(), GetMaxCoins());
}
}
} else { // drop loot for non team user
Loot::DropLoot(owner, m_Parent, GetLootMatrixID(), GetMinCoins(), GetMaxCoins());
Loot::DropLoot(owner, m_Parent->GetObjectID(), GetLootMatrixID(), GetMinCoins(), GetMaxCoins());
}
}
} else {
@@ -784,7 +784,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
coinsTotal -= coinsToLose;
Loot::DropLoot(m_Parent, m_Parent, -1, coinsToLose, coinsToLose);
Loot::DropLoot(m_Parent, m_Parent->GetObjectID(), -1, coinsToLose, coinsToLose);
character->SetCoins(coinsTotal, eLootSourceType::PICKUP);
}
}

View File

@@ -63,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() {
@@ -265,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;
@@ -280,7 +286,6 @@ bool ModelComponent::TrySetVelocity(const NiPoint3& velocity) const {
currentVelocity = velocity;
}
currentVelocity *= m_Speed;
m_Parent->SetVelocity(currentVelocity);
return true;
}

View File

@@ -146,7 +146,11 @@ public:
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.
@@ -190,4 +194,7 @@ private:
// 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

@@ -14,6 +14,7 @@
#include "dZoneManager.h"
#include "CDComponentsRegistryTable.h"
#include "QuickBuildComponent.h"
#include "CDPhysicsComponentTable.h"
#include "dNavMesh.h"
@@ -89,6 +90,9 @@ void MovementAIComponent::Resume() {
void MovementAIComponent::Update(const float deltaTime) {
if (m_Paused) return;
auto* const quickBuildComponent = m_Parent->GetComponent<QuickBuildComponent>();
if (quickBuildComponent && quickBuildComponent->GetState() != eQuickBuildState::COMPLETED) return;
if (m_PullingToPoint) {
const auto source = GetCurrentWaypoint();
@@ -154,6 +158,7 @@ void MovementAIComponent::Update(const float deltaTime) {
}
} else {
// Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1;
if (m_CurrentPath.empty()) {
if (m_Path) {
if (m_Path->pathBehavior == PathBehavior::Loop) {
@@ -161,20 +166,24 @@ void MovementAIComponent::Update(const float deltaTime) {
} else if (m_Path->pathBehavior == PathBehavior::Bounce) {
m_IsBounced = !m_IsBounced;
std::vector<PathWaypoint> waypoints = m_Path->pathWaypoints;
if (m_IsBounced) std::reverse(waypoints.begin(), waypoints.end());
if (m_IsBounced) std::ranges::reverse(waypoints);
SetPath(waypoints);
} else if (m_Path->pathBehavior == PathBehavior::Once) {
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
Stop();
return;
}
} else {
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
Stop();
return;
}
}
SetDestination(m_CurrentPath.top().position);
} else {
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
SetDestination(m_CurrentPath.top().position);
m_CurrentPath.pop();
m_CurrentPath.pop();
}
}
Game::entityManager->SerializeEntity(m_Parent);
@@ -250,6 +259,7 @@ void MovementAIComponent::Stop() {
m_InterpolatedWaypoints.clear();
while (!m_CurrentPath.empty()) m_CurrentPath.pop();
m_CurrentPathWaypointCount = 0;
m_PathIndex = 0;
@@ -272,6 +282,7 @@ void MovementAIComponent::SetPath(std::vector<PathWaypoint> path) {
this->m_CurrentPath.push(point);
});
m_CurrentPathWaypointCount = path.size();
SetDestination(path.front().position);
}

View File

@@ -209,6 +209,8 @@ public:
*/
static float GetBaseSpeed(LOT lot);
bool IsPaused() const { return m_Paused; }
private:
/**
@@ -323,6 +325,9 @@ private:
NiPoint3 m_SavedVelocity;
bool m_IsBounced{};
// The number of waypoints that were on the path in the call to SetPath
uint32_t m_CurrentPathWaypointCount{ 0 };
};
#endif // MOVEMENTAICOMPONENT_H

View File

@@ -459,7 +459,7 @@ void QuickBuildComponent::CompleteQuickBuild(Entity* const user) {
auto* missionComponent = builder->GetComponent<MissionComponent>();
if (missionComponent) missionComponent->Progress(eMissionTaskType::ACTIVITY, m_ActivityId);
}
Loot::DropActivityLoot(builder, m_Parent, m_ActivityId, 1);
Loot::DropActivityLoot(builder, m_Parent->GetObjectID(), m_ActivityId, 1);
}
// Notify scripts

View File

@@ -393,7 +393,7 @@ void RacingControlComponent::HandleMessageBoxResponse(Entity* player, int32_t bu
}
const auto score = playersRating * 10 + data->finished;
Loot::GiveActivityLoot(player, m_Parent, m_ActivityID, score);
Loot::GiveActivityLoot(player, m_Parent->GetObjectID(), m_ActivityID, score);
// Giving rewards
GameMessages::SendNotifyRacingClient(

View File

@@ -6165,12 +6165,9 @@ void GameMessages::HandleRemoveDonationItem(RakNet::BitStream& inStream, Entity*
}
void GameMessages::HandleConfirmDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity) {
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (!inventoryComponent) return;
auto* missionComponent = entity->GetComponent<MissionComponent>();
if (!missionComponent) return;
auto* characterComponent = entity->GetComponent<CharacterComponent>();
if (!characterComponent || !characterComponent->GetCurrentInteracting()) return;
const auto [inventoryComponent, missionComponent, characterComponent] = entity->GetComponentsMut<InventoryComponent, MissionComponent, CharacterComponent>();
if (!inventoryComponent || !missionComponent || !characterComponent || !characterComponent->GetCurrentInteracting()) return;
auto* donationEntity = Game::entityManager->GetEntity(characterComponent->GetCurrentInteracting());
if (!donationEntity) return;
auto* donationVendorComponent = donationEntity->GetComponent<DonationVendorComponent>();
@@ -6312,6 +6309,10 @@ void GameMessages::SendUpdateInventoryUi(LWOOBJID objectId, const SystemAddress&
}
namespace GameMessages {
bool GameMsg::Send() {
return Game::entityManager->SendMessage(*this);
}
void GameMsg::Send(const SystemAddress& sysAddr) const {
CBITSTREAM;
CMSGHEADER;

View File

@@ -15,6 +15,7 @@
#include "eGameMasterLevel.h"
class AMFBaseValue;
class AMFArrayValue;
class Entity;
class Item;
class NiQuaternion;
@@ -54,6 +55,12 @@ namespace GameMessages {
GameMsg(MessageType::Game gmId, eGameMasterLevel lvl) : msgId{ gmId }, requiredGmLevel{ lvl } {}
GameMsg(MessageType::Game gmId) : GameMsg(gmId, eGameMasterLevel::CIVILIAN) {}
virtual ~GameMsg() = default;
// Sends a message to the entity manager to route to the target
bool Send();
// Sends the message to the specified client or
// all clients if UNASSIGNED_SYSTEM_ADDRESS is specified
void Send(const SystemAddress& sysAddr) const;
virtual void Serialize(RakNet::BitStream& bitStream) const {}
virtual bool Deserialize(RakNet::BitStream& bitStream) { return true; }
@@ -782,6 +789,13 @@ namespace GameMessages {
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
};
struct GetObjectReportInfo : public GameMsg {
AMFArrayValue* info{};
bool bVerbose{};
GetObjectReportInfo() : GameMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, eGameMasterLevel::DEVELOPER) {}
};
struct RequestUse : public GameMsg {
RequestUse() : GameMsg(MessageType::Game::REQUEST_USE) {}

View File

@@ -96,20 +96,18 @@ void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) {
if (index >= m_Tasks.size()) {
break;
}
auto* const curTask = m_Tasks[index];
const auto type = m_Tasks[index]->GetType();
const auto type = curTask->GetType();
if (type == eMissionTaskType::COLLECTION ||
type == eMissionTaskType::VISIT_PROPERTY) {
auto value = std::stoul(task->Attribute("v"));
curTask->SetProgress(value, false);
task = task->NextSiblingElement();
// Collection tasks and visit property tasks store each of the collected/visited targets after the progress value
if (type == eMissionTaskType::COLLECTION || type == eMissionTaskType::VISIT_PROPERTY) {
std::vector<uint32_t> uniques;
const auto value = std::stoul(task->Attribute("v"));
m_Tasks[index]->SetProgress(value, false);
task = task->NextSiblingElement();
while (task != nullptr) {
while (task != nullptr && value > 0) {
const auto unique = std::stoul(task->Attribute("v"));
uniques.push_back(unique);
@@ -119,19 +117,10 @@ void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) {
}
task = task->NextSiblingElement();
value--;
}
m_Tasks[index]->SetUnique(uniques);
m_Tasks[index]->SetProgress(uniques.size(), false);
break;
} else {
const auto value = std::stoul(task->Attribute("v"));
m_Tasks[index]->SetProgress(value, false);
task = task->NextSiblingElement();
curTask->SetUnique(uniques);
}
index++;
@@ -163,31 +152,19 @@ void Mission::UpdateXmlCur(tinyxml2::XMLElement& element) {
if (IsComplete()) return;
for (auto* task : m_Tasks) {
if (task->GetType() == eMissionTaskType::COLLECTION ||
task->GetType() == eMissionTaskType::VISIT_PROPERTY) {
auto* child = element.GetDocument()->NewElement("sv");
child->SetAttribute("v", static_cast<unsigned int>(task->GetProgress()));
element.LinkEndChild(child);
for (auto unique : task->GetUnique()) {
auto* uniqueElement = element.GetDocument()->NewElement("sv");
uniqueElement->SetAttribute("v", static_cast<unsigned int>(unique));
element.LinkEndChild(uniqueElement);
}
break;
}
auto* child = element.GetDocument()->NewElement("sv");
for (const auto* const task : m_Tasks) {
auto* const child = element.InsertNewChildElement("sv");
child->SetAttribute("v", static_cast<unsigned int>(task->GetProgress()));
element.LinkEndChild(child);
// Collection and visit property tasks then need to store the collected/visited items after the progress
const auto taskType = task->GetType();
if (taskType == eMissionTaskType::COLLECTION || taskType == eMissionTaskType::VISIT_PROPERTY) {
for (const auto unique : task->GetUnique()) {
auto* uniqueElement = element.InsertNewChildElement("sv");
uniqueElement->SetAttribute("v", static_cast<unsigned int>(unique));
}
}
}
}
@@ -507,11 +484,6 @@ void Mission::YieldRewards() {
// If a mission rewards zero of an item, make it reward 1.
auto count = pair.second > 0 ? pair.second : 1;
// Sanity check, 6 is the max any mission yields
if (count > 6) {
count = 0;
}
inventoryComponent->AddItem(pair.first, count, IsMission() ? eLootSourceType::MISSION : eLootSourceType::ACHIEVEMENT);
}
@@ -540,11 +512,6 @@ void Mission::YieldRewards() {
// If a mission rewards zero of an item, make it reward 1.
auto count = pair.second > 0 ? pair.second : 1;
// Sanity check, 6 is the max any mission yields
if (count > 6) {
count = 0;
}
inventoryComponent->AddItem(pair.first, count, IsMission() ? eLootSourceType::MISSION : eLootSourceType::ACHIEVEMENT);
}

View File

@@ -221,6 +221,8 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
sound.target = modelComponent.GetParent()->GetObjectID();
sound.soundID = numberAsInt;
sound.Send(UNASSIGNED_SYSTEM_ADDRESS);
} else if (nextActionType == "Restart") {
modelComponent.RestartAtEndOfFrame();
}
/* END Action */
/* BEGIN Gameplay */

View File

@@ -65,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

@@ -214,7 +214,7 @@ void Loot::GiveLoot(Entity* player, std::unordered_map<LOT, int32_t>& result, eL
}
}
void Loot::GiveActivityLoot(Entity* player, Entity* source, uint32_t activityID, int32_t rating) {
void Loot::GiveActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating) {
CDActivityRewardsTable* activityRewardsTable = CDClientManager::GetTable<CDActivityRewardsTable>();
std::vector<CDActivityRewards> activityRewards = activityRewardsTable->Query([activityID](CDActivityRewards entry) { return (entry.objectTemplate == activityID); });
@@ -248,7 +248,7 @@ void Loot::GiveActivityLoot(Entity* player, Entity* source, uint32_t activityID,
character->SetCoins(character->GetCoins() + coins, eLootSourceType::ACTIVITY);
}
void Loot::DropLoot(Entity* player, Entity* killedObject, uint32_t matrixIndex, uint32_t minCoins, uint32_t maxCoins) {
void Loot::DropLoot(Entity* player, const LWOOBJID source, uint32_t matrixIndex, uint32_t minCoins, uint32_t maxCoins) {
player = player->GetOwner(); // if the owner is overwritten, we collect that here
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
@@ -258,10 +258,10 @@ void Loot::DropLoot(Entity* player, Entity* killedObject, uint32_t matrixIndex,
std::unordered_map<LOT, int32_t> result = RollLootMatrix(player, matrixIndex);
DropLoot(player, killedObject, result, minCoins, maxCoins);
DropLoot(player, source, result, minCoins, maxCoins);
}
void Loot::DropLoot(Entity* player, Entity* killedObject, std::unordered_map<LOT, int32_t>& result, uint32_t minCoins, uint32_t maxCoins) {
void Loot::DropLoot(Entity* player, const LWOOBJID source, std::unordered_map<LOT, int32_t>& result, uint32_t minCoins, uint32_t maxCoins) {
player = player->GetOwner(); // if the owner is overwritten, we collect that here
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
@@ -269,9 +269,11 @@ void Loot::DropLoot(Entity* player, Entity* killedObject, std::unordered_map<LOT
if (!inventoryComponent)
return;
const auto spawnPosition = killedObject->GetPosition();
GameMessages::GetPosition posMsg;
posMsg.target = source;
posMsg.Send();
const auto source = killedObject->GetObjectID();
const auto spawnPosition = posMsg.pos;
for (const auto& pair : result) {
for (int i = 0; i < pair.second; ++i) {
@@ -284,7 +286,7 @@ void Loot::DropLoot(Entity* player, Entity* killedObject, std::unordered_map<LOT
GameMessages::SendDropClientLoot(player, source, LOT_NULL, coins, spawnPosition);
}
void Loot::DropActivityLoot(Entity* player, Entity* source, uint32_t activityID, int32_t rating) {
void Loot::DropActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating) {
CDActivityRewardsTable* activityRewardsTable = CDClientManager::GetTable<CDActivityRewardsTable>();
std::vector<CDActivityRewards> activityRewards = activityRewardsTable->Query([activityID](CDActivityRewards entry) { return (entry.objectTemplate == activityID); });

View File

@@ -18,8 +18,8 @@ namespace Loot {
void CacheMatrix(const uint32_t matrixIndex);
void GiveLoot(Entity* player, uint32_t matrixIndex, eLootSourceType lootSourceType = eLootSourceType::NONE);
void GiveLoot(Entity* player, std::unordered_map<LOT, int32_t>& result, eLootSourceType lootSourceType = eLootSourceType::NONE);
void GiveActivityLoot(Entity* player, Entity* source, uint32_t activityID, int32_t rating = 0);
void DropLoot(Entity* player, Entity* killedObject, uint32_t matrixIndex, uint32_t minCoins, uint32_t maxCoins);
void DropLoot(Entity* player, Entity* killedObject, std::unordered_map<LOT, int32_t>& result, uint32_t minCoins, uint32_t maxCoins);
void DropActivityLoot(Entity* player, Entity* source, uint32_t activityID, int32_t rating = 0);
void GiveActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating = 0);
void DropLoot(Entity* player, const LWOOBJID source, uint32_t matrixIndex, uint32_t minCoins, uint32_t maxCoins);
void DropLoot(Entity* player, const LWOOBJID source, std::unordered_map<LOT, int32_t>& result, uint32_t minCoins, uint32_t maxCoins);
void DropActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating = 0);
};

View File

@@ -1445,12 +1445,28 @@ void SlashCommandHandler::Startup() {
};
RegisterCommand(removeIgnoreCommand);
Command shutdownCommand{
Command command{
.help = "Shuts this world down",
.info = "Shuts this world down",
.aliases = {"shutdown"},
.handle = DEVGMCommands::Shutdown,
.requiredLevel = eGameMasterLevel::DEVELOPER
};
RegisterCommand(shutdownCommand);
RegisterCommand(command);
RegisterCommand({
.help = "Turns all players' pvp mode on",
.info = "Turns all players' pvp mode on",
.aliases = {"barfight"},
.handle = DEVGMCommands::Barfight,
.requiredLevel = eGameMasterLevel::DEVELOPER
});
RegisterCommand({
.help = "Despawns an object by id",
.info = "Despawns an object by id",
.aliases = {"despawn"},
.handle = DEVGMCommands::Despawn,
.requiredLevel = eGameMasterLevel::DEVELOPER
});
}

View File

@@ -1,5 +1,7 @@
#include "DEVGMCommands.h"
#include <ranges>
// Classes
#include "AssetManager.h"
#include "Character.h"
@@ -48,6 +50,7 @@
#include "MessageType/Master.h"
#include "eInventoryType.h"
#include "ePlayerFlag.h"
#include "StringifiedEnum.h"
namespace DEVGMCommands {
@@ -759,6 +762,7 @@ namespace DEVGMCommands {
info.spawner = nullptr;
info.spawnerID = entity->GetObjectID();
info.spawnerNodeID = 0;
info.settings = { new LDFData<bool>(u"SpawnedFromSlashCommand", true) };
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
@@ -800,6 +804,7 @@ namespace DEVGMCommands {
info.spawner = nullptr;
info.spawnerID = entity->GetObjectID();
info.spawnerNodeID = 0;
info.settings = { new LDFData<bool>(u"SpawnedFromSlashCommand", true) };
auto playerPosition = entity->GetPosition();
while (numberToSpawn > 0) {
@@ -1514,7 +1519,12 @@ namespace DEVGMCommands {
}
if (!closest) return;
LOG("%llu", closest->GetObjectID());
GameMessages::RequestServerObjectInfo objectInfo;
objectInfo.bVerbose = true;
objectInfo.target = closest->GetObjectID();
objectInfo.targetForReport = entity->GetObjectID();
closest->HandleMsg(objectInfo);
Game::entityManager->SerializeEntity(closest);
@@ -1528,16 +1538,6 @@ namespace DEVGMCommands {
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(header.str()));
for (const auto& pair : closest->GetComponents()) {
auto id = pair.first;
std::stringstream stream;
stream << "Component [" << std::to_string(static_cast<uint32_t>(id)) << "]";
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(stream.str()));
}
if (splitArgs.size() >= 2) {
if (splitArgs[1] == "-m" && splitArgs.size() >= 3) {
auto* const movingPlatformComponent = closest->GetComponent<MovingPlatformComponent>();
@@ -1557,13 +1557,6 @@ namespace DEVGMCommands {
Game::entityManager->SerializeEntity(closest);
} else if (splitArgs[1] == "-a" && splitArgs.size() >= 3) {
RenderComponent::PlayAnimation(closest, splitArgs.at(2));
} else if (splitArgs[1] == "-s") {
for (auto* entry : closest->GetSettings()) {
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(entry->GetString()));
}
ChatPackets::SendSystemMessage(sysAddr, u"------");
ChatPackets::SendSystemMessage(sysAddr, u"Spawner ID: " + GeneralUtils::to_u16string(closest->GetSpawnerID()));
} else if (splitArgs[1] == "-p") {
const auto postion = closest->GetPosition();
@@ -1633,4 +1626,40 @@ namespace DEVGMCommands {
if (character) LOG("Mythran (%s) has shutdown the world", character->GetName().c_str());
Game::OnSignal(-1);
}
void Barfight(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
auto* const characterComponent = entity->GetComponent<CharacterComponent>();
if (!characterComponent) return;
for (auto* const player : PlayerManager::GetAllPlayers()) {
auto* const pCharacterComponent = player->GetComponent<CharacterComponent>();
if (pCharacterComponent) pCharacterComponent->SetPvpEnabled(true);
Game::entityManager->SerializeEntity(player);
}
const auto msg = u"Pvp has been turned on for all players by " + GeneralUtils::ASCIIToUTF16(characterComponent->GetName());
ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, msg, true);
}
void Despawn(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
if (args.empty()) return;
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
const auto objId = GeneralUtils::TryParse<LWOOBJID>(splitArgs[0]);
if (!objId) return;
auto* const target = Game::entityManager->GetEntity(*objId);
if (!target) {
ChatPackets::SendSystemMessage(sysAddr, u"Entity not found: " + GeneralUtils::UTF8ToUTF16(splitArgs[0]));
return;
}
if (!target->GetVar<bool>(u"SpawnedFromSlashCommand")) {
ChatPackets::SendSystemMessage(sysAddr, u"You cannot despawn this entity as it was not despawned from a slash command.");
return;
}
target->Smash(LWOOBJID_EMPTY, eKillType::SILENT);
LOG("Despawned entity (%llu)", target->GetObjectID());
ChatPackets::SendSystemMessage(sysAddr, u"Despawned entity: " + GeneralUtils::to_u16string(target->GetObjectID()));
}
};

View File

@@ -74,6 +74,8 @@ namespace DEVGMCommands {
void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void Shutdown(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void Barfight(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void Despawn(Entity* entity, const SystemAddress& sysAddr, const std::string args);
}
#endif //!DEVGMCOMMANDS_H

View File

@@ -14,24 +14,21 @@
#include "Start.h"
InstanceManager::InstanceManager(Logger* logger, const std::string& externalIP) {
mLogger = logger;
mExternalIP = externalIP;
using std::make_unique;
namespace {
const InstancePtr g_Empty{ nullptr };
}
InstanceManager::InstanceManager(const std::string& externalIP) : mExternalIP{ externalIP } {
m_LastPort =
GeneralUtils::TryParse<uint16_t>(Game::config->GetValue("world_port_start")).value_or(m_LastPort);
m_LastInstanceID = LWOINSTANCEID_INVALID;
}
InstanceManager::~InstanceManager() {
for (Instance* i : m_Instances) {
delete i;
i = nullptr;
}
}
Instance* InstanceManager::GetInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneID) {
const InstancePtr& InstanceManager::GetInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneID) {
LOG("Searching for an instance for mapID %i/%i", mapID, cloneID);
Instance* instance = FindInstance(mapID, isFriendTransfer, cloneID);
auto& instance = FindInstance(mapID, isFriendTransfer, cloneID);
if (instance) return instance;
// If we are shutting down, return a nullptr so a new instance is not created.
@@ -40,7 +37,7 @@ Instance* InstanceManager::GetInstance(LWOMAPID mapID, bool isFriendTransfer, LW
mapID,
m_LastInstanceID + 1,
cloneID);
return nullptr;
return g_Empty;
}
//TODO: Update this so that the IP is read from a configuration file instead
@@ -56,23 +53,23 @@ Instance* InstanceManager::GetInstance(LWOMAPID mapID, bool isFriendTransfer, LW
}
uint32_t port = GetFreePort();
instance = new Instance(mExternalIP, port, mapID, ++m_LastInstanceID, cloneID, softCap, maxPlayers);
auto newInstance = make_unique<Instance>(mExternalIP, port, mapID, ++m_LastInstanceID, cloneID, softCap, maxPlayers);
//Start the actual process:
StartWorldServer(mapID, port, m_LastInstanceID, maxPlayers, cloneID);
m_Instances.push_back(instance);
m_Instances.push_back(std::move(newInstance));
if (instance) {
if (m_Instances.back()) {
LOG("Created new instance: %i/%i/%i with min/max %i/%i", mapID, m_LastInstanceID, cloneID, softCap, maxPlayers);
return instance;
return m_Instances.back();
} else LOG("Failed to create a new instance!");
return nullptr;
return g_Empty;
}
bool InstanceManager::IsPortInUse(uint32_t port) {
for (Instance* i : m_Instances) {
for (const auto& i : m_Instances) {
if (i && i->GetPort() == port) {
return true;
}
@@ -84,7 +81,7 @@ bool InstanceManager::IsPortInUse(uint32_t port) {
uint32_t InstanceManager::GetFreePort() {
uint32_t port = m_LastPort;
std::vector<uint32_t> usedPorts;
for (Instance* i : m_Instances) {
for (const auto& i : m_Instances) {
usedPorts.push_back(i->GetPort());
}
@@ -101,7 +98,7 @@ uint32_t InstanceManager::GetFreePort() {
}
void InstanceManager::AddPlayer(SystemAddress systemAddr, LWOMAPID mapID, LWOINSTANCEID instanceID) {
Instance* inst = FindInstance(mapID, instanceID);
const auto& inst = FindInstance(mapID, instanceID);
if (inst) {
Player player;
player.addr = systemAddr;
@@ -111,7 +108,7 @@ void InstanceManager::AddPlayer(SystemAddress systemAddr, LWOMAPID mapID, LWOINS
}
void InstanceManager::RemovePlayer(SystemAddress systemAddr, LWOMAPID mapID, LWOINSTANCEID instanceID) {
Instance* inst = FindInstance(mapID, instanceID);
const auto& inst = FindInstance(mapID, instanceID);
if (inst) {
Player player;
player.addr = systemAddr;
@@ -120,25 +117,23 @@ void InstanceManager::RemovePlayer(SystemAddress systemAddr, LWOMAPID mapID, LWO
}
}
std::vector<Instance*> InstanceManager::GetInstances() const {
const std::vector<InstancePtr>& InstanceManager::GetInstances() const {
return m_Instances;
}
void InstanceManager::AddInstance(Instance* instance) {
void InstanceManager::AddInstance(InstancePtr& instance) {
if (instance == nullptr) return;
m_Instances.push_back(instance);
m_Instances.push_back(std::move(instance));
}
void InstanceManager::RemoveInstance(Instance* instance) {
void InstanceManager::RemoveInstance(const InstancePtr& instance) {
for (uint32_t i = 0; i < m_Instances.size(); ++i) {
if (m_Instances[i] == instance) {
instance->SetShutdownComplete(true);
if (!Game::ShouldShutdown()) RedirectPendingRequests(instance);
delete m_Instances[i];
m_Instances.erase(m_Instances.begin() + i);
break;
@@ -146,7 +141,7 @@ void InstanceManager::RemoveInstance(Instance* instance) {
}
}
void InstanceManager::ReadyInstance(Instance* instance) {
void InstanceManager::ReadyInstance(const InstancePtr& instance) {
instance->SetIsReady(true);
auto& pending = instance->GetPendingRequests();
@@ -172,7 +167,7 @@ void InstanceManager::ReadyInstance(Instance* instance) {
pending.clear();
}
void InstanceManager::RequestAffirmation(Instance* instance, const PendingInstanceRequest& request) {
void InstanceManager::RequestAffirmation(const InstancePtr& instance, const PendingInstanceRequest& request) {
instance->GetPendingAffirmations().push_back(request);
CBITSTREAM;
@@ -189,7 +184,7 @@ void InstanceManager::RequestAffirmation(Instance* instance, const PendingInstan
);
}
void InstanceManager::AffirmTransfer(Instance* instance, const uint64_t transferID) {
void InstanceManager::AffirmTransfer(const InstancePtr& instance, const uint64_t transferID) {
auto& pending = instance->GetPendingAffirmations();
for (auto i = 0u; i < pending.size(); ++i) {
@@ -217,11 +212,11 @@ void InstanceManager::AffirmTransfer(Instance* instance, const uint64_t transfer
}
}
void InstanceManager::RedirectPendingRequests(Instance* instance) {
void InstanceManager::RedirectPendingRequests(const InstancePtr& instance) {
const auto& zoneId = instance->GetZoneID();
for (const auto& request : instance->GetPendingAffirmations()) {
auto* in = Game::im->GetInstance(zoneId.GetMapID(), false, zoneId.GetCloneID());
const auto& in = Game::im->GetInstance(zoneId.GetMapID(), false, zoneId.GetCloneID());
if (in && !in->GetIsReady()) // Instance not ready, make a pending request
{
@@ -234,57 +229,48 @@ void InstanceManager::RedirectPendingRequests(Instance* instance) {
}
}
Instance* InstanceManager::GetInstanceBySysAddr(SystemAddress& sysAddr) {
for (uint32_t i = 0; i < m_Instances.size(); ++i) {
if (m_Instances[i] && m_Instances[i]->GetSysAddr() == sysAddr) {
return m_Instances[i];
const InstancePtr& InstanceManager::GetInstanceBySysAddr(SystemAddress& sysAddr) {
for (const auto& instance : m_Instances) {
if (instance && instance->GetSysAddr() == sysAddr) {
return instance;
}
}
return nullptr;
return g_Empty;
}
bool InstanceManager::IsInstanceFull(Instance* instance, bool isFriendTransfer) {
if (!isFriendTransfer && instance->GetSoftCap() > instance->GetCurrentClientCount())
return false;
else if (isFriendTransfer && instance->GetHardCap() > instance->GetCurrentClientCount())
return false;
return true;
}
Instance* InstanceManager::FindInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneId) {
for (Instance* i : m_Instances) {
if (i && i->GetMapID() == mapID && i->GetCloneID() == cloneId && !IsInstanceFull(i, isFriendTransfer) && !i->GetIsPrivate() && !i->GetShutdownComplete() && !i->GetIsShuttingDown()) {
const InstancePtr& InstanceManager::FindInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneId) {
for (const auto& i : m_Instances) {
if (i && i->GetMapID() == mapID && i->GetCloneID() == cloneId && !i->IsFull(isFriendTransfer) && !i->GetIsPrivate() && !i->GetShutdownComplete() && !i->GetIsShuttingDown()) {
return i;
}
}
return nullptr;
return g_Empty;
}
Instance* InstanceManager::FindInstance(LWOMAPID mapID, LWOINSTANCEID instanceID) {
for (Instance* i : m_Instances) {
const InstancePtr& InstanceManager::FindInstance(LWOMAPID mapID, LWOINSTANCEID instanceID) {
for (const auto& i : m_Instances) {
if (i && i->GetMapID() == mapID && i->GetInstanceID() == instanceID && !i->GetIsPrivate() && !i->GetShutdownComplete() && !i->GetIsShuttingDown()) {
return i;
}
}
return nullptr;
return g_Empty;
}
Instance* InstanceManager::FindInstanceWithPrivate(LWOMAPID mapID, LWOINSTANCEID instanceID) {
for (Instance* i : m_Instances) {
const InstancePtr& InstanceManager::FindInstanceWithPrivate(LWOMAPID mapID, LWOINSTANCEID instanceID) {
for (const auto& i : m_Instances) {
if (i && i->GetMapID() == mapID && i->GetInstanceID() == instanceID && !i->GetShutdownComplete() && !i->GetIsShuttingDown()) {
return i;
}
}
return nullptr;
return g_Empty;
}
Instance* InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password) {
auto* instance = FindPrivateInstance(password);
const InstancePtr& InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password) {
const auto& instance = FindPrivateInstance(password);
if (instance != nullptr) {
return instance;
@@ -295,27 +281,27 @@ Instance* InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID clon
mapID,
m_LastInstanceID + 1,
cloneID);
return nullptr;
return g_Empty;
}
int maxPlayers = 999;
uint32_t port = GetFreePort();
instance = new Instance(mExternalIP, port, mapID, ++m_LastInstanceID, cloneID, maxPlayers, maxPlayers, true, password);
auto newInstance = make_unique<Instance>(mExternalIP, port, mapID, ++m_LastInstanceID, cloneID, maxPlayers, maxPlayers, true, password);
//Start the actual process:
StartWorldServer(mapID, port, m_LastInstanceID, maxPlayers, cloneID);
m_Instances.push_back(instance);
m_Instances.push_back(std::move(newInstance));
if (instance) return instance;
if (m_Instances.back()) return m_Instances.back();
else LOG("Failed to create a new instance!");
return instance;
return g_Empty;
}
Instance* InstanceManager::FindPrivateInstance(const std::string& password) {
for (auto* instance : m_Instances) {
const InstancePtr& InstanceManager::FindPrivateInstance(const std::string& password) {
for (const auto& instance : m_Instances) {
if (!instance) continue;
if (!instance->GetIsPrivate()) {
@@ -329,7 +315,7 @@ Instance* InstanceManager::FindPrivateInstance(const std::string& password) {
}
}
return nullptr;
return g_Empty;
}
int InstanceManager::GetSoftCap(LWOMAPID mapID) {
@@ -363,3 +349,14 @@ void Instance::Shutdown() {
LOG("Triggered world shutdown for zone/clone/instance %i/%i/%i", GetMapID(), GetCloneID(), GetInstanceID());
}
bool Instance::IsFull(bool isFriendTransfer) const {
if (!isFriendTransfer && GetSoftCap() > GetCurrentClientCount())
return false;
else if (isFriendTransfer && GetHardCap() > GetCurrentClientCount())
return false;
return true;
}

View File

@@ -75,6 +75,8 @@ public:
void Shutdown();
bool IsFull(bool isFriendTransfer) const;
private:
std::string m_IP{};
uint32_t m_Port{};
@@ -99,42 +101,42 @@ private:
//Private functions:
};
using InstancePtr = std::unique_ptr<Instance>;
class InstanceManager {
public:
InstanceManager(Logger* logger, const std::string& externalIP);
~InstanceManager();
InstanceManager(const std::string& externalIP);
Instance* GetInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneID); //Creates an instance if none found
const InstancePtr& GetInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneID); //Creates an instance if none found
bool IsPortInUse(uint32_t port);
uint32_t GetFreePort();
void AddPlayer(SystemAddress systemAddr, LWOMAPID mapID, LWOINSTANCEID instanceID);
void RemovePlayer(SystemAddress systemAddr, LWOMAPID mapID, LWOINSTANCEID instanceID);
std::vector<Instance*> GetInstances() const;
void AddInstance(Instance* instance);
void RemoveInstance(Instance* instance);
const std::vector<InstancePtr>& GetInstances() const;
void AddInstance(InstancePtr& instance);
void RemoveInstance(const InstancePtr& instance);
void ReadyInstance(Instance* instance);
void RequestAffirmation(Instance* instance, const PendingInstanceRequest& request);
void AffirmTransfer(Instance* instance, uint64_t transferID);
void ReadyInstance(const InstancePtr& instance);
void RequestAffirmation(const InstancePtr& instance, const PendingInstanceRequest& request);
void AffirmTransfer(const InstancePtr& instance, uint64_t transferID);
void RedirectPendingRequests(Instance* instance);
void RedirectPendingRequests(const InstancePtr& instance);
Instance* GetInstanceBySysAddr(SystemAddress& sysAddr);
const InstancePtr& GetInstanceBySysAddr(SystemAddress& sysAddr);
Instance* FindInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneId = 0);
Instance* FindInstance(LWOMAPID mapID, LWOINSTANCEID instanceID);
Instance* FindInstanceWithPrivate(LWOMAPID mapID, LWOINSTANCEID instanceID);
const InstancePtr& FindInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneId = 0);
const InstancePtr& FindInstance(LWOMAPID mapID, LWOINSTANCEID instanceID);
const InstancePtr& FindInstanceWithPrivate(LWOMAPID mapID, LWOINSTANCEID instanceID);
Instance* CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password);
Instance* FindPrivateInstance(const std::string& password);
const InstancePtr& CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password);
const InstancePtr& FindPrivateInstance(const std::string& password);
void SetIsShuttingDown(bool value) { this->m_IsShuttingDown = value; };
private:
Logger* mLogger;
std::string mExternalIP;
std::vector<Instance*> m_Instances;
std::vector<std::unique_ptr<Instance>> m_Instances;
uint16_t m_LastPort = 3000;
LWOINSTANCEID m_LastInstanceID;
@@ -144,7 +146,6 @@ private:
bool m_IsShuttingDown = false;
//Private functions:
bool IsInstanceFull(Instance* instance, bool isFriendTransfer);
int GetSoftCap(LWOMAPID mapID);
int GetHardCap(LWOMAPID mapID);
};

View File

@@ -359,7 +359,7 @@ int main(int argc, char** argv) {
//Create additional objects here:
PersistentIDManager::Initialize();
Game::im = new InstanceManager(Game::logger, Game::server->GetIP());
Game::im = new InstanceManager(Game::server->GetIP());
//Get CDClient initial information
try {
@@ -434,9 +434,9 @@ int main(int argc, char** argv) {
framesSinceKillUniverseCommand++;
}
const auto instances = Game::im->GetInstances();
const auto& instances = Game::im->GetInstances();
for (auto* instance : instances) {
for (const auto& instance : instances) {
if (instance == nullptr) {
break;
}
@@ -460,7 +460,7 @@ int main(int argc, char** argv) {
}
//Remove dead instances
for (auto* instance : instances) {
for (const auto& instance : instances) {
if (instance == nullptr) {
break;
}
@@ -489,7 +489,7 @@ void HandlePacket(Packet* packet) {
//Since this disconnection is intentional, we'll just delete it as
//we'll start a new one anyway if needed:
Instance* instance =
const auto& instance =
Game::im->GetInstanceBySysAddr(packet->systemAddress);
if (instance) {
LOG("Actually disconnected from zone %i clone %i instance %i port %i", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance->GetPort());
@@ -510,7 +510,7 @@ void HandlePacket(Packet* packet) {
if (packet->data[0] == ID_CONNECTION_LOST) {
LOG("A server has lost the connection");
Instance* instance =
const auto& instance =
Game::im->GetInstanceBySysAddr(packet->systemAddress);
if (instance) {
LWOZONEID zoneID = instance->GetZoneID(); //Get the zoneID so we can recreate a server
@@ -561,9 +561,9 @@ void HandlePacket(Packet* packet) {
LOG("Shutdown sequence has been started. Not creating a new zone.");
break;
}
Instance* in = Game::im->GetInstance(zoneID, false, zoneClone);
const auto& in = Game::im->GetInstance(zoneID, false, zoneClone);
for (auto* instance : Game::im->GetInstances()) {
for (const auto& instance : Game::im->GetInstances()) {
LOG("Instance: %i/%i/%i -> %i %s", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance == in, instance->GetSysAddr().ToString());
}
@@ -603,12 +603,12 @@ void HandlePacket(Packet* packet) {
if (theirServerType == ServerType::World) {
if (!Game::im->IsPortInUse(theirPort)) {
Instance* in = new Instance(theirIP.string, theirPort, theirZoneID, theirInstanceID, 0, 12, 12);
auto in = std::make_unique<Instance>(theirIP.string, theirPort, theirZoneID, theirInstanceID, 0, 12, 12);
in->SetSysAddr(packet->systemAddress);
Game::im->AddInstance(in);
} else {
auto* instance = Game::im->FindInstanceWithPrivate(theirZoneID, static_cast<LWOINSTANCEID>(theirInstanceID));
const auto& instance = Game::im->FindInstanceWithPrivate(theirZoneID, static_cast<LWOINSTANCEID>(theirInstanceID));
if (instance) {
instance->SetSysAddr(packet->systemAddress);
}
@@ -682,7 +682,7 @@ void HandlePacket(Packet* packet) {
inStream.Read(theirZoneID);
inStream.Read(theirInstanceID);
auto instance =
const auto& instance =
Game::im->FindInstance(theirZoneID, theirInstanceID);
if (instance) {
instance->AddPlayer(Player());
@@ -702,7 +702,7 @@ void HandlePacket(Packet* packet) {
inStream.Read(theirZoneID);
inStream.Read(theirInstanceID);
auto instance =
const auto& instance =
Game::im->FindInstance(theirZoneID, theirInstanceID);
if (instance) {
instance->RemovePlayer(Player());
@@ -728,7 +728,7 @@ void HandlePacket(Packet* packet) {
inStream.Read<char>(character);
password += character;
}
auto* newInst = Game::im->CreatePrivateInstance(mapId, cloneId, password.c_str());
const auto& newInst = Game::im->CreatePrivateInstance(mapId, cloneId, password.c_str());
LOG("Creating private zone %i/%i/%i with password %s", newInst->GetMapID(), newInst->GetCloneID(), newInst->GetInstanceID(), password.c_str());
break;
@@ -754,9 +754,9 @@ void HandlePacket(Packet* packet) {
password += character;
}
auto* instance = Game::im->FindPrivateInstance(password.c_str());
const auto& instance = Game::im->FindPrivateInstance(password.c_str());
LOG("Join private zone: %llu %d %s %p", requestID, mythranShift, password.c_str(), instance);
LOG("Join private zone: %llu %d %s %p", requestID, mythranShift, password.c_str(), instance.get());
if (instance == nullptr) {
return;
@@ -781,7 +781,7 @@ void HandlePacket(Packet* packet) {
LOG("Got world ready %i %i", zoneID, instanceID);
auto* instance = Game::im->FindInstance(zoneID, instanceID);
const auto& instance = Game::im->FindInstance(zoneID, instanceID);
if (instance == nullptr) {
LOG("Failed to find zone to ready");
@@ -819,7 +819,7 @@ void HandlePacket(Packet* packet) {
LOG("Got affirmation of transfer %llu", requestID);
auto* instance = Game::im->GetInstanceBySysAddr(packet->systemAddress);
const auto& instance = Game::im->GetInstanceBySysAddr(packet->systemAddress);
if (instance == nullptr)
return;
@@ -832,7 +832,7 @@ void HandlePacket(Packet* packet) {
case MessageType::Master::SHUTDOWN_RESPONSE: {
CINSTREAM_SKIP_HEADER;
auto* instance = Game::im->GetInstanceBySysAddr(packet->systemAddress);
const auto& instance = Game::im->GetInstanceBySysAddr(packet->systemAddress);
LOG("Got shutdown response from %s", packet->systemAddress.ToString());
if (instance == nullptr) {
return;
@@ -882,13 +882,13 @@ int ShutdownSequence(int32_t signal) {
LOG("Saved ObjectIDTracker to DB");
// A server might not be finished spinning up yet, remove all of those here.
for (auto* instance : Game::im->GetInstances()) {
for (const auto& instance : Game::im->GetInstances()) {
if (!instance) continue;
if (!instance->GetIsReady()) {
Game::im->RemoveInstance(instance);
}
}
for (auto* instance : Game::im->GetInstances()) {
instance->SetIsShuttingDown(true);
}
@@ -909,7 +909,7 @@ int ShutdownSequence(int32_t signal) {
allInstancesShutdown = true;
for (auto* instance : Game::im->GetInstances()) {
for (const auto& instance : Game::im->GetInstances()) {
if (instance == nullptr) {
continue;
}

View File

@@ -12,7 +12,7 @@
#include <iostream>
void HTTPMonitorInfo::Serialize(RakNet::BitStream &bitStream) const {
void HTTPMonitorInfo::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(port);
bitStream.Write<uint8_t>(openWeb);
bitStream.Write<uint8_t>(supportsSum);
@@ -81,32 +81,29 @@ void WorldPackets::SendServerState(const SystemAddress& sysAddr) {
SEND_PACKET;
}
void WorldPackets::SendCreateCharacter(const SystemAddress& sysAddr, int64_t reputation, LWOOBJID player, const std::string& xmlData, const std::u16string& username, eGameMasterLevel gm) {
void WorldPackets::SendCreateCharacter(const SystemAddress& sysAddr, int64_t reputation, LWOOBJID player, const std::string& xmlData, const std::u16string& username, eGameMasterLevel gm, const LWOCLONEID cloneID) {
using namespace std;
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::CREATE_CHARACTER);
RakNet::BitStream data;
data.Write<uint32_t>(7); //LDF key count
std::unique_ptr<LDFData<LWOOBJID>> objid(new LDFData<LWOOBJID>(u"objid", player));
std::unique_ptr<LDFData<LOT>> lot(new LDFData<LOT>(u"template", 1));
std::unique_ptr<LDFData<std::string>> xmlConfigData(new LDFData<std::string>(u"xmlData", xmlData));
std::unique_ptr<LDFData<std::u16string>> name(new LDFData<std::u16string>(u"name", username));
std::unique_ptr<LDFData<int32_t>> gmlevel(new LDFData<int32_t>(u"gmlevel", static_cast<int32_t>(gm)));
std::unique_ptr<LDFData<int32_t>> chatmode(new LDFData<int32_t>(u"chatmode", static_cast<int32_t>(gm)));
std::unique_ptr<LDFData<int64_t>> reputationLdf(new LDFData<int64_t>(u"reputation", reputation));
objid->WriteToPacket(data);
lot->WriteToPacket(data);
name->WriteToPacket(data);
gmlevel->WriteToPacket(data);
chatmode->WriteToPacket(data);
xmlConfigData->WriteToPacket(data);
reputationLdf->WriteToPacket(data);
std::vector<std::unique_ptr<LDFBaseData>> ldfData;
ldfData.push_back(move(make_unique<LDFData<LWOOBJID>>(u"objid", player)));
ldfData.push_back(move(make_unique<LDFData<LOT>>(u"template", 1)));
ldfData.push_back(move(make_unique<LDFData<string>>(u"xmlData", xmlData)));
ldfData.push_back(move(make_unique<LDFData<u16string>>(u"name", username)));
ldfData.push_back(move(make_unique<LDFData<int32_t>>(u"gmlevel", static_cast<int32_t>(gm))));
ldfData.push_back(move(make_unique<LDFData<int32_t>>(u"chatmode", static_cast<int32_t>(gm))));
ldfData.push_back(move(make_unique<LDFData<int64_t>>(u"reputation", reputation)));
ldfData.push_back(move(make_unique<LDFData<int32_t>>(u"propertycloneid", cloneID)));
data.Write<uint32_t>(ldfData.size());
for (const auto& toSerialize : ldfData) toSerialize->WriteToPacket(data);
//Compress the data before sending:
const uint32_t reservedSize = ZCompression::GetMaxCompressedLength(data.GetNumberOfBytesUsed());
uint8_t* compressedData = new uint8_t[reservedSize];
const uint32_t reservedSize = ZCompression::GetMaxCompressedLength(data.GetNumberOfBytesUsed());
uint8_t* compressedData = new uint8_t[reservedSize];
// TODO There should be better handling here for not enough memory...
if (!compressedData) return;
@@ -177,7 +174,7 @@ void WorldPackets::SendHTTPMonitorInfo(const SystemAddress& sysAddr, const HTTPM
SEND_PACKET;
}
void WorldPackets::SendDebugOuput(const SystemAddress& sysAddr, const std::string& data){
void WorldPackets::SendDebugOuput(const SystemAddress& sysAddr, const std::string& data) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::DEBUG_OUTPUT);
bitStream.Write<uint32_t>(data.size());

View File

@@ -31,7 +31,7 @@ namespace WorldPackets {
void SendCharacterDeleteResponse(const SystemAddress& sysAddr, bool response);
void SendTransferToWorld(const SystemAddress& sysAddr, const std::string& serverIP, uint32_t serverPort, bool mythranShift);
void SendServerState(const SystemAddress& sysAddr);
void SendCreateCharacter(const SystemAddress& sysAddr, int64_t reputation, LWOOBJID player, const std::string& xmlData, const std::u16string& username, eGameMasterLevel gm);
void SendCreateCharacter(const SystemAddress& sysAddr, int64_t reputation, LWOOBJID player, const std::string& xmlData, const std::u16string& username, eGameMasterLevel gm, const LWOCLONEID cloneID);
void SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::set<std::pair<uint8_t, uint8_t>> unacceptedItems);
void SendGMLevelChange(const SystemAddress& sysAddr, bool success, eGameMasterLevel highestLevel, eGameMasterLevel prevLevel, eGameMasterLevel newLevel);
void SendHTTPMonitorInfo(const SystemAddress& sysAddr, const HTTPMonitorInfo& info);

View File

@@ -33,10 +33,10 @@ void TreasureChestDragonServer::OnUse(Entity* self, Entity* user) {
if (memberObject == nullptr) continue;
Loot::DropActivityLoot(memberObject, self, scriptedActivityComponent->GetActivityID(), rating);
Loot::DropActivityLoot(memberObject, self->GetObjectID(), scriptedActivityComponent->GetActivityID(), rating);
}
} else {
Loot::DropActivityLoot(user, self, scriptedActivityComponent->GetActivityID(), rating);
Loot::DropActivityLoot(user, self->GetObjectID(), scriptedActivityComponent->GetActivityID(), rating);
}
self->Smash(self->GetObjectID());

View File

@@ -45,7 +45,7 @@ BootyDigServer::OnFireEventServerSide(Entity* self, Entity* sender, std::string
if (renderComponent != nullptr)
renderComponent->PlayEffect(7730, u"cast", "bootyshine");
Loot::DropLoot(player, self, 231, 75, 75);
Loot::DropLoot(player, self->GetObjectID(), 231, 75, 75);
}
}
} else if (args == "ChestDead") {

View File

@@ -22,7 +22,7 @@ void BaseInteractDropLootServer::BaseUse(Entity* self, Entity* user) {
self->SetNetworkVar(u"bInUse", true);
Loot::DropLoot(user, self, lootMatrix, 0, 0);
Loot::DropLoot(user, self->GetObjectID(), lootMatrix, 0, 0);
self->AddCallbackTimer(cooldownTime, [this, self]() {
self->SetNetworkVar(u"bInUse", false);

View File

@@ -13,7 +13,7 @@ void GrowingFlower::OnSkillEventFired(Entity* self, Entity* target, const std::s
const auto mission1 = self->GetVar<int32_t>(u"missionID");
const auto mission2 = self->GetVar<int32_t>(u"missionID2");
Loot::DropActivityLoot(target, self, self->GetLOT(), 0);
Loot::DropActivityLoot(target, self->GetObjectID(), self->GetLOT(), 0);
auto* missionComponent = target->GetComponent<MissionComponent>();
if (missionComponent != nullptr) {

View File

@@ -23,7 +23,7 @@ void WishingWellServer::OnUse(Entity* self, Entity* user) {
Loot::DropActivityLoot(
user,
self,
self->GetObjectID(),
static_cast<uint32_t>(scriptedActivity->GetActivityID()),
GeneralUtils::GenerateRandomNumber<int32_t>(1, 1000)
);

View File

@@ -6,7 +6,7 @@
#include "eTerminateType.h"
void VeMissionConsole::OnUse(Entity* self, Entity* user) {
Loot::DropActivityLoot(user, self, 12551);
Loot::DropActivityLoot(user, self->GetObjectID(), 12551);
auto* inventoryComponent = user->GetComponent<InventoryComponent>();
if (inventoryComponent != nullptr) {

View File

@@ -14,6 +14,6 @@ void NjDragonEmblemChestServer::OnUse(Entity* self, Entity* user) {
auto* destroyable = self->GetComponent<DestroyableComponent>();
if (destroyable != nullptr) {
Loot::DropLoot(user, self, destroyable->GetLootMatrixID(), 0, 0);
Loot::DropLoot(user, self->GetObjectID(), destroyable->GetLootMatrixID(), 0, 0);
}
}

View File

@@ -27,7 +27,7 @@ void MinigameTreasureChestServer::OnUse(Entity* self, Entity* user) {
if (self->GetLOT() == frakjawChestId) activityRating = team->members.size();
Loot::DropActivityLoot(teamMember, self, sac->GetActivityID(), activityRating);
Loot::DropActivityLoot(teamMember, self->GetObjectID(), sac->GetActivityID(), activityRating);
}
}
} else {
@@ -35,7 +35,7 @@ void MinigameTreasureChestServer::OnUse(Entity* self, Entity* user) {
if (self->GetLOT() == frakjawChestId) activityRating = 1;
Loot::DropActivityLoot(user, self, sac->GetActivityID(), activityRating);
Loot::DropActivityLoot(user, self->GetObjectID(), sac->GetActivityID(), activityRating);
}
sac->PlayerRemove(user->GetObjectID());

View File

@@ -72,7 +72,7 @@ void ActivityManager::StopActivity(Entity* self, const LWOOBJID playerID, const
SetActivityValue(self, playerID, 1, value1);
SetActivityValue(self, playerID, 2, value2);
Loot::GiveActivityLoot(player, self, gameID, CalculateActivityRating(self, playerID));
Loot::GiveActivityLoot(player, self->GetObjectID(), gameID, CalculateActivityRating(self, playerID));
if (sac != nullptr) {
sac->PlayerRemove(player->GetObjectID());

View File

@@ -29,7 +29,7 @@ void ScriptedPowerupSpawner::OnTimerDone(Entity* self, std::string message) {
renderComponent->PlayEffect(0, u"cast", "N_cast");
}
Loot::DropLoot(owner, self, drops, 0, 0);
Loot::DropLoot(owner, self->GetObjectID(), drops, 0, 0);
}
// Increment the current cycle

View File

@@ -11,7 +11,7 @@ void AgPicnicBlanket::OnUse(Entity* self, Entity* user) {
self->SetVar<bool>(u"active", true);
auto lootTable = std::unordered_map<LOT, int32_t>{ {935, 3} };
Loot::DropLoot(user, self, lootTable, 0, 0);
Loot::DropLoot(user, self->GetObjectID(), lootTable, 0, 0);
self->AddCallbackTimer(5.0f, [self]() {
self->SetVar<bool>(u"active", false);

View File

@@ -572,7 +572,7 @@ void SGCannon::StopGame(Entity* self, bool cancel) {
missionComponent->Progress(eMissionTaskType::ACTIVITY, m_CannonLot, 0, "", self->GetVar<int32_t>(TotalScoreVariable));
}
Loot::GiveActivityLoot(player, self, GetGameID(self), self->GetVar<int32_t>(TotalScoreVariable));
Loot::GiveActivityLoot(player, self->GetObjectID(), GetGameID(self), self->GetVar<int32_t>(TotalScoreVariable));
SaveScore(self, player->GetObjectID(),
static_cast<float>(self->GetVar<int32_t>(TotalScoreVariable)), static_cast<float>(self->GetVar<uint32_t>(MaxStreakVariable)), percentage);

View File

@@ -1040,7 +1040,7 @@ void HandlePacket(Packet* packet) {
auto* characterComponent = player->GetComponent<CharacterComponent>();
if (!characterComponent) return;
WorldPackets::SendCreateCharacter(packet->systemAddress, player->GetComponent<CharacterComponent>()->GetReputation(), player->GetObjectID(), c->GetXMLData(), username, c->GetGMLevel());
WorldPackets::SendCreateCharacter(packet->systemAddress, player->GetComponent<CharacterComponent>()->GetReputation(), player->GetObjectID(), c->GetXMLData(), username, c->GetGMLevel(), c->GetPropertyCloneID());
WorldPackets::SendServerState(packet->systemAddress);
const auto respawnPoint = player->GetCharacter()->GetRespawnPoint(Game::zoneManager->GetZone()->GetWorldID());

View File

@@ -114,21 +114,23 @@ These commands are primarily for development and testing. The usage of many of t
|addfaction|`/addfaction <faction id>`|Add the faction to the users list of factions|8|
|getfactions|`/getfactions`|Shows the player's factions|8|
|setrewardcode|`/setrewardcode <code>`|Sets the rewardcode for the account you are logged into if it's a valid rewardcode, See cdclient table `RewardCodes`|8|
|barfight|`/barfight start`|Starts a barfight (turns everyones pvp on)|8|
|despawn|`/despawn <objectID>`|Despawns the entity objectID IF it was spawned in through a slash command.|8|
|crash|`/crash`|Crashes the server.|9|
|rollloot|`/rollloot <loot matrix index> <item id> <amount>`|Given a `loot matrix index`, look for `item id` in that matrix `amount` times and print to the chat box statistics of rolling that loot matrix.|9|
|castskill|`/castskill <skill id>`|Casts the skill as the player|9|
## Detailed `/inspect` Usage
`/inspect <component> (-m <waypoint> | -a <animation> | -s | -p | -f (faction) | -t)`
`/inspect <component> (-m <waypoint> | -a <animation> | -p | -f (faction) | -t)`
Finds the closest entity with the given component or LDF variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the the IDs of all components the entity has.
This info is then shown in a window on the client which the user can navigate
### `/inspect` Options
* `-m`: If the entity has a moving platform component, sends it to the given waypoint, or stops the platform if `waypoint` is `-1`.
* `-a`: Plays the given animation on the entity.
* `-s`: Prints the entity's settings and spawner ID.
* `-p`: Prints the entity's position
* `-f`: If the entity has a destroyable component, prints whether the entity is smashable and its friendly and enemy faction IDs; if `faction` is specified, adds that faction to the entity.
* `-cf`: check if the entity is enemy or friend

View File

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

View File

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