Merge branch 'main' into MSVCCompilerFlags

This commit is contained in:
jadebenn
2024-11-16 20:24:05 -06:00
135 changed files with 3248 additions and 1572 deletions

View File

@@ -27,6 +27,8 @@ Character::Character(uint32_t id, User* parentUser) {
m_ID = id;
m_ParentUser = parentUser;
m_OurEntity = nullptr;
m_GMLevel = eGameMasterLevel::CIVILIAN;
m_PermissionMap = static_cast<ePermissionMap>(0);
}
Character::~Character() {

View File

@@ -464,22 +464,22 @@ private:
/**
* The ID of this character. First 32 bits of the ObjectID.
*/
uint32_t m_ID;
uint32_t m_ID{};
/**
* The 64-bit unique ID used in the game.
*/
LWOOBJID m_ObjectID;
LWOOBJID m_ObjectID{ LWOOBJID_EMPTY };
/**
* The user that owns this character.
*/
User* m_ParentUser;
User* m_ParentUser{};
/**
* If the character is in game, this is the entity that it represents, else nullptr.
*/
Entity* m_OurEntity;
Entity* m_OurEntity{};
/**
* 0-9, the Game Master level of this character.
@@ -506,17 +506,17 @@ private:
/**
* Whether the custom name of this character is rejected
*/
bool m_NameRejected;
bool m_NameRejected{};
/**
* The current amount of coins of this character
*/
int64_t m_Coins;
int64_t m_Coins{};
/**
* Whether the character is building
*/
bool m_BuildMode;
bool m_BuildMode{};
/**
* The items equipped by the character on world load
@@ -583,7 +583,7 @@ private:
/**
* The ID of the properties of this character
*/
uint32_t m_PropertyCloneID;
uint32_t m_PropertyCloneID{};
/**
* The XML data for this character, stored as string
@@ -613,7 +613,7 @@ private:
/**
* The last time this character logged in
*/
uint64_t m_LastLogin;
uint64_t m_LastLogin{};
/**
* The gameplay flags this character has (not just true values)

View File

@@ -225,7 +225,7 @@ void Entity::Initialize() {
AddComponent<SimplePhysicsComponent>(simplePhysicsComponentID);
AddComponent<ModelComponent>();
AddComponent<ModelComponent>()->LoadBehaviors();
AddComponent<RenderComponent>();
@@ -649,7 +649,7 @@ void Entity::Initialize() {
}
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MODEL, -1) != -1 && !GetComponent<PetComponent>()) {
AddComponent<ModelComponent>();
AddComponent<ModelComponent>()->LoadBehaviors();
if (!HasComponent(eReplicaComponentType::DESTROYABLE)) {
auto* destroyableComponent = AddComponent<DestroyableComponent>();
destroyableComponent->SetHealth(1);
@@ -1534,7 +1534,7 @@ void Entity::Kill(Entity* murderer, const eKillType killType) {
bool waitForDeathAnimation = false;
if (destroyableComponent) {
waitForDeathAnimation = destroyableComponent->GetDeathBehavior() == 0 && killType != eKillType::SILENT;
waitForDeathAnimation = !destroyableComponent->GetIsSmashable() && destroyableComponent->GetDeathBehavior() == 0 && killType != eKillType::SILENT;
}
// Live waited a hard coded 12 seconds for death animations of type 0 before networking destruction!
@@ -1840,6 +1840,12 @@ const NiPoint3& Entity::GetPosition() const {
return vehicel->GetPosition();
}
auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>();
if (rigidBodyPhantomPhysicsComponent != nullptr) {
return rigidBodyPhantomPhysicsComponent->GetPosition();
}
return NiPoint3Constant::ZERO;
}
@@ -1868,6 +1874,12 @@ const NiQuaternion& Entity::GetRotation() const {
return vehicel->GetRotation();
}
auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>();
if (rigidBodyPhantomPhysicsComponent != nullptr) {
return rigidBodyPhantomPhysicsComponent->GetRotation();
}
return NiQuaternionConstant::IDENTITY;
}
@@ -1896,6 +1908,12 @@ void Entity::SetPosition(const NiPoint3& position) {
vehicel->SetPosition(position);
}
auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>();
if (rigidBodyPhantomPhysicsComponent != nullptr) {
rigidBodyPhantomPhysicsComponent->SetPosition(position);
}
Game::entityManager->SerializeEntity(this);
}
@@ -1924,6 +1942,12 @@ void Entity::SetRotation(const NiQuaternion& rotation) {
vehicel->SetRotation(rotation);
}
auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>();
if (rigidBodyPhantomPhysicsComponent != nullptr) {
rigidBodyPhantomPhysicsComponent->SetRotation(rotation);
}
Game::entityManager->SerializeEntity(this);
}

View File

@@ -12,6 +12,7 @@ User::User(const SystemAddress& sysAddr, const std::string& username, const std:
m_AccountID = 0;
m_Username = "";
m_SessionKey = "";
m_MuteExpire = 0;
m_MaxGMLevel = eGameMasterLevel::CIVILIAN; //The max GM level this account can assign to it's characters
m_LastCharID = 0;

View File

@@ -536,13 +536,13 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID
uint32_t FindCharShirtID(uint32_t shirtColor, uint32_t shirtStyle) {
try {
auto stmt = CDClientDatabase::CreatePreppedStmt(
"select obj.id from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ? AND icc.decal == ?"
"select obj.id as objectId from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ? AND icc.decal == ?"
);
stmt.bind(1, "character create shirt");
stmt.bind(2, static_cast<int>(shirtColor));
stmt.bind(3, static_cast<int>(shirtStyle));
auto tableData = stmt.execQuery();
auto shirtLOT = tableData.getIntField(0, 4069);
auto shirtLOT = tableData.getIntField("objectId", 4069);
tableData.finalize();
return shirtLOT;
} catch (const std::exception& ex) {
@@ -555,12 +555,12 @@ uint32_t FindCharShirtID(uint32_t shirtColor, uint32_t shirtStyle) {
uint32_t FindCharPantsID(uint32_t pantsColor) {
try {
auto stmt = CDClientDatabase::CreatePreppedStmt(
"select obj.id from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ?"
"select obj.id as objectId from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ?"
);
stmt.bind(1, "cc pants");
stmt.bind(2, static_cast<int>(pantsColor));
auto tableData = stmt.execQuery();
auto pantsLOT = tableData.getIntField(0, 2508);
auto pantsLOT = tableData.getIntField("objectId", 2508);
tableData.finalize();
return pantsLOT;
} catch (const std::exception& ex) {

View File

@@ -377,10 +377,10 @@ void Behavior::PlayFx(std::u16string type, const LWOOBJID target, const LWOOBJID
return;
}
const auto name = std::string(result.getStringField(0));
const auto name = std::string(result.getStringField("effectName"));
if (type.empty()) {
const auto typeResult = result.getStringField(1);
const auto typeResult = result.getStringField("effectType");
type = GeneralUtils::ASCIIToUTF16(typeResult);

View File

@@ -105,7 +105,7 @@ void BehaviorContext::ExecuteUpdates() {
this->scheduledUpdates.clear();
}
void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bitStream) {
bool BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bitStream) {
BehaviorSyncEntry entry;
auto found = false;
@@ -128,7 +128,7 @@ void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bit
if (!found) {
LOG("Failed to find behavior sync entry with sync id (%i)!", syncId);
return;
return false;
}
auto* behavior = entry.behavior;
@@ -137,10 +137,11 @@ void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bit
if (behavior == nullptr) {
LOG("Invalid behavior for sync id (%i)!", syncId);
return;
return false;
}
behavior->Sync(this, bitStream, branch);
return true;
}
@@ -198,6 +199,26 @@ void BehaviorContext::UpdatePlayerSyncs(float deltaTime) {
i++;
continue;
}
if (this->skillUId != 0 && !clientInitalized) {
EchoSyncSkill echo;
echo.bDone = true;
echo.uiSkillHandle = this->skillUId;
echo.uiBehaviorHandle = entry.handle;
RakNet::BitStream bitStream{};
entry.behavior->SyncCalculation(this, bitStream, entry.branchContext);
echo.sBitStream.assign(reinterpret_cast<char*>(bitStream.GetData()), bitStream.GetNumberOfBytesUsed());
RakNet::BitStream message;
BitStreamUtils::WriteHeader(message, eConnectionType::CLIENT, eClientMessageType::GAME_MSG);
message.Write(this->originator);
echo.Serialize(message);
Game::server->Send(message, UNASSIGNED_SYSTEM_ADDRESS, true);
}
this->syncEntries.erase(this->syncEntries.begin() + i);
}
}
@@ -224,6 +245,16 @@ bool BehaviorContext::CalculateUpdate(const float deltaTime) {
for (auto i = 0u; i < this->syncEntries.size(); ++i) {
auto entry = this->syncEntries.at(i);
if (entry.behavior->m_templateId == BehaviorTemplate::ATTACK_DELAY) {
auto* self = Game::entityManager->GetEntity(originator);
if (self) {
auto* destroyableComponent = self->GetComponent<DestroyableComponent>();
if (destroyableComponent && destroyableComponent->GetHealth() <= 0) {
continue;
}
}
}
if (entry.time > 0) {
entry.time -= deltaTime;
@@ -333,7 +364,7 @@ void BehaviorContext::FilterTargets(std::vector<Entity*>& targets, std::forward_
}
// handle targeting the caster
if (candidate == caster){
if (candidate == caster) {
// if we aren't targeting self, erase, otherise increment and continue
if (!targetSelf) index = targets.erase(index);
else index++;
@@ -356,24 +387,24 @@ void BehaviorContext::FilterTargets(std::vector<Entity*>& targets, std::forward_
}
// if they are dead, then earse and continue
if (candidateDestroyableComponent->GetIsDead()){
if (candidateDestroyableComponent->GetIsDead()) {
index = targets.erase(index);
continue;
}
// if their faction is explicitly included, increment and continue
auto candidateFactions = candidateDestroyableComponent->GetFactionIDs();
if (CheckFactionList(includeFactionList, candidateFactions)){
if (CheckFactionList(includeFactionList, candidateFactions)) {
index++;
continue;
}
// check if they are a team member
if (targetTeam){
if (targetTeam) {
auto* team = TeamManager::Instance()->GetTeam(this->caster);
if (team){
if (team) {
// if we find a team member keep it and continue to skip enemy checks
if(std::find(team->members.begin(), team->members.end(), candidate->GetObjectID()) != team->members.end()){
if (std::find(team->members.begin(), team->members.end(), candidate->GetObjectID()) != team->members.end()) {
index++;
continue;
}
@@ -419,8 +450,8 @@ bool BehaviorContext::CheckTargetingRequirements(const Entity* target) const {
// returns true if any of the object factions are in the faction list
bool BehaviorContext::CheckFactionList(std::forward_list<int32_t>& factionList, std::vector<int32_t>& objectsFactions) const {
if (factionList.empty() || objectsFactions.empty()) return false;
for (auto faction : factionList){
if(std::find(objectsFactions.begin(), objectsFactions.end(), faction) != objectsFactions.end()) return true;
for (auto faction : factionList) {
if (std::find(objectsFactions.begin(), objectsFactions.end(), faction) != objectsFactions.end()) return true;
}
return false;
}

View File

@@ -93,7 +93,7 @@ struct BehaviorContext
void ExecuteUpdates();
void SyncBehavior(uint32_t syncId, RakNet::BitStream& bitStream);
bool SyncBehavior(uint32_t syncId, RakNet::BitStream& bitStream);
void Update(float deltaTime);

View File

@@ -27,6 +27,8 @@ void DamageAbsorptionBehavior::Handle(BehaviorContext* context, RakNet::BitStrea
destroyable->SetIsShielded(true);
context->RegisterTimerBehavior(this, branch, target->GetObjectID());
Game::entityManager->SerializeEntity(target);
}
void DamageAbsorptionBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitStream, BehaviorBranchContext branch) {
@@ -52,7 +54,13 @@ void DamageAbsorptionBehavior::Timer(BehaviorContext* context, BehaviorBranchCon
const auto toRemove = std::min(present, this->m_absorbAmount);
destroyable->SetDamageToAbsorb(present - toRemove);
const auto remaining = present - toRemove;
destroyable->SetDamageToAbsorb(remaining);
destroyable->SetIsShielded(remaining > 0);
Game::entityManager->SerializeEntity(target);
}
void DamageAbsorptionBehavior::Load() {

View File

@@ -47,11 +47,11 @@ void SwitchMultipleBehavior::Load() {
auto result = query.execQuery();
while (!result.eof()) {
const auto behavior_id = static_cast<uint32_t>(result.getFloatField(1));
const auto behavior_id = static_cast<uint32_t>(result.getFloatField("behavior"));
auto* behavior = CreateBehavior(behavior_id);
auto value = result.getFloatField(2);
auto value = result.getFloatField("value");
this->m_behaviors.emplace_back(value, behavior);

View File

@@ -29,7 +29,8 @@
BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): Component(parent) {
m_Target = LWOOBJID_EMPTY;
SetAiState(AiState::spawn);
m_DirtyStateOrTarget = true;
m_State = AiState::spawn;
m_Timer = 1.0f;
m_StartPosition = parent->GetPosition();
m_MovementAI = nullptr;
@@ -45,20 +46,20 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id):
auto componentResult = componentQuery.execQuery();
if (!componentResult.eof()) {
if (!componentResult.fieldIsNull(0))
m_AggroRadius = componentResult.getFloatField(0);
if (!componentResult.fieldIsNull("aggroRadius"))
m_AggroRadius = componentResult.getFloatField("aggroRadius");
if (!componentResult.fieldIsNull(1))
m_TetherSpeed = componentResult.getFloatField(1);
if (!componentResult.fieldIsNull("tetherSpeed"))
m_TetherSpeed = componentResult.getFloatField("tetherSpeed");
if (!componentResult.fieldIsNull(2))
m_PursuitSpeed = componentResult.getFloatField(2);
if (!componentResult.fieldIsNull("pursuitSpeed"))
m_PursuitSpeed = componentResult.getFloatField("pursuitSpeed");
if (!componentResult.fieldIsNull(3))
m_SoftTetherRadius = componentResult.getFloatField(3);
if (!componentResult.fieldIsNull("softTetherRadius"))
m_SoftTetherRadius = componentResult.getFloatField("softTetherRadius");
if (!componentResult.fieldIsNull(4))
m_HardTetherRadius = componentResult.getFloatField(4);
if (!componentResult.fieldIsNull("hardTetherRadius"))
m_HardTetherRadius = componentResult.getFloatField("hardTetherRadius");
}
componentResult.finalize();
@@ -82,11 +83,11 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id):
auto result = skillQuery.execQuery();
while (!result.eof()) {
const auto skillId = static_cast<uint32_t>(result.getIntField(0));
const auto skillId = static_cast<uint32_t>(result.getIntField("skillID"));
const auto abilityCooldown = static_cast<float>(result.getFloatField(1));
const auto abilityCooldown = static_cast<float>(result.getFloatField("cooldown"));
const auto behaviorId = static_cast<uint32_t>(result.getIntField(2));
const auto behaviorId = static_cast<uint32_t>(result.getIntField("behaviorID"));
auto* behavior = Behavior::CreateBehavior(behaviorId);

View File

@@ -450,7 +450,7 @@ const std::vector<BuffParameter>& BuffComponent::GetBuffParameters(int32_t buffI
param.value = result.getFloatField("NumberValue");
param.effectId = result.getIntField("EffectID");
if (!result.fieldIsNull(3)) {
if (!result.fieldIsNull("StringValue")) {
std::istringstream stream(result.getStringField("StringValue"));
std::string token;

View File

@@ -389,9 +389,9 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
if (result.eof()) return;
if (result.fieldIsNull(0)) return;
if (result.fieldIsNull("enemyList")) return;
const auto* list_string = result.getStringField(0);
const auto* list_string = result.getStringField("enemyList");
std::stringstream ss(list_string);
std::string token;

View File

@@ -37,6 +37,9 @@
#include "CDScriptComponentTable.h"
#include "CDObjectSkillsTable.h"
#include "CDSkillBehaviorTable.h"
#include "StringifiedEnum.h"
#include <ranges>
InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) {
this->m_Dirty = true;
@@ -492,6 +495,11 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) {
return;
}
auto* const groups = inventoryElement->FirstChildElement("grps");
if (groups) {
LoadGroupXml(*groups);
}
m_Consumable = inventoryElement->IntAttribute("csl", LOT_NULL);
auto* bag = bags->FirstChildElement();
@@ -558,19 +566,9 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) {
itemElement->QueryAttribute("parent", &parent);
// End custom xml
std::vector<LDFBaseData*> config;
auto* item = new Item(id, lot, inventory, slot, count, bound, {}, parent, subKey);
auto* extraInfo = itemElement->FirstChildElement("x");
if (extraInfo) {
std::string modInfo = extraInfo->Attribute("ma");
LDFBaseData* moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(modInfo.substr(2, modInfo.size() - 1)));
config.push_back(moduleAssembly);
}
const auto* item = new Item(id, lot, inventory, slot, count, bound, config, parent, subKey);
item->LoadConfigXml(*itemElement);
if (equipped) {
const auto info = Inventory::FindItemComponent(lot);
@@ -640,6 +638,15 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) {
bags->LinkEndChild(bag);
}
auto* groups = inventoryElement->FirstChildElement("grps");
if (groups) {
groups->DeleteChildren();
} else {
groups = inventoryElement->InsertNewChildElement("grps");
}
UpdateGroupXml(*groups);
auto* items = inventoryElement->FirstChildElement("items");
if (items == nullptr) {
@@ -676,17 +683,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) {
itemElement->SetAttribute("parent", item->GetParent());
// End custom xml
for (auto* data : item->GetConfig()) {
if (data->GetKey() != u"assemblyPartLOTs") {
continue;
}
auto* extraInfo = document.NewElement("x");
extraInfo->SetAttribute("ma", data->GetString(false).c_str());
itemElement->LinkEndChild(extraInfo);
}
item->SaveConfigXml(*itemElement);
bagElement->LinkEndChild(itemElement);
}
@@ -895,8 +892,6 @@ void InventoryComponent::UnEquipItem(Item* item) {
RemoveSlot(item->GetInfo().equipLocation);
PurgeProxies(item);
UnequipScripts(item);
Game::entityManager->SerializeEntity(m_Parent);
@@ -906,6 +901,8 @@ void InventoryComponent::UnEquipItem(Item* item) {
PropertyManagementComponent::Instance()->GetParent()->OnZonePropertyModelRemovedWhileEquipped(m_Parent);
Game::zoneManager->GetZoneControlObject()->OnZonePropertyModelRemovedWhileEquipped(m_Parent);
}
PurgeProxies(item);
}
@@ -1094,7 +1091,7 @@ void InventoryComponent::CheckItemSet(const LOT lot) {
auto result = query.execQuery();
while (!result.eof()) {
const auto id = result.getIntField(0);
const auto id = result.getIntField("setID");
bool found = false;
@@ -1525,10 +1522,10 @@ void InventoryComponent::PurgeProxies(Item* item) {
const auto root = item->GetParent();
if (root != LWOOBJID_EMPTY) {
item = FindItemById(root);
Item* itemRoot = FindItemById(root);
if (item != nullptr) {
UnEquipItem(item);
if (itemRoot != nullptr) {
UnEquipItem(itemRoot);
}
return;
@@ -1600,18 +1597,18 @@ void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument& document) {
}
bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId){
bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId) {
BehaviorSlot behaviorSlot = BehaviorSlot::Invalid;
if (slot == 1 ) behaviorSlot = BehaviorSlot::Primary;
else if (slot == 2 ) behaviorSlot = BehaviorSlot::Offhand;
else if (slot == 3 ) behaviorSlot = BehaviorSlot::Neck;
else if (slot == 4 ) behaviorSlot = BehaviorSlot::Head;
else if (slot == 5 ) behaviorSlot = BehaviorSlot::Consumable;
if (slot == 1) behaviorSlot = BehaviorSlot::Primary;
else if (slot == 2) behaviorSlot = BehaviorSlot::Offhand;
else if (slot == 3) behaviorSlot = BehaviorSlot::Neck;
else if (slot == 4) behaviorSlot = BehaviorSlot::Head;
else if (slot == 5) behaviorSlot = BehaviorSlot::Consumable;
else return false;
return SetSkill(behaviorSlot, skillId);
}
bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){
bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) {
if (skillId == 0) return false;
const auto index = m_Skills.find(slot);
if (index != m_Skills.end()) {
@@ -1624,3 +1621,109 @@ bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){
return true;
}
void InventoryComponent::UpdateGroup(const GroupUpdate& groupUpdate) {
if (groupUpdate.groupId.empty()) return;
if (groupUpdate.inventory != eInventoryType::BRICKS && groupUpdate.inventory != eInventoryType::MODELS) {
LOG("Invalid inventory type for grouping %s", StringifiedEnum::ToString(groupUpdate.inventory).data());
return;
}
auto& groups = m_Groups[groupUpdate.inventory];
auto groupItr = std::ranges::find_if(groups, [&groupUpdate](const Group& group) {
return group.groupId == groupUpdate.groupId;
});
if (groupUpdate.command != GroupUpdateCommand::ADD && groupItr == groups.end()) {
LOG("Group %s not found in inventory %s. Cannot process command.", groupUpdate.groupId.c_str(), StringifiedEnum::ToString(groupUpdate.inventory).data());
return;
}
if (groupUpdate.command == GroupUpdateCommand::ADD && groups.size() >= MaximumGroupCount) {
LOG("Cannot add group to inventory %s. Maximum group count reached.", StringifiedEnum::ToString(groupUpdate.inventory).data());
return;
}
switch (groupUpdate.command) {
case GroupUpdateCommand::ADD: {
auto& group = groups.emplace_back();
group.groupId = groupUpdate.groupId;
group.groupName = groupUpdate.groupName;
break;
}
case GroupUpdateCommand::ADD_LOT: {
groupItr->lots.insert(groupUpdate.lot);
break;
}
case GroupUpdateCommand::REMOVE: {
groups.erase(groupItr);
break;
}
case GroupUpdateCommand::REMOVE_LOT: {
groupItr->lots.erase(groupUpdate.lot);
break;
}
case GroupUpdateCommand::MODIFY: {
groupItr->groupName = groupUpdate.groupName;
break;
}
default: {
LOG("Invalid group update command %i", groupUpdate.command);
break;
}
}
}
void InventoryComponent::UpdateGroupXml(tinyxml2::XMLElement& groups) const {
for (const auto& [inventory, groupsData] : m_Groups) {
for (const auto& group : groupsData) {
auto* const groupElement = groups.InsertNewChildElement("grp");
groupElement->SetAttribute("id", group.groupId.c_str());
groupElement->SetAttribute("n", group.groupName.c_str());
groupElement->SetAttribute("t", static_cast<uint32_t>(inventory));
groupElement->SetAttribute("u", 0);
std::ostringstream lots;
bool first = true;
for (const auto lot : group.lots) {
if (!first) lots << ' ';
first = false;
lots << lot;
}
groupElement->SetAttribute("l", lots.str().c_str());
}
}
}
void InventoryComponent::LoadGroupXml(const tinyxml2::XMLElement& groups) {
auto* groupElement = groups.FirstChildElement("grp");
while (groupElement) {
const char* groupId = nullptr;
const char* groupName = nullptr;
const char* lots = nullptr;
uint32_t inventory = eInventoryType::INVALID;
groupElement->QueryStringAttribute("id", &groupId);
groupElement->QueryStringAttribute("n", &groupName);
groupElement->QueryStringAttribute("l", &lots);
groupElement->QueryAttribute("t", &inventory);
if (!groupId || !groupName || !lots) {
LOG("Failed to load group from xml id %i name %i lots %i",
groupId == nullptr, groupName == nullptr, lots == nullptr);
} else {
auto& group = m_Groups[static_cast<eInventoryType>(inventory)].emplace_back();
group.groupId = groupId;
group.groupName = groupName;
for (const auto& lotStr : GeneralUtils::SplitString(lots, ' ')) {
auto lot = GeneralUtils::TryParse<LOT>(lotStr);
if (lot) group.lots.insert(*lot);
}
}
groupElement = groupElement->NextSiblingElement("grp");
}
}

View File

@@ -37,6 +37,35 @@ enum class eItemType : int32_t;
*/
class InventoryComponent final : public Component {
public:
struct Group {
// Generated ID for the group. The ID is sent by the client and has the format user_group + Math.random() * UINT_MAX.
std::string groupId;
// Custom name assigned by the user.
std::string groupName;
// All the lots the user has in the group.
std::set<LOT> lots;
};
enum class GroupUpdateCommand {
ADD,
ADD_LOT,
MODIFY,
REMOVE,
REMOVE_LOT,
};
// Based on the command, certain fields will be used or not used.
// for example, ADD_LOT wont use groupName, MODIFY wont use lots, etc.
struct GroupUpdate {
std::string groupId;
std::string groupName;
LOT lot;
eInventoryType inventory;
GroupUpdateCommand command;
};
static constexpr uint32_t MaximumGroupCount = 50;
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY;
InventoryComponent(Entity* parent);
@@ -367,14 +396,23 @@ public:
*/
void UnequipScripts(Item* unequippedItem);
std::map<BehaviorSlot, uint32_t> GetSkills(){ return m_Skills; };
std::map<BehaviorSlot, uint32_t> GetSkills() { return m_Skills; };
bool SetSkill(int slot, uint32_t skillId);
bool SetSkill(BehaviorSlot slot, uint32_t skillId);
void UpdateGroup(const GroupUpdate& groupUpdate);
void RemoveGroup(const std::string& groupId);
~InventoryComponent() override;
private:
/**
* The key is the inventory the group belongs to, the value maps' key is the id for the group.
* This is only used for bricks and model inventories.
*/
std::map<eInventoryType, std::vector<Group>> m_Groups{ { eInventoryType::BRICKS, {} }, { eInventoryType::MODELS, {} } };
/**
* All the inventory this entity possesses
*/
@@ -477,6 +515,9 @@ private:
* @param document the xml doc to load from
*/
void UpdatePetXml(tinyxml2::XMLDocument& document);
void LoadGroupXml(const tinyxml2::XMLElement& groups);
void UpdateGroupXml(tinyxml2::XMLElement& groups) const;
};
#endif

View File

@@ -466,8 +466,8 @@ bool MissionComponent::RequiresItem(const LOT lot) {
return false;
}
if (!result.fieldIsNull(0)) {
const auto type = std::string(result.getStringField(0));
if (!result.fieldIsNull("type")) {
const auto type = std::string(result.getStringField("type"));
result.finalize();

View File

@@ -6,6 +6,9 @@
#include "BehaviorStates.h"
#include "ControlBehaviorMsgs.h"
#include "tinyxml2.h"
#include "Database.h"
ModelComponent::ModelComponent(Entity* parent) : Component(parent) {
m_OriginalPosition = m_Parent->GetDefaultPosition();
@@ -14,6 +17,33 @@ ModelComponent::ModelComponent(Entity* parent) : Component(parent) {
m_userModelID = m_Parent->GetVarAs<LWOOBJID>(u"userModelID");
}
void ModelComponent::LoadBehaviors() {
auto behaviors = GeneralUtils::SplitString(m_Parent->GetVar<std::string>(u"userModelBehaviors"), ',');
for (const auto& behavior : behaviors) {
if (behavior.empty()) continue;
const auto behaviorId = GeneralUtils::TryParse<int32_t>(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);
const auto behaviorStr = Database::Get()->GetBehavior(behaviorId.value());
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* const behaviorRoot = behaviorXml.FirstChildElement("Behavior");
if (!behaviorRoot) {
LOG("Failed to load behavior %d due to missing behavior root", behaviorId.value());
continue;
}
inserted.Deserialize(*behaviorRoot);
}
}
void ModelComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
// ItemComponent Serialization. Pets do not get this serialization.
if (!m_Parent->HasComponent(eReplicaComponentType::PET)) {
@@ -72,3 +102,23 @@ void ModelComponent::MoveToInventory(MoveToInventoryMessage& msg) {
m_Behaviors.erase(m_Behaviors.begin() + msg.GetBehaviorIndex());
// TODO move to the inventory
}
std::array<std::pair<int32_t, std::string>, 5> ModelComponent::GetBehaviorsForSave() const {
std::array<std::pair<int32_t, 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();
}
return toReturn;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <map>
#include "dCommonVars.h"
@@ -28,6 +29,8 @@ public:
ModelComponent(Entity* parent);
void LoadBehaviors();
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
/**
@@ -109,6 +112,8 @@ public:
void VerifyBehaviors();
std::array<std::pair<int32_t, std::string>, 5> GetBehaviorsForSave() const;
private:
/**
* The behaviors of the model

View File

@@ -2,6 +2,7 @@
#include "GameMessages.h"
#include "BrickDatabase.h"
#include "CDClientDatabase.h"
#include "CDTamingBuildPuzzleTable.h"
#include "ChatPackets.h"
#include "EntityManager.h"
#include "Character.h"
@@ -31,8 +32,9 @@
#include "eGameMasterLevel.h"
#include "eMissionState.h"
#include "dNavMesh.h"
#include "eGameActivity.h"
#include "eStateChangeType.h"
std::unordered_map<LOT, PetComponent::PetPuzzleData> PetComponent::buildCache{};
std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::currentActivities{};
std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::activePets{};
@@ -40,7 +42,7 @@ std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::activePets{};
* Maps all the pet lots to a flag indicating that the player has caught it. All basic pets have been guessed by ObjID
* while the faction ones could be checked using their respective missions.
*/
std::map<LOT, int32_t> PetComponent::petFlags = {
const std::map<LOT, int32_t> PetComponent::petFlags{
{ 3050, 801 }, // Elephant
{ 3054, 803 }, // Cat
{ 3195, 806 }, // Triceratops
@@ -87,7 +89,6 @@ PetComponent::PetComponent(Entity* parentEntity, uint32_t componentId) : Compone
m_StartPosition = NiPoint3Constant::ZERO;
m_MovementAI = nullptr;
m_TresureTime = 0;
m_Preconditions = nullptr;
std::string checkPreconditions = GeneralUtils::UTF16ToWTF8(parentEntity->GetVar<std::u16string>(u"CheckPrecondition"));
@@ -152,96 +153,53 @@ void PetComponent::OnUse(Entity* originator) {
m_Tamer = LWOOBJID_EMPTY;
}
auto* inventoryComponent = originator->GetComponent<InventoryComponent>();
auto* const inventoryComponent = originator->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return;
}
if (m_Preconditions != nullptr && !m_Preconditions->Check(originator, true)) {
if (m_Preconditions.has_value() && !m_Preconditions->Check(originator, true)) {
return;
}
auto* movementAIComponent = m_Parent->GetComponent<MovementAIComponent>();
auto* const movementAIComponent = m_Parent->GetComponent<MovementAIComponent>();
if (movementAIComponent != nullptr) {
movementAIComponent->Stop();
}
inventoryComponent->DespawnPet();
const auto& cached = buildCache.find(m_Parent->GetLOT());
int32_t imaginationCost = 0;
std::string buildFile;
if (cached == buildCache.end()) {
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT ValidPiecesLXF, PuzzleModelLot, Timelimit, NumValidPieces, imagCostPerBuild FROM TamingBuildPuzzles WHERE NPCLot = ?;");
query.bind(1, static_cast<int>(m_Parent->GetLOT()));
auto result = query.execQuery();
if (result.eof()) {
ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to find the puzzle minigame for this pet.");
return;
}
if (result.fieldIsNull(0)) {
result.finalize();
return;
}
buildFile = std::string(result.getStringField(0));
PetPuzzleData data;
data.buildFile = buildFile;
data.puzzleModelLot = result.getIntField(1);
data.timeLimit = result.getFloatField(2);
data.numValidPieces = result.getIntField(3);
data.imaginationCost = result.getIntField(4);
if (data.timeLimit <= 0) data.timeLimit = 60;
imaginationCost = data.imaginationCost;
buildCache[m_Parent->GetLOT()] = data;
result.finalize();
} else {
buildFile = cached->second.buildFile;
imaginationCost = cached->second.imaginationCost;
const auto* const entry = CDClientManager::GetTable<CDTamingBuildPuzzleTable>()->GetByLOT(m_Parent->GetLOT());
if (!entry) {
ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to find the puzzle minigame for this pet.");
return;
}
auto* destroyableComponent = originator->GetComponent<DestroyableComponent>();
const auto* const destroyableComponent = originator->GetComponent<DestroyableComponent>();
if (destroyableComponent == nullptr) {
return;
}
auto imagination = destroyableComponent->GetImagination();
if (imagination < imaginationCost) {
const auto imagination = destroyableComponent->GetImagination();
if (imagination < entry->imaginationCost) {
return;
}
const auto& bricks = BrickDatabase::GetBricks(buildFile);
const auto& bricks = BrickDatabase::GetBricks(entry->validPieces);
if (bricks.empty()) {
ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to load the puzzle minigame for this pet.");
LOG("Couldn't find %s for minigame!", buildFile.c_str());
LOG("Couldn't find %s for minigame!", entry->validPieces.c_str());
return;
}
auto petPosition = m_Parent->GetPosition();
const auto petPosition = m_Parent->GetPosition();
auto originatorPosition = originator->GetPosition();
const auto originatorPosition = originator->GetPosition();
m_Parent->SetRotation(NiQuaternion::LookAt(petPosition, originatorPosition));
float interactionDistance = m_Parent->GetVar<float>(u"interaction_distance");
if (interactionDistance <= 0) {
interactionDistance = 15;
}
@@ -254,24 +212,23 @@ void PetComponent::OnUse(Entity* originator) {
if (dpWorld::IsLoaded()) {
NiPoint3 attempt = petPosition + forward * interactionDistance;
float y = dpWorld::GetNavMesh()->GetHeightAtPoint(attempt);
NiPoint3 nearestPoint = dpWorld::GetNavMesh()->NearestPoint(attempt);
while (std::abs(y - petPosition.y) > 4 && interactionDistance > 10) {
while (std::abs(nearestPoint.y - petPosition.y) > 4 && interactionDistance > 10) {
const NiPoint3 forward = m_Parent->GetRotation().GetForwardVector();
attempt = originatorPosition + forward * interactionDistance;
y = dpWorld::GetNavMesh()->GetHeightAtPoint(attempt);
nearestPoint = dpWorld::GetNavMesh()->NearestPoint(attempt);
interactionDistance -= 0.5f;
}
position = attempt;
position = nearestPoint;
} else {
position = petPosition + forward * interactionDistance;
}
auto rotation = NiQuaternion::LookAt(position, petPosition);
GameMessages::SendNotifyPetTamingMinigame(
@@ -290,11 +247,11 @@ void PetComponent::OnUse(Entity* originator) {
m_Parent->GetObjectID(),
LWOOBJID_EMPTY,
originator->GetObjectID(),
true,
false,
ePetTamingNotifyType::BEGIN,
petPosition,
position,
rotation,
NiPoint3Constant::ZERO,
NiPoint3Constant::ZERO,
NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f),
UNASSIGNED_SYSTEM_ADDRESS
);
@@ -302,11 +259,18 @@ void PetComponent::OnUse(Entity* originator) {
m_Tamer = originator->GetObjectID();
SetStatus(5);
Game::entityManager->SerializeEntity(m_Parent);
currentActivities.insert_or_assign(m_Tamer, m_Parent->GetObjectID());
// Notify the start of a pet taming minigame
m_Parent->GetScript()->OnNotifyPetTamingMinigame(m_Parent, originator, ePetTamingNotifyType::BEGIN);
auto* characterComponent = originator->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) {
characterComponent->SetCurrentActivity(eGameActivity::PET_TAMING);
Game::entityManager->SerializeEntity(originator);
}
}
void PetComponent::Update(float deltaTime) {
@@ -477,9 +441,8 @@ void PetComponent::TryBuild(uint32_t numBricks, bool clientFailed) {
return;
}
const auto& cached = buildCache.find(m_Parent->GetLOT());
if (cached == buildCache.end()) return;
const auto* const entry = CDClientManager::GetTable<CDTamingBuildPuzzleTable>()->GetByLOT(m_Parent->GetLOT());
if (!entry) return;
auto* destroyableComponent = tamer->GetComponent<DestroyableComponent>();
@@ -487,14 +450,14 @@ void PetComponent::TryBuild(uint32_t numBricks, bool clientFailed) {
auto imagination = destroyableComponent->GetImagination();
imagination -= cached->second.imaginationCost;
imagination -= entry->imaginationCost;
destroyableComponent->SetImagination(imagination);
Game::entityManager->SerializeEntity(tamer);
if (clientFailed) {
if (imagination < cached->second.imaginationCost) {
if (imagination < entry->imaginationCost) {
ClientFailTamingMinigame();
}
} else {
@@ -517,17 +480,14 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
return;
}
const auto& cached = buildCache.find(m_Parent->GetLOT());
if (cached == buildCache.end()) {
return;
}
const auto* const entry = CDClientManager::GetTable<CDTamingBuildPuzzleTable>()->GetByLOT(m_Parent->GetLOT());
if (!entry) return;
GameMessages::SendPlayFXEffect(tamer, -1, u"petceleb", "", LWOOBJID_EMPTY, 1, 1, true);
RenderComponent::PlayAnimation(tamer, u"rebuild-celebrate");
EntityInfo info{};
info.lot = cached->second.puzzleModelLot;
info.lot = entry->puzzleModelLot;
info.pos = position;
info.rot = NiQuaternionConstant::IDENTITY;
info.spawnerID = tamer->GetObjectID();
@@ -675,6 +635,11 @@ void PetComponent::RequestSetPetName(std::u16string name) {
UNASSIGNED_SYSTEM_ADDRESS
);
auto* characterComponent = tamer->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) {
characterComponent->SetCurrentActivity(eGameActivity::NONE);
Game::entityManager->SerializeEntity(tamer);
}
GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
auto* modelEntity = Game::entityManager->GetEntity(m_ModelId);
@@ -714,6 +679,11 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) {
UNASSIGNED_SYSTEM_ADDRESS
);
auto* characterComponent = tamer->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) {
characterComponent->SetCurrentActivity(eGameActivity::NONE);
Game::entityManager->SerializeEntity(tamer);
}
GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress());
GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
@@ -731,13 +701,10 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) {
}
void PetComponent::StartTimer() {
const auto& cached = buildCache.find(m_Parent->GetLOT());
const auto* const entry = CDClientManager::GetTable<CDTamingBuildPuzzleTable>()->GetByLOT(m_Parent->GetLOT());
if (!entry) return;
if (cached == buildCache.end()) {
return;
}
m_Timer = cached->second.timeLimit;
m_Timer = entry->timeLimit;
}
void PetComponent::ClientFailTamingMinigame() {
@@ -763,6 +730,11 @@ void PetComponent::ClientFailTamingMinigame() {
UNASSIGNED_SYSTEM_ADDRESS
);
auto* characterComponent = tamer->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) {
characterComponent->SetCurrentActivity(eGameActivity::NONE);
Game::entityManager->SerializeEntity(tamer);
}
GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress());
GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
@@ -823,8 +795,6 @@ void PetComponent::Wander() {
}
void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) {
AddDrainImaginationTimer(item, fromTaming);
m_ItemId = item->GetId();
m_DatabaseId = item->GetSubKey();
@@ -835,6 +805,7 @@ void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) {
inventoryComponent->DespawnPet();
m_Owner = inventoryComponent->GetParent()->GetObjectID();
AddDrainImaginationTimer(fromTaming);
auto* owner = GetOwner();
@@ -887,17 +858,14 @@ void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) {
}
}
void PetComponent::AddDrainImaginationTimer(Item* item, bool fromTaming) {
void PetComponent::AddDrainImaginationTimer(bool fromTaming) {
if (Game::config->GetValue("pets_take_imagination") != "1") return;
auto playerInventory = item->GetInventory();
if (!playerInventory) return;
auto playerInventoryComponent = playerInventory->GetComponent();
if (!playerInventoryComponent) return;
auto playerEntity = playerInventoryComponent->GetParent();
if (!playerEntity) return;
auto* playerEntity = Game::entityManager->GetEntity(m_Owner);
if (!playerEntity) {
LOG("owner was null or didnt exist!");
return;
}
auto playerDestroyableComponent = playerEntity->GetComponent<DestroyableComponent>();
if (!playerDestroyableComponent) return;
@@ -906,12 +874,16 @@ void PetComponent::AddDrainImaginationTimer(Item* item, bool fromTaming) {
if (!fromTaming) playerDestroyableComponent->Imagine(-1);
// Set this to a variable so when this is called back from the player the timer doesn't fire off.
m_Parent->AddCallbackTimer(m_PetInfo.imaginationDrainRate, [playerDestroyableComponent, this, item]() {
if (!playerDestroyableComponent) {
LOG("No petComponent and/or no playerDestroyableComponent");
m_Parent->AddCallbackTimer(m_PetInfo.imaginationDrainRate, [this]() {
const auto* owner = Game::entityManager->GetEntity(m_Owner);
if (!owner) {
LOG("owner was null or didnt exist!");
return;
}
const auto* playerDestroyableComponent = owner->GetComponent<DestroyableComponent>();
if (!playerDestroyableComponent) return;
// If we are out of imagination despawn the pet.
if (playerDestroyableComponent->GetImagination() == 0) {
this->Deactivate();
@@ -921,15 +893,13 @@ void PetComponent::AddDrainImaginationTimer(Item* item, bool fromTaming) {
GameMessages::SendUseItemRequirementsResponse(playerEntity->GetObjectID(), playerEntity->GetSystemAddress(), eUseItemResponse::NoImaginationForPet);
}
this->AddDrainImaginationTimer(item);
this->AddDrainImaginationTimer();
});
}
void PetComponent::Deactivate() {
GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), -1, u"despawn", "", LWOOBJID_EMPTY, 1, 1, true);
GameMessages::SendMarkInventoryItemAsActive(m_Owner, false, eUnequippableActiveType::PET, m_ItemId, GetOwner()->GetSystemAddress());
activePets.erase(m_Owner);
m_Parent->Kill();
@@ -938,6 +908,8 @@ void PetComponent::Deactivate() {
if (owner == nullptr) return;
GameMessages::SendMarkInventoryItemAsActive(m_Owner, false, eUnequippableActiveType::PET, m_ItemId, owner->GetSystemAddress());
GameMessages::SendAddPetToPlayer(m_Owner, 0, u"", LWOOBJID_EMPTY, LOT_NULL, owner->GetSystemAddress());
GameMessages::SendRegisterPetID(m_Owner, LWOOBJID_EMPTY, owner->GetSystemAddress());
@@ -1062,6 +1034,7 @@ Entity* PetComponent::GetParentEntity() const {
}
PetComponent::~PetComponent() {
m_Owner = LWOOBJID_EMPTY;
}
void PetComponent::SetPetNameForModeration(const std::string& petName) {
@@ -1086,6 +1059,6 @@ void PetComponent::LoadPetNameFromModeration() {
}
}
void PetComponent::SetPreconditions(std::string& preconditions) {
m_Preconditions = new PreconditionExpression(preconditions);
void PetComponent::SetPreconditions(const std::string& preconditions) {
m_Preconditions = std::make_optional<PreconditionExpression>(preconditions);
}

View File

@@ -165,7 +165,7 @@ public:
* Sets preconditions for the pet that need to be met before it can be tamed
* @param conditions the preconditions to set
*/
void SetPreconditions(std::string& conditions);
void SetPreconditions(const std::string& conditions);
/**
* Returns the entity that this component belongs to
@@ -205,7 +205,7 @@ public:
*
* @param item The item that represents this pet in the inventory.
*/
void AddDrainImaginationTimer(Item* item, bool fromTaming = false);
void AddDrainImaginationTimer(bool fromTaming = false);
private:
@@ -250,15 +250,10 @@ private:
*/
static std::unordered_map<LWOOBJID, LWOOBJID> currentActivities;
/**
* Cache of all the minigames and their information from the database
*/
static std::unordered_map<LOT, PetComponent::PetPuzzleData> buildCache;
/**
* Flags that indicate that a player has tamed a pet, indexed by the LOT of the pet
*/
static std::map<LOT, int32_t> petFlags;
static const std::map<LOT, int32_t> petFlags;
/**
* The ID of the component in the pet component table
@@ -349,7 +344,7 @@ private:
/**
* Preconditions that need to be met before an entity can tame this pet
*/
PreconditionExpression* m_Preconditions;
std::optional<PreconditionExpression> m_Preconditions{};
/**
* Pet information loaded from the CDClientDatabase

View File

@@ -47,7 +47,7 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : PhysicsCompon
m_Direction = NiPoint3(); // * m_DirectionalMultiplier
if (m_Parent->GetVar<bool>(u"create_physics")) {
CreatePhysics();
m_dpEntity = CreatePhysicsLnv(m_Scale, ComponentType);
}
if (m_Parent->GetVar<bool>(u"respawnVol")) {
@@ -89,105 +89,9 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : PhysicsCompon
m_RespawnRot = m_Rotation;
}
/*
for (LDFBaseData* data : settings) {
if (data) {
if (data->GetKey() == u"create_physics") {
if (bool(std::stoi(data->GetValueAsString()))) {
CreatePhysics(settings);
}
}
if (data->GetKey() == u"respawnVol") {
if (bool(std::stoi(data->GetValueAsString()))) {
m_IsRespawnVolume = true;
}
}
if (m_IsRespawnVolume) {
if (data->GetKey() == u"rspPos") {
//Joy, we get to split strings!
std::stringstream test(data->GetValueAsString());
std::string segment;
std::vector<std::string> seglist;
while (std::getline(test, segment, '\x1f')) {
seglist.push_back(segment);
}
m_RespawnPos = NiPoint3(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2]));
}
if (data->GetKey() == u"rspRot") {
//Joy, we get to split strings!
std::stringstream test(data->GetValueAsString());
std::string segment;
std::vector<std::string> seglist;
while (std::getline(test, segment, '\x1f')) {
seglist.push_back(segment);
}
m_RespawnRot = NiQuaternion(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2]), std::stof(seglist[3]));
}
}
if (m_Parent->GetLOT() == 4945) // HF - RespawnPoints
{
m_IsRespawnVolume = true;
m_RespawnPos = m_Position;
m_RespawnRot = m_Rotation;
}
}
}
*/
if (!m_HasCreatedPhysics) {
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), eReplicaComponentType::PHANTOM_PHYSICS);
CDPhysicsComponentTable* physComp = CDClientManager::GetTable<CDPhysicsComponentTable>();
if (physComp == nullptr) return;
auto* info = physComp->GetByID(componentID);
if (info == nullptr || info->physicsAsset == "" || info->physicsAsset == "NO_PHYSICS") return;
//temp test
if (info->physicsAsset == "miscellaneous\\misc_phys_10x1x5.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 10.0f, 5.0f, 1.0f);
} else if (info->physicsAsset == "miscellaneous\\misc_phys_640x640.hkx") {
// TODO Fix physics simulation to do simulation at high velocities due to bullet through paper problem...
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 1638.4f, 13.521004f * 2.0f, 1638.4f);
// Move this down by 13.521004 units so it is still effectively at the same height as before
m_Position = m_Position - NiPoint3Constant::UNIT_Y * 13.521004f;
} else if (info->physicsAsset == "env\\trigger_wall_tall.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 10.0f, 25.0f, 1.0f);
} else if (info->physicsAsset == "env\\env_gen_placeholderphysics.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 20.0f, 20.0f, 20.0f);
} else if (info->physicsAsset == "env\\POI_trigger_wall.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 1.0f, 12.5f, 20.0f); // Not sure what the real size is
} else if (info->physicsAsset == "env\\NG_NinjaGo\\env_ng_gen_gate_chamber_puzzle_ceiling_tile_falling_phantom.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 18.0f, 5.0f, 15.0f);
m_Position += m_Rotation.GetForwardVector() * 7.5f;
} else if (info->physicsAsset == "env\\NG_NinjaGo\\ng_flamejet_brick_phantom.HKX") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 1.0f, 1.0f, 12.0f);
m_Position += m_Rotation.GetForwardVector() * 6.0f;
} else if (info->physicsAsset == "env\\Ring_Trigger.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 6.0f, 6.0f, 6.0f);
} else if (info->physicsAsset == "env\\vfx_propertyImaginationBall.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 4.5f);
} else if (info->physicsAsset == "env\\env_won_fv_gas-blocking-volume.hkx") {
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 390.496826f, 111.467964f, 600.821534f, true);
m_Position.y -= (111.467964f * m_Scale) / 2;
} else {
// LOG_DEBUG("This one is supposed to have %s", info->physicsAsset.c_str());
//add fallback cube:
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 2.0f, 2.0f, 2.0f);
}
if (!m_dpEntity) {
m_dpEntity = CreatePhysicsEntity(ComponentType);
if (!m_dpEntity) return;
m_dpEntity->SetScale(m_Scale);
m_dpEntity->SetRotation(m_Rotation);
m_dpEntity->SetPosition(m_Position);
@@ -201,69 +105,6 @@ PhantomPhysicsComponent::~PhantomPhysicsComponent() {
}
}
void PhantomPhysicsComponent::CreatePhysics() {
unsigned char alpha;
unsigned char red;
unsigned char green;
unsigned char blue;
int type = -1;
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
float width = 0.0f; //aka "radius"
float height = 0.0f;
if (m_Parent->HasVar(u"primitiveModelType")) {
type = m_Parent->GetVar<int32_t>(u"primitiveModelType");
x = m_Parent->GetVar<float>(u"primitiveModelValueX");
y = m_Parent->GetVar<float>(u"primitiveModelValueY");
z = m_Parent->GetVar<float>(u"primitiveModelValueZ");
} else {
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), eReplicaComponentType::PHANTOM_PHYSICS);
CDPhysicsComponentTable* physComp = CDClientManager::GetTable<CDPhysicsComponentTable>();
if (physComp == nullptr) return;
auto info = physComp->GetByID(componentID);
if (info == nullptr) return;
type = info->pcShapeType;
width = info->playerRadius;
height = info->playerHeight;
}
switch (type) {
case 1: { //Make a new box shape
NiPoint3 boxSize(x, y, z);
if (x == 0.0f) {
//LU has some weird values, so I think it's best to scale them down a bit
if (height < 0.5f) height = 2.0f;
if (width < 0.5f) width = 2.0f;
//Scale them:
width = width * m_Scale;
height = height * m_Scale;
boxSize = NiPoint3(width, height, width);
}
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), boxSize);
break;
}
}
if (!m_dpEntity) return;
m_dpEntity->SetPosition({ m_Position.x, m_Position.y - (height / 2), m_Position.z });
dpWorld::AddEntity(m_dpEntity);
m_HasCreatedPhysics = true;
}
void PhantomPhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate);
@@ -308,8 +149,9 @@ void ApplyCollisionEffect(const LWOOBJID& target, const ePhysicsEffectType effec
controllablePhysicsComponent->SetGravityScale(effectScale);
GameMessages::SendSetGravityScale(target, effectScale, targetEntity->GetSystemAddress());
}
break;
}
// The other types are not handled by the server
case ePhysicsEffectType::ATTRACT:
case ePhysicsEffectType::FRICTION:
case ePhysicsEffectType::PUSH:
@@ -317,6 +159,7 @@ void ApplyCollisionEffect(const LWOOBJID& target, const ePhysicsEffectType effec
default:
break;
}
// The other types are not handled by the server and are here to handle all cases of the enum.
}
void PhantomPhysicsComponent::Update(float deltaTime) {
@@ -356,24 +199,12 @@ void PhantomPhysicsComponent::SetDirection(const NiPoint3& pos) {
m_IsDirectional = true;
}
void PhantomPhysicsComponent::SpawnVertices() {
if (!m_dpEntity) return;
LOG("%llu", m_Parent->GetObjectID());
auto box = static_cast<dpShapeBox*>(m_dpEntity->GetShape());
for (auto vert : box->GetVertices()) {
LOG("%f, %f, %f", vert.x, vert.y, vert.z);
EntityInfo info;
info.lot = 33;
info.pos = vert;
info.spawner = nullptr;
info.spawnerID = m_Parent->GetObjectID();
info.spawnerNodeID = 0;
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
Game::entityManager->ConstructEntity(newEntity);
void PhantomPhysicsComponent::SpawnVertices() const {
if (!m_dpEntity) {
LOG("No dpEntity to spawn vertices for %llu:%i", m_Parent->GetObjectID(), m_Parent->GetLOT());
return;
}
PhysicsComponent::SpawnVertices(m_dpEntity);
}
void PhantomPhysicsComponent::SetDirectionalMultiplier(float mul) {

View File

@@ -18,6 +18,7 @@ class LDFBaseData;
class Entity;
class dpEntity;
enum class ePhysicsEffectType : uint32_t ;
enum class eReplicaComponentType : uint32_t;
/**
* Allows the creation of phantom physics for an entity: a physics object that is generally invisible but can be
@@ -34,11 +35,6 @@ public:
void Update(float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
/**
* Creates the physics shape for this entity based on LDF data
*/
void CreatePhysics();
/**
* Sets the direction this physics object is pointed at
* @param pos the direction to set
@@ -109,7 +105,7 @@ public:
/**
* Spawns an object at each of the vertices for debugging purposes
*/
void SpawnVertices();
void SpawnVertices() const;
/**
* Legacy stuff no clue what this does
@@ -166,11 +162,6 @@ private:
*/
dpEntity* m_dpEntity;
/**
* Whether or not the physics object has been created yet
*/
bool m_HasCreatedPhysics = false;
/**
* Whether or not this physics object represents an object that updates the respawn pos of an entity that crosses it
*/

View File

@@ -1,5 +1,19 @@
#include "PhysicsComponent.h"
#include "eReplicaComponentType.h"
#include "NiPoint3.h"
#include "NiQuaternion.h"
#include "CDComponentsRegistryTable.h"
#include "CDPhysicsComponentTable.h"
#include "dpEntity.h"
#include "dpWorld.h"
#include "dpShapeBox.h"
#include "dpShapeSphere.h"
#include "EntityInfo.h"
PhysicsComponent::PhysicsComponent(Entity* parent) : Component(parent) {
m_Position = NiPoint3Constant::ZERO;
m_Rotation = NiQuaternionConstant::IDENTITY;
@@ -19,3 +33,190 @@ void PhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitia
if (!bIsInitialUpdate) m_DirtyPosition = false;
}
}
dpEntity* PhysicsComponent::CreatePhysicsEntity(eReplicaComponentType type) {
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), type);
CDPhysicsComponentTable* physComp = CDClientManager::GetTable<CDPhysicsComponentTable>();
if (physComp == nullptr) return nullptr;
auto* info = physComp->GetByID(componentID);
if (info == nullptr || info->physicsAsset == "" || info->physicsAsset == "NO_PHYSICS") return nullptr;
dpEntity* toReturn;
if (info->physicsAsset == "miscellaneous\\misc_phys_10x1x5.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 10.0f, 5.0f, 1.0f);
} else if (info->physicsAsset == "miscellaneous\\misc_phys_640x640.hkx") {
// TODO Fix physics simulation to do simulation at high velocities due to bullet through paper problem...
toReturn = new dpEntity(m_Parent->GetObjectID(), 1638.4f, 13.521004f * 2.0f, 1638.4f);
// Move this down by 13.521004 units so it is still effectively at the same height as before
m_Position = m_Position - NiPoint3Constant::UNIT_Y * 13.521004f;
} else if (info->physicsAsset == "env\\trigger_wall_tall.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 10.0f, 25.0f, 1.0f);
} else if (info->physicsAsset == "env\\env_gen_placeholderphysics.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 20.0f, 20.0f, 20.0f);
} else if (info->physicsAsset == "env\\POI_trigger_wall.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 1.0f, 12.5f, 20.0f); // Not sure what the real size is
} else if (info->physicsAsset == "env\\NG_NinjaGo\\env_ng_gen_gate_chamber_puzzle_ceiling_tile_falling_phantom.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 18.0f, 5.0f, 15.0f);
m_Position += m_Rotation.GetForwardVector() * 7.5f;
} else if (info->physicsAsset == "env\\NG_NinjaGo\\ng_flamejet_brick_phantom.HKX") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 1.0f, 1.0f, 12.0f);
m_Position += m_Rotation.GetForwardVector() * 6.0f;
} else if (info->physicsAsset == "env\\Ring_Trigger.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 6.0f, 6.0f, 6.0f);
} else if (info->physicsAsset == "env\\vfx_propertyImaginationBall.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 4.5f);
} else if (info->physicsAsset == "env\\env_won_fv_gas-blocking-volume.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 390.496826f, 111.467964f, 600.821534f, true);
m_Position.y -= (111.467964f * m_Parent->GetDefaultScale()) / 2;
} else {
// LOG_DEBUG("This one is supposed to have %s", info->physicsAsset.c_str());
//add fallback cube:
toReturn = new dpEntity(m_Parent->GetObjectID(), 2.0f, 2.0f, 2.0f);
}
return toReturn;
}
dpEntity* PhysicsComponent::CreatePhysicsLnv(const float scale, const eReplicaComponentType type) const {
int pcShapeType = -1;
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
float width = 0.0f; //aka "radius"
float height = 0.0f;
dpEntity* toReturn = nullptr;
if (m_Parent->HasVar(u"primitiveModelType")) {
pcShapeType = m_Parent->GetVar<int32_t>(u"primitiveModelType");
x = m_Parent->GetVar<float>(u"primitiveModelValueX");
y = m_Parent->GetVar<float>(u"primitiveModelValueY");
z = m_Parent->GetVar<float>(u"primitiveModelValueZ");
} else {
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), type);
CDPhysicsComponentTable* physComp = CDClientManager::GetTable<CDPhysicsComponentTable>();
if (physComp == nullptr) return nullptr;
auto info = physComp->GetByID(componentID);
if (info == nullptr) return nullptr;
pcShapeType = info->pcShapeType;
width = info->playerRadius;
height = info->playerHeight;
}
switch (pcShapeType) {
case 0: { // HKX type
break;
}
case 1: { //Make a new box shape
NiPoint3 boxSize(x, y, z);
if (x == 0.0f) {
//LU has some weird values, so I think it's best to scale them down a bit
if (height < 0.5f) height = 2.0f;
if (width < 0.5f) width = 2.0f;
//Scale them:
width = width * scale;
height = height * scale;
boxSize = NiPoint3(width, height, width);
}
toReturn = new dpEntity(m_Parent->GetObjectID(), boxSize);
toReturn->SetPosition({ m_Position.x, m_Position.y - (height / 2), m_Position.z });
break;
}
case 2: { //Make a new cylinder shape
break;
}
case 3: { //Make a new sphere shape
auto [x, y, z] = m_Position;
toReturn = new dpEntity(m_Parent->GetObjectID(), width);
toReturn->SetPosition({ x, y, z });
break;
}
case 4: { //Make a new capsule shape
break;
}
}
if (toReturn) dpWorld::AddEntity(toReturn);
return toReturn;
}
void PhysicsComponent::SpawnVertices(dpEntity* entity) const {
if (!entity) return;
LOG("Spawning vertices for %llu", m_Parent->GetObjectID());
EntityInfo info;
info.lot = 33;
info.spawner = nullptr;
info.spawnerID = m_Parent->GetObjectID();
info.spawnerNodeID = 0;
// These don't use overloaded methods as dPhysics does not link with dGame at the moment.
auto box = dynamic_cast<dpShapeBox*>(entity->GetShape());
if (box) {
for (auto vert : box->GetVertices()) {
LOG("Vertex at %f, %f, %f", vert.x, vert.y, vert.z);
info.pos = vert;
Entity* newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
}
}
auto sphere = dynamic_cast<dpShapeSphere*>(entity->GetShape());
if (sphere) {
auto [x, y, z] = entity->GetPosition(); // Use shapes position instead of the parent's position in case it's different
float plusX = x + sphere->GetRadius();
float minusX = x - sphere->GetRadius();
float plusY = y + sphere->GetRadius();
float minusY = y - sphere->GetRadius();
float plusZ = z + sphere->GetRadius();
float minusZ = z - sphere->GetRadius();
auto radius = sphere->GetRadius();
LOG("Radius: %f", radius);
LOG("Plus Vertices %f %f %f", plusX, plusY, plusZ);
LOG("Minus Vertices %f %f %f", minusX, minusY, minusZ);
info.pos = NiPoint3{ x, plusY, z };
Entity* newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
info.pos = NiPoint3{ x, minusY, z };
newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
info.pos = NiPoint3{ plusX, y, z };
newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
info.pos = NiPoint3{ minusX, y, z };
newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
info.pos = NiPoint3{ x, y, plusZ };
newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
info.pos = NiPoint3{ x, y, minusZ };
newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
info.pos = NiPoint3{ x, y, z };
newEntity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(newEntity);
}
}

