Wincent's attempt at making LU into something it isn't supposed to be, an ARPG.
This commit is contained in:
wincent 2023-07-15 10:54:41 +02:00
parent 0d6bd33f9e
commit 5973430720
54 changed files with 2634 additions and 76 deletions

View File

@ -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(

View 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__

View 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);
}

View 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);
}

View File

@ -35,4 +35,5 @@ set(DDATABASE_TABLES_SOURCES "CDActivitiesTable.cpp"
"CDScriptComponentTable.cpp"
"CDSkillBehaviorTable.cpp"
"CDVendorComponentTable.cpp"
"CDLookupTable.cpp"
"CDZoneTableTable.cpp" PARENT_SCOPE)

View File

@ -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)

View File

@ -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;

View File

@ -110,7 +110,7 @@ void Player::SendToZone(LWOMAPID zoneId, LWOCLONEID cloneId) {
EntityManager::Instance()->DestructEntity(entity);
return;
});
});
}
void Player::AddLimboConstruction(LWOOBJID objectId) {

View File

@ -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();

View File

@ -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());

View File

@ -78,6 +78,8 @@ struct BehaviorContext
LWOOBJID caster = LWOOBJID_EMPTY;
LWOOBJID itemID = LWOOBJID_EMPTY;
uint32_t GetUniqueSkillId() const;
void UpdatePlayerSyncs(float deltaTime);

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
}

View File

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

View File

@ -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) {

View File

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

View File

@ -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;

View File

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

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);
}

View File

@ -0,0 +1,6 @@
set(DGAME_DGRIM_SOURCES "StatProperty.cpp"
"ItemModifierTemplate.cpp"
"DamageProfile.cpp"
"ResistanceProfile.cpp"
"SpawnPatterns.cpp"
"EntityProfile.cpp" PARENT_SCOPE)

View 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;
}

View 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__

View 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;
}

View 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__

View 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;
}

View 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__

View 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;
}

View 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__

View 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));
}

View 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__

View 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__

View 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();
}

View 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
View 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
View 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__

View File

@ -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() {

View File

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

View File

@ -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);
}
}
}

View File

@ -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);

View File

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

View File

@ -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);
}

View File

@ -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)

View File

@ -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;

View File

@ -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)

View File

@ -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() {

View File

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