mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-28 00:17:23 +00:00
Grim LU
Wincent's attempt at making LU into something it isn't supposed to be, an ARPG.
This commit is contained in:
parent
0d6bd33f9e
commit
5973430720
@ -162,6 +162,7 @@ set(INCLUDED_DIRECTORIES
|
||||
"dGame/dInventory"
|
||||
"dGame/dMission"
|
||||
"dGame/dEntity"
|
||||
"dGame/dGrim"
|
||||
"dGame/dPropertyBehaviors"
|
||||
"dGame/dPropertyBehaviors/ControlBehaviorMessages"
|
||||
"dGame/dUtilities"
|
||||
@ -312,13 +313,13 @@ file(
|
||||
)
|
||||
|
||||
# Add our library subdirectories for creation of the library object
|
||||
add_subdirectory(dZoneManager)
|
||||
add_subdirectory(dCommon)
|
||||
add_subdirectory(dDatabase)
|
||||
add_subdirectory(dChatFilter)
|
||||
add_subdirectory(dNet)
|
||||
add_subdirectory(dScripts) # Add for dGame to use
|
||||
add_subdirectory(dGame)
|
||||
add_subdirectory(dZoneManager)
|
||||
add_subdirectory(dNavigation)
|
||||
add_subdirectory(dPhysics)
|
||||
|
||||
@ -346,10 +347,10 @@ target_precompile_headers(
|
||||
${HEADERS_DGAME}
|
||||
)
|
||||
|
||||
target_precompile_headers(
|
||||
dZoneManager PRIVATE
|
||||
${HEADERS_DZONEMANAGER}
|
||||
)
|
||||
#target_precompile_headers(
|
||||
# dZoneManager PRIVATE
|
||||
# ${HEADERS_DZONEMANAGER}
|
||||
#)
|
||||
|
||||
# Need to specify to use the CXX compiler language here or else we get errors including <string>.
|
||||
target_precompile_headers(
|
||||
|
16
dCommon/dEnums/eSkillBar.h
Normal file
16
dCommon/dEnums/eSkillBar.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __ESKILLBAR_H__
|
||||
#define __ESKILLBAR_H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class eSkillBar : int32_t {
|
||||
Primary,
|
||||
Gear,
|
||||
ClassPrimary,
|
||||
ClassSecondary,
|
||||
Consumable
|
||||
};
|
||||
|
||||
#endif // __ESKILLBAR_H__
|
61
dDatabase/Tables/CDLookupTable.cpp
Normal file
61
dDatabase/Tables/CDLookupTable.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include "CDLookupTable.h"
|
||||
|
||||
CDLookupTable::CDLookupTable(void) {
|
||||
|
||||
// First, get the size of the table
|
||||
uint32_t size = 0;
|
||||
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM lookupTable");
|
||||
while (!tableSize.eof()) {
|
||||
size = tableSize.getIntField(0, 0);
|
||||
|
||||
tableSize.nextRow();
|
||||
}
|
||||
|
||||
tableSize.finalize();
|
||||
|
||||
// Reserve the size
|
||||
this->entries.reserve(size);
|
||||
|
||||
// Now get the data
|
||||
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM lookupTable");
|
||||
while (!tableData.eof()) {
|
||||
std::string key = tableData.getStringField("id", "");
|
||||
int32_t value = tableData.getIntField("value", -1);
|
||||
|
||||
this->entries.emplace(key, value);
|
||||
this->reverseEntries.emplace(value, key);
|
||||
|
||||
tableData.nextRow();
|
||||
}
|
||||
|
||||
tableData.finalize();
|
||||
}
|
||||
|
||||
int32_t CDLookupTable::Lookup(const std::string& key) const {
|
||||
// If the key starts with 'lego-universe:', parse what comes
|
||||
// after the ':' as an integer and return that.
|
||||
if (key.find("lego-universe:") == 0) {
|
||||
return std::stoi(key.substr(14));
|
||||
}
|
||||
|
||||
auto it = this->entries.find(key);
|
||||
if (it != this->entries.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<std::string> CDLookupTable::ReverseLookup(int32_t value) const {
|
||||
auto range = this->reverseEntries.equal_range(value);
|
||||
std::vector<std::string> keys;
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
keys.push_back(it->second);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
int32_t rose::id(const std::string& key) {
|
||||
return CDLookupTable::Instance().Lookup(key);
|
||||
}
|
23
dDatabase/Tables/CDLookupTable.h
Normal file
23
dDatabase/Tables/CDLookupTable.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
// Custom Classes
|
||||
#include "CDTable.h"
|
||||
|
||||
class CDLookupTable : public CDTable<CDLookupTable> {
|
||||
private:
|
||||
std::unordered_map<std::string, int32_t> entries;
|
||||
std::unordered_multimap<int32_t, std::string> reverseEntries;
|
||||
|
||||
public:
|
||||
CDLookupTable();
|
||||
|
||||
// Lookup
|
||||
int32_t Lookup(const std::string& key) const;
|
||||
|
||||
// Reverse Lookup
|
||||
std::vector<std::string> ReverseLookup(int32_t value) const;
|
||||
};
|
||||
|
||||
namespace rose {
|
||||
int32_t id(const std::string& key);
|
||||
}
|
@ -35,4 +35,5 @@ set(DDATABASE_TABLES_SOURCES "CDActivitiesTable.cpp"
|
||||
"CDScriptComponentTable.cpp"
|
||||
"CDSkillBehaviorTable.cpp"
|
||||
"CDVendorComponentTable.cpp"
|
||||
"CDLookupTable.cpp"
|
||||
"CDZoneTableTable.cpp" PARENT_SCOPE)
|
||||
|
@ -56,10 +56,21 @@ foreach(file ${DGAME_DUTILITIES_SOURCES})
|
||||
set(DGAME_SOURCES ${DGAME_SOURCES} "dUtilities/${file}")
|
||||
endforeach()
|
||||
|
||||
add_subdirectory(dGrim)
|
||||
|
||||
foreach(file ${DGAME_DGRIM_SOURCES})
|
||||
set(DGAME_SOURCES ${DGAME_SOURCES} "dGrim/${file}")
|
||||
endforeach()
|
||||
|
||||
foreach(file ${DSCRIPTS_SOURCES})
|
||||
set(DGAME_SOURCES ${DGAME_SOURCES} "${PROJECT_SOURCE_DIR}/dScripts/${file}")
|
||||
endforeach()
|
||||
|
||||
foreach(file ${DZONEMANAGER_SOURCES})
|
||||
message("Adding ${file}")
|
||||
set(DGAME_SOURCES ${DGAME_SOURCES} "${PROJECT_SOURCE_DIR}/dZoneManager/${file}")
|
||||
endforeach()
|
||||
|
||||
add_library(dGame STATIC ${DGAME_SOURCES})
|
||||
|
||||
target_link_libraries(dGame dDatabase Recast Detour)
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "eMissionTaskType.h"
|
||||
#include "eTriggerEventType.h"
|
||||
#include "eObjectBits.h"
|
||||
#include "EntityProfile.h"
|
||||
|
||||
//Component includes:
|
||||
#include "Component.h"
|
||||
@ -361,6 +362,18 @@ void Entity::Initialize() {
|
||||
destCompData[0].imagination = 60;
|
||||
}
|
||||
|
||||
auto& info = destCompData[0];
|
||||
|
||||
auto* entityProfile = EntityProfile::FindEntityProfile(m_TemplateID);
|
||||
|
||||
if (entityProfile != nullptr) {
|
||||
if (entityProfile->GetLevel() > 0) info.level = entityProfile->GetLevel();
|
||||
if (entityProfile->GetHealth() > 0) info.life = entityProfile->GetHealth();
|
||||
if (entityProfile->GetArmor() > 0) info.armor = entityProfile->GetArmor();
|
||||
}
|
||||
|
||||
comp->SetInfo(destCompData[0]);
|
||||
|
||||
comp->SetHealth(destCompData[0].life);
|
||||
comp->SetImagination(destCompData[0].imagination);
|
||||
comp->SetArmor(destCompData[0].armor);
|
||||
@ -373,6 +386,15 @@ void Entity::Initialize() {
|
||||
|
||||
comp->SetLootMatrixID(destCompData[0].LootMatrixIndex);
|
||||
|
||||
comp->ComputeBaseStats(true);
|
||||
|
||||
if (true)
|
||||
{
|
||||
comp->SetHealth(comp->GetMaxHealth());
|
||||
comp->SetImagination(comp->GetMaxImagination());
|
||||
comp->SetArmor(comp->GetMaxArmor());
|
||||
}
|
||||
|
||||
// Now get currency information
|
||||
uint32_t npcMinLevel = destCompData[0].level;
|
||||
uint32_t currencyIndex = destCompData[0].CurrencyIndex;
|
||||
|
@ -110,7 +110,7 @@ void Player::SendToZone(LWOMAPID zoneId, LWOCLONEID cloneId) {
|
||||
|
||||
EntityManager::Instance()->DestructEntity(entity);
|
||||
return;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void Player::AddLimboConstruction(LWOOBJID objectId) {
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "DestroyableComponent.h"
|
||||
#include "BehaviorContext.h"
|
||||
#include "eBasicAttackSuccessTypes.h"
|
||||
#include "DamageProfile.h"
|
||||
|
||||
void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
if (context->unmanaged) {
|
||||
@ -101,6 +102,17 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit
|
||||
totalDamageDealt = this->m_MinDamage;
|
||||
}
|
||||
|
||||
auto* source = EntityManager::Instance()->GetEntity(context->originator);
|
||||
|
||||
auto* damageProfile = DamageProfile::FindDamageProfile(context->skillID);
|
||||
|
||||
auto damageMap = destroyableComponent->ComputeDamage(totalDamageDealt, source, damageProfile);
|
||||
|
||||
// Apply a standard diviation of (+/-)20%
|
||||
for (auto& damage : damageMap) {
|
||||
damage.second = static_cast<uint32_t>(damage.second * (1.0f + (static_cast<float>(rand() % 40) / 100.0f) - 0.2f));
|
||||
}
|
||||
|
||||
bool died{};
|
||||
if (!bitStream->Read(died)) {
|
||||
Game::logger->Log("BasicAttackBehavior", "Unable to read died");
|
||||
@ -109,7 +121,8 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit
|
||||
auto previousArmor = destroyableComponent->GetArmor();
|
||||
auto previousHealth = destroyableComponent->GetHealth();
|
||||
PlayFx(u"onhit", targetEntity->GetObjectID());
|
||||
destroyableComponent->Damage(totalDamageDealt, context->originator, context->skillID);
|
||||
destroyableComponent->Damage(damageMap, context->originator, context->skillID);
|
||||
//destroyableComponent->Damage(totalDamageDealt, context->originator, context->skillID);
|
||||
}
|
||||
|
||||
uint8_t successState{};
|
||||
@ -191,10 +204,28 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
|
||||
const uint32_t previousHealth = destroyableComponent->GetHealth();
|
||||
const uint32_t previousArmor = destroyableComponent->GetArmor();
|
||||
|
||||
const auto damage = this->m_MinDamage;
|
||||
auto damage = this->m_MinDamage;
|
||||
|
||||
auto* source = EntityManager::Instance()->GetEntity(context->originator);
|
||||
|
||||
auto* damageProfile = DamageProfile::FindDamageProfile(context->skillID);
|
||||
|
||||
if (damageProfile != nullptr) {
|
||||
Game::logger->Log("BasicAttackBehavior", "Found damage profile for skill %i, originator %i", context->skillID, source->GetLOT());
|
||||
}
|
||||
else {
|
||||
Game::logger->Log("BasicAttackBehavior", "No damage profile found for skill %i, originator %i", context->skillID, source->GetLOT());
|
||||
}
|
||||
|
||||
auto damageMap = destroyableComponent->ComputeDamage(damage, source, damageProfile);
|
||||
|
||||
// Apply a standard diviation of (+/-)20%
|
||||
for (auto& damage : damageMap) {
|
||||
damage.second = static_cast<uint32_t>(damage.second * (1.0f + (static_cast<float>(rand() % 40) / 100.0f) - 0.2f));
|
||||
}
|
||||
|
||||
PlayFx(u"onhit", targetEntity->GetObjectID(), 1);
|
||||
destroyableComponent->Damage(damage, context->originator, context->skillID, false);
|
||||
destroyableComponent->Damage(damageMap, context->originator, context->skillID, false);
|
||||
context->ScheduleUpdate(branch.target);
|
||||
|
||||
const uint32_t armorDamageDealt = previousArmor - destroyableComponent->GetArmor();
|
||||
|
@ -361,7 +361,7 @@ void Behavior::PlayFx(std::u16string type, const LWOOBJID target, const LWOOBJID
|
||||
"SELECT effectName FROM BehaviorEffect WHERE effectType = ? AND effectID = ?;");
|
||||
|
||||
auto idQuery = CDClientDatabase::CreatePreppedStmt(
|
||||
"SELECT effectName, effectType FROM BehaviorEffect WHERE effectID = ?;");
|
||||
"SELECT effectName, effectType FROM BehaviorEffect WHERE effectID = ? AND effectType IS NOT NULL;");
|
||||
|
||||
if (!type.empty()) {
|
||||
typeQuery.bind(1, typeString.c_str());
|
||||
|
@ -78,6 +78,8 @@ struct BehaviorContext
|
||||
|
||||
LWOOBJID caster = LWOOBJID_EMPTY;
|
||||
|
||||
LWOOBJID itemID = LWOOBJID_EMPTY;
|
||||
|
||||
uint32_t GetUniqueSkillId() const;
|
||||
|
||||
void UpdatePlayerSyncs(float deltaTime);
|
||||
|
@ -26,9 +26,16 @@ void BuffBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream
|
||||
return;
|
||||
}
|
||||
|
||||
component->AddStat(StatProperty(eStatTypes::Health, eStatModifier::Absolute, this->m_health * BASE_MULTIPLIER));
|
||||
component->AddStat(StatProperty(eStatTypes::Armor, eStatModifier::Absolute, this->m_armor * BASE_MULTIPLIER));
|
||||
//component->AddStat(StatProperty(eStatTypes::Imagination, eStatModifier::Absolute, this->m_imagination * BASE_MULTIPLIER));
|
||||
|
||||
/*
|
||||
component->SetMaxHealth(component->GetMaxHealth() + this->m_health);
|
||||
component->SetMaxArmor(component->GetMaxArmor() + this->m_armor);
|
||||
component->SetMaxImagination(component->GetMaxImagination() + this->m_imagination);
|
||||
*/
|
||||
component->SetMaxImagination(component->GetMaxImagination() + this->m_imagination);
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(entity);
|
||||
|
||||
@ -60,9 +67,16 @@ void BuffBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branch
|
||||
return;
|
||||
}
|
||||
|
||||
component->RemoveStat(StatProperty(eStatTypes::Health, eStatModifier::Absolute, this->m_health * BASE_MULTIPLIER));
|
||||
component->RemoveStat(StatProperty(eStatTypes::Armor, eStatModifier::Absolute, this->m_armor * BASE_MULTIPLIER));
|
||||
//component->RemoveStat(StatProperty(eStatTypes::Imagination, eStatModifier::Absolute, this->m_imagination * BASE_MULTIPLIER));
|
||||
|
||||
/*
|
||||
component->SetMaxHealth(component->GetMaxHealth() - this->m_health);
|
||||
component->SetMaxArmor(component->GetMaxArmor() - this->m_armor);
|
||||
component->SetMaxImagination(component->GetMaxImagination() - this->m_imagination);
|
||||
*/
|
||||
component->SetMaxImagination(component->GetMaxImagination() - this->m_imagination);
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(entity);
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ void DamageAbsorptionBehavior::Handle(BehaviorContext* context, RakNet::BitStrea
|
||||
return;
|
||||
}
|
||||
|
||||
destroyable->SetDamageToAbsorb(static_cast<uint32_t>(destroyable->GetDamageToAbsorb()) + this->m_absorbAmount);
|
||||
destroyable->SetDamageToAbsorb(static_cast<uint32_t>(destroyable->GetDamageToAbsorb()) + this->m_absorbAmount * 50);
|
||||
|
||||
destroyable->SetIsShielded(true);
|
||||
|
||||
@ -50,7 +50,7 @@ void DamageAbsorptionBehavior::Timer(BehaviorContext* context, BehaviorBranchCon
|
||||
|
||||
const auto present = static_cast<uint32_t>(destroyable->GetDamageToAbsorb());
|
||||
|
||||
const auto toRemove = std::min(present, this->m_absorbAmount);
|
||||
const auto toRemove = std::min(present, this->m_absorbAmount * 50);
|
||||
|
||||
destroyable->SetDamageToAbsorb(present - toRemove);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "EntityManager.h"
|
||||
#include "DestroyableComponent.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "LevelProgressionComponent.h"
|
||||
|
||||
|
||||
void HealBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bit_stream, const BehaviorBranchContext branch) {
|
||||
@ -24,7 +25,18 @@ void HealBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bit_strea
|
||||
return;
|
||||
}
|
||||
|
||||
destroyable->Heal(this->m_health);
|
||||
int32_t toApply = this->m_health * 5;
|
||||
|
||||
auto* levelProgressComponent = entity->GetComponent<LevelProgressionComponent>();
|
||||
|
||||
if (levelProgressComponent != nullptr) {
|
||||
toApply *= levelProgressComponent->GetLevel();
|
||||
}
|
||||
|
||||
// Apply a standard deviations of 20%
|
||||
toApply = static_cast<uint32_t>(toApply * (1.0f + (static_cast<float>(rand() % 40) / 100.0f) - 0.2f));
|
||||
|
||||
destroyable->Heal(toApply);
|
||||
}
|
||||
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "dLogger.h"
|
||||
#include "Game.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "LevelProgressionComponent.h"
|
||||
|
||||
void RepairBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bit_stream, const BehaviorBranchContext branch) {
|
||||
auto* entity = EntityManager::Instance()->GetEntity(branch.target);
|
||||
@ -24,7 +25,18 @@ void RepairBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bit_str
|
||||
return;
|
||||
}
|
||||
|
||||
destroyable->Repair(this->m_armor);
|
||||
int32_t toApply = this->m_armor * 5;
|
||||
|
||||
auto* levelProgressComponent = entity->GetComponent<LevelProgressionComponent>();
|
||||
|
||||
if (levelProgressComponent != nullptr) {
|
||||
toApply *= levelProgressComponent->GetLevel();
|
||||
}
|
||||
|
||||
// Apply a standard deviations of 20%
|
||||
toApply = static_cast<uint32_t>(toApply * (1.0f + (static_cast<float>(rand() % 40) / 100.0f) - 0.2f));
|
||||
|
||||
destroyable->Repair(toApply);
|
||||
}
|
||||
|
||||
void RepairBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bit_stream, const BehaviorBranchContext branch) {
|
||||
|
@ -39,7 +39,7 @@ void StunBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream
|
||||
return;
|
||||
}
|
||||
|
||||
combatAiComponent->Stun(branch.duration);
|
||||
combatAiComponent->Stun(branch.duration / 2.0f);
|
||||
}
|
||||
|
||||
void StunBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, const BehaviorBranchContext branch) {
|
||||
|
@ -146,6 +146,12 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
|
||||
//First, we need to process physics:
|
||||
if (!m_dpEntity) return;
|
||||
|
||||
if (m_State == AiState::spawn) {
|
||||
auto* destroyable = m_Parent->GetComponent<DestroyableComponent>();
|
||||
|
||||
destroyable->ComputeBaseStats(true);
|
||||
}
|
||||
|
||||
m_dpEntity->SetPosition(m_Parent->GetPosition()); //make sure our position is synced with our dpEntity
|
||||
m_dpEntityEnemy->SetPosition(m_Parent->GetPosition());
|
||||
|
||||
@ -234,7 +240,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
||||
bool hasSkillToCast = false;
|
||||
for (auto& entry : m_SkillEntries) {
|
||||
if (entry.cooldown > 0.0f) {
|
||||
entry.cooldown -= deltaTime;
|
||||
entry.cooldown -= deltaTime * 2;
|
||||
} else {
|
||||
hasSkillToCast = true;
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ void BuffComponent::ApplyBuffEffect(int32_t id) {
|
||||
|
||||
if (destroyable == nullptr) return;
|
||||
|
||||
destroyable->SetMaxHealth(destroyable->GetMaxHealth() + maxHealth);
|
||||
destroyable->SetMaxHealth(destroyable->GetMaxHealth() + maxHealth * BASE_MULTIPLIER);
|
||||
} else if (parameter.name == "max_armor") {
|
||||
const auto maxArmor = parameter.value;
|
||||
|
||||
@ -161,7 +161,7 @@ void BuffComponent::ApplyBuffEffect(int32_t id) {
|
||||
|
||||
if (destroyable == nullptr) return;
|
||||
|
||||
destroyable->SetMaxArmor(destroyable->GetMaxArmor() + maxArmor);
|
||||
destroyable->SetMaxArmor(destroyable->GetMaxArmor() + maxArmor * BASE_MULTIPLIER);
|
||||
} else if (parameter.name == "max_imagination") {
|
||||
const auto maxImagination = parameter.value;
|
||||
|
||||
@ -169,7 +169,7 @@ void BuffComponent::ApplyBuffEffect(int32_t id) {
|
||||
|
||||
if (destroyable == nullptr) return;
|
||||
|
||||
destroyable->SetMaxImagination(destroyable->GetMaxImagination() + maxImagination);
|
||||
destroyable->SetMaxImagination(destroyable->GetMaxImagination() + maxImagination * BASE_MULTIPLIER);
|
||||
} else if (parameter.name == "speed") {
|
||||
auto* controllablePhysicsComponent = this->GetParent()->GetComponent<ControllablePhysicsComponent>();
|
||||
if (!controllablePhysicsComponent) return;
|
||||
@ -189,7 +189,7 @@ void BuffComponent::RemoveBuffEffect(int32_t id) {
|
||||
|
||||
if (destroyable == nullptr) return;
|
||||
|
||||
destroyable->SetMaxHealth(destroyable->GetMaxHealth() - maxHealth);
|
||||
destroyable->SetMaxHealth(destroyable->GetMaxHealth() - maxHealth * BASE_MULTIPLIER);
|
||||
} else if (parameter.name == "max_armor") {
|
||||
const auto maxArmor = parameter.value;
|
||||
|
||||
@ -197,7 +197,7 @@ void BuffComponent::RemoveBuffEffect(int32_t id) {
|
||||
|
||||
if (destroyable == nullptr) return;
|
||||
|
||||
destroyable->SetMaxArmor(destroyable->GetMaxArmor() - maxArmor);
|
||||
destroyable->SetMaxArmor(destroyable->GetMaxArmor() - maxArmor * BASE_MULTIPLIER);
|
||||
} else if (parameter.name == "max_imagination") {
|
||||
const auto maxImagination = parameter.value;
|
||||
|
||||
@ -205,7 +205,7 @@ void BuffComponent::RemoveBuffEffect(int32_t id) {
|
||||
|
||||
if (destroyable == nullptr) return;
|
||||
|
||||
destroyable->SetMaxImagination(destroyable->GetMaxImagination() - maxImagination);
|
||||
destroyable->SetMaxImagination(destroyable->GetMaxImagination() - maxImagination * BASE_MULTIPLIER);
|
||||
} else if (parameter.name == "speed") {
|
||||
auto* controllablePhysicsComponent = this->GetParent()->GetComponent<ControllablePhysicsComponent>();
|
||||
if (!controllablePhysicsComponent) return;
|
||||
|
@ -35,6 +35,9 @@
|
||||
#include "eMissionTaskType.h"
|
||||
#include "eStateChangeType.h"
|
||||
#include "eGameActivity.h"
|
||||
#include "LevelProgressionComponent.h"
|
||||
#include "ResistanceProfile.h"
|
||||
#include "DamageProfile.h"
|
||||
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
|
||||
@ -62,6 +65,7 @@ DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
|
||||
m_MinCoins = 0;
|
||||
m_MaxCoins = 0;
|
||||
m_DamageReduction = 0;
|
||||
m_DirtyStats = true;
|
||||
|
||||
m_ImmuneToBasicAttackCount = 0;
|
||||
m_ImmuneToDamageOverTimeCount = 0;
|
||||
@ -72,6 +76,8 @@ DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
|
||||
m_ImmuneToImaginationLossCount = 0;
|
||||
m_ImmuneToQuickbuildInterruptCount = 0;
|
||||
m_ImmuneToPullToPointCount = 0;
|
||||
|
||||
m_ResistanceProfile = ResistanceProfile::FindResistanceProfile(m_Parent->GetLOT());
|
||||
}
|
||||
|
||||
DestroyableComponent::~DestroyableComponent() {
|
||||
@ -95,6 +101,8 @@ void DestroyableComponent::Reinitialize(LOT templateID) {
|
||||
if (componentID > 0) {
|
||||
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([=](CDDestructibleComponent entry) { return (entry.id == componentID); });
|
||||
|
||||
m_Info = destCompData[0];
|
||||
|
||||
if (destCompData.size() > 0) {
|
||||
SetHealth(destCompData[0].life);
|
||||
SetImagination(destCompData[0].imagination);
|
||||
@ -107,6 +115,11 @@ void DestroyableComponent::Reinitialize(LOT templateID) {
|
||||
SetIsSmashable(destCompData[0].isSmashable);
|
||||
}
|
||||
} else {
|
||||
m_Info = {};
|
||||
m_Info.life = 1;
|
||||
m_Info.imagination = 0;
|
||||
m_Info.armor = 0;
|
||||
|
||||
SetHealth(1);
|
||||
SetImagination(0);
|
||||
SetArmor(0);
|
||||
@ -117,6 +130,15 @@ void DestroyableComponent::Reinitialize(LOT templateID) {
|
||||
|
||||
SetIsSmashable(true);
|
||||
}
|
||||
|
||||
ComputeBaseStats();
|
||||
|
||||
if (!m_Parent->IsPlayer())
|
||||
{
|
||||
SetHealth(GetMaxHealth());
|
||||
SetImagination(GetMaxImagination());
|
||||
SetArmor(GetMaxArmor());
|
||||
}
|
||||
}
|
||||
|
||||
void DestroyableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, uint32_t& flags) {
|
||||
@ -810,6 +832,18 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Entity*> scripts = EntityManager::Instance()->GetEntitiesByComponent(eReplicaComponentType::SCRIPT);
|
||||
for (Entity* scriptEntity : scripts) {
|
||||
// Prevent double triggering
|
||||
if (scriptEntity->GetObjectID() == zoneControl->GetObjectID()) continue;
|
||||
if (std::find(scriptedActs.begin(), scriptedActs.end(), scriptEntity) != scriptedActs.end()) continue;
|
||||
if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
|
||||
for (CppScripts::Script* script : CppScripts::GetEntityScripts(scriptEntity)) {
|
||||
script->OnPlayerDied(scriptEntity, m_Parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_Parent->Kill(owner);
|
||||
@ -949,6 +983,8 @@ void DestroyableComponent::FixStats() {
|
||||
item->Equip();
|
||||
}
|
||||
|
||||
destroyableComponent->ComputeBaseStats();
|
||||
|
||||
// Fetch correct max stats after everything is done
|
||||
maxHealth = destroyableComponent->GetMaxHealth();
|
||||
maxArmor = destroyableComponent->GetMaxArmor();
|
||||
@ -968,6 +1004,325 @@ void DestroyableComponent::FixStats() {
|
||||
EntityManager::Instance()->SerializeEntity(entity);
|
||||
}
|
||||
|
||||
void DestroyableComponent::AddStat(const StatProperty& stat) {
|
||||
m_DirtyStats = true;
|
||||
|
||||
const auto& typeIter = m_Stats.find(stat.type);
|
||||
|
||||
if (typeIter == m_Stats.end()) {
|
||||
m_Stats.emplace(stat.type, std::map<eStatModifier, float>());
|
||||
}
|
||||
|
||||
auto& typeMap = m_Stats.at(stat.type);
|
||||
|
||||
const auto& modifierIter = typeMap.find(stat.modifier);
|
||||
|
||||
if (modifierIter == typeMap.end()) {
|
||||
typeMap.emplace(stat.modifier, stat.value);
|
||||
return;
|
||||
}
|
||||
|
||||
typeMap.at(stat.modifier) += stat.value;
|
||||
}
|
||||
|
||||
void DestroyableComponent::RemoveStat(const StatProperty& stat) {
|
||||
m_DirtyStats = true;
|
||||
|
||||
const auto& typeIter = m_Stats.find(stat.type);
|
||||
|
||||
if (typeIter == m_Stats.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& typeMap = m_Stats.at(stat.type);
|
||||
|
||||
const auto& modifierIter = typeMap.find(stat.modifier);
|
||||
|
||||
if (modifierIter == typeMap.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
typeMap.at(stat.modifier) -= stat.value;
|
||||
|
||||
if (typeMap.at(stat.modifier) <= 0) {
|
||||
typeMap.erase(stat.modifier);
|
||||
}
|
||||
|
||||
if (typeMap.empty()) {
|
||||
m_Stats.erase(stat.type);
|
||||
}
|
||||
}
|
||||
|
||||
float DestroyableComponent::GetStat(eStatTypes statType, eStatModifier statModifier) const {
|
||||
const auto& typeIter = m_Stats.find(statType);
|
||||
|
||||
float baseResistance = 0;
|
||||
|
||||
if (statModifier == eStatModifier::DamageResistance) {
|
||||
if (m_ResistanceProfile != nullptr) {
|
||||
baseResistance = m_ResistanceProfile->GetResistanceProfile(statType);
|
||||
|
||||
Game::logger->Log("DestroyableComponent", "Base resistance: %f for %i, type %i", baseResistance, m_Parent->GetLOT(), statType);
|
||||
}
|
||||
else {
|
||||
Game::logger->Log("DestroyableComponent", "No resistance profile for %i", m_Parent->GetLOT());
|
||||
}
|
||||
}
|
||||
|
||||
if (typeIter == m_Stats.end()) {
|
||||
return baseResistance;
|
||||
}
|
||||
|
||||
const auto& typeMap = m_Stats.at(statType);
|
||||
|
||||
const auto& modifierIter = typeMap.find(statModifier);
|
||||
|
||||
if (modifierIter == typeMap.end()) {
|
||||
return baseResistance;
|
||||
}
|
||||
|
||||
return typeMap.at(statModifier);
|
||||
}
|
||||
|
||||
void DestroyableComponent::ComputeBaseStats(bool refill) {
|
||||
if (!m_Parent->IsPlayer())
|
||||
{
|
||||
auto* combatAI = m_Parent->GetComponent<BaseCombatAIComponent>();
|
||||
|
||||
if (combatAI == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Store the current health, armor and imagination
|
||||
const auto currentHealth = GetHealth();
|
||||
const auto currentArmor = GetArmor();
|
||||
|
||||
float maxHealth = 0.0f;
|
||||
float maxArmor = 0.0f;
|
||||
|
||||
int32_t level = (static_cast<int32_t>(m_Info.level) <= 1) ? 1 : m_Info.level;
|
||||
|
||||
if (m_Parent->IsPlayer())
|
||||
{
|
||||
level = m_Parent->GetComponent<LevelProgressionComponent>()->GetLevel();
|
||||
m_Info.life = 4;
|
||||
}
|
||||
|
||||
maxHealth += level * m_Info.life;
|
||||
|
||||
maxHealth *= 10;
|
||||
|
||||
maxHealth += GetStat(eStatTypes::Health, eStatModifier::Absolute);
|
||||
maxHealth *= 1.0f + GetStat(eStatTypes::Health, eStatModifier::Percent);
|
||||
|
||||
maxArmor = level * m_Info.armor;
|
||||
|
||||
maxArmor *= 10;
|
||||
|
||||
maxArmor += GetStat(eStatTypes::Armor, eStatModifier::Absolute);
|
||||
maxArmor *= 1.0f + GetStat(eStatTypes::Armor, eStatModifier::Percent);
|
||||
|
||||
// Set the base stats
|
||||
SetMaxHealth(maxHealth);
|
||||
SetMaxArmor(maxArmor);
|
||||
|
||||
// If any of the current stats are more than their max, set them to the max
|
||||
if (currentHealth > maxHealth || refill) SetHealth(maxHealth);
|
||||
else SetHealth(currentHealth);
|
||||
|
||||
if (currentArmor > maxArmor || refill) SetArmor(maxArmor);
|
||||
else SetArmor(currentArmor);
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
}
|
||||
|
||||
std::map<eStatTypes, float> DestroyableComponent::ComputeDamage(uint32_t baseDamage, Entity* source, DamageProfile* damageProfile) {
|
||||
const bool debug = true;
|
||||
|
||||
auto* sourceDestroyable = source->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (sourceDestroyable == nullptr) {
|
||||
Game::logger->Log("Damage", "Source entity has no destroyable componen %i", source->GetLOT());
|
||||
return {{eStatTypes::Physical, baseDamage}};
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
std::map<eStatTypes, float> damageMap = {{eStatTypes::Physical, baseDamage}};
|
||||
|
||||
for (eStatTypes damageType = eStatTypes::Physical; damageType < eStatTypes::MAX;
|
||||
damageType = static_cast<eStatTypes>(static_cast<uint32_t>(damageType) + 1)) {
|
||||
// Get the damage for this damage type
|
||||
float damage = sourceDestroyable->GetStat(damageType, eStatModifier::DamageAbsolute);
|
||||
|
||||
damage *= 1.0f + sourceDestroyable->GetStat(damageType, eStatModifier::DamagePercent) + ::log(baseDamage);
|
||||
|
||||
if (damageProfile != nullptr)
|
||||
{
|
||||
damage += damageProfile->GetDamageProfile(damageType);
|
||||
}
|
||||
|
||||
/*
|
||||
Level scaling
|
||||
*/
|
||||
float ourLevel = (static_cast<int32_t>(m_Info.level) <= 1) ? 1 : m_Info.level;
|
||||
float sourceLevel;
|
||||
|
||||
if (source->IsPlayer())
|
||||
{
|
||||
auto* levelProgressionComponent = source->GetComponent<LevelProgressionComponent>();
|
||||
|
||||
if (levelProgressionComponent == nullptr)
|
||||
{
|
||||
Game::logger->Log("Damage", "Source entity is player but has no level progression component %i", source->GetLOT());
|
||||
return {{eStatTypes::Physical, baseDamage}};
|
||||
}
|
||||
|
||||
sourceLevel = levelProgressionComponent->GetLevel();
|
||||
|
||||
// Player max is level 45, scale it to between 1 and 10
|
||||
sourceLevel = static_cast<int32_t>(static_cast<float>(sourceLevel) / 4.5f);
|
||||
|
||||
// Make sure it's between 1 and 10
|
||||
sourceLevel = std::max(sourceLevel, 1.0f);
|
||||
sourceLevel = std::min(sourceLevel, 10.0f);
|
||||
|
||||
if (ourLevel < sourceLevel)
|
||||
{
|
||||
damage /= 1 + (sourceLevel - ourLevel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceLevel = (static_cast<int32_t>(sourceDestroyable->m_Info.level) <= 1) ? 1 : sourceDestroyable->m_Info.level;
|
||||
|
||||
if (m_Parent->IsPlayer())
|
||||
{
|
||||
auto* levelProgressionComponent = m_Parent->GetComponent<LevelProgressionComponent>();
|
||||
|
||||
if (levelProgressionComponent == nullptr)
|
||||
{
|
||||
Game::logger->Log("Damage", "Source entity is player but has no level progression component %i", m_Parent->GetLOT());
|
||||
return {{eStatTypes::Physical, baseDamage}};
|
||||
}
|
||||
|
||||
ourLevel = levelProgressionComponent->GetLevel();
|
||||
|
||||
// Player max is level 45, scale it to between 1 and 10
|
||||
ourLevel = static_cast<int32_t>(static_cast<float>(ourLevel) / 4.5f);
|
||||
|
||||
// Make sure it's between 1 and 10
|
||||
ourLevel = std::max(ourLevel, 1.0f);
|
||||
ourLevel = std::min(ourLevel, 10.0f);
|
||||
|
||||
if (ourLevel > sourceLevel)
|
||||
{
|
||||
damage *= 1 + (ourLevel - sourceLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Idk
|
||||
|
||||
// If the damage is 0, skip this damage type
|
||||
if (damage == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate our resistance for this damage type
|
||||
const auto resistance = this->GetStat(damageType, eStatModifier::DamageResistance);
|
||||
|
||||
// Cap resistance at 80%
|
||||
const auto cappedResistance = std::min(resistance, 0.8f);
|
||||
|
||||
// Calculate the damage we take
|
||||
const auto damageTaken = damage * (1.0f - cappedResistance);
|
||||
|
||||
// Add the damage to our total damage
|
||||
const auto& it = damageMap.find(damageType);
|
||||
|
||||
if (it == damageMap.end()) {
|
||||
damageMap[damageType] = damageTaken;
|
||||
} else {
|
||||
damageMap[damageType] += damageTaken;
|
||||
}
|
||||
|
||||
// Log the calculation
|
||||
if (debug) {
|
||||
ss << "Damage type: " << static_cast<int32_t>(damageType) << std::endl;
|
||||
ss << "\tDamage: " << damage << std::endl;
|
||||
ss << "\tResistance: " << resistance << std::endl;
|
||||
ss << "\tCapped resistance: " << cappedResistance << std::endl;
|
||||
ss << "\tDamage taken: " << damageTaken << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return damageMap;
|
||||
}
|
||||
|
||||
void DestroyableComponent::Damage(const std::map<eStatTypes, float>& damage, LWOOBJID source, uint32_t skillID, bool echo) {
|
||||
float totalDamage = 0;
|
||||
|
||||
for (const auto& [damageType, damageAmount] : damage) {
|
||||
totalDamage += damageAmount;
|
||||
}
|
||||
|
||||
// Find the greatest damage type
|
||||
eStatTypes greatestDamageType = eStatTypes::Physical;
|
||||
|
||||
for (const auto& [damageType, damageAmount] : damage) {
|
||||
if (damageAmount > damage.at(greatestDamageType)) {
|
||||
greatestDamageType = damageType;
|
||||
}
|
||||
}
|
||||
|
||||
const auto effectName = std::to_string(GeneralUtils::GenerateRandomNumber<size_t>(100, 10000));
|
||||
|
||||
const auto& objectId = m_Parent->GetObjectID();
|
||||
|
||||
const bool critical = GeneralUtils::GenerateRandomNumber<size_t>(0, 100) <= 10;
|
||||
|
||||
if (critical) {
|
||||
totalDamage *= 2;
|
||||
}
|
||||
|
||||
// Play an effect on us representing the damage
|
||||
switch (greatestDamageType) {
|
||||
case eStatTypes::Heat:
|
||||
if (!critical) GameMessages::SendPlayFXEffect(objectId, 50, u"onhit", effectName);
|
||||
else GameMessages::SendPlayFXEffect(objectId, 1578, u"onhit", effectName);
|
||||
break;
|
||||
case eStatTypes::Electric:
|
||||
if (!critical) GameMessages::SendPlayFXEffect(objectId, 4027, u"create", effectName);
|
||||
else GameMessages::SendPlayFXEffect(objectId, 953, u"onhit", effectName);
|
||||
break;
|
||||
case eStatTypes::Corruption:
|
||||
if (!critical) GameMessages::SendPlayFXEffect(objectId, 7, u"onhit", effectName);
|
||||
else GameMessages::SendPlayFXEffect(objectId, 1153, u"death", effectName);
|
||||
break;
|
||||
case eStatTypes::Physical:
|
||||
default:
|
||||
if (!critical) GameMessages::SendPlayFXEffect(objectId, 5039, u"on-anim", effectName);
|
||||
else GameMessages::SendPlayFXEffect(objectId, 4972, u"onhitObject", effectName);
|
||||
break;
|
||||
}
|
||||
|
||||
m_Parent->AddCallbackTimer(1.5f, [this, effectName]() {
|
||||
GameMessages::SendStopFXEffect(m_Parent, true, effectName);
|
||||
});
|
||||
|
||||
Damage(totalDamage, source, skillID, echo);
|
||||
}
|
||||
|
||||
void DestroyableComponent::Update(float deltaTime) {
|
||||
if (m_DirtyStats) {
|
||||
ComputeBaseStats();
|
||||
m_DirtyStats = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DestroyableComponent::AddOnHitCallback(const std::function<void(Entity*)>& callback) {
|
||||
m_OnHitCallbacks.push_back(callback);
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include "Entity.h"
|
||||
#include "Component.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "StatProperty.h"
|
||||
#include "CDDestructibleComponentTable.h"
|
||||
|
||||
namespace CppScripts {
|
||||
class Script;
|
||||
@ -429,6 +431,46 @@ public:
|
||||
*/
|
||||
void FixStats();
|
||||
|
||||
void SetInfo(const CDDestructibleComponent& info) { m_Info = info; }
|
||||
|
||||
CDDestructibleComponent& GetInfo() { return m_Info; }
|
||||
|
||||
/**
|
||||
* Add a stat property to this entity
|
||||
*/
|
||||
void AddStat(const StatProperty& stat);
|
||||
|
||||
/**
|
||||
* Remove a stat property from this entity
|
||||
*/
|
||||
void RemoveStat(const StatProperty& stat);
|
||||
|
||||
/**
|
||||
* Get a stat property from this entity
|
||||
*/
|
||||
float GetStat(eStatTypes statType, eStatModifier statModifier) const;
|
||||
|
||||
/**
|
||||
* Compute the entity's health, armor, and imagination based on its stats
|
||||
*/
|
||||
void ComputeBaseStats(bool refill = false);
|
||||
|
||||
/**
|
||||
* Compute damage
|
||||
*/
|
||||
std::map<eStatTypes, float> ComputeDamage(uint32_t baseDamage, Entity* source, class DamageProfile* damageProfile);
|
||||
|
||||
/**
|
||||
* Damage this entity with a damage map
|
||||
*/
|
||||
void Damage(const std::map<eStatTypes, float>& damage, LWOOBJID source, uint32_t skillID = 0, bool echo = true);
|
||||
|
||||
/**
|
||||
* Updates the component in the game loop
|
||||
* @param deltaTime time passed since last update
|
||||
*/
|
||||
void Update(float deltaTime) override;
|
||||
|
||||
/**
|
||||
* Adds a callback that is called when this entity is hit by some other entity
|
||||
* @param callback the callback to add
|
||||
@ -605,6 +647,26 @@ private:
|
||||
uint32_t m_ImmuneToImaginationLossCount;
|
||||
uint32_t m_ImmuneToQuickbuildInterruptCount;
|
||||
uint32_t m_ImmuneToPullToPointCount;
|
||||
|
||||
/**
|
||||
* Stats for the entity
|
||||
*/
|
||||
std::map<eStatTypes, std::map<eStatModifier, float>> m_Stats;
|
||||
|
||||
/**
|
||||
* Dirty flag for stats
|
||||
*/
|
||||
bool m_DirtyStats;
|
||||
|
||||
/**
|
||||
* The info for this component
|
||||
*/
|
||||
CDDestructibleComponent m_Info;
|
||||
|
||||
/**
|
||||
* Resistance profile for this entity
|
||||
*/
|
||||
class ResistanceProfile* m_ResistanceProfile;
|
||||
};
|
||||
|
||||
#endif // DESTROYABLECOMPONENT_H
|
||||
|
@ -490,6 +490,11 @@ bool InventoryComponent::HasSpaceForLoot(const std::unordered_map<LOT, int32_t>&
|
||||
void InventoryComponent::LoadXml(tinyxml2::XMLDocument* document) {
|
||||
LoadPetXml(document);
|
||||
|
||||
EquipSkill(eSkillBar::Primary, BehaviorSlot::Primary, 1);
|
||||
EquipSkill(eSkillBar::Primary, BehaviorSlot::Head, 6);
|
||||
EquipSkill(eSkillBar::Primary, BehaviorSlot::Offhand, 7);
|
||||
EquipSkill(eSkillBar::Primary, BehaviorSlot::Neck, 8);
|
||||
|
||||
auto* inventoryElement = document->FirstChildElement("obj")->FirstChildElement("inv");
|
||||
|
||||
if (inventoryElement == nullptr) {
|
||||
@ -577,14 +582,79 @@ void InventoryComponent::LoadXml(tinyxml2::XMLDocument* document) {
|
||||
auto* extraInfo = itemElement->FirstChildElement("x");
|
||||
|
||||
if (extraInfo) {
|
||||
std::string modInfo = extraInfo->Attribute("ma");
|
||||
// Check if has attribute "ma"
|
||||
if (extraInfo->Attribute("ma") != nullptr) {
|
||||
std::string modInfo = extraInfo->Attribute("ma");
|
||||
|
||||
LDFBaseData* moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(modInfo.substr(2, modInfo.size() - 1)));
|
||||
LDFBaseData* moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(modInfo.substr(2, modInfo.size() - 1)));
|
||||
|
||||
config.push_back(moduleAssembly);
|
||||
config.push_back(moduleAssembly);
|
||||
}
|
||||
}
|
||||
|
||||
const auto* item = new Item(id, lot, inventory, slot, count, bound, config, parent, subKey);
|
||||
auto* statInfo = itemElement->Attribute("st");
|
||||
|
||||
std::vector<StatProperty> stats;
|
||||
|
||||
if (statInfo != nullptr) {
|
||||
// type,modifier,value;...
|
||||
std::vector<std::string> statData = GeneralUtils::SplitString(statInfo, ';');
|
||||
|
||||
for (const auto& stat : statData) {
|
||||
if (stat.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<std::string> statParts = GeneralUtils::SplitString(stat, ',');
|
||||
|
||||
if (statParts.size() != 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
eStatTypes type = static_cast<eStatTypes>(std::stoi(statParts[0]));
|
||||
eStatModifier modifier = static_cast<eStatModifier>(std::stoi(statParts[1]));
|
||||
float value = std::stof(statParts[2]);
|
||||
|
||||
stats.push_back(StatProperty(type, modifier, value));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ItemModifierTemplate*> modifiers;
|
||||
|
||||
auto* modifierInfo = itemElement->Attribute("mo");
|
||||
|
||||
if (modifierInfo != nullptr) {
|
||||
// name;...
|
||||
std::vector<std::string> modifierData = GeneralUtils::SplitString(modifierInfo, ';');
|
||||
|
||||
for (const auto& modifier : modifierData) {
|
||||
if (modifier.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* modifierTemplate = ItemModifierTemplate::FindItemModifierTemplate(modifier);
|
||||
|
||||
if (modifierTemplate == nullptr) {
|
||||
Game::logger->Log("InventoryComponent", "Failed to find modifier template (%s)!", modifier.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
modifiers.push_back(modifierTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto* item = new Item(id, lot, inventory, slot, count, bound, config, parent, subKey);
|
||||
|
||||
if (!stats.empty())
|
||||
{
|
||||
item->GetStats() = stats;
|
||||
}
|
||||
|
||||
if (!modifiers.empty())
|
||||
{
|
||||
item->GetModifiers() = modifiers;
|
||||
}
|
||||
|
||||
if (equipped) {
|
||||
const auto info = Inventory::FindItemComponent(lot);
|
||||
@ -702,6 +772,24 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) {
|
||||
itemElement->LinkEndChild(extraInfo);
|
||||
}
|
||||
|
||||
// St attribute
|
||||
std::stringstream ss;
|
||||
|
||||
for (const auto& stat : item->GetStats()) {
|
||||
ss << static_cast<uint32_t>(stat.type) << "," << static_cast<uint32_t>(stat.modifier) << "," << stat.value << ";";
|
||||
}
|
||||
|
||||
itemElement->SetAttribute("st", ss.str().c_str());
|
||||
|
||||
// Mo attribute
|
||||
ss.str("");
|
||||
|
||||
for (const auto& modifier : item->GetModifiers()) {
|
||||
ss << modifier->GetName() << ";";
|
||||
}
|
||||
|
||||
itemElement->SetAttribute("mo", ss.str().c_str());
|
||||
|
||||
bagElement->LinkEndChild(itemElement);
|
||||
}
|
||||
|
||||
@ -1031,7 +1119,17 @@ void InventoryComponent::ApplyBuff(Item* item) const {
|
||||
const auto buffs = FindBuffs(item, true);
|
||||
|
||||
for (const auto buff : buffs) {
|
||||
SkillComponent::HandleUnmanaged(buff, m_Parent->GetObjectID());
|
||||
SkillComponent::HandleUnmanaged(buff, m_Parent->GetObjectID(), LWOOBJID_EMPTY, item->GetId());
|
||||
}
|
||||
|
||||
auto* destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (destroyableComponent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& stat : item->GetStats()) {
|
||||
destroyableComponent->AddStat(stat);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1040,7 +1138,17 @@ void InventoryComponent::RemoveBuff(Item* item) const {
|
||||
const auto buffs = FindBuffs(item, false);
|
||||
|
||||
for (const auto buff : buffs) {
|
||||
SkillComponent::HandleUnCast(buff, m_Parent->GetObjectID());
|
||||
SkillComponent::HandleUnCast(buff, m_Parent->GetObjectID(), item->GetId());
|
||||
}
|
||||
|
||||
auto* destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (destroyableComponent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& stat : item->GetStats()) {
|
||||
destroyableComponent->RemoveStat(stat);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1157,23 +1265,35 @@ void InventoryComponent::AddItemSkills(const LOT lot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = m_Skills.find(slot);
|
||||
eSkillBar bar = eSkillBar::Gear;
|
||||
|
||||
switch (slot)
|
||||
{
|
||||
case BehaviorSlot::Primary:
|
||||
bar = eSkillBar::Primary;
|
||||
break;
|
||||
case BehaviorSlot::Consumable:
|
||||
bar = eSkillBar::Consumable;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto skill = FindSkill(lot);
|
||||
|
||||
if (skill == 0) {
|
||||
UnequipSkill(bar, slot);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (index != m_Skills.end()) {
|
||||
const auto old = index->second;
|
||||
|
||||
GameMessages::SendRemoveSkill(m_Parent, old);
|
||||
if (slot == BehaviorSlot::Primary) {
|
||||
EquipSkill(eSkillBar::Primary, BehaviorSlot::Primary, skill);
|
||||
EquipSkill(eSkillBar::Gear, BehaviorSlot::Primary, skill);
|
||||
EquipSkill(eSkillBar::ClassPrimary, BehaviorSlot::Primary, skill);
|
||||
EquipSkill(eSkillBar::ClassSecondary, BehaviorSlot::Primary, skill);
|
||||
}
|
||||
else {
|
||||
EquipSkill(bar, slot, skill);
|
||||
}
|
||||
|
||||
GameMessages::SendAddSkill(m_Parent, skill, static_cast<int>(slot));
|
||||
|
||||
m_Skills.insert_or_assign(slot, skill);
|
||||
}
|
||||
|
||||
void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
||||
@ -1185,23 +1305,155 @@ void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = m_Skills.find(slot);
|
||||
if (slot == BehaviorSlot::Primary) {
|
||||
EquipSkill(eSkillBar::Primary, BehaviorSlot::Primary, 1);
|
||||
EquipSkill(eSkillBar::Gear, BehaviorSlot::Primary, 1);
|
||||
EquipSkill(eSkillBar::ClassPrimary, BehaviorSlot::Primary, 1);
|
||||
EquipSkill(eSkillBar::ClassSecondary, BehaviorSlot::Primary, 1);
|
||||
|
||||
if (index == m_Skills.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto old = index->second;
|
||||
eSkillBar bar = eSkillBar::Gear;
|
||||
|
||||
GameMessages::SendRemoveSkill(m_Parent, old);
|
||||
|
||||
m_Skills.erase(slot);
|
||||
|
||||
if (slot == BehaviorSlot::Primary) {
|
||||
m_Skills.insert_or_assign(BehaviorSlot::Primary, 1);
|
||||
|
||||
GameMessages::SendAddSkill(m_Parent, 1, static_cast<int>(BehaviorSlot::Primary));
|
||||
switch (slot)
|
||||
{
|
||||
case BehaviorSlot::Primary:
|
||||
bar = eSkillBar::Primary;
|
||||
break;
|
||||
case BehaviorSlot::Consumable:
|
||||
bar = eSkillBar::Consumable;
|
||||
break;
|
||||
}
|
||||
|
||||
UnequipSkill(bar, slot);
|
||||
}
|
||||
|
||||
void InventoryComponent::EquipSkill(eSkillBar bar, BehaviorSlot slot, uint32_t skillID) {
|
||||
Game::logger->Log("InventoryComponent", "Equipping skill %i to slot %i on bar %i", skillID, slot, bar);
|
||||
|
||||
const auto& barIt = m_EquippedSkills.find(bar);
|
||||
|
||||
if (barIt == m_EquippedSkills.end()) {
|
||||
m_EquippedSkills.emplace(bar, std::map<BehaviorSlot, uint32_t>());
|
||||
}
|
||||
|
||||
auto& barMap = m_EquippedSkills.at(bar);
|
||||
|
||||
const auto& slotIt = barMap.find(slot);
|
||||
|
||||
barMap.insert_or_assign(slot, skillID);
|
||||
|
||||
UpdateSkills();
|
||||
}
|
||||
|
||||
void InventoryComponent::UnequipSkill(eSkillBar bar, BehaviorSlot slot) {
|
||||
Game::logger->Log("InventoryComponent", "Unequipping skill from slot %i on bar %i", slot, bar);
|
||||
|
||||
const auto& barIt = m_EquippedSkills.find(bar);
|
||||
|
||||
if (barIt == m_EquippedSkills.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& barMap = m_EquippedSkills.at(bar);
|
||||
|
||||
const auto& slotIt = barMap.find(slot);
|
||||
|
||||
if (slotIt == barMap.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto old = slotIt->second;
|
||||
|
||||
barMap.erase(slot);
|
||||
|
||||
if (bar == eSkillBar::Primary && slot == BehaviorSlot::Primary) {
|
||||
barMap.insert_or_assign(BehaviorSlot::Primary, 1);
|
||||
}
|
||||
|
||||
if (barMap.empty()) {
|
||||
m_EquippedSkills.erase(bar);
|
||||
}
|
||||
|
||||
UpdateSkills();
|
||||
}
|
||||
|
||||
uint32_t InventoryComponent::GetSkill(eSkillBar bar, BehaviorSlot slot) const {
|
||||
const auto& barIt = m_EquippedSkills.find(bar);
|
||||
|
||||
if (barIt == m_EquippedSkills.end()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& barMap = barIt->second;
|
||||
|
||||
const auto& slotIt = barMap.find(slot);
|
||||
|
||||
if (slotIt == barMap.end()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return slotIt->second;
|
||||
}
|
||||
|
||||
eSkillBar InventoryComponent::GetSelectedSkillBar() const {
|
||||
return m_SelectedSkillBar;
|
||||
}
|
||||
|
||||
void InventoryComponent::SetSelectedSkillBar(eSkillBar bar) {
|
||||
if (bar == m_SelectedSkillBar) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_SelectedSkillBar = bar;
|
||||
|
||||
UpdateSkills();
|
||||
}
|
||||
|
||||
void InventoryComponent::UpdateSkills() {
|
||||
// The active skills are kept in m_Skills, compare what is there to what should be there
|
||||
// and add/remove skills as needed
|
||||
const auto& barIt = m_EquippedSkills.find(m_SelectedSkillBar);
|
||||
|
||||
if (barIt == m_EquippedSkills.end()) {
|
||||
// Unequip all skills
|
||||
for (auto& pair : m_Skills) {
|
||||
GameMessages::SendRemoveSkill(m_Parent, pair.second);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& barMap = barIt->second;
|
||||
|
||||
for (BehaviorSlot slot = BehaviorSlot::Primary; slot < BehaviorSlot::Consumable;) {
|
||||
const auto& slotIt = barMap.find(slot);
|
||||
|
||||
if (slotIt == barMap.end()) {
|
||||
// Unequip the skill
|
||||
const auto& skillIt = m_Skills.find(slot);
|
||||
|
||||
if (skillIt != m_Skills.end()) {
|
||||
GameMessages::SendRemoveSkill(m_Parent, skillIt->second);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Equip the skill
|
||||
const auto& skillIt = m_Skills.find(slot);
|
||||
|
||||
if (skillIt != m_Skills.end()) {
|
||||
GameMessages::SendRemoveSkill(m_Parent, skillIt->second);
|
||||
}
|
||||
|
||||
GameMessages::SendAddSkill(m_Parent, slotIt->second, static_cast<int32_t>(slot));
|
||||
}
|
||||
|
||||
slot = static_cast<BehaviorSlot>(static_cast<int32_t>(slot) + 1);
|
||||
}
|
||||
|
||||
// Update the active skills
|
||||
m_Skills = barMap;
|
||||
}
|
||||
|
||||
void InventoryComponent::TriggerPassiveAbility(PassiveAbilityTrigger trigger, Entity* target) {
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "eInventoryType.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "eLootSourceType.h"
|
||||
#include "eSkillBar.h"
|
||||
|
||||
class Entity;
|
||||
class ItemSet;
|
||||
@ -281,6 +282,36 @@ public:
|
||||
*/
|
||||
void RemoveItemSkills(LOT lot);
|
||||
|
||||
/**
|
||||
* Equip a skill to a bar and slot
|
||||
*/
|
||||
void EquipSkill(eSkillBar bar, BehaviorSlot slot, uint32_t skillID);
|
||||
|
||||
/**
|
||||
* Unequip a skill from a bar and slot
|
||||
*/
|
||||
void UnequipSkill(eSkillBar bar, BehaviorSlot slot);
|
||||
|
||||
/**
|
||||
* Get the skill in a bar and slot
|
||||
*/
|
||||
uint32_t GetSkill(eSkillBar bar, BehaviorSlot slot) const;
|
||||
|
||||
/**
|
||||
* Get the currently selected skill bar
|
||||
*/
|
||||
eSkillBar GetSelectedSkillBar() const;
|
||||
|
||||
/**
|
||||
* Set the currently selected skill bar
|
||||
*/
|
||||
void SetSelectedSkillBar(eSkillBar bar);
|
||||
|
||||
/**
|
||||
* Update skills on client
|
||||
*/
|
||||
void UpdateSkills();
|
||||
|
||||
/**
|
||||
* Triggers one of the passive abilities from the equipped item set
|
||||
* @param trigger the trigger to fire
|
||||
@ -416,6 +447,16 @@ private:
|
||||
*/
|
||||
LOT m_Consumable;
|
||||
|
||||
/**
|
||||
* Equipped skills
|
||||
*/
|
||||
std::map<eSkillBar, std::map<BehaviorSlot, uint32_t>> m_EquippedSkills;
|
||||
|
||||
/**
|
||||
* The currently selected skill bar
|
||||
*/
|
||||
eSkillBar m_SelectedSkillBar = eSkillBar::Primary;
|
||||
|
||||
/**
|
||||
* Currently has a car equipped
|
||||
*/
|
||||
|
@ -448,11 +448,12 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry)
|
||||
delete bitStream;
|
||||
}
|
||||
|
||||
void SkillComponent::HandleUnmanaged(const uint32_t behaviorId, const LWOOBJID target, LWOOBJID source) {
|
||||
void SkillComponent::HandleUnmanaged(const uint32_t behaviorId, const LWOOBJID target, LWOOBJID source, LWOOBJID itemID) {
|
||||
auto* context = new BehaviorContext(source);
|
||||
|
||||
context->unmanaged = true;
|
||||
context->caster = target;
|
||||
context->itemID = itemID;
|
||||
|
||||
auto* behavior = Behavior::CreateBehavior(behaviorId);
|
||||
|
||||
@ -465,7 +466,7 @@ void SkillComponent::HandleUnmanaged(const uint32_t behaviorId, const LWOOBJID t
|
||||
delete context;
|
||||
}
|
||||
|
||||
void SkillComponent::HandleUnCast(const uint32_t behaviorId, const LWOOBJID target) {
|
||||
void SkillComponent::HandleUnCast(const uint32_t behaviorId, const LWOOBJID target, LWOOBJID itemID) {
|
||||
auto* context = new BehaviorContext(target);
|
||||
|
||||
context->caster = target;
|
||||
|
@ -169,15 +169,17 @@ public:
|
||||
* @param behaviorId the root behavior ID of the skill
|
||||
* @param target the explicit target of the skill
|
||||
* @param source the explicit source of the skill
|
||||
* @param itemID the explicit item ID of the skill
|
||||
*/
|
||||
static void HandleUnmanaged(uint32_t behaviorId, LWOOBJID target, LWOOBJID source = LWOOBJID_EMPTY);
|
||||
static void HandleUnmanaged(uint32_t behaviorId, LWOOBJID target, LWOOBJID source = LWOOBJID_EMPTY, LWOOBJID itemID = LWOOBJID_EMPTY);
|
||||
|
||||
/**
|
||||
* Computes a server-side skill uncast calculation without an associated entity.
|
||||
* @param behaviorId the root behavior ID of the skill
|
||||
* @param target the explicit target of the skill
|
||||
* @param itemID the explicit item ID of the skill
|
||||
*/
|
||||
static void HandleUnCast(uint32_t behaviorId, LWOOBJID target);
|
||||
static void HandleUnCast(uint32_t behaviorId, LWOOBJID target, LWOOBJID itemID = LWOOBJID_EMPTY);
|
||||
|
||||
/**
|
||||
* @returns a unique ID for the next skill calculation
|
||||
|
@ -20,7 +20,8 @@ VendorComponent::~VendorComponent() = default;
|
||||
void VendorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
|
||||
outBitStream->Write1();
|
||||
outBitStream->Write1(); // Has standard items (Required for vendors with missions.)
|
||||
outBitStream->Write(HasCraftingStation()); // Has multi use items
|
||||
outBitStream->Write1();
|
||||
//outBitStream->Write(HasCraftingStation()); // Has multi use items
|
||||
}
|
||||
|
||||
void VendorComponent::OnUse(Entity* originator) {
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "eMissionTaskType.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "eConnectionType.h"
|
||||
#include "CDLookupTable.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@ -307,6 +308,48 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System
|
||||
delete bs;
|
||||
}
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
const auto switching = entity->HasVar(u"switching") && entity->GetVar<bool>(u"switching");
|
||||
|
||||
if (inventoryComponent != nullptr && !switching) {
|
||||
bool switched;
|
||||
|
||||
const auto primarySkill = inventoryComponent->GetSkill(eSkillBar::Primary, BehaviorSlot::Primary);
|
||||
|
||||
if (startSkill.skillID == rose::id("grim:skills:main-1")) {
|
||||
inventoryComponent->SetSelectedSkillBar(eSkillBar::Gear);
|
||||
switched = true;
|
||||
}
|
||||
else if (startSkill.skillID == rose::id("grim:skills:main-2")) {
|
||||
inventoryComponent->SetSelectedSkillBar(eSkillBar::ClassPrimary);
|
||||
switched = true;
|
||||
}
|
||||
else if (startSkill.skillID == rose::id("grim:skills:main-3")) {
|
||||
inventoryComponent->SetSelectedSkillBar(eSkillBar::ClassSecondary);
|
||||
switched = true;
|
||||
}
|
||||
else if (startSkill.skillID != primarySkill) {
|
||||
inventoryComponent->SetSelectedSkillBar(eSkillBar::Primary);
|
||||
switched = false;
|
||||
}
|
||||
else {
|
||||
switched = false;
|
||||
}
|
||||
|
||||
if (switched) {
|
||||
entity->SetVar(u"switching", true);
|
||||
|
||||
entity->AddCallbackTimer(2, [inventoryComponent]() {
|
||||
inventoryComponent->SetSelectedSkillBar(eSkillBar::Primary);
|
||||
});
|
||||
|
||||
entity->AddCallbackTimer(0.25, [entity]() {
|
||||
entity->SetVar(u"switching", false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (Game::server->GetZoneID() == 1302) {
|
||||
break;
|
||||
}
|
||||
|
@ -96,6 +96,7 @@
|
||||
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
#include "CDObjectsTable.h"
|
||||
#include "CDLookupTable.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;
|
||||
@ -1200,6 +1201,8 @@ void GameMessages::SendAddSkill(Entity* entity, TSkillID skillID, int slotID) {
|
||||
|
||||
bitStream.Write(temporary);
|
||||
|
||||
Game::logger->Log("GameMessages", "SendAddSkill: %d", skillID);
|
||||
|
||||
SystemAddress sysAddr = entity->GetSystemAddress();
|
||||
SEND_PACKET;
|
||||
}
|
||||
@ -1213,6 +1216,8 @@ void GameMessages::SendRemoveSkill(Entity* entity, TSkillID skillID) {
|
||||
bitStream.Write(false);
|
||||
bitStream.Write(skillID);
|
||||
|
||||
Game::logger->Log("GameMessages", "SendRemoveSkill: %d", skillID);
|
||||
|
||||
SystemAddress sysAddr = entity->GetSystemAddress();
|
||||
SEND_PACKET;
|
||||
}
|
||||
@ -4845,15 +4850,49 @@ void GameMessages::HandleSellToVendor(RakNet::BitStream* inStream, Entity* entit
|
||||
// Items with a base value of 0 or max int are special items that should not be sold if they're not sub items
|
||||
if (itemComp.baseValue == 0 || itemComp.baseValue == UINT_MAX) return;
|
||||
|
||||
float sellScalar = vend->GetSellScalar();
|
||||
if (Inventory::IsValidItem(itemComp.currencyLOT)) {
|
||||
const auto altCurrency = static_cast<uint32_t>(itemComp.altCurrencyCost * sellScalar) * count;
|
||||
inv->AddItem(itemComp.currencyLOT, std::floor(altCurrency), eLootSourceType::VENDOR); // Return alt currencies like faction tokens.
|
||||
auto craftingCurrencies = CDItemComponentTable::ParseCraftingCurrencies(itemComp);
|
||||
|
||||
Game::logger->Log("GameMessages", "User %llu %s selling %i to a vendor %i", player->GetObjectID(), user->GetUsername().c_str(), item->GetLot(), entity->GetLOT());
|
||||
|
||||
if (entity->GetLOT() == rose::id("grim:froge-anvil")) {
|
||||
Game::logger->Log("GameMessages", "User %llu %s tried to sell an item %i to a forge anvil", player->GetObjectID(), user->GetUsername().c_str(), item->GetLot());
|
||||
|
||||
if (craftingCurrencies.empty()) return;
|
||||
|
||||
for (const auto& craftingCurrency : craftingCurrencies) {
|
||||
uint32_t count;
|
||||
|
||||
if (craftingCurrency.second == 1) {
|
||||
count = GeneralUtils::GenerateRandomNumber<size_t>(0, 100) > 75 ? 1 : 0;
|
||||
} else {
|
||||
count = static_cast<uint32_t>(std::floor(craftingCurrency.second * GeneralUtils::GenerateRandomNumber<float>(0, 1)));
|
||||
}
|
||||
|
||||
if (count == 0) continue;
|
||||
|
||||
inv->AddItem(craftingCurrency.first, count, eLootSourceType::VENDOR);
|
||||
}
|
||||
|
||||
item->SetCount(0);
|
||||
|
||||
Game::logger->Log("GameMessages", "User %llu %s sold an item %i to a forge anvil", player->GetObjectID(), user->GetUsername().c_str(), item->GetLot());
|
||||
} else {
|
||||
float sellScalar = vend->GetSellScalar();
|
||||
if (Inventory::IsValidItem(itemComp.currencyLOT)) {
|
||||
const auto altCurrency = static_cast<uint32_t>(itemComp.altCurrencyCost * sellScalar) * count;
|
||||
inv->AddItem(itemComp.currencyLOT, std::floor(altCurrency), eLootSourceType::VENDOR); // Return alt currencies like faction tokens.
|
||||
}
|
||||
|
||||
if (!craftingCurrencies.empty() || !item->GetStats().empty()) {
|
||||
item->SetCount(0);
|
||||
} else {
|
||||
inv->MoveItemToInventory(item, eInventoryType::VENDOR_BUYBACK, count, true, false, true);
|
||||
}
|
||||
|
||||
character->SetCoins(std::floor(character->GetCoins() + (static_cast<uint32_t>(itemComp.baseValue * sellScalar) * count)), eLootSourceType::VENDOR);
|
||||
}
|
||||
|
||||
//inv->RemoveItem(count, -1, iObjID);
|
||||
inv->MoveItemToInventory(item, eInventoryType::VENDOR_BUYBACK, count, true, false, true);
|
||||
character->SetCoins(std::floor(character->GetCoins() + (static_cast<uint32_t>(itemComp.baseValue * sellScalar) * count)), eLootSourceType::VENDOR);
|
||||
//EntityManager::Instance()->SerializeEntity(player); // so inventory updates
|
||||
GameMessages::SendVendorTransactionResult(entity, sysAddr);
|
||||
}
|
||||
|
6
dGame/dGrim/CMakeLists.txt
Normal file
6
dGame/dGrim/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
set(DGAME_DGRIM_SOURCES "StatProperty.cpp"
|
||||
"ItemModifierTemplate.cpp"
|
||||
"DamageProfile.cpp"
|
||||
"ResistanceProfile.cpp"
|
||||
"SpawnPatterns.cpp"
|
||||
"EntityProfile.cpp" PARENT_SCOPE)
|
56
dGame/dGrim/DamageProfile.cpp
Normal file
56
dGame/dGrim/DamageProfile.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
#include "DamageProfile.h"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
std::map<int32_t, DamageProfile> DamageProfile::s_DamageProfiles;
|
||||
|
||||
void DamageProfile::LoadDamageProfiles(const std::string& filename) {
|
||||
tinyxml2::XMLDocument doc;
|
||||
doc.LoadFile(filename.c_str());
|
||||
|
||||
auto root = doc.FirstChildElement("DamageProfiles");
|
||||
|
||||
for (auto element = root->FirstChildElement("DamageProfile"); element != nullptr; element = element->NextSiblingElement("DamageProfile")) {
|
||||
auto skillID = element->IntAttribute("skillID");
|
||||
|
||||
DamageProfile damageProfile(skillID);
|
||||
|
||||
for (auto damageElement = element->FirstChildElement("Damage"); damageElement != nullptr; damageElement = damageElement->NextSiblingElement("Damage")) {
|
||||
auto statType = damageElement->IntAttribute("type");
|
||||
auto value = damageElement->FloatAttribute("value");
|
||||
|
||||
damageProfile.AddDamageProfile(static_cast<eStatTypes>(statType), value);
|
||||
}
|
||||
|
||||
s_DamageProfiles.emplace(skillID, damageProfile);
|
||||
}
|
||||
}
|
||||
|
||||
DamageProfile* DamageProfile::FindDamageProfile(int32_t skillID) {
|
||||
const auto& it = s_DamageProfiles.find(skillID);
|
||||
|
||||
if (it != s_DamageProfiles.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
DamageProfile::DamageProfile(int32_t skillID) {
|
||||
this->m_SkillID = skillID;
|
||||
}
|
||||
|
||||
void DamageProfile::AddDamageProfile(eStatTypes statType, float value) {
|
||||
m_DamageProfile[statType] = value;
|
||||
}
|
||||
|
||||
float DamageProfile::GetDamageProfile(eStatTypes statType) const {
|
||||
const auto& it = m_DamageProfile.find(statType);
|
||||
|
||||
if (it != m_DamageProfile.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
33
dGame/dGrim/DamageProfile.h
Normal file
33
dGame/dGrim/DamageProfile.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __EDAMAGEPROFILE__H__
|
||||
#define __EDAMAGEPROFILE__H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "ItemModifierTemplate.h"
|
||||
|
||||
class DamageProfile {
|
||||
public:
|
||||
DamageProfile(int32_t skillID);
|
||||
|
||||
~DamageProfile() = default;
|
||||
|
||||
void AddDamageProfile(eStatTypes statType, float value);
|
||||
|
||||
float GetDamageProfile(eStatTypes statType) const;
|
||||
|
||||
static void LoadDamageProfiles(const std::string& filename);
|
||||
|
||||
static DamageProfile* FindDamageProfile(int32_t skillID);
|
||||
|
||||
private:
|
||||
int32_t m_SkillID;
|
||||
|
||||
std::map<eStatTypes, float> m_DamageProfile;
|
||||
|
||||
static std::map<int32_t, DamageProfile> s_DamageProfiles;
|
||||
};
|
||||
|
||||
#endif //!__EDAMAGEPROFILE__H__
|
38
dGame/dGrim/EntityProfile.cpp
Normal file
38
dGame/dGrim/EntityProfile.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
#include "EntityProfile.h"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
std::map<int32_t, EntityProfile> EntityProfile::s_EntityProfiles;
|
||||
|
||||
EntityProfile::EntityProfile(int32_t lot) {
|
||||
this->m_Lot = lot;
|
||||
}
|
||||
|
||||
void EntityProfile::LoadEntityProfiles(const std::string& filename) {
|
||||
tinyxml2::XMLDocument doc;
|
||||
doc.LoadFile(filename.c_str());
|
||||
|
||||
const auto root = doc.FirstChildElement("EntityProfiles");
|
||||
|
||||
for (auto elem = root->FirstChildElement("EntityProfile"); elem != nullptr; elem = elem->NextSiblingElement("EntityProfile")) {
|
||||
const auto lot = elem->IntAttribute("lot");
|
||||
|
||||
EntityProfile profile(lot);
|
||||
|
||||
profile.m_Level = elem->IntAttribute("level");
|
||||
profile.m_Health = elem->IntAttribute("health");
|
||||
profile.m_Armor = elem->IntAttribute("armor");
|
||||
|
||||
s_EntityProfiles.emplace(lot, profile);
|
||||
}
|
||||
}
|
||||
|
||||
EntityProfile* EntityProfile::FindEntityProfile(int32_t lot) {
|
||||
const auto it = s_EntityProfiles.find(lot);
|
||||
|
||||
if (it != s_EntityProfiles.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
49
dGame/dGrim/EntityProfile.h
Normal file
49
dGame/dGrim/EntityProfile.h
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __EENTITYPROFILE__H__
|
||||
#define __EENTITYPROFILE__H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "ItemModifierTemplate.h"
|
||||
|
||||
class EntityProfile {
|
||||
public:
|
||||
EntityProfile(int32_t lot);
|
||||
|
||||
~EntityProfile() = default;
|
||||
|
||||
int32_t GetLot() const {
|
||||
return this->m_Lot;
|
||||
}
|
||||
|
||||
int32_t GetLevel() const {
|
||||
return this->m_Level;
|
||||
}
|
||||
|
||||
int32_t GetHealth() const {
|
||||
return this->m_Health;
|
||||
}
|
||||
|
||||
int32_t GetArmor() const {
|
||||
return this->m_Armor;
|
||||
}
|
||||
|
||||
static void LoadEntityProfiles(const std::string& filename);
|
||||
|
||||
static EntityProfile* FindEntityProfile(int32_t lot);
|
||||
|
||||
private:
|
||||
int32_t m_Lot;
|
||||
|
||||
int32_t m_Level;
|
||||
|
||||
int32_t m_Health;
|
||||
|
||||
int32_t m_Armor;
|
||||
|
||||
static std::map<int32_t, EntityProfile> s_EntityProfiles;
|
||||
};
|
||||
|
||||
#endif //!__EENTITYPROFILE__H__
|
315
dGame/dGrim/ItemModifierTemplate.cpp
Normal file
315
dGame/dGrim/ItemModifierTemplate.cpp
Normal file
@ -0,0 +1,315 @@
|
||||
#include "ItemModifierTemplate.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <tinyxml2.h>
|
||||
#include "Item.h"
|
||||
#include "Entity.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "LevelProgressionComponent.h"
|
||||
|
||||
std::vector<ItemModifierTemplate> ItemModifierTemplate::s_ItemModifierTemplates;
|
||||
|
||||
void ItemModifierTemplate::LoadItemModifierTemplates(const std::string& filename) {
|
||||
std::vector<ItemModifierTemplate> itemModifierTemplates;
|
||||
|
||||
tinyxml2::XMLDocument doc;
|
||||
doc.LoadFile(filename.c_str());
|
||||
|
||||
tinyxml2::XMLElement* root = doc.FirstChildElement("Templates");
|
||||
|
||||
for (tinyxml2::XMLElement* element = root->FirstChildElement("Template"); element != nullptr; element = element->NextSiblingElement("Template")) {
|
||||
std::string name = element->Attribute("name");
|
||||
eStatRarity rarity = static_cast<eStatRarity>(element->IntAttribute("rarity"));
|
||||
bool isPrefix = element->BoolAttribute("isPrefix");
|
||||
bool isSuffix = element->BoolAttribute("isSuffix");
|
||||
int32_t priority = element->IntAttribute("priority");
|
||||
|
||||
ModifierRoll roll;
|
||||
roll.chance = element->FloatAttribute("chance");
|
||||
roll.levelMultiplier = element->FloatAttribute("levelMultiplier");
|
||||
roll.minRatity = element->IntAttribute("minRarity");
|
||||
roll.standardDeviation = element->FloatAttribute("standardDeviation");
|
||||
|
||||
std::string itemTypes = element->Attribute("itemTypes");
|
||||
std::stringstream ss(itemTypes);
|
||||
std::string itemType;
|
||||
while (std::getline(ss, itemType, ',')) {
|
||||
roll.itemTypes.push_back(static_cast<eItemType>(std::stoi(itemType)));
|
||||
}
|
||||
|
||||
ItemModifierTemplate itemModifierTemplate(name, rarity, isPrefix, isSuffix, roll, priority);
|
||||
|
||||
for (tinyxml2::XMLElement* statElement = element->FirstChildElement("Stat"); statElement != nullptr; statElement = statElement->NextSiblingElement("Stat")) {
|
||||
eStatTypes type = static_cast<eStatTypes>(statElement->IntAttribute("type"));
|
||||
eStatModifier modifier = static_cast<eStatModifier>(statElement->IntAttribute("modifier"));
|
||||
float value = statElement->FloatAttribute("value");
|
||||
|
||||
StatProperty statProperty(type, modifier, value);
|
||||
|
||||
itemModifierTemplate.AddStatProperty(statProperty);
|
||||
}
|
||||
|
||||
itemModifierTemplates.push_back(itemModifierTemplate);
|
||||
}
|
||||
|
||||
s_ItemModifierTemplates = itemModifierTemplates;
|
||||
}
|
||||
|
||||
ItemModifierTemplate* ItemModifierTemplate::FindItemModifierTemplate(const std::string& name) {
|
||||
for (ItemModifierTemplate& itemModifierTemplate : s_ItemModifierTemplates) {
|
||||
if (itemModifierTemplate.m_Name == name) {
|
||||
return &itemModifierTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ItemModifierTemplate::RollItemModifierTemplates(Item* item, eLootSourceType lootSourceType) {
|
||||
if (item->GetLot() == 6086) return;
|
||||
|
||||
auto* entity = item->GetInventory()->GetComponent()->GetParent();
|
||||
|
||||
auto* levelProgressionComponent = entity->GetComponent<LevelProgressionComponent>();
|
||||
|
||||
if (levelProgressionComponent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t level = levelProgressionComponent->GetLevel();
|
||||
|
||||
std::vector<ItemModifierTemplate*> prefixes;
|
||||
std::vector<ItemModifierTemplate*> suffixes;
|
||||
std::vector<StatProperty> statProperties;
|
||||
|
||||
const auto& itemInfo = item->GetInfo();
|
||||
|
||||
int32_t rarity = itemInfo.rarity;
|
||||
int32_t value = itemInfo.baseValue;
|
||||
|
||||
for (ItemModifierTemplate& itemModifierTemplate : s_ItemModifierTemplates) {
|
||||
const auto& rollInfo = itemModifierTemplate.m_ModifierRoll;
|
||||
|
||||
if (rollInfo.minRatity > rarity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rollInfo.itemTypes.size() > 0) {
|
||||
bool found = false;
|
||||
for (eItemType itemType : rollInfo.itemTypes) {
|
||||
if (itemType == static_cast<eItemType>(itemInfo.itemType)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
float chance = rollInfo.chance;
|
||||
|
||||
float rng = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
|
||||
|
||||
if (rng > chance) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (itemModifierTemplate.m_IsPrefix) {
|
||||
prefixes.push_back(&itemModifierTemplate);
|
||||
}
|
||||
else if (itemModifierTemplate.m_IsSuffix) {
|
||||
suffixes.push_back(&itemModifierTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
// Randomize order of prefixes and suffixes
|
||||
std::random_shuffle(prefixes.begin(), prefixes.end());
|
||||
std::random_shuffle(suffixes.begin(), suffixes.end());
|
||||
|
||||
// Add the first prefix and suffix
|
||||
if (prefixes.size() > 0) {
|
||||
ItemModifierTemplate* prefix = prefixes[0];
|
||||
|
||||
item->GetModifiers().push_back(prefix);
|
||||
}
|
||||
|
||||
if (suffixes.size() > 0) {
|
||||
ItemModifierTemplate* suffix = suffixes[0];
|
||||
|
||||
item->GetModifiers().push_back(suffix);
|
||||
}
|
||||
|
||||
// If there are more than one prefix or suffix, there is a 0.05 chance to add another one
|
||||
if (prefixes.size() > 1) {
|
||||
float rng = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
|
||||
|
||||
if (rng < 0.05f) {
|
||||
ItemModifierTemplate* prefix = prefixes[1];
|
||||
|
||||
item->GetModifiers().push_back(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
if (suffixes.size() > 1) {
|
||||
float rng = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
|
||||
|
||||
if (rng < 0.05f) {
|
||||
ItemModifierTemplate* suffix = suffixes[1];
|
||||
|
||||
item->GetModifiers().push_back(suffix);
|
||||
}
|
||||
}
|
||||
|
||||
// Add stat properties from prefixes and suffixes
|
||||
for (ItemModifierTemplate* itemModifierTemplate : item->GetModifiers()) {
|
||||
const std::vector<StatProperty>& templateStatProperties = itemModifierTemplate->GetStatProperties();
|
||||
|
||||
auto rollInfo = itemModifierTemplate->m_ModifierRoll;
|
||||
|
||||
// Roll stat properties
|
||||
for (const StatProperty& statProperty : templateStatProperties) {
|
||||
float value = statProperty.value;
|
||||
|
||||
float rng = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
|
||||
|
||||
float standardDeviation = rollInfo.standardDeviation;
|
||||
|
||||
// (+/-) standardDeviation
|
||||
float deviation = value * ((rng * standardDeviation * 2.0f) - standardDeviation);
|
||||
|
||||
value += deviation;
|
||||
|
||||
float levelMultiplier = rollInfo.levelMultiplier;
|
||||
|
||||
float multiplier = 1.0f + (level * levelMultiplier);
|
||||
|
||||
switch (itemInfo.rarity)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
multiplier += 0.0f;
|
||||
break;
|
||||
case 2:
|
||||
multiplier += 0.5f;
|
||||
break;
|
||||
case 3:
|
||||
multiplier += 0.75f;
|
||||
break;
|
||||
case 4:
|
||||
multiplier += 1.25f;
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
value *= multiplier;
|
||||
|
||||
if (itemInfo.isTwoHanded) {
|
||||
value *= 1.75f;
|
||||
}
|
||||
|
||||
if (lootSourceType == eLootSourceType::VENDOR) {
|
||||
value *= 0.75f;
|
||||
}
|
||||
|
||||
// Round to 2 decimal places
|
||||
value = static_cast<float>(static_cast<int32_t>(value * 100.0f)) / 100.0f;
|
||||
|
||||
StatProperty newStatProperty(statProperty.type, statProperty.modifier, value);
|
||||
|
||||
statProperties.push_back(newStatProperty);
|
||||
}
|
||||
}
|
||||
|
||||
// Add stat properties
|
||||
for (const StatProperty& statProperty : statProperties) {
|
||||
item->GetStats().push_back(statProperty);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ItemModifierTemplate::ItemModifierTemplate(const std::string& name, eStatRarity rarity, bool isPrefix, bool isSuffix, const ModifierRoll& roll, int32_t priority) {
|
||||
this->m_Name = name;
|
||||
this->m_Rarity = rarity;
|
||||
this->m_IsPrefix = isPrefix;
|
||||
this->m_IsSuffix = isSuffix;
|
||||
this->m_Priority = priority;
|
||||
this->m_ModifierRoll = roll;
|
||||
this->m_StatProperties = {};
|
||||
}
|
||||
|
||||
void ItemModifierTemplate::AddStatProperty(const StatProperty& statProperty) {
|
||||
m_StatProperties.push_back(statProperty);
|
||||
}
|
||||
|
||||
const std::vector<StatProperty>& ItemModifierTemplate::GetStatProperties() const {
|
||||
return m_StatProperties;
|
||||
}
|
||||
|
||||
std::string ItemModifierTemplate::HtmlString() const {
|
||||
std::stringstream ss;
|
||||
ss << "<font color=\"#";
|
||||
|
||||
switch (m_Rarity)
|
||||
{
|
||||
case eStatRarity::Common:
|
||||
ss << "FFFFFF";
|
||||
break;
|
||||
case eStatRarity::Uncommon:
|
||||
ss << "00FF00";
|
||||
break;
|
||||
case eStatRarity::Rare:
|
||||
ss << "0077FF";
|
||||
break;
|
||||
case eStatRarity::Epic:
|
||||
ss << "FF00FF";
|
||||
break;
|
||||
case eStatRarity::Legendary:
|
||||
ss << "FF7700";
|
||||
break;
|
||||
case eStatRarity::Relic:
|
||||
ss << "FFC391";
|
||||
break;
|
||||
default:
|
||||
ss << "FFFFFF";
|
||||
break;
|
||||
}
|
||||
|
||||
ss << "\">";
|
||||
|
||||
ss << m_Name << "</font>\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ItemModifierTemplate::HtmlString(const std::vector<ItemModifierTemplate*>& itemModifierTemplates) {
|
||||
/*
|
||||
Prefix-1 Prefix-2 NAME Suffix-1 Suffix-2
|
||||
*/
|
||||
std::stringstream ss;
|
||||
|
||||
for (ItemModifierTemplate* itemModifierTemplate : itemModifierTemplates) {
|
||||
if (itemModifierTemplate->m_IsPrefix) {
|
||||
ss << itemModifierTemplate->HtmlString();
|
||||
}
|
||||
}
|
||||
|
||||
ss << "<font color=\"#56B555\">NAME</font>";
|
||||
|
||||
for (ItemModifierTemplate* itemModifierTemplate : itemModifierTemplates) {
|
||||
if (itemModifierTemplate->m_IsSuffix) {
|
||||
ss << itemModifierTemplate->HtmlString();
|
||||
}
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
const std::string& ItemModifierTemplate::GetName() const {
|
||||
return m_Name;
|
||||
}
|
60
dGame/dGrim/ItemModifierTemplate.h
Normal file
60
dGame/dGrim/ItemModifierTemplate.h
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __ITEMMODIFIERTEMPLATE__H__
|
||||
#define __ITEMMODIFIERTEMPLATE__H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "StatProperty.h"
|
||||
#include "StatRarity.h"
|
||||
#include "eItemType.h"
|
||||
#include "eLootSourceType.h"
|
||||
|
||||
struct ModifierRoll {
|
||||
std::vector<eItemType> itemTypes;
|
||||
int32_t minRatity;
|
||||
float chance;
|
||||
float levelMultiplier;
|
||||
float standardDeviation;
|
||||
};
|
||||
|
||||
class ItemModifierTemplate {
|
||||
public:
|
||||
ItemModifierTemplate(const std::string& name, eStatRarity rarity, bool isPrefix, bool isSuffix, const ModifierRoll& roll, int32_t priority = 0);
|
||||
~ItemModifierTemplate() = default;
|
||||
|
||||
void AddStatProperty(const StatProperty& statProperty);
|
||||
|
||||
const std::vector<StatProperty>& GetStatProperties() const;
|
||||
|
||||
std::string HtmlString() const;
|
||||
|
||||
const std::string& GetName() const;
|
||||
|
||||
static void LoadItemModifierTemplates(const std::string& filename);
|
||||
|
||||
static ItemModifierTemplate* FindItemModifierTemplate(const std::string& name);
|
||||
|
||||
static void RollItemModifierTemplates(class Item* item, eLootSourceType lootSourceType);
|
||||
|
||||
static std::string HtmlString(const std::vector<ItemModifierTemplate*>& itemModifierTemplates);
|
||||
|
||||
private:
|
||||
std::string m_Name;
|
||||
|
||||
eStatRarity m_Rarity;
|
||||
|
||||
ModifierRoll m_ModifierRoll;
|
||||
|
||||
bool m_IsPrefix;
|
||||
bool m_IsSuffix;
|
||||
|
||||
int32_t m_Priority;
|
||||
|
||||
std::vector<StatProperty> m_StatProperties;
|
||||
|
||||
static std::vector<ItemModifierTemplate> s_ItemModifierTemplates;
|
||||
};
|
||||
|
||||
#endif //!__ITEMMODIFIERTEMPLATE__H__
|
67
dGame/dGrim/ResistanceProfile.cpp
Normal file
67
dGame/dGrim/ResistanceProfile.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "ResistanceProfile.h"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
std::map<int32_t, ResistanceProfile> ResistanceProfile::s_ResistanceProfiles;
|
||||
|
||||
void ResistanceProfile::LoadResistanceProfiles(const std::string& filename) {
|
||||
tinyxml2::XMLDocument doc;
|
||||
doc.LoadFile(filename.c_str());
|
||||
|
||||
auto root = doc.FirstChildElement("ResistanceProfiles");
|
||||
|
||||
for (auto element = root->FirstChildElement("ResistanceProfile"); element != nullptr; element = element->NextSiblingElement("ResistanceProfile")) {
|
||||
// lot,lot,...
|
||||
auto lots = element->Attribute("lot");
|
||||
|
||||
if (lots == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& splits = GeneralUtils::SplitString(lots, ',');
|
||||
|
||||
for (const auto& split : splits) {
|
||||
const auto lot = std::stoi(split);
|
||||
|
||||
ResistanceProfile resistanceProfile(lot);
|
||||
|
||||
for (auto resistanceElement = element->FirstChildElement("Resistance"); resistanceElement != nullptr; resistanceElement = resistanceElement->NextSiblingElement("Resistance")) {
|
||||
auto statType = resistanceElement->IntAttribute("type");
|
||||
auto value = resistanceElement->FloatAttribute("value");
|
||||
|
||||
resistanceProfile.AddResistanceProfile(static_cast<eStatTypes>(statType), value);
|
||||
}
|
||||
|
||||
s_ResistanceProfiles.emplace(lot, resistanceProfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ResistanceProfile* ResistanceProfile::FindResistanceProfile(int32_t lot) {
|
||||
const auto& it = s_ResistanceProfiles.find(lot);
|
||||
|
||||
if (it != s_ResistanceProfiles.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResistanceProfile::ResistanceProfile(int32_t lot) {
|
||||
this->m_Lot = lot;
|
||||
}
|
||||
|
||||
void ResistanceProfile::AddResistanceProfile(eStatTypes statType, float value) {
|
||||
m_ResistanceProfile[statType] = value;
|
||||
}
|
||||
|
||||
float ResistanceProfile::GetResistanceProfile(eStatTypes statType) const {
|
||||
const auto& it = m_ResistanceProfile.find(statType);
|
||||
|
||||
if (it != m_ResistanceProfile.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
33
dGame/dGrim/ResistanceProfile.h
Normal file
33
dGame/dGrim/ResistanceProfile.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __ERESISTANCEPROFILE__H__
|
||||
#define __ERESISTANCEPROFILE__H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "ItemModifierTemplate.h"
|
||||
|
||||
class ResistanceProfile {
|
||||
public:
|
||||
ResistanceProfile(int32_t lot);
|
||||
|
||||
~ResistanceProfile() = default;
|
||||
|
||||
void AddResistanceProfile(eStatTypes statType, float value);
|
||||
|
||||
float GetResistanceProfile(eStatTypes statType) const;
|
||||
|
||||
static void LoadResistanceProfiles(const std::string& filename);
|
||||
|
||||
static ResistanceProfile* FindResistanceProfile(int32_t lot);
|
||||
|
||||
private:
|
||||
int32_t m_Lot;
|
||||
|
||||
std::map<eStatTypes, float> m_ResistanceProfile;
|
||||
|
||||
static std::map<int32_t, ResistanceProfile> s_ResistanceProfiles;
|
||||
};
|
||||
|
||||
#endif //!__ERESISTANCEPROFILE__H__
|
58
dGame/dGrim/SpawnPatterns.cpp
Normal file
58
dGame/dGrim/SpawnPatterns.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include "SpawnPatterns.h"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
std::map<int32_t, SpawnPatterns> SpawnPatterns::s_SpawnPatterns;
|
||||
|
||||
void SpawnPatterns::LoadSpawnPatterns(const std::string& filename) {
|
||||
tinyxml2::XMLDocument doc;
|
||||
doc.LoadFile(filename.c_str());
|
||||
|
||||
auto root = doc.FirstChildElement("SpawnPatterns");
|
||||
|
||||
for (auto element = root->FirstChildElement("SpawnPattern"); element != nullptr; element = element->NextSiblingElement("SpawnPattern")) {
|
||||
auto lot = element->IntAttribute("lot");
|
||||
|
||||
SpawnPatterns spawnPatterns(lot);
|
||||
|
||||
for (auto spawnElement = element->FirstChildElement("Spawn"); spawnElement != nullptr; spawnElement = spawnElement->NextSiblingElement("Spawn")) {
|
||||
auto rating = spawnElement->FloatAttribute("rating");
|
||||
auto chance = spawnElement->FloatAttribute("chance");
|
||||
|
||||
std::vector<int32_t> spawns;
|
||||
|
||||
for (auto spawn = spawnElement->FirstChildElement("SpawnLot"); spawn != nullptr; spawn = spawn->NextSiblingElement("SpawnID")) {
|
||||
auto spawnID = spawn->IntAttribute("lot");
|
||||
|
||||
spawns.push_back(spawnID);
|
||||
}
|
||||
|
||||
spawnPatterns.AddSpawnPatterns(rating, chance, spawns);
|
||||
}
|
||||
|
||||
s_SpawnPatterns.emplace(lot, spawnPatterns);
|
||||
}
|
||||
}
|
||||
|
||||
SpawnPatterns* SpawnPatterns::FindSpawnPatterns(int32_t lot) {
|
||||
const auto& it = s_SpawnPatterns.find(lot);
|
||||
|
||||
if (it != s_SpawnPatterns.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SpawnPatterns::SpawnPatterns(int32_t lot)
|
||||
{
|
||||
this->m_Lot = lot;
|
||||
}
|
||||
|
||||
const std::map<float, std::pair<float, std::vector<int32_t>>>& SpawnPatterns::GetSpawnPatterns() const {
|
||||
return m_SpawnPatterns;
|
||||
}
|
||||
|
||||
void SpawnPatterns::AddSpawnPatterns(float rating, float change, std::vector<int32_t> spawns) {
|
||||
m_SpawnPatterns.emplace(rating, std::make_pair(change, spawns));
|
||||
}
|
33
dGame/dGrim/SpawnPatterns.h
Normal file
33
dGame/dGrim/SpawnPatterns.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __ESPAWNPATTERNS__H__
|
||||
#define __ESPAWNPATTERNS__H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "ItemModifierTemplate.h"
|
||||
|
||||
class SpawnPatterns {
|
||||
public:
|
||||
SpawnPatterns(int32_t lot);
|
||||
|
||||
~SpawnPatterns() = default;
|
||||
|
||||
void AddSpawnPatterns(float rating, float change, std::vector<int32_t> spawns);
|
||||
|
||||
const std::map<float, std::pair<float, std::vector<int32_t>>>& GetSpawnPatterns() const;
|
||||
|
||||
static void LoadSpawnPatterns(const std::string& filename);
|
||||
|
||||
static SpawnPatterns* FindSpawnPatterns(int32_t lot);
|
||||
|
||||
private:
|
||||
int32_t m_Lot;
|
||||
|
||||
std::map<float, std::pair<float, std::vector<int32_t>>> m_SpawnPatterns;
|
||||
|
||||
static std::map<int32_t, SpawnPatterns> s_SpawnPatterns;
|
||||
};
|
||||
|
||||
#endif //!__ESPAWNPATTERNS__H__
|
18
dGame/dGrim/StatModifier.h
Normal file
18
dGame/dGrim/StatModifier.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __ESTATMODIFIER__H__
|
||||
#define __ESTATMODIFIER__H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class eStatModifier : uint32_t {
|
||||
Absolute = 0,
|
||||
Percent = 1,
|
||||
DamageAbsolute = 2,
|
||||
DamagePercent = 3,
|
||||
DamageResistance = 4,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
#endif //!__ESTATMODIFIER__H__
|
132
dGame/dGrim/StatProperty.cpp
Normal file
132
dGame/dGrim/StatProperty.cpp
Normal file
@ -0,0 +1,132 @@
|
||||
#include "StatProperty.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
StatProperty::StatProperty(eStatTypes type, eStatModifier modifier, float value) {
|
||||
this->type = type;
|
||||
this->modifier = modifier;
|
||||
this->value = value;
|
||||
}
|
||||
|
||||
std::string StatProperty::HtmlString() {
|
||||
// "<font color=\"#38B6FF\">Physical: +20%</font>\n..."
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "<font color=\"";
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case eStatTypes::Health:
|
||||
ss << "#FF0000";
|
||||
break;
|
||||
case eStatTypes::Armor:
|
||||
ss << "#525252";
|
||||
break;
|
||||
case eStatTypes::Imagination:
|
||||
ss << "#0077FF";
|
||||
break;
|
||||
case eStatTypes::Physical:
|
||||
ss << "#FF9500";
|
||||
break;
|
||||
case eStatTypes::Electric:
|
||||
ss << "#0059FF";
|
||||
break;
|
||||
case eStatTypes::Corruption:
|
||||
ss << "#5500FF";
|
||||
break;
|
||||
case eStatTypes::Heat:
|
||||
ss << "#FF6A00";
|
||||
break;
|
||||
case eStatTypes::Shadow:
|
||||
ss << "#0D0061";
|
||||
break;
|
||||
case eStatTypes::Pierce:
|
||||
ss << "#611200";
|
||||
break;
|
||||
case eStatTypes::Vitality:
|
||||
ss << "#2D0800";
|
||||
break;
|
||||
case eStatTypes::Domination:
|
||||
ss << "#CF00A5";
|
||||
break;
|
||||
default:
|
||||
ss << "#FFFFFF";
|
||||
break;
|
||||
}
|
||||
|
||||
ss << "\">";
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case eStatTypes::Health:
|
||||
ss << "Health";
|
||||
break;
|
||||
case eStatTypes::Armor:
|
||||
ss << "Armor";
|
||||
break;
|
||||
case eStatTypes::Imagination:
|
||||
ss << "Imagination";
|
||||
break;
|
||||
case eStatTypes::Physical:
|
||||
ss << "Physical";
|
||||
break;
|
||||
case eStatTypes::Electric:
|
||||
ss << "Electric";
|
||||
break;
|
||||
case eStatTypes::Corruption:
|
||||
ss << "Corruption";
|
||||
break;
|
||||
case eStatTypes::Heat:
|
||||
ss << "Heat";
|
||||
break;
|
||||
case eStatTypes::Shadow:
|
||||
ss << "Shadow";
|
||||
break;
|
||||
case eStatTypes::Pierce:
|
||||
ss << "Pierce";
|
||||
break;
|
||||
case eStatTypes::Vitality:
|
||||
ss << "Vitality";
|
||||
break;
|
||||
case eStatTypes::Domination:
|
||||
ss << "Domination";
|
||||
break;
|
||||
default:
|
||||
ss << "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
switch (modifier)
|
||||
{
|
||||
case eStatModifier::DamageResistance:
|
||||
ss << " Resistance";
|
||||
break;
|
||||
case eStatModifier::DamagePercent:
|
||||
case eStatModifier::DamageAbsolute:
|
||||
ss << " Damage";
|
||||
break;
|
||||
}
|
||||
|
||||
ss << "</font>";
|
||||
|
||||
switch (modifier)
|
||||
{
|
||||
case eStatModifier::Percent:
|
||||
case eStatModifier::DamagePercent:
|
||||
case eStatModifier::DamageResistance:
|
||||
{
|
||||
float percent = value * 100.0f;
|
||||
// Round to 2 decimal places
|
||||
percent = static_cast<float>(static_cast<int32_t>(percent * 100.0f)) / 100.0f;
|
||||
ss << ": +" << percent << "%";
|
||||
}
|
||||
break;
|
||||
case eStatModifier::Absolute:
|
||||
case eStatModifier::DamageAbsolute:
|
||||
default:
|
||||
ss << ": +" << value;
|
||||
break;
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
26
dGame/dGrim/StatProperty.h
Normal file
26
dGame/dGrim/StatProperty.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __ESTAT__H__
|
||||
#define __ESTAT__H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "StatModifier.h"
|
||||
#include "StatTypes.h"
|
||||
|
||||
#define BASE_MULTIPLIER 100.0f
|
||||
|
||||
struct StatProperty
|
||||
{
|
||||
eStatTypes type;
|
||||
eStatModifier modifier;
|
||||
float value;
|
||||
|
||||
StatProperty(eStatTypes type, eStatModifier modifier, float value);
|
||||
|
||||
std::string HtmlString();
|
||||
};
|
||||
|
||||
|
||||
#endif //!__ESTAT__H__
|
19
dGame/dGrim/StatRarity.h
Normal file
19
dGame/dGrim/StatRarity.h
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __ESTATRARITY__H__
|
||||
#define __ESTATRARITY__H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class eStatRarity : uint32_t {
|
||||
Common = 0,
|
||||
Uncommon = 1,
|
||||
Rare = 2,
|
||||
Epic = 3,
|
||||
Legendary = 4,
|
||||
Relic = 5,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
#endif //!__ESTATRARITY__H__
|
27
dGame/dGrim/StatTypes.h
Normal file
27
dGame/dGrim/StatTypes.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __ESTATTYPES__H__
|
||||
#define __ESTATTYPES__H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class eStatTypes : uint32_t {
|
||||
// Stats
|
||||
Health = 0,
|
||||
Armor = 1,
|
||||
Imagination = 2,
|
||||
|
||||
// Damage
|
||||
Physical = 3,
|
||||
Electric = 4,
|
||||
Corruption = 5,
|
||||
Heat = 6,
|
||||
Shadow = 7,
|
||||
Pierce = 8,
|
||||
Vitality = 9,
|
||||
Domination = 10,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
#endif //!__ESTATTYPES__H__
|
@ -41,6 +41,7 @@ Item::Item(const LWOOBJID id, const LOT lot, Inventory* inventory, const uint32_
|
||||
this->info = &Inventory::FindItemComponent(lot);
|
||||
this->preconditions = new PreconditionExpression(this->info->reqPrecondition);
|
||||
this->subKey = subKey;
|
||||
this->stats = {};
|
||||
|
||||
inventory->AddManagedItem(this);
|
||||
}
|
||||
@ -76,6 +77,7 @@ Item::Item(
|
||||
this->bound = info->isBOP || bound;
|
||||
this->preconditions = new PreconditionExpression(this->info->reqPrecondition);
|
||||
this->subKey = subKey;
|
||||
this->stats = {};
|
||||
|
||||
LWOOBJID id = ObjectIDManager::GenerateRandomObjectID();
|
||||
|
||||
@ -102,6 +104,21 @@ Item::Item(
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(inventory->GetComponent()->GetParent());
|
||||
}
|
||||
|
||||
if (parent != LWOOBJID_EMPTY) return;
|
||||
|
||||
eItemType itemType = static_cast<eItemType>(info->itemType);
|
||||
|
||||
if (info->stackSize != 1) return;
|
||||
|
||||
if (itemType != eItemType::RIGHT_HAND &&
|
||||
itemType != eItemType::LEFT_HAND &&
|
||||
itemType != eItemType::HAT &&
|
||||
itemType != eItemType::CHEST &&
|
||||
itemType != eItemType::LEGS &&
|
||||
itemType != eItemType::NECK) return;
|
||||
|
||||
ItemModifierTemplate::RollItemModifierTemplates(this, lootSourceType);
|
||||
}
|
||||
|
||||
LWOOBJID Item::GetId() const {
|
||||
@ -124,6 +141,14 @@ std::vector<LDFBaseData*>& Item::GetConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
std::vector<StatProperty>& Item::GetStats() {
|
||||
return stats;
|
||||
}
|
||||
|
||||
std::vector<ItemModifierTemplate*>& Item::GetModifiers() {
|
||||
return templates;
|
||||
}
|
||||
|
||||
const CDItemComponent& Item::GetInfo() const {
|
||||
return *info;
|
||||
}
|
||||
@ -220,6 +245,37 @@ void Item::Equip(const bool skipChecks) {
|
||||
}
|
||||
|
||||
inventory->GetComponent()->EquipItem(this, skipChecks);
|
||||
|
||||
if (info->equipLocation != "special_r") return;
|
||||
|
||||
inventory->GetComponent()->GetParent()->AddCallbackTimer(1, [this]() {
|
||||
// Find the greatest stat for this item
|
||||
eStatTypes statType = eStatTypes::Physical;
|
||||
float statValue = 0.0f;
|
||||
|
||||
for (const auto& stat : stats) {
|
||||
if (stat.value > statValue) {
|
||||
statType = stat.type;
|
||||
statValue = stat.value;
|
||||
}
|
||||
}
|
||||
|
||||
switch (statType)
|
||||
{
|
||||
case eStatTypes::Electric:
|
||||
GameMessages::SendPlayFXEffect(id, 4027, u"create", "electric");
|
||||
break;
|
||||
case eStatTypes::Shadow:
|
||||
GameMessages::SendPlayFXEffect(id, 2710, u"create", "shadow");
|
||||
break;
|
||||
case eStatTypes::Corruption:
|
||||
GameMessages::SendPlayFXEffect(id, 663, u"create", "corruption");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Item::UnEquip() {
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include "Preconditions.h"
|
||||
#include "eInventoryType.h"
|
||||
#include "eLootSourceType.h"
|
||||
#include "StatProperty.h"
|
||||
#include "ItemModifierTemplate.h"
|
||||
|
||||
/**
|
||||
* An item that can be stored in an inventory and optionally consumed or equipped
|
||||
@ -116,6 +118,18 @@ public:
|
||||
*/
|
||||
std::vector<LDFBaseData*>& GetConfig();
|
||||
|
||||
/**
|
||||
* Returns the stats for this item
|
||||
* @return the stats for this item
|
||||
*/
|
||||
std::vector<StatProperty>& GetStats();
|
||||
|
||||
/**
|
||||
* Returns the modifiers for this item
|
||||
* @return the modifiers for this item
|
||||
*/
|
||||
std::vector<ItemModifierTemplate*>& GetModifiers();
|
||||
|
||||
/**
|
||||
* Returns the database info for this item
|
||||
* @return the database info for this item
|
||||
@ -255,6 +269,16 @@ private:
|
||||
*/
|
||||
std::vector<LDFBaseData*> config;
|
||||
|
||||
/**
|
||||
* Modifiers
|
||||
*/
|
||||
std::vector<StatProperty> stats;
|
||||
|
||||
/**
|
||||
* Templates
|
||||
*/
|
||||
std::vector<ItemModifierTemplate*> templates;
|
||||
|
||||
/**
|
||||
* The inventory this item belongs to
|
||||
*/
|
||||
|
@ -82,6 +82,7 @@
|
||||
#include "eConnectionType.h"
|
||||
#include "eChatInternalMessageType.h"
|
||||
#include "eMasterMessageType.h"
|
||||
#include "CDSkillBehaviorTable.h"
|
||||
|
||||
#include "CDObjectsTable.h"
|
||||
#include "CDZoneTableTable.h"
|
||||
@ -194,6 +195,67 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
||||
return;
|
||||
}
|
||||
|
||||
if (chatCommand == "d" && args.size() >= 2) {
|
||||
// Print all the arguments
|
||||
Game::logger->Log("SlashCommandHandler", "Args: %s, %s", args[0].c_str(), args[1].c_str());
|
||||
|
||||
int32_t ticketIndex;
|
||||
LWOOBJID itemID;
|
||||
|
||||
AMFArrayValue amfArgs;
|
||||
|
||||
if (!GeneralUtils::TryParse(args[0], ticketIndex) || !GeneralUtils::TryParse(args[1], itemID)) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::stringstream message;
|
||||
message << "desc";
|
||||
message << ticketIndex;
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* item = inventoryComponent->FindItemById(itemID);
|
||||
|
||||
if (item == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
Game::logger->Log("SlashCommandHandler", "Sending ticket %i, item LOT %i", ticketIndex, item->GetLot());
|
||||
|
||||
auto& stats = item->GetStats();
|
||||
|
||||
if (!stats.empty())
|
||||
{
|
||||
std::stringstream description;
|
||||
std::stringstream name;
|
||||
|
||||
name << "NAME";
|
||||
|
||||
amfArgs.Insert("t", true);
|
||||
|
||||
description << ItemModifierTemplate::HtmlString(item->GetModifiers()) << "\n";
|
||||
|
||||
for (auto& stat : stats) {
|
||||
description << "\n" << stat.HtmlString();
|
||||
}
|
||||
|
||||
amfArgs.Insert("d", description.str());
|
||||
|
||||
amfArgs.Insert("n", name.str());
|
||||
}
|
||||
else
|
||||
{
|
||||
amfArgs.Insert("t", false);
|
||||
}
|
||||
|
||||
GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, message.str(), amfArgs);
|
||||
}
|
||||
|
||||
if (chatCommand == "who") {
|
||||
ChatPackets::SendSystemMessage(
|
||||
sysAddr,
|
||||
@ -722,10 +784,141 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
||||
GameMessages::SendPlayFXEffect(entity->GetObjectID(), effectID, GeneralUtils::ASCIIToUTF16(args[1]), args[2]);
|
||||
}
|
||||
|
||||
if (chatCommand == "playeffect-weapon" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) {
|
||||
int32_t effectID = 0;
|
||||
|
||||
if (!GeneralUtils::TryParse(args[0], effectID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& equipped = inventoryComponent->GetEquippedItems();
|
||||
|
||||
const auto& weapon = equipped.find("special_r");
|
||||
|
||||
if (weapon == equipped.end()) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"You need to have a weapon equipped.");
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: use fallible ASCIIToUTF16 conversion, because non-ascii isn't valid anyway
|
||||
GameMessages::SendPlayFXEffect(weapon->second.id, effectID, GeneralUtils::ASCIIToUTF16(args[1]), args[2]);
|
||||
}
|
||||
|
||||
|
||||
if (chatCommand == "itemx" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) {
|
||||
int32_t itemLot;
|
||||
|
||||
if (!GeneralUtils::TryParse(args[0], itemLot)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<LDFBaseData*> data;
|
||||
|
||||
for (int32_t i = 1; (i + 1) < args.size(); i += 2) {
|
||||
const auto& key = args[i];
|
||||
const auto& value = args[i + 1];
|
||||
|
||||
|
||||
auto* x = new LDFData<std::string>(GeneralUtils::ASCIIToUTF16(key), value);
|
||||
|
||||
data.push_back(x);
|
||||
}
|
||||
|
||||
inventoryComponent->AddItem(itemLot, 1, eLootSourceType::NONE, INVALID, data);
|
||||
}
|
||||
|
||||
if (chatCommand == "dismantle" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
||||
GameMessages::SendServerTradeInvite(
|
||||
entity->GetObjectID(),
|
||||
false,
|
||||
entity->GetObjectID(),
|
||||
GeneralUtils::UTF8ToUTF16("Dismantle"),
|
||||
sysAddr
|
||||
);
|
||||
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Opened dismantle window.");
|
||||
}
|
||||
|
||||
if (chatCommand == "bar" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
|
||||
int32_t barID;
|
||||
|
||||
if (!GeneralUtils::TryParse(args[0], barID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
inventoryComponent->SetSelectedSkillBar(static_cast<eSkillBar>(barID));
|
||||
}
|
||||
|
||||
if (chatCommand == "setskill" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) {
|
||||
int32_t barID;
|
||||
int32_t slotID;
|
||||
int32_t skillID;
|
||||
|
||||
if (!GeneralUtils::TryParse(args[0], barID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GeneralUtils::TryParse(args[1], slotID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GeneralUtils::TryParse(args[2], skillID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (skillID == 0) {
|
||||
inventoryComponent->UnequipSkill(static_cast<eSkillBar>(barID), static_cast<BehaviorSlot>(slotID));
|
||||
return;
|
||||
}
|
||||
|
||||
inventoryComponent->EquipSkill(static_cast<eSkillBar>(barID), static_cast<BehaviorSlot>(slotID), skillID);
|
||||
}
|
||||
|
||||
if (chatCommand == "stopeffect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
|
||||
GameMessages::SendStopFXEffect(entity, true, args[0]);
|
||||
}
|
||||
|
||||
if (chatCommand == "skill" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
|
||||
int32_t skillID;
|
||||
|
||||
if (!GeneralUtils::TryParse(args[0], skillID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CDSkillBehaviorTable* skillTable = CDClientManager::Instance().GetTable<CDSkillBehaviorTable>();
|
||||
uint32_t behaviorID = skillTable->GetSkillByID(skillID).behaviorID;
|
||||
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Skill ID: " + GeneralUtils::to_u16string(skillID) + u" Behavior ID: " + GeneralUtils::to_u16string(behaviorID));
|
||||
|
||||
auto* skillComponent = entity->GetComponent<SkillComponent>();
|
||||
|
||||
skillComponent->CalculateBehavior(skillID, behaviorID, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
if (chatCommand == "setanntitle" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
||||
if (args.size() < 0) return;
|
||||
|
||||
@ -2010,6 +2203,38 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Trigger: " + (GeneralUtils::to_u16string(trigger->id)));
|
||||
}
|
||||
}
|
||||
} else if (args[1] == "-h") {
|
||||
auto* destroyableComponent = closest->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (destroyableComponent == nullptr) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!");
|
||||
return;
|
||||
}
|
||||
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Health: " + (GeneralUtils::to_u16string(destroyableComponent->GetHealth())));
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Max health: " + (GeneralUtils::to_u16string(destroyableComponent->GetMaxHealth())));
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Armor: " + (GeneralUtils::to_u16string(destroyableComponent->GetArmor())));
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Max armor: " + (GeneralUtils::to_u16string(destroyableComponent->GetMaxArmor())));
|
||||
} else if (args[1] == "-c" && args.size() >= 3) {
|
||||
int32_t skillID;
|
||||
|
||||
if (!GeneralUtils::TryParse(args[2], skillID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CDSkillBehaviorTable* skillTable = CDClientManager::Instance().GetTable<CDSkillBehaviorTable>();
|
||||
uint32_t behaviorID = skillTable->GetSkillByID(skillID).behaviorID;
|
||||
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Skill ID: " + GeneralUtils::to_u16string(skillID) + u" Behavior ID: " + GeneralUtils::to_u16string(behaviorID));
|
||||
|
||||
auto* skillComponent = closest->GetComponent<SkillComponent>();
|
||||
|
||||
if (skillComponent == nullptr) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"No skill component on this entity!");
|
||||
return;
|
||||
}
|
||||
|
||||
skillComponent->CalculateBehavior(skillID, behaviorID, LWOOBJID_EMPTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include "SkillComponent.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "RenderComponent.h"
|
||||
#include "Player.h"
|
||||
#include "ZoneInstanceManager.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
@ -35,6 +37,12 @@ void BossSpiderQueenEnemyServer::OnStartup(Entity* self) {
|
||||
|
||||
if (!destroyable || !controllable) return;
|
||||
|
||||
destroyable->GetInfo().level = 3;
|
||||
destroyable->GetInfo().armor = 330;
|
||||
destroyable->ComputeBaseStats(true);
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(self);
|
||||
|
||||
// Determine Spider Boss health transition thresholds
|
||||
int spiderBossHealth = destroyable->GetMaxHealth();
|
||||
int transitionTickHealth = spiderBossHealth / 3;
|
||||
@ -45,6 +53,11 @@ void BossSpiderQueenEnemyServer::OnStartup(Entity* self) {
|
||||
|
||||
originRotation = controllable->GetRotation();
|
||||
combat->SetStunImmune(true);
|
||||
combat->SetDisabled(true);
|
||||
|
||||
self->AddCallbackTimer(10, [this, self]() {
|
||||
combat->SetDisabled(false);
|
||||
});
|
||||
|
||||
m_CurrentBossStage = 1;
|
||||
|
||||
@ -103,20 +116,22 @@ void BossSpiderQueenEnemyServer::WithdrawSpider(Entity* self, const bool withdra
|
||||
|
||||
baseCombatAi->SetDisabled(true);
|
||||
|
||||
float animTime = PlayAnimAndReturnTime(self, spiderWithdrawAnim);
|
||||
float withdrawTime = animTime - 0.25f;
|
||||
self->AddCallbackTimer(3, [this, self]() {
|
||||
float animTime = PlayAnimAndReturnTime(self, spiderWithdrawAnim);
|
||||
float withdrawTime = animTime - 0.25f;
|
||||
|
||||
combat->SetStunImmune(false);
|
||||
combat->Stun(withdrawTime + 6.0f);
|
||||
combat->SetStunImmune(true);
|
||||
combat->SetStunImmune(false);
|
||||
combat->Stun(withdrawTime + 6.0f);
|
||||
combat->SetStunImmune(true);
|
||||
|
||||
//TODO: Set faction to -1 and set immunity
|
||||
destroyable->SetFaction(-1);
|
||||
destroyable->SetIsImmune(true);
|
||||
EntityManager::Instance()->SerializeEntity(self);
|
||||
//TODO: Set faction to -1 and set immunity
|
||||
destroyable->SetFaction(-1);
|
||||
destroyable->SetIsImmune(true);
|
||||
EntityManager::Instance()->SerializeEntity(self);
|
||||
|
||||
self->AddTimer("WithdrawComplete", withdrawTime + 1.0f);
|
||||
waitForIdle = true;
|
||||
self->AddTimer("WithdrawComplete", withdrawTime + 1.0f);
|
||||
waitForIdle = true;
|
||||
});
|
||||
} else {
|
||||
controllable->SetStatic(false);
|
||||
|
||||
@ -146,6 +161,10 @@ void BossSpiderQueenEnemyServer::WithdrawSpider(Entity* self, const bool withdra
|
||||
//Reset the current wave death counter
|
||||
m_DeathCounter = 0;
|
||||
|
||||
auto* destroyable = self->GetComponent<DestroyableComponent>();
|
||||
|
||||
destroyable->SetArmor(destroyable->GetMaxArmor() / 3);
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(self);
|
||||
|
||||
// Prepare a timer for post leap attack
|
||||
@ -297,9 +316,20 @@ void BossSpiderQueenEnemyServer::RunRainOfFire(Entity* self) {
|
||||
index++;
|
||||
}
|
||||
|
||||
const auto animTime = PlayAnimAndReturnTime(self, spiderROFAnim);
|
||||
self->AddCallbackTimer(5, [self, this]() {
|
||||
/*
|
||||
auto* skillComponent = self->GetComponent<SkillComponent>();
|
||||
|
||||
self->AddTimer("StartROF", animTime);
|
||||
skillComponent->Interrupt();
|
||||
|
||||
auto* baseCombatAIComponent = self->GetComponent<BaseCombatAIComponent>();*/
|
||||
|
||||
const auto animTime = PlayAnimAndReturnTime(self, spiderROFAnim);
|
||||
|
||||
//baseCombatAIComponent->Stun(animTime * 2);
|
||||
|
||||
self->AddTimer("StartROF", animTime);
|
||||
});
|
||||
}
|
||||
|
||||
void BossSpiderQueenEnemyServer::RainOfFireManager(Entity* self) {
|
||||
@ -324,6 +354,44 @@ void BossSpiderQueenEnemyServer::RainOfFireManager(Entity* self) {
|
||||
|
||||
skillComponent->CalculateBehavior(1376, 32168, LWOOBJID_EMPTY, true);
|
||||
|
||||
if (GeneralUtils::GenerateRandomNumber<int32_t>(0, 2) == 1)
|
||||
{
|
||||
entity->AddCallbackTimer(2, [entity](){
|
||||
EntityInfo info;
|
||||
info.lot = 16197;
|
||||
info.pos = entity->GetPosition();
|
||||
info.spawnerID = entity->GetObjectID();
|
||||
|
||||
auto* spawned = EntityManager::Instance()->CreateEntity(info);
|
||||
|
||||
EntityManager::Instance()->ConstructEntity(spawned);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < 15; i++)
|
||||
{
|
||||
entity->AddCallbackTimer((0.15 * i), [entity](){
|
||||
// Random area within 10 units on the X and Z axis, circle, using sin and cos
|
||||
float angle = GeneralUtils::GenerateRandomNumber<float>(0, 360) * M_PI / 180.0f;
|
||||
float radius = GeneralUtils::GenerateRandomNumber<float>(0, 10);
|
||||
|
||||
float x = radius * cos(angle);
|
||||
float z = radius * sin(angle);
|
||||
|
||||
EntityInfo info;
|
||||
info.lot = 10314;
|
||||
info.pos = entity->GetPosition() + NiPoint3(x, 0, z);
|
||||
info.spawnerID = entity->GetObjectID();
|
||||
|
||||
auto* spawned = EntityManager::Instance()->CreateEntity(info);
|
||||
|
||||
EntityManager::Instance()->ConstructEntity(spawned);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self->AddTimer("PollROFManager", 0.5f);
|
||||
|
||||
return;
|
||||
@ -563,6 +631,22 @@ void BossSpiderQueenEnemyServer::OnTimerDone(Entity* self, const std::string tim
|
||||
}
|
||||
}
|
||||
|
||||
void BossSpiderQueenEnemyServer::OnPlayerDied(Entity* self, Entity* player) {
|
||||
Game::logger->Log("BossSpiderQueenEnemyServer", "OnPlayerDied");
|
||||
|
||||
if (!player->IsPlayer()) return;
|
||||
|
||||
Game::logger->Log("BossSpiderQueenEnemyServer", "OnPlayerDied 2");
|
||||
|
||||
auto* ply = static_cast<Player*>(player);
|
||||
|
||||
ply->SendToZone(1100);
|
||||
|
||||
self->AddCallbackTimer(10, [] () {
|
||||
dZoneManager::Instance()->GetZoneControlObject()->SetVar(u"shutdown", true);
|
||||
});
|
||||
}
|
||||
|
||||
void BossSpiderQueenEnemyServer::OnHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) {
|
||||
if (m_CurrentBossStage > 0 && !self->HasTimer("RFS")) {
|
||||
self->AddTimer("RFS", 5.0f);
|
||||
|
@ -46,6 +46,8 @@ public:
|
||||
|
||||
void OnTimerDone(Entity* self, std::string timerName) override;
|
||||
|
||||
void OnPlayerDied(Entity* self, Entity* player) override;
|
||||
|
||||
private:
|
||||
//Regular variables:
|
||||
DestroyableComponent* destroyable = nullptr;
|
||||
@ -61,7 +63,7 @@ private:
|
||||
|
||||
//Const variables:
|
||||
|
||||
const std::vector<int> spiderWaveCntTable = { 2, 3 }; //The number of Dark Spiderling enemies to spawn per indexed wave number
|
||||
const std::vector<int> spiderWaveCntTable = { 6, 8 }; //The number of Dark Spiderling enemies to spawn per indexed wave number
|
||||
|
||||
const int ROFImpactCnt = 2; //The number of ROF impacts in each quadrant of the arena selected at random
|
||||
|
||||
|
@ -37,6 +37,7 @@ void EnemySpiderSpawner::OnTimerDone(Entity* self, std::string timerName) {
|
||||
if (timerName == "StartSpawnTime") {
|
||||
SpawnSpiderling(self);
|
||||
} else if (timerName == "SpawnSpiderling") {
|
||||
GameMessages::SendStopFXEffect(self, true, "egg_puff_b");
|
||||
GameMessages::SendPlayFXEffect(self->GetObjectID(), 644, u"create", "egg_puff_b", LWOOBJID_EMPTY, 1.0f, 1.0f, true);
|
||||
|
||||
//TODO: set the aggro radius larger
|
||||
@ -59,8 +60,10 @@ void EnemySpiderSpawner::OnTimerDone(Entity* self, std::string timerName) {
|
||||
movementAi->SetDestination(newEntity->GetPosition());
|
||||
*/
|
||||
}
|
||||
|
||||
self->AddTimer("StartSpawnTime", 5);
|
||||
|
||||
self->ScheduleKillAfterUpdate();
|
||||
//self->ScheduleKillAfterUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,6 +72,7 @@ void EnemySpiderSpawner::OnTimerDone(Entity* self, std::string timerName) {
|
||||
//--------------------------------------------------------------
|
||||
void EnemySpiderSpawner::SpawnSpiderling(Entity* self) {
|
||||
//Initiate the actual spawning
|
||||
GameMessages::SendStopFXEffect(self, true, "dropdustmedium");
|
||||
GameMessages::SendPlayFXEffect(self->GetObjectID(), 2260, u"rebuild_medium", "dropdustmedium", LWOOBJID_EMPTY, 1.0f, 1.0f, true);
|
||||
self->AddTimer("SpawnSpiderling", spawnTime);
|
||||
}
|
||||
|
@ -7,5 +7,5 @@ add_library(dWorldServer ${DWORLDSERVER_SOURCES})
|
||||
add_executable(WorldServer "WorldServer.cpp")
|
||||
|
||||
target_link_libraries(dWorldServer ${COMMON_LIBRARIES})
|
||||
target_link_libraries(WorldServer ${COMMON_LIBRARIES} dChatFilter dGame dZoneManager dPhysics Detour Recast tinyxml2 dWorldServer dNavigation)
|
||||
target_link_libraries(WorldServer ${COMMON_LIBRARIES} dChatFilter dGame dPhysics Detour Recast tinyxml2 dWorldServer dNavigation)
|
||||
|
||||
|
@ -71,6 +71,11 @@
|
||||
#include "eMasterMessageType.h"
|
||||
#include "eGameMessageType.h"
|
||||
#include "ZCompression.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "DamageProfile.h"
|
||||
#include "ResistanceProfile.h"
|
||||
#include "SpawnPatterns.h"
|
||||
#include "EntityProfile.h"
|
||||
|
||||
namespace Game {
|
||||
dLogger* logger = nullptr;
|
||||
@ -147,6 +152,12 @@ int main(int argc, char** argv) {
|
||||
Game::logger->Log("WorldServer", "Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
|
||||
Game::logger->Log("WorldServer", "Compiled on: %s", __TIMESTAMP__);
|
||||
|
||||
ItemModifierTemplate::LoadItemModifierTemplates((BinaryPathFinder::GetBinaryDir() / "ItemModifierTemplates.xml").string());
|
||||
DamageProfile::LoadDamageProfiles((BinaryPathFinder::GetBinaryDir() / "DamageProfiles.xml").string());
|
||||
ResistanceProfile::LoadResistanceProfiles((BinaryPathFinder::GetBinaryDir() / "ResistanceProfiles.xml").string());
|
||||
SpawnPatterns::LoadSpawnPatterns((BinaryPathFinder::GetBinaryDir() / "SpawnPatterns.xml").string());
|
||||
EntityProfile::LoadEntityProfiles((BinaryPathFinder::GetBinaryDir() / "EntityProfiles.xml").string());
|
||||
|
||||
if (Game::config->GetValue("disable_chat") == "1") chatDisabled = true;
|
||||
|
||||
try {
|
||||
@ -489,6 +500,8 @@ int main(int argc, char** argv) {
|
||||
framesSinceLastSQLPing = 0;
|
||||
} else framesSinceLastSQLPing++;
|
||||
|
||||
Spawner::UpdateRatings(deltaTime);
|
||||
|
||||
Metrics::EndMeasurement(MetricVariable::GameLoop);
|
||||
|
||||
Metrics::StartMeasurement(MetricVariable::Sleep);
|
||||
@ -512,6 +525,12 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
auto* controller = dZoneManager::Instance()->GetZoneControlObject();
|
||||
|
||||
if (controller != nullptr && controller->HasVar(u"shutdown") && controller->GetVar<bool>(u"shutdown")) {
|
||||
Game::shouldShutdown = true;
|
||||
}
|
||||
|
||||
if (Game::shouldShutdown && !worldShutdownSequenceComplete) {
|
||||
WorldShutdownProcess(zoneID);
|
||||
break;
|
||||
|
@ -3,4 +3,6 @@ set(DZONEMANAGER_SOURCES "dZoneManager.cpp"
|
||||
"Spawner.cpp"
|
||||
"Zone.cpp")
|
||||
|
||||
add_library(dZoneManager STATIC ${DZONEMANAGER_SOURCES})
|
||||
#add_library(dZoneManager STATIC ${DZONEMANAGER_SOURCES})
|
||||
|
||||
set(DZONEMANAGER_SOURCES ${DZONEMANAGER_SOURCES} PARENT_SCOPE)
|
||||
|
@ -6,6 +6,9 @@
|
||||
#include <functional>
|
||||
#include "GeneralUtils.h"
|
||||
#include "dZoneManager.h"
|
||||
#include "SpawnPatterns.h"
|
||||
|
||||
std::map<LOT, std::vector<std::pair<NiPoint3, float>>> Spawner::m_Ratings;
|
||||
|
||||
Spawner::Spawner(const SpawnerInfo info) {
|
||||
m_Info = info;
|
||||
@ -69,6 +72,10 @@ Spawner::Spawner(const SpawnerInfo info) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
m_SpawnPattern = SpawnPatterns::FindSpawnPatterns(m_Info.templateID);
|
||||
|
||||
m_LotsToCheck.push_back(m_Info.templateID);
|
||||
}
|
||||
|
||||
Spawner::~Spawner() {
|
||||
@ -102,6 +109,95 @@ Entity* Spawner::Spawn(std::vector<SpawnerNode*> freeNodes, const bool force) {
|
||||
m_EntityInfo.spawnerID = m_Info.spawnerID;
|
||||
}
|
||||
|
||||
bool usedSpawnPattern = false;
|
||||
|
||||
if (m_SpawnPattern != nullptr) {
|
||||
auto pattern = m_SpawnPattern->GetSpawnPatterns();
|
||||
|
||||
// Check the area rating
|
||||
// std::map<LOT, std::vector<std::pair<NiPoint3, float>>> m_Ratings
|
||||
for (const auto& lot : m_LotsToCheck)
|
||||
{
|
||||
const auto& it = m_Ratings.find(lot);
|
||||
|
||||
int32_t rating = 0;
|
||||
|
||||
if (it != m_Ratings.end()) {
|
||||
// Check if we are within 50units of a rating
|
||||
for (const auto& ratingIt : it->second)
|
||||
{
|
||||
if (NiPoint3::DistanceSquared(ratingIt.first, m_EntityInfo.pos) <= 100.0f * 100.0f)
|
||||
{
|
||||
rating = ratingIt.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& it : pattern)
|
||||
{
|
||||
if (it.first > rating) continue;
|
||||
|
||||
// Random number between 0 and 1
|
||||
float random = GeneralUtils::GenerateRandomNumber<float>(0, 1);
|
||||
|
||||
const auto& change = it.second.first;
|
||||
|
||||
if (random >= change) continue;
|
||||
|
||||
usedSpawnPattern = true;
|
||||
|
||||
Entity* first = nullptr;
|
||||
|
||||
for (const auto& spawn : it.second.second)
|
||||
{
|
||||
float angle = GeneralUtils::GenerateRandomNumber<float>(0, 360) * M_PI / 180.0f;
|
||||
float radius = GeneralUtils::GenerateRandomNumber<float>(0, 6);
|
||||
|
||||
float x = radius * cos(angle);
|
||||
float z = radius * sin(angle);
|
||||
|
||||
auto copy = m_EntityInfo;
|
||||
copy.pos.x += x;
|
||||
copy.pos.z += z;
|
||||
copy.lot = spawn;
|
||||
|
||||
if (std::find(m_LotsToCheck.begin(), m_LotsToCheck.end(), spawn) == m_LotsToCheck.end()) {
|
||||
m_LotsToCheck.push_back(spawn);
|
||||
}
|
||||
|
||||
Entity* rezdE = EntityManager::Instance()->CreateEntity(copy, nullptr);
|
||||
|
||||
rezdE->GetGroups() = m_Info.groups;
|
||||
|
||||
EntityManager::Instance()->ConstructEntity(rezdE);
|
||||
|
||||
m_Entities.insert({ rezdE->GetObjectID(), spawnNode });
|
||||
|
||||
spawnNode->entities.push_back(rezdE->GetObjectID());
|
||||
|
||||
for (const auto& cb : m_EntitySpawnedCallbacks) {
|
||||
cb(rezdE);
|
||||
}
|
||||
|
||||
if (first == nullptr) {
|
||||
first = rezdE;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
usedSpawnPattern = true;
|
||||
|
||||
if (m_Entities.size() == m_Info.amountMaintained) {
|
||||
m_NeedsUpdate = false;
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Entity* rezdE = EntityManager::Instance()->CreateEntity(m_EntityInfo, nullptr);
|
||||
|
||||
rezdE->GetGroups() = m_Info.groups;
|
||||
@ -233,6 +329,65 @@ void Spawner::NotifyOfEntityDeath(const LWOOBJID& objectID) {
|
||||
if (m_SpawnOnSmash != nullptr) {
|
||||
m_SpawnOnSmash->Reset();
|
||||
}
|
||||
|
||||
const auto& lot = m_Info.templateID;
|
||||
|
||||
// Add to area rating
|
||||
// std::map<LOT, std::vector<std::pair<NiPoint3, float>>> m_Ratings
|
||||
// First check if the lot is in the map, if not, add it
|
||||
// Than check if there exist any ratings for that lot within 50units of the spawner
|
||||
// If there is, add 1 to the rating
|
||||
// If there isn't, add a new rating
|
||||
const auto& pos = node->position;
|
||||
|
||||
const auto& it2 = m_Ratings.find(lot);
|
||||
|
||||
if (it2 == m_Ratings.end()) {
|
||||
m_Ratings.insert({ lot, { { pos, 1.0f } } });
|
||||
} else {
|
||||
auto& ratings = it2->second;
|
||||
|
||||
bool found = false;
|
||||
for (auto& rating : ratings) {
|
||||
if (NiPoint3::DistanceSquared(rating.first, pos) < 100.0f * 100.0f) {
|
||||
rating.second += 1.0f;
|
||||
|
||||
Game::logger->Log("Spawner", "Rating %f", rating.second);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
ratings.push_back({ pos, 1.0f });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Spawner::UpdateRatings(float deltaTime) {
|
||||
// Loop through all ratings and decrease them by deltaTime
|
||||
for (auto& rating : m_Ratings) {
|
||||
for (auto& rating2 : rating.second) {
|
||||
rating2.second -= deltaTime * 0.1f;
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through all ratings and remove any that are 0 or less
|
||||
for (auto it = m_Ratings.begin(); it != m_Ratings.end();) {
|
||||
for (auto it2 = it->second.begin(); it2 != it->second.end();) {
|
||||
if (it2->second <= 0.0f) {
|
||||
it2 = it->second.erase(it2);
|
||||
} else {
|
||||
++it2;
|
||||
}
|
||||
}
|
||||
|
||||
if (it->second.empty()) {
|
||||
it = m_Ratings.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Spawner::Activate() {
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <functional>
|
||||
#include "LDFFormat.h"
|
||||
#include "EntityInfo.h"
|
||||
#include "SpawnPatterns.h"
|
||||
|
||||
struct SpawnerNode {
|
||||
NiPoint3 position = NiPoint3::ZERO;
|
||||
@ -67,6 +68,8 @@ public:
|
||||
void SetNumToMaintain(int32_t value);
|
||||
bool GetIsSpawnSmashGroup() const { return m_SpawnSmashFoundGroup; };
|
||||
|
||||
static void UpdateRatings(float deltaTime);
|
||||
|
||||
SpawnerInfo m_Info;
|
||||
bool m_Active = true;
|
||||
private:
|
||||
@ -82,6 +85,12 @@ private:
|
||||
int32_t m_AmountSpawned = 0;
|
||||
bool m_Start = false;
|
||||
Spawner* m_SpawnOnSmash = nullptr;
|
||||
|
||||
SpawnPatterns* m_SpawnPattern = nullptr;
|
||||
|
||||
std::vector<LOT> m_LotsToCheck = {};
|
||||
|
||||
static std::map<LOT, std::vector<std::pair<NiPoint3, float>>> m_Ratings;
|
||||
};
|
||||
|
||||
#endif // SPAWNER_H
|
||||
|
Loading…
Reference in New Issue
Block a user