View File

@@ -9,6 +9,10 @@ namespace Raknet {
class BitStream;
};
enum class eReplicaComponentType : uint32_t;
class dpEntity;
class PhysicsComponent : public Component {
public:
PhysicsComponent(Entity* parent);
@@ -22,6 +26,12 @@ public:
const NiQuaternion& GetRotation() const { return m_Rotation; }
virtual void SetRotation(const NiQuaternion& rot) { if (m_Rotation == rot) return; m_Rotation = rot; m_DirtyPosition = true; }
protected:
dpEntity* CreatePhysicsEntity(eReplicaComponentType type);
dpEntity* CreatePhysicsLnv(const float scale, const eReplicaComponentType type) const;
void SpawnVertices(dpEntity* entity) const;
NiPoint3 m_Position;
NiQuaternion m_Rotation;

View File

@@ -18,8 +18,8 @@ PossessableComponent::PossessableComponent(Entity* parent, uint32_t componentId)
// Should a result not exist for this default to attached visible
if (!result.eof()) {
m_PossessionType = static_cast<ePossessionType>(result.getIntField(0, 1)); // Default to Attached Visible
m_DepossessOnHit = static_cast<bool>(result.getIntField(1, 0));
m_PossessionType = static_cast<ePossessionType>(result.getIntField("possessionType", 1)); // Default to Attached Visible
m_DepossessOnHit = static_cast<bool>(result.getIntField("depossessOnHit", 0));
} else {
m_PossessionType = ePossessionType::ATTACHED_VISIBLE;
m_DepossessOnHit = false;

View File

@@ -21,9 +21,11 @@
#include "eObjectBits.h"
#include "CharacterComponent.h"
#include "PlayerManager.h"
#include "ModelComponent.h"
#include <vector>
#include "CppScripts.h"
#include <ranges>
PropertyManagementComponent* PropertyManagementComponent::instance = nullptr;
@@ -49,11 +51,11 @@ PropertyManagementComponent::PropertyManagementComponent(Entity* parent) : Compo
auto result = query.execQuery();
if (result.eof() || result.fieldIsNull(0)) {
if (result.eof() || result.fieldIsNull("id")) {
return;
}
templateId = result.getIntField(0);
templateId = result.getIntField("id");
auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId);
@@ -105,7 +107,7 @@ std::vector<NiPoint3> PropertyManagementComponent::GetPaths() const {
std::vector<float> points;
std::istringstream stream(result.getStringField(0));
std::istringstream stream(result.getStringField("path"));
std::string token;
while (std::getline(stream, token, ' ')) {
@@ -593,6 +595,20 @@ void PropertyManagementComponent::Load() {
settings.push_back(new LDFData<int>(u"componentWhitelist", 1));
}
std::ostringstream userModelBehavior;
bool firstAdded = false;
for (auto behavior : databaseModel.behaviors) {
if (behavior < 0) {
LOG("Invalid behavior ID: %d, removing behavior reference from model", behavior);
behavior = 0;
}
if (firstAdded) userModelBehavior << ",";
userModelBehavior << behavior;
firstAdded = true;
}
settings.push_back(new LDFData<std::string>(u"userModelBehaviors", userModelBehavior.str()));
node->config = settings;
const auto spawnerId = Game::zoneManager->MakeSpawner(info);
@@ -610,6 +626,12 @@ void PropertyManagementComponent::Save() {
return;
}
const auto* const owner = GetOwner();
if (!owner) return;
const auto* const character = owner->GetCharacter();
if (!character) return;
auto present = Database::Get()->GetPropertyModels(propertyId);
std::vector<LWOOBJID> modelIds;
@@ -624,6 +646,20 @@ void PropertyManagementComponent::Save() {
if (entity == nullptr) {
continue;
}
auto* modelComponent = entity->GetComponent<ModelComponent>();
if (!modelComponent) continue;
const auto modelBehaviors = modelComponent->GetBehaviorsForSave();
// save the behaviors of the model
for (const auto& [behaviorId, behaviorStr] : modelBehaviors) {
if (behaviorStr.empty() || behaviorId == -1 || behaviorId == 0) continue;
IBehaviors::Info info {
.behaviorId = behaviorId,
.characterId = character->GetID(),
.behaviorInfo = behaviorStr
};
Database::Get()->AddBehavior(info);
}
const auto position = entity->GetPosition();
const auto rotation = entity->GetRotation();
@@ -635,10 +671,13 @@ void PropertyManagementComponent::Save() {
model.position = position;
model.rotation = rotation;
model.ugcId = 0;
for (auto i = 0; i < model.behaviors.size(); i++) {
model.behaviors[i] = modelBehaviors[i].first;
}
Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_" + std::to_string(model.lot) + "_name");
} else {
Database::Get()->UpdateModelPositionRotation(id, position, rotation);
Database::Get()->UpdateModel(id, position, rotation, modelBehaviors);
}
}

View File

@@ -25,6 +25,7 @@
#include "LeaderboardManager.h"
#include "dZoneManager.h"
#include "CDActivitiesTable.h"
#include "eStateChangeType.h"
#include <ctime>
#ifndef M_PI
@@ -44,7 +45,6 @@ RacingControlComponent::RacingControlComponent(Entity* parent)
m_LoadedPlayers = 0;
m_LoadTimer = 0;
m_Finished = 0;
m_StartTime = 0;
m_EmptyTimer = 0;
m_SoloRacing = Game::config->GetValue("solo_racing") == "1";
@@ -77,6 +77,9 @@ void RacingControlComponent::OnPlayerLoaded(Entity* player) {
m_LoadedPlayers++;
// not live accurate to stun the player but prevents them from using skills during the race that are not meant to be used.
GameMessages::SendSetStunned(player->GetObjectID(), eStateChangeType::PUSH, player->GetSystemAddress(), LWOOBJID_EMPTY, true, true, true, true, true, true, true, true, true);
LOG("Loading player %i",
m_LoadedPlayers);
m_LobbyPlayers.push_back(player->GetObjectID());
@@ -394,25 +397,6 @@ void RacingControlComponent::HandleMessageBoxResponse(Entity* player, int32_t bu
GameMessages::SendNotifyRacingClient(
m_Parent->GetObjectID(), 2, 0, LWOOBJID_EMPTY, u"",
player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent == nullptr) return;
missionComponent->Progress(eMissionTaskType::RACING, 0, static_cast<LWOOBJID>(eRacingTaskParam::COMPETED_IN_RACE)); // Progress task for competing in a race
missionComponent->Progress(eMissionTaskType::RACING, data->smashedTimes, static_cast<LWOOBJID>(eRacingTaskParam::SAFE_DRIVER)); // Finish a race without being smashed.
// If solo racing is enabled OR if there are 3 players in the race, progress placement tasks.
if (m_SoloRacing || m_LoadedPlayers > 2) {
missionComponent->Progress(eMissionTaskType::RACING, data->finished, static_cast<LWOOBJID>(eRacingTaskParam::FINISH_WITH_PLACEMENT)); // Finish in 1st place on a race
if (data->finished == 1) {
missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast<LWOOBJID>(eRacingTaskParam::FIRST_PLACE_MULTIPLE_TRACKS)); // Finish in 1st place on multiple tracks.
missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast<LWOOBJID>(eRacingTaskParam::WIN_RACE_IN_WORLD)); // Finished first place in specific world.
}
if (data->finished == m_LoadedPlayers) {
missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast<LWOOBJID>(eRacingTaskParam::LAST_PLACE_FINISH)); // Finished first place in specific world.
}
}
} else if ((id == "ACT_RACE_EXIT_THE_RACE?" || id == "Exit") && button == m_ActivityExitConfirm) {
auto* vehicle = Game::entityManager->GetEntity(data->vehicleID);
@@ -442,9 +426,9 @@ void RacingControlComponent::Serialize(RakNet::BitStream& outBitStream, bool bIs
outBitStream.Write(player.playerID);
outBitStream.Write(player.data[0]);
if (player.finished != 0) outBitStream.Write<float>(player.raceTime);
if (player.finished != 0) outBitStream.Write<float>(player.raceTime.count() / 1000.0f);
else outBitStream.Write(player.data[1]);
if (player.finished != 0) outBitStream.Write<float>(player.bestLapTime);
if (player.finished != 0) outBitStream.Write<float>(player.bestLapTime.count() / 1000.0f);
else outBitStream.Write(player.data[2]);
if (player.finished == 1) outBitStream.Write<float>(1.0f);
else outBitStream.Write(player.data[3]);
@@ -505,8 +489,8 @@ void RacingControlComponent::Serialize(RakNet::BitStream& outBitStream, bool bIs
if (player.finished == 0) continue;
outBitStream.Write1(); // Has more data
outBitStream.Write(player.playerID);
outBitStream.Write<float>(player.bestLapTime);
outBitStream.Write<float>(player.raceTime);
outBitStream.Write<float>(player.bestLapTime.count() / 1000.0f);
outBitStream.Write<float>(player.raceTime.count() / 1000.0f);
}
outBitStream.Write0(); // No more data
@@ -736,7 +720,7 @@ void RacingControlComponent::Update(float deltaTime) {
Game::entityManager->SerializeEntity(m_Parent);
m_StartTime = std::time(nullptr);
m_StartTime = std::chrono::high_resolution_clock::now();
}
m_StartTimer += deltaTime;
@@ -817,52 +801,68 @@ void RacingControlComponent::Update(float deltaTime) {
// Some offset up to make they don't fall through the terrain on a
// respawn, seems to fix itself to the track anyhow
player.respawnPosition = position + NiPoint3Constant::UNIT_Y * 5;
player.respawnRotation = vehicle->GetRotation();
if (waypoint.racing.isResetNode) {
player.respawnPosition = position + NiPoint3Constant::UNIT_Y * 5;
player.respawnRotation = vehicle->GetRotation();
}
player.respawnIndex = respawnIndex;
// Reached the start point, lapped
if (respawnIndex == 0) {
time_t lapTime = std::time(nullptr) - (player.lap == 0 ? m_StartTime : player.lapTime);
const auto now = std::chrono::high_resolution_clock::now();
const auto lapTime = std::chrono::duration_cast<std::chrono::milliseconds>(now - (player.lap == 0 ? m_StartTime : player.lapTime));
// Cheating check
if (lapTime < 40) {
if (lapTime.count() < 40000) {
continue;
}
player.lap++;
player.lapTime = now;
player.lapTime = std::time(nullptr);
if (player.bestLapTime == 0 || player.bestLapTime > lapTime) {
if (player.bestLapTime > lapTime || player.lap == 0) {
player.bestLapTime = lapTime;
LOG("Best lap time (%llu)", lapTime);
}
player.lap++;
auto* missionComponent =
playerEntity->GetComponent<MissionComponent>();
if (missionComponent != nullptr) {
// Progress lap time tasks
missionComponent->Progress(eMissionTaskType::RACING, (lapTime) * 1000, static_cast<LWOOBJID>(eRacingTaskParam::LAP_TIME));
missionComponent->Progress(eMissionTaskType::RACING, lapTime.count(), static_cast<LWOOBJID>(eRacingTaskParam::LAP_TIME));
if (player.lap == 3) {
m_Finished++;
player.finished = m_Finished;
const auto raceTime =
(std::time(nullptr) - m_StartTime);
const auto raceTime = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_StartTime);
player.raceTime = raceTime;
LOG("Completed time %llu, %llu",
raceTime, raceTime * 1000);
LOG("Completed time %llums %fs", raceTime.count(), raceTime.count() / 1000.0f);
LeaderboardManager::SaveScore(playerEntity->GetObjectID(), m_ActivityID, static_cast<float>(player.raceTime), static_cast<float>(player.bestLapTime), static_cast<float>(player.finished == 1));
LeaderboardManager::SaveScore(playerEntity->GetObjectID(), m_ActivityID, static_cast<float>(player.raceTime.count()) / 1000, static_cast<float>(player.bestLapTime.count()) / 1000, static_cast<float>(player.finished == 1));
// Entire race time
missionComponent->Progress(eMissionTaskType::RACING, (raceTime) * 1000, static_cast<LWOOBJID>(eRacingTaskParam::TOTAL_TRACK_TIME));
missionComponent->Progress(eMissionTaskType::RACING, player.raceTime.count(), static_cast<LWOOBJID>(eRacingTaskParam::TOTAL_TRACK_TIME));
missionComponent->Progress(eMissionTaskType::RACING, 0, static_cast<LWOOBJID>(eRacingTaskParam::COMPETED_IN_RACE)); // Progress task for competing in a race
missionComponent->Progress(eMissionTaskType::RACING, player.smashedTimes, static_cast<LWOOBJID>(eRacingTaskParam::SAFE_DRIVER)); // Finish a race without being smashed.
// If solo racing is enabled OR if there are 3 players in the race, progress placement tasks.
if (m_SoloRacing || m_RacingPlayers.size() > 2) {
missionComponent->Progress(eMissionTaskType::RACING, player.finished, static_cast<LWOOBJID>(eRacingTaskParam::FINISH_WITH_PLACEMENT)); // Finish in 1st place on a race
if (player.finished == 1) {
missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast<LWOOBJID>(eRacingTaskParam::FIRST_PLACE_MULTIPLE_TRACKS)); // Finish in 1st place on multiple tracks.
missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast<LWOOBJID>(eRacingTaskParam::WIN_RACE_IN_WORLD)); // Finished first place in specific world.
}
if (player.finished == m_RacingPlayers.size()) {
missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast<LWOOBJID>(eRacingTaskParam::LAST_PLACE_FINISH)); // Finished first place in specific world.
}
}
auto* characterComponent = playerEntity->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) {
@@ -871,8 +871,8 @@ void RacingControlComponent::Update(float deltaTime) {
}
}
LOG("Lapped (%i) in (%llu)", player.lap,
lapTime);
LOG("Lapped (%i) in (%llums %fs)", player.lap,
lapTime.count(), lapTime.count() / 1000.0f);
}
LOG("Reached point (%i)/(%i)", player.respawnIndex,

View File

@@ -8,6 +8,7 @@
#include "Entity.h"
#include "Component.h"
#include "eReplicaComponentType.h"
#include <chrono>
/**
* Information for each player in the race
@@ -72,12 +73,12 @@ struct RacingPlayerInfo {
/**
* The fastest lap time of the player
*/
time_t bestLapTime = 0;
std::chrono::milliseconds bestLapTime;
/**
* The current lap time of the player
*/
time_t lapTime = 0;
std::chrono::high_resolution_clock::time_point lapTime;
/**
* The number of times this player smashed their car
@@ -97,7 +98,7 @@ struct RacingPlayerInfo {
/**
* Unused
*/
time_t raceTime = 0;
std::chrono::milliseconds raceTime;
};
/**
@@ -231,7 +232,7 @@ private:
/**
* The time the race was started
*/
time_t m_StartTime;
std::chrono::high_resolution_clock::time_point m_StartTime;
/**
* Timer for tracking how long a player was alone in this race

View File

@@ -117,7 +117,7 @@ void RenderComponent::PlayEffect(const int32_t effectId, const std::u16string& e
auto result = query.execQuery();
if (result.eof() || result.fieldIsNull(0)) {
if (result.eof() || result.fieldIsNull("animation_length")) {
result.finalize();
m_DurationCache[effectId] = 0;
@@ -127,7 +127,7 @@ void RenderComponent::PlayEffect(const int32_t effectId, const std::u16string& e
return;
}
effect.time = static_cast<float>(result.getFloatField(0));
effect.time = static_cast<float>(result.getFloatField("animation_length"));
result.finalize();

View File

@@ -1,16 +1,57 @@
/*
* Darkflame Universe
* Copyright 2023
*/
// Darkflame Universe
// Copyright 2024
#include "RigidbodyPhantomPhysicsComponent.h"
#include "Entity.h"
#include "dpEntity.h"
#include "CDComponentsRegistryTable.h"
#include "CDPhysicsComponentTable.h"
#include "dpWorld.h"
#include "dpShapeBox.h"
#include "dpShapeSphere.h"
#include"EntityInfo.h"
RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent) : PhysicsComponent(parent) {
m_Position = m_Parent->GetDefaultPosition();
m_Rotation = m_Parent->GetDefaultRotation();
m_Scale = m_Parent->GetDefaultScale();
if (m_Parent->GetVar<bool>(u"create_physics")) {
m_dpEntity = CreatePhysicsLnv(m_Scale, ComponentType);
if (!m_dpEntity) {
m_dpEntity = CreatePhysicsEntity(ComponentType);
if (!m_dpEntity) return;
m_dpEntity->SetScale(m_Scale);
m_dpEntity->SetRotation(m_Rotation);
m_dpEntity->SetPosition(m_Position);
dpWorld::AddEntity(m_dpEntity);
}
}
}
void RigidbodyPhantomPhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate);
}
void RigidbodyPhantomPhysicsComponent::Update(const float deltaTime) {
if (!m_dpEntity) return;
//Process enter events
for (const auto id : m_dpEntity->GetNewObjects()) {
m_Parent->OnCollisionPhantom(id);
}
//Process exit events
for (const auto id : m_dpEntity->GetRemovedObjects()) {
m_Parent->OnCollisionLeavePhantom(id);
}
}
void RigidbodyPhantomPhysicsComponent::SpawnVertices() const {
if (!m_dpEntity) {
LOG("No dpEntity to spawn vertices for %llu:%i", m_Parent->GetObjectID(), m_Parent->GetLOT());
return;
}
PhysicsComponent::SpawnVertices(m_dpEntity);
}

View File

@@ -1,10 +1,8 @@
/*
* Darkflame Universe
* Copyright 2023
*/
// Darkflame Universe
// Copyright 2024
#ifndef __RIGIDBODYPHANTOMPHYSICS_H__
#define __RIGIDBODYPHANTOMPHYSICS_H__
#ifndef RIGIDBODYPHANTOMPHYSICS_H
#define RIGIDBODYPHANTOMPHYSICS_H
#include "BitStream.h"
#include "dCommonVars.h"
@@ -13,6 +11,8 @@
#include "PhysicsComponent.h"
#include "eReplicaComponentType.h"
class dpEntity;
/**
* Component that handles rigid bodies that can be interacted with, mostly client-side rendered. An example is the
* bananas that fall from trees in GF.
@@ -23,7 +23,15 @@ public:
RigidbodyPhantomPhysicsComponent(Entity* parent);
void Update(const float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void SpawnVertices() const;
private:
float m_Scale{};
dpEntity* m_dpEntity{};
};
#endif // __RIGIDBODYPHANTOMPHYSICS_H__
#endif // RIGIDBODYPHANTOMPHYSICS_H

View File

@@ -27,12 +27,12 @@ RocketLaunchpadControlComponent::RocketLaunchpadControlComponent(Entity* parent,
auto result = query.execQuery();
if (!result.eof() && !result.fieldIsNull(0)) {
m_TargetZone = result.getIntField(0);
m_DefaultZone = result.getIntField(1);
m_TargetScene = result.getStringField(2);
m_AltPrecondition = new PreconditionExpression(result.getStringField(3));
m_AltLandingScene = result.getStringField(4);
if (!result.eof() && !result.fieldIsNull("targetZone")) {
m_TargetZone = result.getIntField("targetZone");
m_DefaultZone = result.getIntField("defaultZoneID");
m_TargetScene = result.getStringField("targetScene");
m_AltPrecondition = new PreconditionExpression(result.getStringField("altLandingPrecondition"));
m_AltLandingScene = result.getStringField("altLandingSpawnPointName");
}
result.finalize();

View File

@@ -38,7 +38,7 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s
context->skillID = skillID;
this->m_managedBehaviors.insert_or_assign(skillUid, context);
this->m_managedBehaviors.insert({ skillUid, context });
auto* behavior = Behavior::CreateBehavior(behaviorId);
@@ -52,17 +52,24 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s
}
void SkillComponent::SyncPlayerSkill(const uint32_t skillUid, const uint32_t syncId, RakNet::BitStream& bitStream) {
const auto index = this->m_managedBehaviors.find(skillUid);
const auto index = this->m_managedBehaviors.equal_range(skillUid);
if (index == this->m_managedBehaviors.end()) {
if (index.first == this->m_managedBehaviors.end()) {
LOG("Failed to find skill with uid (%i)!", skillUid, syncId);
return;
}
auto* context = index->second;
bool foundSyncId = false;
for (auto it = index.first; it != index.second && !foundSyncId; ++it) {
const auto& context = it->second;
context->SyncBehavior(syncId, bitStream);
foundSyncId = context->SyncBehavior(syncId, bitStream);
}
if (!foundSyncId) {
LOG("Failed to find sync id (%i) for skill with uid (%i)!", syncId, skillUid);
}
}
@@ -99,7 +106,7 @@ void SkillComponent::SyncPlayerProjectile(const LWOOBJID projectileId, RakNet::B
return;
}
const auto behavior_id = static_cast<uint32_t>(result.getIntField(0));
const auto behavior_id = static_cast<uint32_t>(result.getIntField("behaviorID"));
result.finalize();
@@ -138,7 +145,7 @@ void SkillComponent::Update(const float deltaTime) {
for (const auto& pair : this->m_managedBehaviors) pair.second->UpdatePlayerSyncs(deltaTime);
}
std::map<uint32_t, BehaviorContext*> keep{};
std::multimap<uint32_t, BehaviorContext*> keep{};
for (const auto& pair : this->m_managedBehaviors) {
auto* context = pair.second;
@@ -176,7 +183,7 @@ void SkillComponent::Update(const float deltaTime) {
}
}
keep.insert_or_assign(pair.first, context);
keep.insert({ pair.first, context });
}
this->m_managedBehaviors = keep;
@@ -285,7 +292,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior(
return { false, 0 };
}
this->m_managedBehaviors.insert_or_assign(context->skillUId, context);
this->m_managedBehaviors.insert({ context->skillUId, context });
if (!clientInitalized) {
// Echo start skill
@@ -425,7 +432,7 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry)
return;
}
const auto behaviorId = static_cast<uint32_t>(result.getIntField(0));
const auto behaviorId = static_cast<uint32_t>(result.getIntField("behaviorID"));
result.finalize();

View File

@@ -188,7 +188,7 @@ private:
/**
* All of the active skills mapped by their unique ID.
*/
std::map<uint32_t, BehaviorContext*> m_managedBehaviors;
std::multimap<uint32_t, BehaviorContext*> m_managedBehaviors;
/**
* All active projectiles.

View File

@@ -76,8 +76,8 @@ void VendorComponent::RefreshInventory(bool isCreation) {
if (vendorItems.empty()) break;
auto randomItemIndex = GeneralUtils::GenerateRandomNumber<int32_t>(0, vendorItems.size() - 1);
const auto& randomItem = vendorItems.at(randomItemIndex);
vendorItems.erase(vendorItems.begin() + randomItemIndex);
if (SetupItem(randomItem.itemid)) m_Inventory.push_back(SoldItem(randomItem.itemid, randomItem.sortPriority));
vendorItems.erase(vendorItems.begin() + randomItemIndex);
}
}
}

View File

@@ -685,6 +685,13 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
case eGameMessageType::REQUEST_VENDOR_STATUS_UPDATE:
GameMessages::SendVendorStatusUpdate(entity, sysAddr, true);
break;
case eGameMessageType::UPDATE_INVENTORY_GROUP:
GameMessages::HandleUpdateInventoryGroup(inStream, entity, sysAddr);
break;
case eGameMessageType::UPDATE_INVENTORY_GROUP_CONTENTS:
GameMessages::HandleUpdateInventoryGroupContents(inStream, entity, sysAddr);
break;
default:
LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());
break;

View File

@@ -99,9 +99,11 @@
#include "ActivityManager.h"
#include "PlayerManager.h"
#include "eVendorTransactionResult.h"
#include "eReponseMoveItemBetweenInventoryTypeCode.h"
#include "CDComponentsRegistryTable.h"
#include "CDObjectsTable.h"
#include "eItemType.h"
void GameMessages::SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender) {
CBITSTREAM;
@@ -369,8 +371,8 @@ void GameMessages::SendPlatformResync(Entity* entity, const SystemAddress& sysAd
const auto lot = entity->GetLOT();
if (lot == 12341 || lot == 5027 || lot == 5028 || lot == 14335 || lot == 14447 || lot == 14449) {
iDesiredWaypointIndex = 0;
if (lot == 12341 || lot == 5027 || lot == 5028 || lot == 14335 || lot == 14447 || lot == 14449 || lot == 11306 || lot == 11308) {
iDesiredWaypointIndex = (lot == 11306 || lot == 11308) ? 1 : 0;
iIndex = 0;
nextIndex = 0;
bStopAtDesiredWaypoint = true;
@@ -412,7 +414,8 @@ void GameMessages::SendPlatformResync(Entity* entity, const SystemAddress& sysAd
bitStream.Write(qUnexpectedRotation.w);
}
SEND_PACKET_BROADCAST;
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
SEND_PACKET;
}
void GameMessages::SendRestoreToPostLoadStats(Entity* entity, const SystemAddress& sysAddr) {
@@ -4563,16 +4566,31 @@ void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream&
if (inStream.ReadBit()) inStream.Read(itemLOT);
if (invTypeDst == invTypeSrc) {
SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC);
return;
}
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent != nullptr) {
if (inventoryComponent) {
if (itemID != LWOOBJID_EMPTY) {
auto* item = inventoryComponent->FindItemById(itemID);
if (!item) return;
if (!item) {
SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_ITEM_NOT_FOUND);
return;
}
if (item->GetLot() == 6086) { // Thinking hat
SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_CANT_MOVE_THINKING_HAT);
return;
}
auto* destInv = inventoryComponent->GetInventory(invTypeDst);
if (destInv && destInv->GetEmptySlots() == 0) {
SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_INV_FULL);
return;
}
// Despawn the pet if we are moving that pet to the vault.
auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID());
@@ -4581,10 +4599,32 @@ void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream&
}
inventoryComponent->MoveItemToInventory(item, invTypeDst, iStackCount, showFlyingLoot, false, false, destSlot);
SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::SUCCESS);
}
} else {
SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC);
}
}
void GameMessages::SendResponseMoveItemBetweenInventoryTypes(LWOOBJID objectId, const SystemAddress& sysAddr, eInventoryType inventoryTypeDestination, eInventoryType inventoryTypeSource, eReponseMoveItemBetweenInventoryTypeCode response) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(objectId);
bitStream.Write(eGameMessageType::RESPONSE_MOVE_ITEM_BETWEEN_INVENTORY_TYPES);
bitStream.Write(inventoryTypeDestination != eInventoryType::ITEMS);
if (inventoryTypeDestination != eInventoryType::ITEMS) bitStream.Write(inventoryTypeDestination);
bitStream.Write(inventoryTypeSource != eInventoryType::ITEMS);
if (inventoryTypeSource != eInventoryType::ITEMS) bitStream.Write(inventoryTypeSource);
bitStream.Write(response != eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC);
if (response != eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC) bitStream.Write(response);
SEND_PACKET;
}
void GameMessages::SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr) {
CBITSTREAM;
@@ -5351,17 +5391,18 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, En
iStackCount = std::min<uint32_t>(item->GetCount(), iStackCount);
if (bConfirmed) {
if (eInvType == eInventoryType::MODELS) {
const auto itemType = static_cast<eItemType>(item->GetInfo().itemType);
if (itemType == eItemType::MODEL || itemType == eItemType::LOOT_MODEL) {
item->DisassembleModel(iStackCount);
}
auto lot = item->GetLot();
item->SetCount(item->GetCount() - iStackCount, true);
Game::entityManager->SerializeEntity(entity);
auto* missionComponent = entity->GetComponent<MissionComponent>();
if (missionComponent != nullptr) {
missionComponent->Progress(eMissionTaskType::GATHER, item->GetLot(), LWOOBJID_EMPTY, "", -iStackCount);
missionComponent->Progress(eMissionTaskType::GATHER, lot, LWOOBJID_EMPTY, "", -iStackCount);
}
}
}
@@ -6209,3 +6250,81 @@ void GameMessages::SendSlashCommandFeedbackText(Entity* entity, std::u16string t
auto sysAddr = entity->GetSystemAddress();
SEND_PACKET;
}
void GameMessages::HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
std::string action;
std::u16string groupName;
InventoryComponent::GroupUpdate groupUpdate;
bool locked{}; // All groups are locked by default
uint32_t size{};
if (!inStream.Read(size)) return;
action.resize(size);
if (!inStream.Read(action.data(), size)) return;
if (!inStream.Read(size)) return;
groupUpdate.groupId.resize(size);
if (!inStream.Read(groupUpdate.groupId.data(), size)) return;
if (!inStream.Read(size)) return;
groupName.resize(size);
if (!inStream.Read(reinterpret_cast<char*>(groupName.data()), size * 2)) return;
if (!inStream.Read(groupUpdate.inventory)) return;
if (!inStream.Read(locked)) return;
groupUpdate.groupName = GeneralUtils::UTF16ToWTF8(groupName);
if (action == "ADD") groupUpdate.command = InventoryComponent::GroupUpdateCommand::ADD;
else if (action == "MODIFY") groupUpdate.command = InventoryComponent::GroupUpdateCommand::MODIFY;
else if (action == "REMOVE") groupUpdate.command = InventoryComponent::GroupUpdateCommand::REMOVE;
else {
LOG("Invalid action %s", action.c_str());
return;
}
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent) inventoryComponent->UpdateGroup(groupUpdate);
}
void GameMessages::HandleUpdateInventoryGroupContents(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
std::string action;
InventoryComponent::GroupUpdate groupUpdate;
uint32_t size{};
if (!inStream.Read(size)) return;
action.resize(size);
if (!inStream.Read(action.data(), size)) return;
if (action == "ADD") groupUpdate.command = InventoryComponent::GroupUpdateCommand::ADD_LOT;
else if (action == "REMOVE") groupUpdate.command = InventoryComponent::GroupUpdateCommand::REMOVE_LOT;
else {
LOG("Invalid action %s", action.c_str());
return;
}
if (!inStream.Read(size)) return;
groupUpdate.groupId.resize(size);
if (!inStream.Read(groupUpdate.groupId.data(), size)) return;
if (!inStream.Read(groupUpdate.inventory)) return;
if (!inStream.Read(groupUpdate.lot)) return;
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent) inventoryComponent->UpdateGroup(groupUpdate);
}
void GameMessages::SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(entity->GetObjectID());
bitStream.Write(eGameMessageType::FORCE_CAMERA_TARGET_CYCLE);
bitStream.Write(bForceCycling);
bitStream.Write(cyclingMode != eCameraTargetCyclingMode::ALLOW_CYCLE_TEAMMATES);
if (cyclingMode != eCameraTargetCyclingMode::ALLOW_CYCLE_TEAMMATES) bitStream.Write(cyclingMode);
bitStream.Write(optionalTargetID);
auto sysAddr = entity->GetSystemAddress();
SEND_PACKET;
}

View File

@@ -39,6 +39,12 @@ enum class eQuickBuildFailReason : uint32_t;
enum class eQuickBuildState : uint32_t;
enum class BehaviorSlot : int32_t;
enum class eVendorTransactionResult : uint32_t;
enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t;
enum class eCameraTargetCyclingMode : int32_t {
ALLOW_CYCLE_TEAMMATES,
DISALLOW_CYCLING
};
namespace GameMessages {
class PropertyDataMessage;
@@ -589,6 +595,7 @@ namespace GameMessages {
//NT:
void HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void SendResponseMoveItemBetweenInventoryTypes(LWOOBJID objectId, const SystemAddress& sysAddr, eInventoryType inventoryTypeDestination, eInventoryType inventoryTypeSource, eReponseMoveItemBetweenInventoryTypeCode response);
void SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr);
@@ -666,6 +673,10 @@ namespace GameMessages {
void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity);
void SendSlashCommandFeedbackText(Entity* entity, std::u16string text);
void HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleUpdateInventoryGroupContents(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID);
};
#endif // GAMEMESSAGES_H

View File

@@ -27,6 +27,23 @@
#include "CDComponentsRegistryTable.h"
#include "CDPackageComponentTable.h"
namespace {
const std::map<std::string, std::string> ExtraSettingAbbreviations = {
{ "assemblyPartLOTs", "ma" },
{ "blueprintID", "b" },
{ "userModelID", "ui" },
{ "userModelName", "un" },
{ "userModelDesc", "ud" },
{ "userModelHasBhvr", "ub" },
{ "userModelBehaviors", "ubh" },
{ "userModelBehaviorSourceID", "ubs" },
{ "userModelPhysicsType", "up" },
{ "userModelMod", "um" },
{ "userModelOpt", "uo" },
{ "reforgedLOT", "rl" },
};
}
Item::Item(const LWOOBJID id, const LOT lot, Inventory* inventory, const uint32_t slot, const uint32_t count, const bool bound, const std::vector<LDFBaseData*>& config, const LWOOBJID parent, LWOOBJID subKey, eLootSourceType lootSourceType) {
if (!Inventory::IsValidItem(lot)) {
return;
@@ -122,6 +139,10 @@ uint32_t Item::GetSlot() const {
return slot;
}
std::vector<LDFBaseData*> Item::GetConfig() const {
return config;
}
std::vector<LDFBaseData*>& Item::GetConfig() {
return config;
}
@@ -251,7 +272,7 @@ bool Item::Consume() {
auto skills = skillsTable->Query([this](const CDObjectSkills entry) {
return entry.objectTemplate == static_cast<uint32_t>(lot);
});
});
auto success = false;
@@ -405,18 +426,18 @@ void Item::DisassembleModel(uint32_t numToDismantle) {
auto result = query.execQuery();
if (result.eof() || result.fieldIsNull(0)) {
if (result.eof() || result.fieldIsNull("render_asset")) {
return;
}
std::string renderAsset = std::string(result.getStringField(0));
std::string renderAsset = std::string(result.getStringField("render_asset"));
// normalize path slashes
for (auto& c : renderAsset) {
if (c == '\\') c = '/';
}
std::string lxfmlFolderName = std::string(result.getStringField(1));
std::string lxfmlFolderName = std::string(result.getStringField("LXFMLFolder"));
if (!lxfmlFolderName.empty()) lxfmlFolderName.insert(0, "/");
std::vector<std::string> renderAssetSplit = GeneralUtils::SplitString(renderAsset, '/');
@@ -515,3 +536,35 @@ Item::~Item() {
config.clear();
}
void Item::SaveConfigXml(tinyxml2::XMLElement& i) const {
tinyxml2::XMLElement* x = nullptr;
for (const auto* config : this->config) {
const auto& key = GeneralUtils::UTF16ToWTF8(config->GetKey());
const auto saveKey = ExtraSettingAbbreviations.find(key);
if (saveKey == ExtraSettingAbbreviations.end()) {
continue;
}
if (!x) {
x = i.InsertNewChildElement("x");
}
const auto dataToSave = config->GetString(false);
x->SetAttribute(saveKey->second.c_str(), dataToSave.c_str());
}
}
void Item::LoadConfigXml(const tinyxml2::XMLElement& i) {
const auto* x = i.FirstChildElement("x");
if (!x) return;
for (const auto& pair : ExtraSettingAbbreviations) {
const auto* data = x->Attribute(pair.second.c_str());
if (!data) continue;
const auto value = pair.first + "=" + data;
config.push_back(LDFBaseData::DataFromString(value));
}
}

View File

@@ -9,6 +9,10 @@
#include "eInventoryType.h"
#include "eLootSourceType.h"
namespace tinyxml2 {
class XMLElement;
};
/**
* An item that can be stored in an inventory and optionally consumed or equipped
* TODO: ideally this should be a component
@@ -116,6 +120,12 @@ public:
*/
std::vector<LDFBaseData*>& GetConfig();
/**
* Returns current config info for this item, e.g. for rockets
* @return current config info for this item
*/
std::vector<LDFBaseData*> GetConfig() const;
/**
* Returns the database info for this item
* @return the database info for this item
@@ -214,6 +224,10 @@ public:
*/
void RemoveFromInventory();
void SaveConfigXml(tinyxml2::XMLElement& i) const;
void LoadConfigXml(const tinyxml2::XMLElement& i);
private:
/**
* The object ID of this item

View File

@@ -8,10 +8,13 @@
#include "MissionComponent.h"
#include "eMissionTaskType.h"
#include <algorithm>
#include <array>
#include "CDSkillBehaviorTable.h"
ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) {
using namespace std::string_view_literals;
this->m_ID = id;
this->m_InventoryComponent = inventoryComponent;
@@ -27,14 +30,16 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) {
return;
}
for (auto i = 0; i < 5; ++i) {
if (result.fieldIsNull(i)) {
constexpr std::array rowNames = { "skillSetWith2"sv, "skillSetWith3"sv, "skillSetWith4"sv, "skillSetWith5"sv, "skillSetWith6"sv };
for (auto i = 0; i < rowNames.size(); ++i) {
const auto rowName = rowNames[i];
if (result.fieldIsNull(rowName.data())) {
continue;
}
auto skillQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT SkillID FROM ItemSetSkills WHERE SkillSetID = ?;");
skillQuery.bind(1, result.getIntField(i));
skillQuery.bind(1, result.getIntField(rowName.data()));
auto skillResult = skillQuery.execQuery();
@@ -43,13 +48,13 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) {
}
while (!skillResult.eof()) {
if (skillResult.fieldIsNull(0)) {
if (skillResult.fieldIsNull("SkillID")) {
skillResult.nextRow();
continue;
}
const auto skillId = skillResult.getIntField(0);
const auto skillId = skillResult.getIntField("SkillID");
switch (i) {
case 0:
@@ -75,7 +80,7 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) {
}
}
std::string ids = result.getStringField(5);
std::string ids = result.getStringField("itemIDs");
ids.erase(std::remove_if(ids.begin(), ids.end(), ::isspace), ids.end());

View File

@@ -454,6 +454,16 @@ void Mission::YieldRewards() {
}
}
// Even with no repeatable column, reputation is repeatable
if (info.reward_reputation > 0) {
missionComponent->Progress(eMissionTaskType::EARN_REPUTATION, 0, LWOOBJID_EMPTY, "", info.reward_reputation);
auto* const character = entity->GetComponent<CharacterComponent>();
if (character) {
character->SetReputation(character->GetReputation() + info.reward_reputation);
GameMessages::SendUpdateReputation(entity->GetObjectID(), character->GetReputation(), entity->GetSystemAddress());
}
}
if (m_Completions > 0) {
std::vector<std::pair<LOT, uint32_t>> items;
@@ -532,15 +542,6 @@ void Mission::YieldRewards() {
modelInventory->SetSize(modelInventory->GetSize() + info.reward_bankinventory);
}
if (info.reward_reputation > 0) {
missionComponent->Progress(eMissionTaskType::EARN_REPUTATION, 0, 0L, "", info.reward_reputation);
auto character = entity->GetComponent<CharacterComponent>();
if (character) {
character->SetReputation(character->GetReputation() + info.reward_reputation);
GameMessages::SendUpdateReputation(entity->GetObjectID(), character->GetReputation(), entity->GetSystemAddress());
}
}
if (info.reward_maxhealth > 0) {
destroyableComponent->SetMaxHealth(destroyableComponent->GetMaxHealth() + static_cast<float>(info.reward_maxhealth), true);
}

View File

@@ -186,7 +186,7 @@ void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string&
if (count < 0) {
if (mission->IsMission() && type == eMissionTaskType::GATHER && InAllTargets(value)) {
if (parameters.size() > 0 && (parameters[0] & 1) != 0) {
if (parameters.size() > 0 && (parameters[0] & 4) != 0) {
return;
}

View File

@@ -1,6 +1,8 @@
#include "Action.h"
#include "Amf3.h"
#include "tinyxml2.h"
Action::Action(const AMFArrayValue& arguments) {
for (const auto& [paramName, paramValue] : arguments.GetAssociative()) {
if (paramName == "Type") {
@@ -32,3 +34,46 @@ void Action::SendBehaviorBlocksToClient(AMFArrayValue& args) const {
actionArgs->Insert(m_ValueParameterName, m_ValueParameterDouble);
}
}
void Action::Serialize(tinyxml2::XMLElement& action) const {
action.SetAttribute("Type", m_Type.c_str());
if (m_ValueParameterName.empty()) return;
action.SetAttribute("ValueParameterName", m_ValueParameterName.c_str());
if (m_ValueParameterName == "Message") {
action.SetAttribute("Value", m_ValueParameterString.c_str());
} else {
action.SetAttribute("Value", m_ValueParameterDouble);
}
}
void Action::Deserialize(const tinyxml2::XMLElement& action) {
const char* type = nullptr;
action.QueryAttribute("Type", &type);
if (!type) {
LOG("No type found for an action?");
return;
}
m_Type = type;
const char* valueParameterName = nullptr;
action.QueryAttribute("ValueParameterName", &valueParameterName);
if (valueParameterName) {
m_ValueParameterName = valueParameterName;
if (m_ValueParameterName == "Message") {
const char* value = nullptr;
action.QueryAttribute("Value", &value);
if (value) {
m_ValueParameterString = value;
} else {
LOG("No value found for an action message?");
}
} else {
action.QueryDoubleAttribute("Value", &m_ValueParameterDouble);
}
}
}

View File

@@ -3,6 +3,10 @@
#include <string>
namespace tinyxml2 {
class XMLElement;
};
class AMFArrayValue;
/**
@@ -20,6 +24,8 @@ public:
void SendBehaviorBlocksToClient(AMFArrayValue& args) const;
void Serialize(tinyxml2::XMLElement& action) const;
void Deserialize(const tinyxml2::XMLElement& action);
private:
double m_ValueParameterDouble{ 0.0 };
std::string m_Type{ "" };

View File

@@ -1,6 +1,7 @@
#include "StripUiPosition.h"
#include "Amf3.h"
#include "tinyxml2.h"
StripUiPosition::StripUiPosition(const AMFArrayValue& arguments, const std::string& uiKeyName) {
const auto* const uiArray = arguments.GetArray(uiKeyName);
@@ -21,3 +22,13 @@ void StripUiPosition::SendBehaviorBlocksToClient(AMFArrayValue& args) const {
uiArgs->Insert("x", m_XPosition);
uiArgs->Insert("y", m_YPosition);
}
void StripUiPosition::Serialize(tinyxml2::XMLElement& position) const {
position.SetAttribute("x", m_XPosition);
position.SetAttribute("y", m_YPosition);
}
void StripUiPosition::Deserialize(const tinyxml2::XMLElement& position) {
position.QueryDoubleAttribute("x", &m_XPosition);
position.QueryDoubleAttribute("y", &m_YPosition);
}

View File

@@ -3,6 +3,10 @@
class AMFArrayValue;
namespace tinyxml2 {
class XMLElement;
}
/**
* @brief The position of the first Action in a Strip
*
@@ -15,6 +19,8 @@ public:
[[nodiscard]] double GetX() const noexcept { return m_XPosition; }
[[nodiscard]] double GetY() const noexcept { return m_YPosition; }
void Serialize(tinyxml2::XMLElement& position) const;
void Deserialize(const tinyxml2::XMLElement& position);
private:
double m_XPosition{ 0.0 };
double m_YPosition{ 0.0 };

View File

@@ -3,6 +3,7 @@
#include "Amf3.h"
#include "BehaviorStates.h"
#include "ControlBehaviorMsgs.h"
#include "tinyxml2.h"
PropertyBehavior::PropertyBehavior() {
m_LastEditedState = BehaviorState::HOME_STATE;
@@ -124,3 +125,31 @@ void PropertyBehavior::SendBehaviorBlocksToClient(AMFArrayValue& args) const {
// TODO Serialize the execution state of the behavior
}
void PropertyBehavior::Serialize(tinyxml2::XMLElement& behavior) const {
behavior.SetAttribute("id", m_BehaviorId);
behavior.SetAttribute("name", m_Name.c_str());
behavior.SetAttribute("isLocked", isLocked);
behavior.SetAttribute("isLoot", isLoot);
for (const auto& [stateId, state] : m_States) {
if (state.IsEmpty()) continue;
auto* const stateElement = behavior.InsertNewChildElement("State");
stateElement->SetAttribute("id", static_cast<uint32_t>(stateId));
state.Serialize(*stateElement);
}
}
void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) {
m_Name = behavior.Attribute("name");
behavior.QueryBoolAttribute("isLocked", &isLocked);
behavior.QueryBoolAttribute("isLoot", &isLoot);
for (const auto* stateElement = behavior.FirstChildElement("State"); stateElement; stateElement = stateElement->NextSiblingElement("State")) {
int32_t stateId = -1;
stateElement->QueryIntAttribute("id", &stateId);
if (stateId < 0 || stateId > 5) continue;
m_States[static_cast<BehaviorState>(stateId)].Deserialize(*stateElement);
}
}

View File

@@ -3,6 +3,10 @@
#include "State.h"
namespace tinyxml2 {
class XMLElement;
}
enum class BehaviorState : uint32_t;
class AMFArrayValue;
@@ -25,6 +29,8 @@ public:
[[nodiscard]] int32_t GetBehaviorId() const noexcept { return m_BehaviorId; }
void SetBehaviorId(int32_t id) noexcept { m_BehaviorId = id; }
void Serialize(tinyxml2::XMLElement& behavior) const;
void Deserialize(const tinyxml2::XMLElement& behavior);
private:
// The states this behavior has.

View File

@@ -2,6 +2,7 @@
#include "Amf3.h"
#include "ControlBehaviorMsgs.h"
#include "tinyxml2.h"
template <>
void State::HandleMsg(AddStripMessage& msg) {
@@ -134,4 +135,20 @@ void State::SendBehaviorBlocksToClient(AMFArrayValue& args) const {
strip.SendBehaviorBlocksToClient(*stripArgs);
}
};
}
void State::Serialize(tinyxml2::XMLElement& state) const {
for (const auto& strip : m_Strips) {
if (strip.IsEmpty()) continue;
auto* const stripElement = state.InsertNewChildElement("Strip");
strip.Serialize(*stripElement);
}
}
void State::Deserialize(const tinyxml2::XMLElement& state) {
for (const auto* stripElement = state.FirstChildElement("Strip"); stripElement; stripElement = stripElement->NextSiblingElement("Strip")) {
auto& strip = m_Strips.emplace_back();
strip.Deserialize(*stripElement);
}
}

View File

@@ -3,6 +3,10 @@
#include "Strip.h"
namespace tinyxml2 {
class XMLElement;
}
class AMFArrayValue;
class State {
@@ -13,6 +17,8 @@ public:
void SendBehaviorBlocksToClient(AMFArrayValue& args) const;
bool IsEmpty() const;
void Serialize(tinyxml2::XMLElement& state) const;
void Deserialize(const tinyxml2::XMLElement& state);
private:
std::vector<Strip> m_Strips;
};

View File

@@ -2,6 +2,7 @@
#include "Amf3.h"
#include "ControlBehaviorMsgs.h"
#include "tinyxml2.h"
template <>
void Strip::HandleMsg(AddStripMessage& msg) {
@@ -83,4 +84,25 @@ void Strip::SendBehaviorBlocksToClient(AMFArrayValue& args) const {
for (const auto& action : m_Actions) {
action.SendBehaviorBlocksToClient(*actions);
}
};
}
void Strip::Serialize(tinyxml2::XMLElement& strip) const {
auto* const positionElement = strip.InsertNewChildElement("Position");
m_Position.Serialize(*positionElement);
for (const auto& action : m_Actions) {
auto* const actionElement = strip.InsertNewChildElement("Action");
action.Serialize(*actionElement);
}
}
void Strip::Deserialize(const tinyxml2::XMLElement& strip) {
const auto* positionElement = strip.FirstChildElement("Position");
if (positionElement) {
m_Position.Deserialize(*positionElement);
}
for (const auto* actionElement = strip.FirstChildElement("Action"); actionElement; actionElement = actionElement->NextSiblingElement("Action")) {
auto& action = m_Actions.emplace_back();
action.Deserialize(*actionElement);
}
}

View File

@@ -6,6 +6,10 @@
#include <vector>
namespace tinyxml2 {
class XMLElement;
}
class AMFArrayValue;
class Strip {
@@ -16,6 +20,8 @@ public:
void SendBehaviorBlocksToClient(AMFArrayValue& args) const;
bool IsEmpty() const noexcept { return m_Actions.empty(); }
void Serialize(tinyxml2::XMLElement& strip) const;
void Deserialize(const tinyxml2::XMLElement& strip);
private:
std::vector<Action> m_Actions;
StripUiPosition m_Position;

View File

@@ -130,12 +130,6 @@ bool CheatDetection::VerifyLwoobjidIsSender(const LWOOBJID& id, const SystemAddr
// This will be true if the player does not possess the entity they are trying to send a packet as.
// or if the user does not own the character they are trying to send a packet as.
if (invalidPacket) {
va_list args;
va_start(args, messageIfNotSender);
LogAndSaveFailedAntiCheatCheck(id, sysAddr, checkType, messageIfNotSender, args);
va_end(args);
}
return !invalidPacket;
}

View File

@@ -94,35 +94,6 @@ void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJ
SendNotification(sysAddr, 1); //Show the "one new mail" message
}
//Because we need it:
std::string ReadWStringAsString(RakNet::BitStream& bitStream, uint32_t size) {
std::string toReturn = "";
uint8_t buffer;
bool isFinishedReading = false;
for (uint32_t i = 0; i < size; ++i) {
bitStream.Read(buffer);
if (!isFinishedReading) toReturn.push_back(buffer);
if (buffer == '\0') isFinishedReading = true; //so we don't continue to read garbage as part of the string.
bitStream.Read(buffer); //Read the null term
}
return toReturn;
}
void WriteStringAsWString(RakNet::BitStream& bitStream, std::string str, uint32_t size) {
uint32_t sizeToFill = size - str.size();
for (uint32_t i = 0; i < str.size(); ++i) {
bitStream.Write(str[i]);
bitStream.Write(uint8_t(0));
}
for (uint32_t i = 0; i < sizeToFill; ++i) {
bitStream.Write(uint16_t(0));
}
}
void Mail::HandleMailStuff(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* entity) {
int mailStuffID = 0;
packet.Read(mailStuffID);
@@ -176,11 +147,20 @@ void Mail::HandleSendMail(RakNet::BitStream& packet, const SystemAddress& sysAdd
return;
}
std::string subject = ReadWStringAsString(packet, 50);
std::string body = ReadWStringAsString(packet, 400);
std::string recipient = ReadWStringAsString(packet, 32);
LUWString subjectRead(50);
packet.Read(subjectRead);
LUWString bodyRead(400);
packet.Read(bodyRead);
LUWString recipientRead(32);
packet.Read(recipientRead);
const std::string subject = subjectRead.GetAsString();
const std::string body = bodyRead.GetAsString();
//Cleanse recipient:
recipient = std::regex_replace(recipient, std::regex("[^0-9a-zA-Z]+"), "");
const std::string recipient = std::regex_replace(recipientRead.GetAsString(), std::regex("[^0-9a-zA-Z]+"), "");
uint64_t unknown64 = 0;
LWOOBJID attachmentID;
@@ -267,40 +247,44 @@ void Mail::HandleDataRequest(RakNet::BitStream& packet, const SystemAddress& sys
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::MAIL);
bitStream.Write(int(MailMessageID::MailData));
bitStream.Write(int(0));
bitStream.Write(int(0)); // throttled
bitStream.Write<uint16_t>(playerMail.size());
bitStream.Write<uint16_t>(playerMail.size()); // size
bitStream.Write<uint16_t>(0);
for (const auto& mail : playerMail) {
bitStream.Write(mail.id); //MailID
WriteStringAsWString(bitStream, mail.subject.c_str(), 50); //subject
WriteStringAsWString(bitStream, mail.body.c_str(), 400); //body
WriteStringAsWString(bitStream, mail.senderUsername.c_str(), 32); //sender
const LUWString subject(mail.subject, 50);
bitStream.Write(subject); //subject
const LUWString body(mail.body, 400);
bitStream.Write(body); //body
const LUWString sender(mail.senderUsername, 32);
bitStream.Write(sender); //sender
bitStream.Write(uint32_t(0)); // packing
bitStream.Write(uint32_t(0));
bitStream.Write(uint64_t(0));
bitStream.Write(uint64_t(0)); // attachedCurrency
bitStream.Write(mail.itemID); //Attachment ID
LOT lot = mail.itemLOT;
if (lot <= 0) bitStream.Write(LOT(-1));
else bitStream.Write(lot);
bitStream.Write(uint32_t(0));
bitStream.Write(uint32_t(0)); // packing
bitStream.Write(mail.itemSubkey); //Attachment subKey
bitStream.Write<uint16_t>(mail.itemCount); //Attachment count
bitStream.Write(mail.itemSubkey); // Attachment subKey
bitStream.Write(uint32_t(0));
bitStream.Write(uint16_t(0));
bitStream.Write<uint16_t>(mail.itemCount); // Attachment count
bitStream.Write(uint8_t(0)); // subject type (used for auction)
bitStream.Write(uint8_t(0)); // packing
bitStream.Write(uint32_t(0)); // packing
bitStream.Write<uint64_t>(mail.timeSent); //time sent (twice?)
bitStream.Write<uint64_t>(mail.timeSent);
bitStream.Write<uint64_t>(mail.timeSent); // expiration date
bitStream.Write<uint64_t>(mail.timeSent);// send date
bitStream.Write<uint8_t>(mail.wasRead); //was read
bitStream.Write(uint8_t(0));
bitStream.Write(uint16_t(0));
bitStream.Write(uint32_t(0));
bitStream.Write(uint8_t(0)); // isLocalized
bitStream.Write(uint16_t(0)); // packing
bitStream.Write(uint32_t(0)); // packing
}
Game::server->Send(bitStream, sysAddr, false);

View File

@@ -26,17 +26,18 @@ Precondition::Precondition(const uint32_t condition) {
if (result.eof()) {
this->type = PreconditionType::ItemEquipped;
this->count = 1;
this->values = { 0 };
this->values.clear();
this->values.push_back(0);
LOG("Failed to find precondition of id (%i)!", condition);
return;
}
this->type = static_cast<PreconditionType>(result.fieldIsNull(0) ? 0 : result.getIntField(0));
this->type = static_cast<PreconditionType>(result.fieldIsNull("type") ? 0 : result.getIntField("type"));
if (!result.fieldIsNull(1)) {
std::istringstream stream(result.getStringField(1));
if (!result.fieldIsNull("targetLOT")) {
std::istringstream stream(result.getStringField("targetLOT"));
std::string token;
while (std::getline(stream, token, ',')) {
@@ -45,7 +46,7 @@ Precondition::Precondition(const uint32_t condition) {
}
}
this->count = result.fieldIsNull(2) ? 1 : result.getIntField(2);
this->count = result.fieldIsNull("targetCount") ? 1 : result.getIntField("targetCount");
result.finalize();
}
@@ -138,21 +139,20 @@ bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluat
case PreconditionType::DoesNotHaveItem:
return inventoryComponent->IsEquipped(value) < count;
case PreconditionType::HasAchievement:
mission = missionComponent->GetMission(value);
return mission == nullptr || mission->GetMissionState() >= eMissionState::COMPLETE;
if (missionComponent == nullptr) return false;
return missionComponent->GetMissionState(value) >= eMissionState::COMPLETE;
case PreconditionType::MissionAvailable:
mission = missionComponent->GetMission(value);
return mission == nullptr || mission->GetMissionState() >= eMissionState::AVAILABLE;
if (missionComponent == nullptr) return false;
return missionComponent->GetMissionState(value) == eMissionState::AVAILABLE || missionComponent->GetMissionState(value) == eMissionState::COMPLETE_AVAILABLE;
case PreconditionType::OnMission:
mission = missionComponent->GetMission(value);
return mission == nullptr || mission->GetMissionState() >= eMissionState::ACTIVE;
if (missionComponent == nullptr) return false;
return missionComponent->GetMissionState(value) == eMissionState::ACTIVE ||
missionComponent->GetMissionState(value) == eMissionState::COMPLETE_ACTIVE ||
missionComponent->GetMissionState(value) == eMissionState::READY_TO_COMPLETE ||
missionComponent->GetMissionState(value) == eMissionState::COMPLETE_READY_TO_COMPLETE;
case PreconditionType::MissionComplete:
mission = missionComponent->GetMission(value);
return mission == nullptr ? false : mission->GetMissionState() >= eMissionState::COMPLETE;
if (missionComponent == nullptr) return false;
return missionComponent->GetMissionState(value) >= eMissionState::COMPLETE;
case PreconditionType::PetDeployed:
return false; // TODO
case PreconditionType::HasFlag:

View File

@@ -8,6 +8,7 @@
#include "SlashCommandHandler.h"
#include <iomanip>
#include <ranges>
#include "DEVGMCommands.h"
#include "GMGreaterThanZeroCommands.h"
@@ -19,7 +20,7 @@
#include "dServer.h"
namespace {
std::vector<Command> CommandInfos;
std::map<std::string, Command> CommandInfos;
std::map<std::string, Command> RegisteredCommands;
}
@@ -30,7 +31,6 @@ void SlashCommandHandler::RegisterCommand(Command command) {
}
for (const auto& alias : command.aliases) {
LOG_DEBUG("Registering command %s", alias.c_str());
auto [_, success] = RegisteredCommands.try_emplace(alias, command);
// Don't allow duplicate commands
if (!success) {
@@ -38,9 +38,8 @@ void SlashCommandHandler::RegisterCommand(Command command) {
continue;
}
}
CommandInfos.push_back(command);
};
CommandInfos[command.aliases[0]] = command;
}
void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* entity, const SystemAddress& sysAddr) {
auto input = GeneralUtils::UTF16ToWTF8(chat);
@@ -61,11 +60,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity*
if (commandHandle.requiredLevel > eGameMasterLevel::CIVILIAN) Database::Get()->InsertSlashCommandUsage(entity->GetObjectID(), input);
commandHandle.handle(entity, sysAddr, args);
} else if (entity->GetGMLevel() != eGameMasterLevel::CIVILIAN) {
// We don't need to tell civilians they aren't high enough level
error = "You are not high enough GM level to use \"" + command + "\"";
}
} else if (entity->GetGMLevel() == eGameMasterLevel::CIVILIAN) {
// We don't need to tell civilians commands don't exist
error = "Command " + command + " does not exist!";
}
@@ -74,41 +71,76 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity*
}
}
// This commands in here so we can access the CommandInfos to display info
void GMZeroCommands::Help(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
std::ostringstream feedback;
if (args.empty()) {
feedback << "----- Commands -----";
for (const auto& command : CommandInfos) {
// TODO: Limit displaying commands based on GM level they require
if (command.requiredLevel > entity->GetGMLevel()) continue;
LOG("Help command: %s", command.aliases[0].c_str());
feedback << "\n/" << command.aliases[0] << ": " << command.help;
constexpr size_t pageSize = 10;
std::string trimmedArgs = args;
trimmedArgs.erase(trimmedArgs.begin(), std::find_if_not(trimmedArgs.begin(), trimmedArgs.end(), [](unsigned char ch) {
return std::isspace(ch);
}));
trimmedArgs.erase(std::find_if_not(trimmedArgs.rbegin(), trimmedArgs.rend(), [](unsigned char ch) {
return std::isspace(ch);
}).base(), trimmedArgs.end());
std::optional<uint32_t> parsedPage = GeneralUtils::TryParse<uint32_t>(trimmedArgs);
if (trimmedArgs.empty() || parsedPage.has_value()) {
size_t page = parsedPage.value_or(1);
std::map<std::string, Command> accessibleCommands;
for (const auto& [commandName, command] : CommandInfos) {
if (command.requiredLevel <= entity->GetGMLevel()) {
accessibleCommands.emplace(commandName, command);
}
}
} else {
bool foundCommand = false;
for (const auto& command : CommandInfos) {
if (std::ranges::find(command.aliases, args) == command.aliases.end()) continue;
if (entity->GetGMLevel() < command.requiredLevel) break;
foundCommand = true;
feedback << "----- " << command.aliases.at(0) << " -----\n";
// info can be a localizable string
feedback << command.info;
if (command.aliases.size() == 1) break;
size_t totalPages = (accessibleCommands.size() + pageSize - 1) / pageSize;
feedback << "\nAliases: ";
for (size_t i = 0; i < command.aliases.size(); i++) {
if (page < 1 || page > totalPages) {
feedback << "Invalid page number. Total pages: " << totalPages;
GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedback.str()));
return;
}
auto it = accessibleCommands.begin();
std::advance(it, (page - 1) * pageSize);
size_t endIdx = std::min(page * pageSize, accessibleCommands.size());
feedback << "----- Commands (Page " << page << " of " << totalPages << ") -----";
for (size_t i = (page - 1) * pageSize; i < endIdx; ++i, ++it) {
feedback << "\n/" << it->first << ": " << it->second.help;
}
const auto feedbackStr = feedback.str();
if (!feedbackStr.empty()) {
GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr));
}
return;
}
auto it = std::ranges::find_if(CommandInfos, [&trimmedArgs](const auto& pair) {
return std::ranges::find(pair.second.aliases, trimmedArgs) != pair.second.aliases.end();
});
if (it != CommandInfos.end() && entity->GetGMLevel() >= it->second.requiredLevel) {
const auto& command = it->second;
feedback << "----- " << it->first << " Info -----\n";
feedback << command.info << "\n";
if (command.aliases.size() > 1) {
feedback << "Aliases: ";
for (size_t i = 0; i < command.aliases.size(); ++i) {
if (i > 0) feedback << ", ";
feedback << command.aliases[i];
}
}
// Let GameMasters know if the command doesn't exist
if (!foundCommand && entity->GetGMLevel() > eGameMasterLevel::CIVILIAN) feedback << "Command " << std::quoted(args) << " does not exist!";
} else {
feedback << "Command not found.";
}
const auto feedbackStr = feedback.str();
if (!feedbackStr.empty()) GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr));
if (!feedbackStr.empty()) {
GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr));
}
}
void SlashCommandHandler::SendAnnouncement(const std::string& title, const std::string& message) {
@@ -878,11 +910,38 @@ void SlashCommandHandler::Startup() {
};
RegisterCommand(TitleCommand);
Command ShowAllCommand{
.help = "Show all online players across World Servers",
.info = "Usage: /showall (displayZoneData: Default 1) (displayIndividualPlayers: Default 1)",
.aliases = { "showall" },
.handle = GMGreaterThanZeroCommands::ShowAll,
.requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR
};
RegisterCommand(ShowAllCommand);
Command FindPlayerCommand{
.help = "Find the World Server a player is in if they are online",
.info = "Find the World Server a player is in if they are online",
.aliases = { "findplayer" },
.handle = GMGreaterThanZeroCommands::FindPlayer,
.requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR
};
RegisterCommand(FindPlayerCommand);
Command SpectateCommand{
.help = "Spectate a player",
.info = "Specify a player name to spectate. They must be in the same world as you. Leave blank to stop spectating",
.aliases = { "spectate", "follow" },
.handle = GMGreaterThanZeroCommands::Spectate,
.requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR
};
RegisterCommand(SpectateCommand);
// Register GM Zero Commands
Command HelpCommand{
.help = "Display command info",
.info = "If a command is given, display detailed info on that command. Otherwise display a list of commands with short desctiptions.",
.info = "If a command is given, display detailed info on that command. Otherwise display a list of commands with short descriptions.",
.aliases = { "help", "h"},
.handle = GMZeroCommands::Help,
.requiredLevel = eGameMasterLevel::CIVILIAN
@@ -997,4 +1056,383 @@ void SlashCommandHandler::Startup() {
};
RegisterCommand(InstanceInfoCommand);
//Commands that are handled by the client
Command faqCommand{
.help = "Show the LU FAQ Page",
.info = "Show the LU FAQ Page",
.aliases = {"faq","faqs"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(faqCommand);
Command teamChatCommand{
.help = "Send a message to your teammates.",
.info = "Send a message to your teammates.",
.aliases = {"team","t"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(teamChatCommand);
Command showStoreCommand{
.help = "Show the LEGO shop page.",
.info = "Show the LEGO shop page.",
.aliases = {"shop","store"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(showStoreCommand);
Command minigamesCommand{
.help = "Show the LEGO minigames page!",
.info = "Show the LEGO minigames page!",
.aliases = {"minigames"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(minigamesCommand);
Command forumsCommand{
.help = "Show the LU Forums!",
.info = "Show the LU Forums!",
.aliases = {"forums"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(forumsCommand);
Command exitGameCommand{
.help = "Exit to desktop",
.info = "Exit to desktop",
.aliases = {"exit","quit"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(exitGameCommand);
Command thumbsUpCommand{
.help = "Oh, yeah!",
.info = "Oh, yeah!",
.aliases = {"thumb","thumbs","thumbsup"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(thumbsUpCommand);
Command victoryCommand{
.help = "Victory!",
.info = "Victory!",
.aliases = {"victory!"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(victoryCommand);
Command backflipCommand{
.help = "Do a flip!",
.info = "Do a flip!",
.aliases = {"backflip"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(backflipCommand);
Command clapCommand{
.help = "A round of applause!",
.info = "A round of applause!",
.aliases = {"clap"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(clapCommand);
Command logoutCharacterCommand{
.help = "Returns you to the character select screen.",
.info = "Returns you to the character select screen.",
.aliases = {"camp","logoutcharacter"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(logoutCharacterCommand);
Command sayCommand{
.help = "Say something outloud so that everyone can hear you",
.info = "Say something outloud so that everyone can hear you",
.aliases = {"s","say"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(sayCommand);
Command whisperCommand{
.help = "Send a private message to another player.",
.info = "Send a private message to another player.",
.aliases = {"tell","w","whisper"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(whisperCommand);
Command locationCommand{
.help = "Output your current location on the map to the chat box.",
.info = "Output your current location on the map to the chat box.",
.aliases = {"loc","locate","location"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(locationCommand);
Command logoutCommand{
.help = "Returns you to the login screen.",
.info = "Returns you to the login screen.",
.aliases = {"logout","logoutaccount"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(logoutCommand);
Command shrugCommand{
.help = "I dunno...",
.info = "I dunno...",
.aliases = {"shrug"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(shrugCommand);
Command leaveTeamCommand{
.help = "Leave your current team.",
.info = "Leave your current team.",
.aliases = {"leave","leaveteam","teamleave","tleave"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(leaveTeamCommand);
Command teamLootTypeCommand{
.help = "[rr|ffa] Set the loot for your current team (round-robin/free for all).",
.info = "[rr|ffa] Set the loot for your current team (round-robin/free for all).",
.aliases = {"setloot","teamsetloot","tloot","tsetloot"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(teamLootTypeCommand);
Command removeFriendCommand{
.help = "[name] Removes a player from your friends list.",
.info = "[name] Removes a player from your friends list.",
.aliases = {"removefriend"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(removeFriendCommand);
Command yesCommand{
.help = "Aye aye, captain!",
.info = "Aye aye, captain!",
.aliases = {"yes"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(yesCommand);
Command teamInviteCommand{
.help = "[name] Invite a player to your team.",
.info = "[name] Invite a player to your team.",
.aliases = {"invite","inviteteam","teaminvite","tinvite"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(teamInviteCommand);
Command danceCommand{
.help = "Dance 'til you can't dance no more.",
.info = "Dance 'til you can't dance no more.",
.aliases = {"dance"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(danceCommand);
Command sighCommand{
.help = "Another day, another brick.",
.info = "Another day, another brick.",
.aliases = {"sigh"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(sighCommand);
Command recommendedOptionsCommand{
.help = "Sets the recommended performance options in the cfg file",
.info = "Sets the recommended performance options in the cfg file",
.aliases = {"recommendedperfoptions"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(recommendedOptionsCommand);
Command setTeamLeaderCommand{
.help = "[name] Set the leader for your current team.",
.info = "[name] Set the leader for your current team.",
.aliases = {"leader","setleader","teamsetleader","tleader","tsetleader"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(setTeamLeaderCommand);
Command cringeCommand{
.help = "I don't even want to talk about it...",
.info = "I don't even want to talk about it...",
.aliases = {"cringe"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(cringeCommand);
Command talkCommand{
.help = "Jibber Jabber",
.info = "Jibber Jabber",
.aliases = {"talk"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(talkCommand);
Command cancelQueueCommand{
.help = "Cancel Your position in the queue if you are in one.",
.info = "Cancel Your position in the queue if you are in one.",
.aliases = {"cancelqueue"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(cancelQueueCommand);
Command lowPerformanceCommand{
.help = "Sets the default low-spec performance options in the cfg file",
.info = "Sets the default low-spec performance options in the cfg file",
.aliases = {"perfoptionslow"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(lowPerformanceCommand);
Command kickFromTeamCommand{
.help = "[name] Kick a player from your current team.",
.info = "[name] Kick a player from your current team.",
.aliases = {"kick","kickplayer","teamkickplayer","tkick","tkickplayer"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(kickFromTeamCommand);
Command thanksCommand{
.help = "Express your gratitude for another.",
.info = "Express your gratitude for another.",
.aliases = {"thanks"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(thanksCommand);
Command waveCommand{
.help = "Wave to other players.",
.info = "Wave to other players.",
.aliases = {"wave"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(waveCommand);
Command whyCommand{
.help = "Why|!?!!",
.info = "Why|!?!!",
.aliases = {"why"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(whyCommand);
Command midPerformanceCommand{
.help = "Sets the default medium-spec performance options in the cfg file",
.info = "Sets the default medium-spec performance options in the cfg file",
.aliases = {"perfoptionsmid"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(midPerformanceCommand);
Command highPerformanceCommand{
.help = "Sets the default high-spec performance options in the cfg file",
.info = "Sets the default high-spec performance options in the cfg file",
.aliases = {"perfoptionshigh"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(highPerformanceCommand);
Command gaspCommand{
.help = "Oh my goodness!",
.info = "Oh my goodness!",
.aliases = {"gasp"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(gaspCommand);
Command ignoreCommand{
.help = "[name] Add a player to your ignore list.",
.info = "[name] Add a player to your ignore list.",
.aliases = {"addignore"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(ignoreCommand);
Command addFriendCommand{
.help = "[name] Add a player to your friends list.",
.info = "[name] Add a player to your friends list.",
.aliases = {"addfriend"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(addFriendCommand);
Command cryCommand{
.help = "Show everyone your 'Aw' face.",
.info = "Show everyone your 'Aw' face.",
.aliases = {"cry"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(cryCommand);
Command giggleCommand{
.help = "A good little chuckle",
.info = "A good little chuckle",
.aliases = {"giggle"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(giggleCommand);
Command saluteCommand{
.help = "For those about to build...",
.info = "For those about to build...",
.aliases = {"salute"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(saluteCommand);
Command removeIgnoreCommand{
.help = "[name] Removes a player from your ignore list.",
.info = "[name] Removes a player from your ignore list.",
.aliases = {"removeIgnore"},
.handle = GMZeroCommands::ClientHandled,
.requiredLevel = eGameMasterLevel::CIVILIAN
};
RegisterCommand(removeIgnoreCommand);
}

View File

@@ -41,6 +41,7 @@
#include "ScriptedActivityComponent.h"
#include "SkillComponent.h"
#include "TriggerComponent.h"
#include "RigidbodyPhantomPhysicsComponent.h"
// Enums
#include "eGameMasterLevel.h"
@@ -704,7 +705,7 @@ namespace DEVGMCommands {
auto tables = query.execQuery();
while (!tables.eof()) {
std::string message = std::to_string(tables.getIntField(0)) + " - " + tables.getStringField(1);
std::string message = std::to_string(tables.getIntField("id")) + " - " + tables.getStringField("name");
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(message, message.size()));
tables.nextRow();
}
@@ -1129,8 +1130,13 @@ namespace DEVGMCommands {
void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
//Go tell physics to spawn all the vertices:
auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PHANTOM_PHYSICS);
for (auto en : entities) {
auto phys = static_cast<PhantomPhysicsComponent*>(en->GetComponent(eReplicaComponentType::PHANTOM_PHYSICS));
for (const auto* en : entities) {
const auto* phys = static_cast<PhantomPhysicsComponent*>(en->GetComponent(eReplicaComponentType::PHANTOM_PHYSICS));
if (phys)
phys->SpawnVertices();
}
for (const auto* en : Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS)) {
const auto* phys = en->GetComponent<RigidbodyPhantomPhysicsComponent>();
if (phys)
phys->SpawnVertices();
}

View File

@@ -1,3 +1,6 @@
#ifndef DEVGMCOMMANDS_H
#define DEVGMCOMMANDS_H
namespace DEVGMCommands {
void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args);
@@ -70,4 +73,6 @@ namespace DEVGMCommands {
void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args);
}
}
#endif //!DEVGMCOMMANDS_H

View File

@@ -287,4 +287,54 @@ namespace GMGreaterThanZeroCommands {
std::string name = entity->GetCharacter()->GetName() + " - " + args;
GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS);
}
void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
bool displayZoneData = true;
bool displayIndividualPlayers = true;
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (!splitArgs.empty() && !splitArgs.at(0).empty()) displayZoneData = splitArgs.at(0) == "1";
if (splitArgs.size() > 1) displayIndividualPlayers = splitArgs.at(1) == "1";
ShowAllRequest request {
.requestor = entity->GetObjectID(),
.displayZoneData = displayZoneData,
.displayIndividualPlayers = displayIndividualPlayers
};
CBITSTREAM;
request.Serialize(bitStream);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
}
void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
if (args.empty()) {
GameMessages::SendSlashCommandFeedbackText(entity, u"No player Given");
return;
}
FindPlayerRequest request {
.requestor = entity->GetObjectID(),
.playerName = LUWString(args)
};
CBITSTREAM;
request.Serialize(bitStream);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
}
void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
if (args.empty()) {
GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, entity->GetObjectID());
return;
}
auto player = PlayerManager::GetPlayer(args);
if (!player) {
GameMessages::SendSlashCommandFeedbackText(entity, u"Player not found");
return;
}
GameMessages::SendSlashCommandFeedbackText(entity, u"Spectating Player");
GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, player->GetObjectID());
}
}

View File

@@ -1,3 +1,6 @@
#ifndef GMGREATERTHANZEROCOMMANDS_H
#define GMGREATERTHANZEROCOMMANDS_H
namespace GMGreaterThanZeroCommands {
void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args);
@@ -10,4 +13,9 @@ namespace GMGreaterThanZeroCommands {
void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args);
}
void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args);
}
#endif //!GMGREATERTHANZEROCOMMANDS_H

View File

@@ -224,5 +224,9 @@ namespace GMZeroCommands {
ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID())));
}
//For client side commands
void ClientHandled(Entity* entity, const SystemAddress& sysAddr, const std::string args) {}
};

View File

@@ -1,3 +1,6 @@
#ifndef GMZEROCOMMANDS_H
#define GMZEROCOMMANDS_H
namespace GMZeroCommands {
void Help(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void Credits(Entity* entity, const SystemAddress& sysAddr, const std::string args);
@@ -12,4 +15,7 @@ namespace GMZeroCommands {
void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void ClientHandled(Entity* entity, const SystemAddress& sysAddr, const std::string args);
}
#endif //!GMZEROCOMMANDS_H

View File

@@ -285,7 +285,6 @@ void ParseXml(const std::string& file) {
}
if (zoneID.value() != currentZoneID) {
LOG_DEBUG("Skipping (%s) %i location because it is in %i and not the current zone (%i)", name, lot, zoneID.value(), currentZoneID);
continue;
}