mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-22 05:27:19 +00:00
Refactor and combat changes
This commit is contained in:
parent
0bf5ee02e4
commit
d5b2278dc5
296
dGame/AdditionalEntityData.cpp
Normal file
296
dGame/AdditionalEntityData.cpp
Normal file
@ -0,0 +1,296 @@
|
||||
#include "AdditionalEntityData.h"
|
||||
|
||||
#include "NejlikaData.h"
|
||||
|
||||
#include <DestroyableComponent.h>
|
||||
#include <LevelProgressionComponent.h>
|
||||
#include <InventoryComponent.h>
|
||||
#include <BaseCombatAIComponent.h>
|
||||
#include <TeamManager.h>
|
||||
|
||||
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, ModifierOperator op, bool resistance) const
|
||||
{
|
||||
float total = 0;
|
||||
|
||||
for (const auto& modifier : activeModifiers) {
|
||||
if (modifier.GetType() != type || modifier.GetOperator() != op || modifier.IsResistance() != resistance) {
|
||||
continue;
|
||||
}
|
||||
|
||||
total += modifier.GetValue();
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const {
|
||||
float total = 0;
|
||||
|
||||
for (const auto& modifier : additionalModifiers) {
|
||||
if (modifier.GetType() != type || modifier.GetOperator() != op || modifier.IsResistance() != resistance) {
|
||||
continue;
|
||||
}
|
||||
|
||||
total += modifier.GetValue();
|
||||
}
|
||||
|
||||
return total + CalculateModifier(type, op, resistance);
|
||||
}
|
||||
|
||||
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, int32_t level) const
|
||||
{
|
||||
const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
|
||||
|
||||
if (!templateDataOpt.has_value()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& templateData = *templateDataOpt.value();
|
||||
|
||||
const auto scaler = templateData.GetScaler(type, false, level);
|
||||
|
||||
float additive = CalculateModifier(type, ModifierOperator::Additive, false);
|
||||
|
||||
if (scaler != 0 && additive >= scaler) {
|
||||
additive -= scaler;
|
||||
}
|
||||
|
||||
float multiplicative = CalculateModifier(type, ModifierOperator::Multiplicative, false);
|
||||
|
||||
std::cout << "Scaler: " << scaler << " Additive: " << additive << " Multiplicative: " << multiplicative << std::endl;
|
||||
|
||||
return (scaler + additive) * (1 + multiplicative / 100);
|
||||
}
|
||||
|
||||
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type) const {
|
||||
return CalculateModifier(type, level);
|
||||
}
|
||||
|
||||
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers, int32_t level) const
|
||||
{
|
||||
const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
|
||||
|
||||
if (!templateDataOpt.has_value()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& templateData = *templateDataOpt.value();
|
||||
|
||||
const auto scaler = templateData.GetScaler(type, false, level);
|
||||
|
||||
float additive = CalculateModifier(type, additionalModifiers, ModifierOperator::Additive, false);
|
||||
|
||||
if (scaler != 0 && additive >= scaler) {
|
||||
additive -= scaler;
|
||||
}
|
||||
|
||||
float multiplicative = CalculateModifier(type, additionalModifiers, ModifierOperator::Multiplicative, false);
|
||||
|
||||
std::cout << "Scaler: " << scaler << " Additive: " << additive << " Multiplicative: " << multiplicative << std::endl;
|
||||
|
||||
return (scaler + additive) * (1 + multiplicative / 100);
|
||||
}
|
||||
|
||||
float nejlika::AdditionalEntityData::CalculateResistance(ModifierType type) const
|
||||
{
|
||||
return CalculateModifier(type, ModifierOperator::Multiplicative, true);
|
||||
}
|
||||
|
||||
void nejlika::AdditionalEntityData::RollStandardModifiers(int32_t level) {
|
||||
standardModifiers.clear();
|
||||
|
||||
const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
|
||||
|
||||
if (templateDataOpt.has_value()) {
|
||||
const auto& templateData = *templateDataOpt.value();
|
||||
|
||||
const auto modifiers = templateData.GenerateModifiers(level);
|
||||
|
||||
standardModifiers.insert(standardModifiers.end(), modifiers.begin(), modifiers.end());
|
||||
}
|
||||
|
||||
const auto objectDataVec = NejlikaData::GetModifierNameTemplates(ModifierNameType::Object);
|
||||
|
||||
for (const auto& objectData : objectDataVec) {
|
||||
if (objectData.GetLOT() != lot) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto modifiers = objectData.GenerateModifiers(level);
|
||||
|
||||
standardModifiers.insert(standardModifiers.end(), modifiers.begin(), modifiers.end());
|
||||
}
|
||||
}
|
||||
|
||||
void nejlika::AdditionalEntityData::ApplyToEntity() {
|
||||
const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
|
||||
|
||||
if (!templateDataOpt.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& templateData = *templateDataOpt.value();
|
||||
|
||||
auto* entity = Game::entityManager->GetEntity(id);
|
||||
|
||||
if (entity == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* destroyable = entity->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (destroyable == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* levelProgression = entity->GetComponent<LevelProgressionComponent>();
|
||||
|
||||
if (levelProgression != nullptr) {
|
||||
this->level = levelProgression->GetLevel();
|
||||
}
|
||||
else {
|
||||
this->level = templateData.GetMinLevel();
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
RollStandardModifiers(level);
|
||||
}
|
||||
|
||||
activeModifiers = standardModifiers;
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent) {
|
||||
for (const auto& [location, item] : inventoryComponent->GetEquippedItems()) {
|
||||
const auto itemDataOpt = NejlikaData::GetAdditionalItemData(item.id);
|
||||
|
||||
if (!itemDataOpt.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& itemData = *itemDataOpt.value();
|
||||
|
||||
const auto& itemModifiers = itemData.GetModifierInstances();
|
||||
|
||||
activeModifiers.insert(activeModifiers.end(), itemModifiers.begin(), itemModifiers.end());
|
||||
}
|
||||
}
|
||||
|
||||
destroyable->SetMaxHealth(static_cast<int32_t>(CalculateModifier(ModifierType::Health, level)));
|
||||
destroyable->SetMaxArmor(static_cast<int32_t>(CalculateModifier(ModifierType::Armor, level)));
|
||||
if (!entity->IsPlayer()) {
|
||||
destroyable->SetMaxImagination(static_cast<int32_t>(CalculateModifier(ModifierType::Imagination, level)));
|
||||
}
|
||||
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
destroyable->SetHealth(destroyable->GetMaxHealth());
|
||||
destroyable->SetArmor(destroyable->GetMaxArmor());
|
||||
|
||||
if (!entity->IsPlayer()) {
|
||||
destroyable->SetImagination(destroyable->GetMaxImagination());
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void nejlika::AdditionalEntityData::CheckForRescale(AdditionalEntityData* other) {
|
||||
auto* entity = Game::entityManager->GetEntity(id);
|
||||
|
||||
if (entity == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity->IsPlayer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* baseCombat = entity->GetComponent<BaseCombatAIComponent>();
|
||||
|
||||
if (baseCombat == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& threats = baseCombat->GetThreats();
|
||||
|
||||
int32_t totalThreats = 0;
|
||||
int32_t totalLevel = 0;
|
||||
|
||||
for (const auto& [threat, _] : threats) {
|
||||
const auto threatEntityOpt = NejlikaData::GetAdditionalEntityData(threat);
|
||||
|
||||
if (!threatEntityOpt.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& threatEntity = *threatEntityOpt.value();
|
||||
|
||||
if (other->id == threatEntity.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
totalLevel += threatEntity.level;
|
||||
totalThreats++;
|
||||
}
|
||||
|
||||
if (other != nullptr) {
|
||||
totalLevel += other->level;
|
||||
totalThreats++;
|
||||
|
||||
auto* team = TeamManager::Instance()->GetTeam(other->id);
|
||||
|
||||
if (team != nullptr) {
|
||||
for (const auto& member : team->members) {
|
||||
const auto memberEntityOpt = NejlikaData::GetAdditionalEntityData(member);
|
||||
|
||||
if (!memberEntityOpt.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& memberEntity = *memberEntityOpt.value();
|
||||
|
||||
if (other->id == memberEntity.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
totalLevel += memberEntity.level;
|
||||
totalThreats++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (totalThreats == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto averageLevel = totalLevel / totalThreats;
|
||||
|
||||
// Can't rescale to a lower level
|
||||
if (averageLevel <= level) {
|
||||
return;
|
||||
}
|
||||
|
||||
level = averageLevel;
|
||||
|
||||
auto* destroyable = entity->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (destroyable == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
float healthPercentage = destroyable->GetMaxHealth() == 0 ? 1 : static_cast<float>(destroyable->GetHealth()) / destroyable->GetMaxHealth();
|
||||
float armorPercentage = destroyable->GetMaxArmor() == 0 ? 1 : static_cast<float>(destroyable->GetArmor()) / destroyable->GetMaxArmor();
|
||||
float imaginationPercentage = destroyable->GetMaxImagination() == 0 ? 1 : static_cast<float>(destroyable->GetImagination()) / destroyable->GetMaxImagination();
|
||||
|
||||
RollStandardModifiers(level);
|
||||
|
||||
ApplyToEntity();
|
||||
|
||||
destroyable->SetHealth(static_cast<int32_t>(destroyable->GetMaxHealth() * healthPercentage));
|
||||
destroyable->SetArmor(static_cast<int32_t>(destroyable->GetMaxArmor() * armorPercentage));
|
||||
destroyable->SetImagination(static_cast<int32_t>(destroyable->GetMaxImagination() * imaginationPercentage));
|
||||
|
||||
LOG("Rescaled entity %i to level %d", entity->GetLOT(), level);
|
||||
}
|
57
dGame/AdditionalEntityData.h
Normal file
57
dGame/AdditionalEntityData.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "Entity.h"
|
||||
|
||||
#include "ModifierInstance.h"
|
||||
#include "EntityTemplate.h"
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
class AdditionalEntityData
|
||||
{
|
||||
public:
|
||||
AdditionalEntityData() = default;
|
||||
|
||||
AdditionalEntityData(LWOOBJID id, LOT lot) : id(id), lot(lot) {}
|
||||
|
||||
float CalculateModifier(ModifierType type, ModifierOperator op, bool resistance) const;
|
||||
|
||||
float CalculateModifier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const;
|
||||
|
||||
float CalculateModifier(ModifierType type, int32_t level) const;
|
||||
|
||||
float CalculateModifier(ModifierType type) const;
|
||||
|
||||
float CalculateModifier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers, int32_t level) const;
|
||||
|
||||
float CalculateResistance(ModifierType type) const;
|
||||
|
||||
void ApplyToEntity();
|
||||
|
||||
void CheckForRescale(AdditionalEntityData* other);
|
||||
|
||||
int32_t GetLevel() const { return level; }
|
||||
|
||||
LWOOBJID GetID() const { return id; }
|
||||
|
||||
LOT GetLOT() const { return lot; }
|
||||
|
||||
private:
|
||||
void RollStandardModifiers(int32_t level);
|
||||
|
||||
bool initialized = false;
|
||||
|
||||
std::vector<ModifierInstance> standardModifiers;
|
||||
std::vector<ModifierInstance> activeModifiers;
|
||||
|
||||
LWOOBJID id;
|
||||
LOT lot;
|
||||
|
||||
int32_t level = 1;
|
||||
};
|
||||
|
||||
}
|
227
dGame/AdditionalItemData.cpp
Normal file
227
dGame/AdditionalItemData.cpp
Normal file
@ -0,0 +1,227 @@
|
||||
#include "AdditionalItemData.h"
|
||||
|
||||
#include "Item.h"
|
||||
#include "eItemType.h"
|
||||
#include "NejlikaData.h"
|
||||
|
||||
using namespace nejlika;
|
||||
|
||||
nejlika::AdditionalItemData::AdditionalItemData(Item* item)
|
||||
{
|
||||
const auto& config = item->GetConfig();
|
||||
|
||||
for (const auto& entry : config)
|
||||
{
|
||||
if (entry->GetKey() != u"modifiers")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto str = entry->GetValueAsString();
|
||||
|
||||
if (str.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const auto json = nlohmann::json::parse(str);
|
||||
|
||||
Load(json);
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
std::cout << "Failed to parse additional item data: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nejlika::AdditionalItemData::AdditionalItemData(const nlohmann::json& json) {
|
||||
Load(json);
|
||||
}
|
||||
|
||||
void nejlika::AdditionalItemData::Load(const nlohmann::json& json) {
|
||||
if (json.contains("names"))
|
||||
{
|
||||
for (const auto& name : json["names"])
|
||||
{
|
||||
modifierNames.emplace_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (json.contains("instances"))
|
||||
{
|
||||
for (const auto& instance : json["instances"])
|
||||
{
|
||||
modifierInstances.emplace_back(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json nejlika::AdditionalItemData::ToJson() const {
|
||||
nlohmann::json json;
|
||||
|
||||
json["names"] = nlohmann::json::array();
|
||||
|
||||
for (const auto& name : modifierNames)
|
||||
{
|
||||
json["names"].push_back(name.ToJson());
|
||||
}
|
||||
|
||||
json["instances"] = nlohmann::json::array();
|
||||
|
||||
for (const auto& instance : modifierInstances)
|
||||
{
|
||||
json["instances"].push_back(instance.ToJson());
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void nejlika::AdditionalItemData::Save(Item* item) {
|
||||
auto& config = item->GetConfig();
|
||||
|
||||
// Remove the old data
|
||||
for (size_t i = 0; i < config.size(); i++)
|
||||
{
|
||||
if (config[i]->GetKey() == u"modifiers")
|
||||
{
|
||||
config.erase(config.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
ss << ToJson().dump();
|
||||
|
||||
std::cout << ss.str() << std::endl;
|
||||
|
||||
config.push_back(new LDFData<std::string>(u"modifiers", ToJson().dump()));
|
||||
}
|
||||
|
||||
void nejlika::AdditionalItemData::RollModifiers(Item* item, int32_t level) {
|
||||
modifierNames.clear();
|
||||
modifierInstances.clear();
|
||||
|
||||
const auto& info = item->GetInfo();
|
||||
|
||||
const auto itemType = static_cast<eItemType>(info.itemType);
|
||||
const auto itemRarity = info.rarity == 0 ? 1 : info.rarity;
|
||||
|
||||
uint32_t rarityRollPrefix = 0;
|
||||
uint32_t rarityRollSuffix = 0;
|
||||
|
||||
// Generate (itemRarity) amout of names and modifiers rolls, take the highest rarity. 0-1000
|
||||
for (int i = 0; i < itemRarity; i++) {
|
||||
auto roll = GeneralUtils::GenerateRandomNumber<uint32_t>() % 1000;
|
||||
|
||||
if (roll > rarityRollPrefix) {
|
||||
rarityRollPrefix = roll;
|
||||
}
|
||||
|
||||
roll = GeneralUtils::GenerateRandomNumber<uint32_t>() % 1000;
|
||||
|
||||
if (roll > rarityRollSuffix) {
|
||||
rarityRollSuffix = roll;
|
||||
}
|
||||
}
|
||||
|
||||
const auto& templates = NejlikaData::GetModifierNameTemplates();
|
||||
|
||||
std::vector<ModifierNameTemplate> availablePrefixes;
|
||||
std::vector<ModifierNameTemplate> availableSuffixes;
|
||||
|
||||
for (const auto& [type, nameTemplates] : templates) {
|
||||
for (const auto& nameTemplate : nameTemplates) {
|
||||
if (type != ModifierNameType::Prefix && type != ModifierNameType::Suffix) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nameTemplate.GetMinLevel() > level || nameTemplate.GetMaxLevel() < level) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto rarity = nameTemplate.GetRarity();
|
||||
|
||||
if (rarity == ModifierRarity::Common) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& itemTypes = nameTemplate.GetItemTypes();
|
||||
|
||||
if (std::find(itemTypes.begin(), itemTypes.end(), itemType) == itemTypes.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
Uncommon: rarityRoll > 500,
|
||||
Rare: rarityRoll > 900,
|
||||
Epic: rarityRoll > 990,
|
||||
Legendary: rarityRoll = 999
|
||||
*/
|
||||
const auto roll = type == ModifierNameType::Prefix ? rarityRollPrefix : rarityRollSuffix;
|
||||
|
||||
if (rarity == ModifierRarity::Uncommon && roll > 900) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rarity == ModifierRarity::Rare && (roll <= 900 || roll > 990)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rarity == ModifierRarity::Epic && (roll <= 990 || roll > 998)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rarity == ModifierRarity::Legendary && roll != 999) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == ModifierNameType::Prefix) {
|
||||
availablePrefixes.push_back(nameTemplate);
|
||||
}
|
||||
else {
|
||||
availableSuffixes.push_back(nameTemplate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!availablePrefixes.empty()) {
|
||||
const auto& prefix = availablePrefixes[GeneralUtils::GenerateRandomNumber<uint32_t>() % availablePrefixes.size()];
|
||||
|
||||
modifierNames.push_back(ModifierName(prefix));
|
||||
|
||||
const auto modifiers = prefix.GenerateModifiers(level);
|
||||
|
||||
modifierInstances.insert(modifierInstances.end(), modifiers.begin(), modifiers.end());
|
||||
}
|
||||
|
||||
if (!availableSuffixes.empty()) {
|
||||
const auto& suffix = availableSuffixes[GeneralUtils::GenerateRandomNumber<uint32_t>() % availableSuffixes.size()];
|
||||
|
||||
modifierNames.push_back(ModifierName(suffix));
|
||||
|
||||
const auto modifiers = suffix.GenerateModifiers(level);
|
||||
|
||||
modifierInstances.insert(modifierInstances.end(), modifiers.begin(), modifiers.end());
|
||||
}
|
||||
|
||||
const auto& itemTemplateVec = NejlikaData::GetModifierNameTemplates(ModifierNameType::Object);
|
||||
|
||||
const auto itemTemplateIt = std::find_if(itemTemplateVec.begin(), itemTemplateVec.end(), [item](const auto& it) {
|
||||
return it.GetLOT() == static_cast<int32_t>(item->GetLot());
|
||||
});
|
||||
|
||||
if (itemTemplateIt != itemTemplateVec.end()) {
|
||||
const auto& itemTemplate = *itemTemplateIt;
|
||||
|
||||
const auto itemModifiers = itemTemplate.GenerateModifiers(level);
|
||||
|
||||
modifierInstances.insert(modifierInstances.end(), itemModifiers.begin(), itemModifiers.end());
|
||||
}
|
||||
|
||||
Save(item);
|
||||
}
|
||||
|
41
dGame/AdditionalItemData.h
Normal file
41
dGame/AdditionalItemData.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "ModifierName.h"
|
||||
#include "ModifierInstance.h"
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
class Item;
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
class AdditionalItemData
|
||||
{
|
||||
public:
|
||||
AdditionalItemData() = default;
|
||||
|
||||
AdditionalItemData(Item* item);
|
||||
|
||||
AdditionalItemData(const nlohmann::json& json);
|
||||
|
||||
nlohmann::json ToJson() const;
|
||||
|
||||
void Load(const nlohmann::json& json);
|
||||
|
||||
void Save(Item* item);
|
||||
|
||||
void RollModifiers(Item* item, int32_t level);
|
||||
|
||||
const std::vector<ModifierName>& GetModifierNames() const { return modifierNames; }
|
||||
const std::vector<ModifierInstance>& GetModifierInstances() const { return modifierInstances; }
|
||||
|
||||
private:
|
||||
std::vector<ModifierName> modifierNames;
|
||||
std::vector<ModifierInstance> modifierInstances;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -7,7 +7,19 @@ set(DGAME_SOURCES "Character.cpp"
|
||||
"TradingManager.cpp"
|
||||
"User.cpp"
|
||||
"UserManager.cpp"
|
||||
"Nejlika.cpp")
|
||||
"ModifierTemplate.cpp"
|
||||
"ModifierInstance.cpp"
|
||||
"ModifierRarity.cpp"
|
||||
"ModifierType.cpp"
|
||||
"ModifierScale.cpp"
|
||||
"ModifierName.cpp"
|
||||
"ModifierNameTemplate.cpp"
|
||||
"NejlikaData.cpp"
|
||||
"AdditionalItemData.cpp"
|
||||
"EntityTemplate.cpp"
|
||||
"AdditionalEntityData.cpp"
|
||||
"NejlikaHooks.cpp"
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/dScripts
|
||||
|
@ -26,6 +26,9 @@
|
||||
#include "GhostComponent.h"
|
||||
#include <ranges>
|
||||
|
||||
Observable<Entity*> EntityManager::OnEntityCreated;
|
||||
Observable<Entity*> EntityManager::OnEntityDestroyed;
|
||||
|
||||
// Configure which zones have ghosting disabled, mostly small worlds.
|
||||
std::vector<LWOMAPID> EntityManager::m_GhostingExcludedZones = {
|
||||
// Small zones
|
||||
@ -138,6 +141,9 @@ Entity* EntityManager::CreateEntity(EntityInfo info, User* user, Entity* parentE
|
||||
m_SpawnPoints.insert_or_assign(GeneralUtils::UTF16ToWTF8(spawnName), entity->GetObjectID());
|
||||
}
|
||||
|
||||
// Notify observers that a new entity has been created
|
||||
OnEntityCreated(entity);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
@ -161,6 +167,9 @@ void EntityManager::DestroyEntity(Entity* entity) {
|
||||
DestructEntity(entity);
|
||||
}
|
||||
|
||||
// Notify observers that an entity is about to be destroyed
|
||||
OnEntityDestroyed(entity);
|
||||
|
||||
// Delete this entity at the end of the frame
|
||||
ScheduleForDeletion(id);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include "dCommonVars.h"
|
||||
#include "Observable.h"
|
||||
|
||||
class Entity;
|
||||
class EntityInfo;
|
||||
@ -72,6 +73,9 @@ public:
|
||||
const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; };
|
||||
const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; };
|
||||
|
||||
static Observable<Entity*> OnEntityCreated;
|
||||
static Observable<Entity*> OnEntityDestroyed;
|
||||
|
||||
private:
|
||||
void SerializeEntities();
|
||||
void KillEntities();
|
||||
|
88
dGame/EntityTemplate.cpp
Normal file
88
dGame/EntityTemplate.cpp
Normal file
@ -0,0 +1,88 @@
|
||||
#include "EntityTemplate.h"
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
nejlika::EntityTemplate::EntityTemplate(const nlohmann::json& json) {
|
||||
lot = json["lot"].get<LOT>();
|
||||
minLevel = json.contains("min-level") ? json["min-level"].get<int32_t>() : 1;
|
||||
|
||||
for (const auto& scaler : json["scaling"])
|
||||
{
|
||||
EntityTemplateScaler s;
|
||||
|
||||
s.type = magic_enum::enum_cast<ModifierType>(scaler["type"].get<std::string>()).value();
|
||||
s.isResistance = scaler.contains("resistance") && scaler["resistance"].get<bool>();
|
||||
s.polynomial = scaler["polynomial"].get<std::vector<float>>();
|
||||
|
||||
scalers.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json nejlika::EntityTemplate::ToJson() const {
|
||||
nlohmann::json json;
|
||||
|
||||
json["lot"] = lot;
|
||||
json["min-level"] = minLevel;
|
||||
|
||||
nlohmann::json scalersJson;
|
||||
|
||||
for (const auto& scaler : scalers)
|
||||
{
|
||||
nlohmann::json s;
|
||||
|
||||
s["type"] = magic_enum::enum_name(scaler.type);
|
||||
s["resistance"] = scaler.isResistance;
|
||||
s["polynomial"] = scaler.polynomial;
|
||||
|
||||
scalersJson.push_back(s);
|
||||
}
|
||||
|
||||
json["scaling"] = scalersJson;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
float nejlika::EntityTemplate::GetScaler(ModifierType type, bool isResistance, int32_t level) const {
|
||||
for (const auto& scaler : scalers)
|
||||
{
|
||||
if (scaler.type == type && scaler.isResistance == isResistance)
|
||||
{
|
||||
return CalculateScaler(scaler, level);
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
std::vector<nejlika::ModifierInstance> nejlika::EntityTemplate::GenerateModifiers(int32_t level) const {
|
||||
std::vector<ModifierInstance> modifiers;
|
||||
|
||||
for (const auto& scaler : scalers)
|
||||
{
|
||||
ModifierInstance modifier(
|
||||
scaler.type,
|
||||
CalculateScaler(scaler, level),
|
||||
ModifierOperator::Additive,
|
||||
scaler.isResistance,
|
||||
ModifierCategory::Player,
|
||||
0,
|
||||
""
|
||||
);
|
||||
|
||||
modifiers.push_back(modifier);
|
||||
}
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
float nejlika::EntityTemplate::CalculateScaler(const EntityTemplateScaler& scaler, int32_t level) const {
|
||||
float result = 0.0f;
|
||||
|
||||
for (size_t i = 0; i < scaler.polynomial.size(); ++i)
|
||||
{
|
||||
result += scaler.polynomial[i] * std::pow(level, i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
47
dGame/EntityTemplate.h
Normal file
47
dGame/EntityTemplate.h
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "Entity.h"
|
||||
#include "ModifierType.h"
|
||||
#include "ModifierInstance.h"
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
class EntityTemplate
|
||||
{
|
||||
public:
|
||||
EntityTemplate() = default;
|
||||
|
||||
EntityTemplate(const nlohmann::json& json);
|
||||
|
||||
nlohmann::json ToJson() const;
|
||||
|
||||
LOT GetLOT() const { return lot; }
|
||||
|
||||
int32_t GetMinLevel() const { return minLevel; }
|
||||
|
||||
float GetScaler(ModifierType type, bool isResistance, int32_t level) const;
|
||||
|
||||
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
|
||||
|
||||
private:
|
||||
struct EntityTemplateScaler
|
||||
{
|
||||
ModifierType type;
|
||||
bool isResistance;
|
||||
std::vector<float> polynomial;
|
||||
};
|
||||
|
||||
float CalculateScaler(const EntityTemplateScaler& scaler, int32_t level) const;
|
||||
|
||||
LOT lot;
|
||||
std::vector<EntityTemplateScaler> scalers;
|
||||
int32_t minLevel;
|
||||
};
|
||||
|
||||
}
|
14
dGame/ModifierCategory.h
Normal file
14
dGame/ModifierCategory.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
enum class ModifierCategory : uint8_t
|
||||
{
|
||||
Player = 0 << 0,
|
||||
Pet = 1 << 0
|
||||
};
|
||||
|
||||
}
|
96
dGame/ModifierInstance.cpp
Normal file
96
dGame/ModifierInstance.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include "ModifierInstance.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
nejlika::ModifierInstance::ModifierInstance(const nlohmann::json& config) {
|
||||
type = magic_enum::enum_cast<ModifierType>(config["type"].get<std::string>()).value_or(ModifierType::Invalid);
|
||||
value = config["value"].get<float>();
|
||||
|
||||
if (config.contains("op")) {
|
||||
op = magic_enum::enum_cast<ModifierOperator>(config["op"].get<std::string>()).value_or(ModifierOperator::Additive);
|
||||
}
|
||||
else {
|
||||
op = ModifierOperator::Additive;
|
||||
}
|
||||
|
||||
isResistance = config.contains("resistance") ? config["resistance"].get<bool>() : false;
|
||||
|
||||
if (config.contains("category")) {
|
||||
category = magic_enum::enum_cast<ModifierCategory>(config["category"].get<std::string>()).value_or(ModifierCategory::Player);
|
||||
}
|
||||
else {
|
||||
category = ModifierCategory::Player;
|
||||
}
|
||||
|
||||
effectID = config.contains("effect-id") ? config["effect-id"].get<uint32_t>() : 0;
|
||||
effectType = config.contains("effect-type") ? config["effect-type"].get<std::string>() : "";
|
||||
}
|
||||
|
||||
nlohmann::json nejlika::ModifierInstance::ToJson() const
|
||||
{
|
||||
nlohmann::json config;
|
||||
|
||||
config["type"] = magic_enum::enum_name(type);
|
||||
config["value"] = value;
|
||||
config["op"] = magic_enum::enum_name(op);
|
||||
config["resistance"] = isResistance;
|
||||
config["category"] = magic_enum::enum_name(category);
|
||||
config["effect-id"] = effectID;
|
||||
config["effect-type"] = effectType;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
std::string nejlika::ModifierInstance::GenerateHtmlString(const std::vector<ModifierInstance>& modifiers)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
// target -> resistance -> op -> type -> value
|
||||
std::unordered_map<ModifierCategory, std::unordered_map<bool, std::unordered_map<ModifierOperator, std::unordered_map<ModifierType, float>>>> modifierMap;
|
||||
|
||||
for (const auto& modifier : modifiers) {
|
||||
if (modifier.type == ModifierType::Invalid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
modifierMap[modifier.category][modifier.isResistance][modifier.op][modifier.type] = modifier.value;
|
||||
}
|
||||
|
||||
// Resistances and addatives are not separated, pet and player are
|
||||
// Summarize the resistances and addatives
|
||||
for (const auto& target : modifierMap) {
|
||||
if (target.first == ModifierCategory::Pet) {
|
||||
ss << "\n<font color=\"#D0AB62\">Pets:</font>\n";
|
||||
}
|
||||
|
||||
for (const auto& resistance : target.second) {
|
||||
|
||||
ss << "\n<font color=\"#D0AB62\">";
|
||||
|
||||
ss << ((resistance.first) ? "Resistances" : "Modifiers");
|
||||
|
||||
ss << ":</font>\n";
|
||||
|
||||
for (const auto& math : resistance.second) {
|
||||
for (const auto& modifier : math.second) {
|
||||
ss << "<font color=\"" << GetModifierTypeColor(modifier.first) << "\">";
|
||||
|
||||
ss << magic_enum::enum_name<ModifierType>(modifier.first) << ": ";
|
||||
|
||||
ss << ((modifier.second > 0) ? "+" : "-");
|
||||
|
||||
ss << std::fixed << std::setprecision(0) << std::abs(modifier.second);
|
||||
|
||||
if (math.first == ModifierOperator::Multiplicative) {
|
||||
ss << "%";
|
||||
}
|
||||
|
||||
ss << "</font>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
84
dGame/ModifierInstance.h
Normal file
84
dGame/ModifierInstance.h
Normal file
@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include "ModifierType.h"
|
||||
#include "ModifierCategory.h"
|
||||
#include "ModifierOperator.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
class ModifierInstance
|
||||
{
|
||||
public:
|
||||
ModifierInstance(
|
||||
ModifierType type, float value, ModifierOperator op, bool isResistance, ModifierCategory category, uint32_t effectID, const std::string& effectType
|
||||
) : type(type), value(value), op(op), isResistance(isResistance), category(category), effectID(effectID), effectType(effectType) {}
|
||||
|
||||
/**
|
||||
* @brief Construct a new Modifier Instance object from a json configuration.
|
||||
*
|
||||
* @param config The json configuration.
|
||||
*/
|
||||
ModifierInstance(const nlohmann::json& config);
|
||||
|
||||
/**
|
||||
* @brief Convert the modifier instance to a json representation.
|
||||
*
|
||||
* @return The json representation.
|
||||
*/
|
||||
nlohmann::json ToJson() const;
|
||||
|
||||
/**
|
||||
* @brief Generate a HTML string representation of a set of modifiers.
|
||||
*
|
||||
* @param modifiers The modifiers to generate the HTML string for.
|
||||
* @return The HTML string.
|
||||
*/
|
||||
static std::string GenerateHtmlString(const std::vector<ModifierInstance>& modifiers);
|
||||
|
||||
// Getters and setters
|
||||
|
||||
ModifierType GetType() const { return type; }
|
||||
|
||||
float GetValue() const { return value; }
|
||||
|
||||
ModifierOperator GetOperator() const { return op; }
|
||||
|
||||
bool IsResistance() const { return isResistance; }
|
||||
|
||||
ModifierCategory GetCategory() const { return category; }
|
||||
|
||||
uint32_t GetEffectID() const { return effectID; }
|
||||
|
||||
std::string GetEffectType() const { return effectType; }
|
||||
|
||||
void SetType(ModifierType type) { this->type = type; }
|
||||
|
||||
void SetValue(float value) { this->value = value; }
|
||||
|
||||
void SetOperator(ModifierOperator op) { this->op = op; }
|
||||
|
||||
void SetIsResistance(bool isResistance) { this->isResistance = isResistance; }
|
||||
|
||||
void SetCategory(ModifierCategory category) { this->category = category; }
|
||||
|
||||
void SetEffectID(uint32_t effectID) { this->effectID = effectID; }
|
||||
|
||||
void SetEffectType(const std::string& effectType) { this->effectType = effectType; }
|
||||
|
||||
private:
|
||||
ModifierType type;
|
||||
float value;
|
||||
ModifierOperator op;
|
||||
bool isResistance;
|
||||
ModifierCategory category;
|
||||
uint32_t effectID;
|
||||
std::string effectType;
|
||||
};
|
||||
|
||||
}
|
83
dGame/ModifierName.cpp
Normal file
83
dGame/ModifierName.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#include "ModifierName.h"
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
using namespace nejlika;
|
||||
|
||||
nejlika::ModifierName::ModifierName(const nlohmann::json & json)
|
||||
{
|
||||
name = json["name"].get<std::string>();
|
||||
|
||||
if (json.contains("type"))
|
||||
{
|
||||
type = magic_enum::enum_cast<ModifierNameType>(json["type"].get<std::string>()).value_or(ModifierNameType::Prefix);
|
||||
}
|
||||
else
|
||||
{
|
||||
type = ModifierNameType::Prefix;
|
||||
}
|
||||
|
||||
if (json.contains("rarity"))
|
||||
{
|
||||
rarity = magic_enum::enum_cast<ModifierRarity>(json["rarity"].get<std::string>()).value_or(ModifierRarity::Common);
|
||||
}
|
||||
else
|
||||
{
|
||||
rarity = ModifierRarity::Common;
|
||||
}
|
||||
}
|
||||
|
||||
nejlika::ModifierName::ModifierName(const ModifierNameTemplate& templateData) {
|
||||
name = templateData.GetName();
|
||||
type = templateData.GetType();
|
||||
rarity = templateData.GetRarity();
|
||||
}
|
||||
|
||||
nlohmann::json nejlika::ModifierName::ToJson() const
|
||||
{
|
||||
nlohmann::json json;
|
||||
|
||||
json["name"] = name;
|
||||
json["type"] = magic_enum::enum_name(type);
|
||||
json["rarity"] = magic_enum::enum_name(rarity);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
std::string nejlika::ModifierName::GenerateHtmlString() const {
|
||||
const auto& rarityColor = ModifierRarityHelper::GetModifierRarityColor(rarity);
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "<font color=\"" << rarityColor << "\">" << name << "</font>";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string nejlika::ModifierName::GenerateHtmlString(const std::vector<ModifierName>& names)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
for (const auto& name : names) {
|
||||
if (name.GetType() == ModifierNameType::Prefix && !name.name.empty()) {
|
||||
ss << name.GenerateHtmlString() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
ss << "<font color=\"#D0AB62\">NAME</font>";
|
||||
|
||||
for (const auto& name : names) {
|
||||
if (name.GetType() == ModifierNameType::Suffix && !name.name.empty()) {
|
||||
ss << "\n" << name.GenerateHtmlString();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the last newline
|
||||
auto str = ss.str();
|
||||
|
||||
if (!str.empty() && str.back() == '\n') {
|
||||
str.pop_back();
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
57
dGame/ModifierName.h
Normal file
57
dGame/ModifierName.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "ModifierNameType.h"
|
||||
#include "ModifierRarity.h"
|
||||
#include "ModifierNameTemplate.h"
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
class ModifierName
|
||||
{
|
||||
public:
|
||||
ModifierName(const std::string& name, ModifierNameType type, ModifierRarity rarity) :
|
||||
name(name), type(type), rarity(rarity) {}
|
||||
|
||||
ModifierName(const nlohmann::json& json);
|
||||
|
||||
ModifierName(const ModifierNameTemplate& templateData);
|
||||
|
||||
nlohmann::json ToJson() const;
|
||||
|
||||
std::string GenerateHtmlString() const;
|
||||
|
||||
/**
|
||||
* @brief Generate a HTML string representation of a set of names.
|
||||
*
|
||||
* @param modifiers The names to generate the HTML string for.
|
||||
* @return The HTML string.
|
||||
*/
|
||||
static std::string GenerateHtmlString(const std::vector<ModifierName>& names);
|
||||
|
||||
// Getters and setters
|
||||
|
||||
const std::string& GetName() const { return name; }
|
||||
|
||||
ModifierNameType GetType() const { return type; }
|
||||
|
||||
ModifierRarity GetRarity() const { return rarity; }
|
||||
|
||||
void SetName(const std::string& name) { this->name = name; }
|
||||
|
||||
void SetType(ModifierNameType type) { this->type = type; }
|
||||
|
||||
void SetRarity(ModifierRarity rarity) { this->rarity = rarity; }
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
ModifierNameType type;
|
||||
ModifierRarity rarity;
|
||||
};
|
||||
|
||||
}
|
155
dGame/ModifierNameTemplate.cpp
Normal file
155
dGame/ModifierNameTemplate.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
#include "ModifierNameTemplate.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "magic_enum.hpp"
|
||||
|
||||
using namespace nejlika;
|
||||
|
||||
nejlika::ModifierNameTemplate::ModifierNameTemplate(const nlohmann::json & json)
|
||||
{
|
||||
name = json["name"].get<std::string>();
|
||||
|
||||
if (json.contains("lot"))
|
||||
{
|
||||
lot = json["lot"].get<int32_t>();
|
||||
}
|
||||
else
|
||||
{
|
||||
lot = 0;
|
||||
}
|
||||
|
||||
if (json.contains("type"))
|
||||
{
|
||||
type = magic_enum::enum_cast<ModifierNameType>(json["type"].get<std::string>()).value_or(ModifierNameType::Prefix);
|
||||
}
|
||||
else
|
||||
{
|
||||
type = ModifierNameType::Prefix;
|
||||
}
|
||||
|
||||
if (json.contains("items"))
|
||||
{
|
||||
for (const auto& itemType : json["items"])
|
||||
{
|
||||
std::string type = itemType.get<std::string>();
|
||||
|
||||
// Make uppercase
|
||||
std::transform(type.begin(), type.end(), type.begin(), ::toupper);
|
||||
|
||||
// Replace spaces with underscores
|
||||
std::replace(type.begin(), type.end(), ' ', '_');
|
||||
|
||||
const auto itemTypeEnum = magic_enum::enum_cast<eItemType>(type);
|
||||
|
||||
if (itemTypeEnum.has_value())
|
||||
{
|
||||
itemTypes.push_back(itemTypeEnum.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Invalid item type: " << type << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (json.contains("modifiers"))
|
||||
{
|
||||
for (const auto& modifier : json["modifiers"])
|
||||
{
|
||||
modifiers.push_back(ModifierTemplate(modifier));
|
||||
}
|
||||
}
|
||||
|
||||
if (json.contains("levels"))
|
||||
{
|
||||
auto levels = json["levels"];
|
||||
|
||||
if (levels.contains("min"))
|
||||
{
|
||||
minLevel = levels["min"].get<int32_t>();
|
||||
}
|
||||
else
|
||||
{
|
||||
minLevel = 1;
|
||||
}
|
||||
|
||||
if (levels.contains("max"))
|
||||
{
|
||||
maxLevel = levels["max"].get<int32_t>();
|
||||
}
|
||||
else
|
||||
{
|
||||
maxLevel = 45;
|
||||
}
|
||||
}
|
||||
|
||||
if (json.contains("rarity"))
|
||||
{
|
||||
rarity = magic_enum::enum_cast<ModifierRarity>(json["rarity"].get<std::string>()).value_or(ModifierRarity::Common);
|
||||
}
|
||||
else
|
||||
{
|
||||
rarity = ModifierRarity::Common;
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json nejlika::ModifierNameTemplate::ToJson() const {
|
||||
nlohmann::json json;
|
||||
|
||||
json["name"] = name;
|
||||
json["type"] = magic_enum::enum_name(type);
|
||||
|
||||
if (lot != 0)
|
||||
{
|
||||
json["lot"] = lot;
|
||||
}
|
||||
|
||||
if (!itemTypes.empty())
|
||||
{
|
||||
nlohmann::json items;
|
||||
|
||||
for (const auto& itemType : itemTypes)
|
||||
{
|
||||
items.push_back(magic_enum::enum_name(itemType));
|
||||
}
|
||||
|
||||
json["items"] = items;
|
||||
}
|
||||
|
||||
if (!modifiers.empty())
|
||||
{
|
||||
nlohmann::json modifierTemplates;
|
||||
|
||||
for (const auto& modifier : modifiers)
|
||||
{
|
||||
modifierTemplates.push_back(modifier.ToJson());
|
||||
}
|
||||
|
||||
json["modifiers"] = modifierTemplates;
|
||||
}
|
||||
|
||||
nlohmann::json levels;
|
||||
|
||||
levels["min"] = minLevel;
|
||||
levels["max"] = maxLevel;
|
||||
|
||||
json["levels"] = levels;
|
||||
json["rarity"] = magic_enum::enum_name(rarity);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
std::vector<ModifierInstance> nejlika::ModifierNameTemplate::GenerateModifiers(int32_t level) const
|
||||
{
|
||||
std::vector<ModifierInstance> result;
|
||||
|
||||
for (const auto& modifierTemplate : modifiers)
|
||||
{
|
||||
auto modifiers = modifierTemplate.GenerateModifiers(level);
|
||||
|
||||
result.insert(result.end(), modifiers.begin(), modifiers.end());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
55
dGame/ModifierNameTemplate.h
Normal file
55
dGame/ModifierNameTemplate.h
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "eItemType.h"
|
||||
#include "ModifierNameType.h"
|
||||
#include "ModifierRarity.h"
|
||||
#include "ModifierTemplate.h"
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
class ModifierNameTemplate
|
||||
{
|
||||
public:
|
||||
ModifierNameTemplate(const nlohmann::json& json);
|
||||
|
||||
nlohmann::json ToJson() const;
|
||||
|
||||
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
|
||||
|
||||
// Getters
|
||||
|
||||
const std::string& GetName() const { return name; }
|
||||
|
||||
ModifierNameType GetType() const { return type; }
|
||||
|
||||
const std::vector<ModifierTemplate>& GetModifiers() const { return modifiers; }
|
||||
|
||||
const std::vector<eItemType>& GetItemTypes() const { return itemTypes; }
|
||||
|
||||
int32_t GetMinLevel() const { return minLevel; }
|
||||
|
||||
int32_t GetMaxLevel() const { return maxLevel; }
|
||||
|
||||
ModifierRarity GetRarity() const { return rarity; }
|
||||
|
||||
int32_t GetLOT() const { return lot; }
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
int32_t lot;
|
||||
ModifierNameType type;
|
||||
std::vector<ModifierTemplate> modifiers;
|
||||
std::vector<eItemType> itemTypes;
|
||||
int32_t minLevel;
|
||||
int32_t maxLevel;
|
||||
ModifierRarity rarity;
|
||||
};
|
||||
|
||||
}
|
16
dGame/ModifierNameType.h
Normal file
16
dGame/ModifierNameType.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
enum class ModifierNameType : uint8_t
|
||||
{
|
||||
Prefix,
|
||||
Suffix,
|
||||
Object,
|
||||
Skill
|
||||
};
|
||||
|
||||
}
|
14
dGame/ModifierOperator.h
Normal file
14
dGame/ModifierOperator.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
enum class ModifierOperator : uint8_t
|
||||
{
|
||||
Additive,
|
||||
Multiplicative
|
||||
};
|
||||
|
||||
}
|
31
dGame/ModifierRarity.cpp
Normal file
31
dGame/ModifierRarity.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include "ModifierRarity.h"
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace nejlika;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
const std::unordered_map<ModifierRarity, std::string> colorMap = {
|
||||
{ModifierRarity::Common, "#FFFFFF"},
|
||||
{ModifierRarity::Uncommon, "#B5AC15"},
|
||||
{ModifierRarity::Rare, "#3EEA4A"},
|
||||
{ModifierRarity::Epic, "#2F83C1"},
|
||||
{ModifierRarity::Legendary, "#852DCA"},
|
||||
{ModifierRarity::Relic, "#00FFFF"}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const std::string& nejlika::ModifierRarityHelper::GetModifierRarityColor(ModifierRarity rarity)
|
||||
{
|
||||
const auto color = colorMap.find(rarity);
|
||||
|
||||
if (color != colorMap.end()) {
|
||||
return color->second;
|
||||
}
|
||||
|
||||
static const std::string white = "#FFFFFF";
|
||||
|
||||
return white;
|
||||
}
|
24
dGame/ModifierRarity.h
Normal file
24
dGame/ModifierRarity.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
enum class ModifierRarity : uint8_t
|
||||
{
|
||||
Common,
|
||||
Uncommon,
|
||||
Rare,
|
||||
Epic,
|
||||
Legendary,
|
||||
Relic
|
||||
};
|
||||
|
||||
namespace ModifierRarityHelper
|
||||
{
|
||||
const std::string& GetModifierRarityColor(ModifierRarity rarity);
|
||||
}
|
||||
|
||||
}
|
19
dGame/ModifierScale.cpp
Normal file
19
dGame/ModifierScale.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "ModifierScale.h"
|
||||
|
||||
nejlika::ModifierScale::ModifierScale(const nlohmann::json & json)
|
||||
{
|
||||
level = json["level"].get<int32_t>();
|
||||
min = json["min"].get<float>();
|
||||
max = json["max"].get<float>();
|
||||
}
|
||||
|
||||
nlohmann::json nejlika::ModifierScale::ToJson() const
|
||||
{
|
||||
nlohmann::json json;
|
||||
|
||||
json["level"] = level;
|
||||
json["min"] = min;
|
||||
json["max"] = max;
|
||||
|
||||
return json;
|
||||
}
|
33
dGame/ModifierScale.h
Normal file
33
dGame/ModifierScale.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
class ModifierScale
|
||||
{
|
||||
public:
|
||||
ModifierScale() = default;
|
||||
|
||||
ModifierScale(int32_t level, float min, float max) : level(level), min(min), max(max) {}
|
||||
|
||||
ModifierScale(const nlohmann::json& json);
|
||||
|
||||
nlohmann::json ToJson() const;
|
||||
|
||||
int32_t GetLevel() const { return level; }
|
||||
|
||||
float GetMin() const { return min; }
|
||||
|
||||
float GetMax() const { return max; }
|
||||
|
||||
private:
|
||||
int32_t level = 0;
|
||||
float min = 0.0f;
|
||||
float max = 0.0f;
|
||||
};
|
||||
|
||||
}
|
231
dGame/ModifierTemplate.cpp
Normal file
231
dGame/ModifierTemplate.cpp
Normal file
@ -0,0 +1,231 @@
|
||||
#include "ModifierTemplate.h"
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace nejlika;
|
||||
|
||||
nejlika::ModifierTemplate::ModifierTemplate(const nlohmann::json& config) {
|
||||
if (config.contains("type"))
|
||||
{
|
||||
selector = ModifierTemplateSelector::One;
|
||||
|
||||
const auto type = magic_enum::enum_cast<ModifierType>(config["type"].get<std::string>());
|
||||
|
||||
if (type.has_value())
|
||||
{
|
||||
types = {type.value()};
|
||||
}
|
||||
else
|
||||
{
|
||||
types = {};
|
||||
}
|
||||
}
|
||||
else if (config.contains("all"))
|
||||
{
|
||||
selector = ModifierTemplateSelector::All;
|
||||
|
||||
types = {};
|
||||
|
||||
for (const auto& type : config["all"])
|
||||
{
|
||||
const auto t = magic_enum::enum_cast<ModifierType>(type.get<std::string>());
|
||||
|
||||
if (t.has_value())
|
||||
{
|
||||
types.push_back(t.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (config.contains("two-of"))
|
||||
{
|
||||
selector = ModifierTemplateSelector::Two;
|
||||
|
||||
types = {};
|
||||
|
||||
for (const auto& type : config["two-of"])
|
||||
{
|
||||
const auto t = magic_enum::enum_cast<ModifierType>(type.get<std::string>());
|
||||
|
||||
if (t.has_value())
|
||||
{
|
||||
types.push_back(t.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
types = {};
|
||||
}
|
||||
|
||||
if (!config.contains("scaling"))
|
||||
{
|
||||
throw std::runtime_error("Modifier template is missing scaling.");
|
||||
}
|
||||
|
||||
const auto scaling = config["scaling"];
|
||||
|
||||
for (const auto& scaler : scaling)
|
||||
{
|
||||
scales.push_back(ModifierScale(scaler));
|
||||
}
|
||||
|
||||
if (config.contains("category"))
|
||||
{
|
||||
category = magic_enum::enum_cast<ModifierCategory>(config["category"].get<std::string>()).value_or(ModifierCategory::Player);
|
||||
}
|
||||
else
|
||||
{
|
||||
category = ModifierCategory::Player;
|
||||
}
|
||||
|
||||
isResistance = config.contains("resistance") ? config["resistance"].get<bool>() : false;
|
||||
|
||||
effectID = config.contains("effect-id") ? config["effect-id"].get<uint32_t>() : 0;
|
||||
|
||||
effectType = config.contains("effect-type") ? config["effect-type"].get<std::string>() : "";
|
||||
|
||||
if (config.contains("operator"))
|
||||
{
|
||||
operatorType = magic_enum::enum_cast<ModifierOperator>(config["operator"].get<std::string>()).value_or(ModifierOperator::Additive);
|
||||
}
|
||||
else
|
||||
{
|
||||
operatorType = ModifierOperator::Additive;
|
||||
}
|
||||
|
||||
// Old format
|
||||
if (config.contains("percentage"))
|
||||
{
|
||||
if (config["percentage"].get<bool>()) {
|
||||
operatorType = ModifierOperator::Multiplicative;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.contains("pet"))
|
||||
{
|
||||
if (config["pet"].get<bool>()) {
|
||||
category = ModifierCategory::Pet;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.contains("owner"))
|
||||
{
|
||||
if (config["owner"].get<bool>()) {
|
||||
category = ModifierCategory::Player;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json nejlika::ModifierTemplate::ToJson() const {
|
||||
nlohmann::json config;
|
||||
|
||||
if (selector == ModifierTemplateSelector::One)
|
||||
{
|
||||
config["type"] = magic_enum::enum_name(types[0]);
|
||||
}
|
||||
else if (selector == ModifierTemplateSelector::All)
|
||||
{
|
||||
config["all"] = true;
|
||||
|
||||
for (const auto& type : types)
|
||||
{
|
||||
config["types"].push_back(magic_enum::enum_name(type));
|
||||
}
|
||||
}
|
||||
else if (selector == ModifierTemplateSelector::Two)
|
||||
{
|
||||
config["two-of"] = true;
|
||||
|
||||
for (const auto& type : types)
|
||||
{
|
||||
config["two-of"].push_back(magic_enum::enum_name(type));
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json scaling;
|
||||
|
||||
for (const auto& scale : scales)
|
||||
{
|
||||
scaling.push_back(scale.ToJson());
|
||||
}
|
||||
|
||||
config["scaling"] = scaling;
|
||||
|
||||
config["category"] = magic_enum::enum_name(category);
|
||||
config["resistance"] = isResistance;
|
||||
config["effect-id"] = effectID;
|
||||
config["effect-type"] = effectType;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
std::vector<ModifierInstance> nejlika::ModifierTemplate::GenerateModifiers(int32_t level) const {
|
||||
std::vector<ModifierInstance> modifiers;
|
||||
|
||||
std::vector<ModifierType> selectedTypes;
|
||||
|
||||
if (types.empty())
|
||||
{
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
if (selector == ModifierTemplateSelector::One)
|
||||
{
|
||||
selectedTypes = {types[0]};
|
||||
}
|
||||
else if (selector == ModifierTemplateSelector::All)
|
||||
{
|
||||
selectedTypes = types;
|
||||
}
|
||||
else if (selector == ModifierTemplateSelector::Two)
|
||||
{
|
||||
if (types.size() < 2)
|
||||
{
|
||||
selectedTypes = types;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Randomly select two types
|
||||
selectedTypes = types;
|
||||
|
||||
std::shuffle(selectedTypes.begin(), selectedTypes.end(), std::mt19937(std::random_device()()));
|
||||
|
||||
selectedTypes.resize(2);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& selectedType : selectedTypes)
|
||||
{
|
||||
auto modifierOpt = GenerateModifier(selectedType, level);
|
||||
|
||||
if (modifierOpt.has_value())
|
||||
{
|
||||
modifiers.push_back(modifierOpt.value());
|
||||
}
|
||||
}
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
std::optional<ModifierInstance> nejlika::ModifierTemplate::GenerateModifier(ModifierType type, int32_t level) const {
|
||||
ModifierScale scale;
|
||||
bool found = false;
|
||||
|
||||
// Select the scale with the highest level that is less than or equal to the current level
|
||||
for (const auto& s : scales) {
|
||||
if (s.GetLevel() <= level && s.GetLevel() > scale.GetLevel()) {
|
||||
scale = s;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
float value = GeneralUtils::GenerateRandomNumber<float>(scale.GetMin(), scale.GetMax());
|
||||
|
||||
return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType);
|
||||
}
|
88
dGame/ModifierTemplate.h
Normal file
88
dGame/ModifierTemplate.h
Normal file
@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
#include "ModifierInstance.h"
|
||||
#include "ModifierScale.h"
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
enum ModifierTemplateSelector : uint8_t
|
||||
{
|
||||
One,
|
||||
All,
|
||||
Two
|
||||
};
|
||||
|
||||
class ModifierTemplate
|
||||
{
|
||||
public:
|
||||
ModifierTemplate(
|
||||
const std::vector<ModifierType>& types, ModifierTemplateSelector selector, ModifierCategory category, bool isResistance, uint32_t effectID, const std::string& effectType
|
||||
) : types(types), selector(selector), category(category), isResistance(isResistance), effectID(effectID), effectType(effectType) {}
|
||||
|
||||
/**
|
||||
* @brief Construct a new Modifier Template object from a json configuration.
|
||||
*
|
||||
* @param config The json configuration.
|
||||
*/
|
||||
ModifierTemplate(const nlohmann::json& config);
|
||||
|
||||
/**
|
||||
* @brief Convert the modifier template to a json representation.
|
||||
*
|
||||
* @return The json representation.
|
||||
*/
|
||||
nlohmann::json ToJson() const;
|
||||
|
||||
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
|
||||
|
||||
// Getters and setters
|
||||
|
||||
const std::vector<ModifierType>& GetTypes() const { return types; }
|
||||
|
||||
ModifierTemplateSelector GetSelector() const { return selector; }
|
||||
|
||||
const std::vector<ModifierScale>& GetScales() const { return scales; }
|
||||
|
||||
ModifierCategory GetCategory() const { return category; }
|
||||
|
||||
bool IsResistance() const { return isResistance; }
|
||||
|
||||
uint32_t GetEffectID() const { return effectID; }
|
||||
|
||||
std::string GetEffectType() const { return effectType; }
|
||||
|
||||
void SetTypes(const std::vector<ModifierType>& types) { this->types = types; }
|
||||
|
||||
void SetSelector(ModifierTemplateSelector selector) { this->selector = selector; }
|
||||
|
||||
void SetScales(const std::vector<ModifierScale>& scales) { this->scales = scales; }
|
||||
|
||||
void SetCategory(ModifierCategory category) { this->category = category; }
|
||||
|
||||
void SetIsResistance(bool isResistance) { this->isResistance = isResistance; }
|
||||
|
||||
void SetEffectID(uint32_t effectID) { this->effectID = effectID; }
|
||||
|
||||
void SetEffectType(const std::string& effectType) { this->effectType = effectType; }
|
||||
|
||||
private:
|
||||
std::optional<ModifierInstance> GenerateModifier(ModifierType type, int32_t level) const;
|
||||
|
||||
std::vector<ModifierType> types;
|
||||
ModifierTemplateSelector selector;
|
||||
std::vector<ModifierScale> scales;
|
||||
ModifierCategory category;
|
||||
ModifierOperator operatorType;
|
||||
bool isResistance;
|
||||
uint32_t effectID;
|
||||
std::string effectType;
|
||||
|
||||
};
|
||||
|
||||
}
|
38
dGame/ModifierType.cpp
Normal file
38
dGame/ModifierType.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
#include "ModifierType.h"
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace nejlika;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
const std::unordered_map<ModifierType, std::string> colorMap = {
|
||||
{ModifierType::Health, "#750000"},
|
||||
{ModifierType::Armor, "#525252"},
|
||||
{ModifierType::Imagination, "#0077FF"},
|
||||
{ModifierType::Offensive, "#71583B"},
|
||||
{ModifierType::Defensive, "#71583B"},
|
||||
{ModifierType::Slashing, "#666666"},
|
||||
{ModifierType::Piercing, "#4f4f4f"},
|
||||
{ModifierType::Bludgeoning, "#e84646"},
|
||||
{ModifierType::Fire, "#ff0000"},
|
||||
{ModifierType::Cold, "#94d0f2"},
|
||||
{ModifierType::Lightning, "#00a2ff"},
|
||||
{ModifierType::Corruption, "#3d00ad"},
|
||||
{ModifierType::Psychic, "#4b0161"}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const std::string& nejlika::GetModifierTypeColor(ModifierType type)
|
||||
{
|
||||
const auto color = colorMap.find(type);
|
||||
|
||||
if (color != colorMap.end()) {
|
||||
return color->second;
|
||||
}
|
||||
|
||||
static const std::string white = "#FFFFFF";
|
||||
|
||||
return white;
|
||||
}
|
34
dGame/ModifierType.h
Normal file
34
dGame/ModifierType.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
enum class ModifierType : uint8_t
|
||||
{
|
||||
Health,
|
||||
Armor,
|
||||
Imagination,
|
||||
|
||||
Offensive,
|
||||
Defensive,
|
||||
|
||||
Slashing,
|
||||
Piercing,
|
||||
Bludgeoning,
|
||||
|
||||
Fire,
|
||||
Cold,
|
||||
Lightning,
|
||||
Corruption,
|
||||
|
||||
Psychic,
|
||||
|
||||
Invalid
|
||||
};
|
||||
|
||||
const std::string& GetModifierTypeColor(ModifierType type);
|
||||
|
||||
}
|
@ -1,307 +0,0 @@
|
||||
#include "Nejlika.h"
|
||||
|
||||
#include "SlashCommandHandler.h"
|
||||
|
||||
#include <InventoryComponent.h>
|
||||
#include <Item.h>
|
||||
#include <ChatPackets.h>
|
||||
#include <Amf3.h>
|
||||
#include <iomanip>
|
||||
|
||||
void nejlika::Initalize()
|
||||
{
|
||||
Command itemDescriptionCommand{
|
||||
.help = "Special UI command, does nothing when used in chat.",
|
||||
.info = "Special UI command, does nothing when used in chat.",
|
||||
.aliases = {"d"},
|
||||
.handle = ItemDescription,
|
||||
.requiredLevel = eGameMasterLevel::CIVILIAN
|
||||
};
|
||||
SlashCommandHandler::RegisterCommand(itemDescriptionCommand);
|
||||
}
|
||||
|
||||
void nejlika::ItemDescription(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
if (splitArgs.empty()) return;
|
||||
|
||||
auto requestId = GeneralUtils::TryParse<int32_t>(splitArgs[0]);
|
||||
|
||||
if (!requestId.has_value()) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto itemId = GeneralUtils::TryParse<LWOOBJID>(splitArgs[1]);
|
||||
|
||||
if (!itemId.has_value()) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (!inventoryComponent) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* item = inventoryComponent->FindItemById(itemId.value());
|
||||
|
||||
if (!item) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Item not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& itemData = item->GetConfig();
|
||||
|
||||
LDFBaseData* modifiersData = nullptr;
|
||||
|
||||
for (const auto& data : itemData) {
|
||||
if (data->GetKey() == u"modifiers") {
|
||||
modifiersData = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!modifiersData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto modifiersStr = dynamic_cast<LDFData<std::string>*>(modifiersData)->GetValueAsString();
|
||||
|
||||
if (modifiersStr.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::stringstream name;
|
||||
std::stringstream desc;
|
||||
|
||||
auto parts = GeneralUtils::SplitString(modifiersStr, '&');
|
||||
|
||||
if (parts.size() != 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto names = GeneralUtils::SplitString(parts[0], ';');
|
||||
|
||||
std::vector<ItemName> itemNames;
|
||||
|
||||
for (const auto& name : names) {
|
||||
ItemName itemName(name);
|
||||
|
||||
itemNames.push_back(itemName);
|
||||
}
|
||||
|
||||
name << ItemName::GenerateHtmlString(itemNames) << "\n";
|
||||
|
||||
auto modifiers = GeneralUtils::SplitString(parts[1], ';');
|
||||
|
||||
for (const auto& modifier : modifiers) {
|
||||
ItemModifier itemModifier(modifier);
|
||||
|
||||
desc << itemModifier.GenerateHtmlString() << "\n";
|
||||
}
|
||||
|
||||
std::stringstream messageName;
|
||||
messageName << "desc" << requestId.value();
|
||||
|
||||
AMFArrayValue amfArgs;
|
||||
|
||||
amfArgs.Insert("t", true);
|
||||
amfArgs.Insert("d", desc.str());
|
||||
amfArgs.Insert("n", name.str());
|
||||
|
||||
GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, messageName.str(), amfArgs);
|
||||
}
|
||||
|
||||
nejlika::ItemModifier::ItemModifier(const std::string& config) {
|
||||
auto splitConfig = GeneralUtils::SplitString(config, ',');
|
||||
|
||||
if (splitConfig.size() != 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
type = static_cast<ItemModifierType>(GeneralUtils::TryParse<uint8_t>(splitConfig[0]).value());
|
||||
value = GeneralUtils::TryParse<float>(splitConfig[1]).value();
|
||||
isPercentage = GeneralUtils::TryParse<bool>(splitConfig[2]).value();
|
||||
}
|
||||
|
||||
std::string nejlika::ItemModifier::ToString() const {
|
||||
std::stringstream ss;
|
||||
ss << static_cast<uint8_t>(type) << ',' << value << ',' << isPercentage;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string nejlika::ItemModifier::GenerateHtmlString() const {
|
||||
// "<font color=\"#38B6FF\">Physical: +20%</font>\n..."
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "<font color=\"";
|
||||
|
||||
/*
|
||||
* Health: #750000
|
||||
* Armor: #525252
|
||||
* Imagination: #0077FF
|
||||
* Slashing: #666666
|
||||
* Piercing: #4f4f4f
|
||||
* Bludgeoning: #e84646
|
||||
* Fire: #ff0000
|
||||
* Cold: #94d0f2
|
||||
* Lightning: #00a2ff
|
||||
* Corruption: #3d00ad
|
||||
* Psychic: #4b0161
|
||||
*/
|
||||
static const std::unordered_map<ItemModifierType, std::string> colorMap = {
|
||||
{ItemModifierType::Health, "#750000"},
|
||||
{ItemModifierType::Armor, "#525252"},
|
||||
{ItemModifierType::Imagination, "#0077FF"},
|
||||
{ItemModifierType::Slashing, "#666666"},
|
||||
{ItemModifierType::Piercing, "#4f4f4f"},
|
||||
{ItemModifierType::Bludgeoning, "#e84646"},
|
||||
{ItemModifierType::Fire, "#ff0000"},
|
||||
{ItemModifierType::Cold, "#94d0f2"},
|
||||
{ItemModifierType::Lightning, "#00a2ff"},
|
||||
{ItemModifierType::Corruption, "#3d00ad"},
|
||||
{ItemModifierType::Psychic, "#4b0161"}
|
||||
};
|
||||
|
||||
static const std::unordered_map<ItemModifierType, std::string> namesMap = {
|
||||
{ItemModifierType::Health, "Health"},
|
||||
{ItemModifierType::Armor, "Armor"},
|
||||
{ItemModifierType::Imagination, "Imagination"},
|
||||
{ItemModifierType::Slashing, "Slashing"},
|
||||
{ItemModifierType::Piercing, "Piercing"},
|
||||
{ItemModifierType::Bludgeoning, "Bludgeoning"},
|
||||
{ItemModifierType::Fire, "Fire"},
|
||||
{ItemModifierType::Cold, "Cold"},
|
||||
{ItemModifierType::Lightning, "Lightning"},
|
||||
{ItemModifierType::Corruption, "Corruption"},
|
||||
{ItemModifierType::Psychic, "Psychic"}
|
||||
};
|
||||
|
||||
const auto color = colorMap.find(type);
|
||||
|
||||
if (color != colorMap.end()) {
|
||||
ss << color->second;
|
||||
} else {
|
||||
ss << "#FFFFFF";
|
||||
}
|
||||
|
||||
ss << "\">";
|
||||
|
||||
const auto name = namesMap.find(type);
|
||||
|
||||
if (name != namesMap.end()) {
|
||||
ss << name->second;
|
||||
} else {
|
||||
ss << "Unknown";
|
||||
}
|
||||
|
||||
ss << ": ";
|
||||
|
||||
if (value > 0) {
|
||||
ss << "+";
|
||||
}
|
||||
else if (value < 0) {
|
||||
ss << "-";
|
||||
}
|
||||
|
||||
// Only show 2 decimal places
|
||||
ss << std::fixed << std::setprecision(2) << std::abs(value);
|
||||
|
||||
if (isPercentage) {
|
||||
ss << "%";
|
||||
}
|
||||
|
||||
ss << "</font>";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
nejlika::ItemName::ItemName(const std::string& config) {
|
||||
auto splitConfig = GeneralUtils::SplitString(config, ',');
|
||||
|
||||
if (splitConfig.size() != 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
type = static_cast<ItemNameType>(GeneralUtils::TryParse<uint8_t>(splitConfig[0]).value());
|
||||
name = splitConfig[1];
|
||||
prefix = GeneralUtils::TryParse<bool>(splitConfig[2]).value();
|
||||
}
|
||||
|
||||
std::string nejlika::ItemName::ToString() const {
|
||||
std::stringstream ss;
|
||||
ss << static_cast<uint8_t>(type) << ',' << name << ',' << prefix;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string nejlika::ItemName::GenerateHtmlString() const {
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "<font color=\"";
|
||||
|
||||
/*
|
||||
* Common: #FFFFFF
|
||||
* Uncommon: #00FF00
|
||||
* Rare: #0077FF
|
||||
* Epic: #FF00FF
|
||||
* Legendary: #FF7700
|
||||
* Relic: #FFC391
|
||||
*/
|
||||
|
||||
static const std::unordered_map<ItemNameType, std::string> colorMap = {
|
||||
{ItemNameType::Common, "#FFFFFF"},
|
||||
{ItemNameType::Uncommon, "#00FF00"},
|
||||
{ItemNameType::Rare, "#0077FF"},
|
||||
{ItemNameType::Epic, "#FF00FF"},
|
||||
{ItemNameType::Legendary, "#FF7700"},
|
||||
{ItemNameType::Relic, "#FFC391"}
|
||||
};
|
||||
|
||||
const auto color = colorMap.find(type);
|
||||
|
||||
if (color != colorMap.end()) {
|
||||
ss << color->second;
|
||||
} else {
|
||||
ss << "#FFFFFF";
|
||||
}
|
||||
|
||||
ss << "\">";
|
||||
|
||||
ss << name;
|
||||
|
||||
ss << "</font>";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string nejlika::ItemName::GenerateHtmlString(const std::vector<ItemName>& names) {
|
||||
// Prefix-1 Prefix-2 NAME Suffix-1 Suffix-2
|
||||
std::stringstream ss;
|
||||
|
||||
for (const auto& name : names) {
|
||||
if (name.prefix) {
|
||||
ss << name.GenerateHtmlString() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
ss << "<font color=\"#56B555\">NAME</font>";
|
||||
|
||||
for (const auto& name : names) {
|
||||
if (!name.prefix && !name.name.empty()) {
|
||||
ss << name.GenerateHtmlString() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the last newline
|
||||
auto str = ss.str();
|
||||
|
||||
if (!str.empty() && str.back() == '\n') {
|
||||
str.pop_back();
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
|
@ -1,67 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
|
||||
void Initalize();
|
||||
|
||||
void ItemDescription(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
|
||||
enum class ItemModifierType : uint8_t
|
||||
{
|
||||
Health,
|
||||
Armor,
|
||||
Imagination,
|
||||
Slashing,
|
||||
Piercing,
|
||||
Bludgeoning,
|
||||
Fire,
|
||||
Cold,
|
||||
Lightning,
|
||||
Corruption,
|
||||
Psychic
|
||||
};
|
||||
|
||||
struct ItemModifier
|
||||
{
|
||||
ItemModifierType type;
|
||||
float value;
|
||||
bool isPercentage;
|
||||
|
||||
ItemModifier(ItemModifierType type, float value, bool isPercentage) : type(type), value(value), isPercentage(isPercentage) {}
|
||||
|
||||
ItemModifier(const std::string& config);
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
std::string GenerateHtmlString() const;
|
||||
};
|
||||
|
||||
enum class ItemNameType : uint8_t
|
||||
{
|
||||
Common,
|
||||
Uncommon,
|
||||
Rare,
|
||||
Epic,
|
||||
Legendary,
|
||||
Relic
|
||||
};
|
||||
|
||||
struct ItemName
|
||||
{
|
||||
ItemNameType type;
|
||||
std::string name;
|
||||
bool prefix;
|
||||
|
||||
ItemName(ItemNameType type, const std::string& name, bool prefix) : type(type), name(name), prefix(prefix) {}
|
||||
|
||||
ItemName(const std::string& config);
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
std::string GenerateHtmlString() const;
|
||||
|
||||
static std::string GenerateHtmlString(const std::vector<ItemName>& names);
|
||||
};
|
||||
|
||||
}
|
151
dGame/NejlikaData.cpp
Normal file
151
dGame/NejlikaData.cpp
Normal file
@ -0,0 +1,151 @@
|
||||
#include "NejlikaData.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "dConfig.h"
|
||||
#include "json.hpp"
|
||||
#include "Logger.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
using namespace nejlika;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::unordered_map<nejlika::ModifierNameType, std::vector<nejlika::ModifierNameTemplate>> modifierNameTemplates;
|
||||
|
||||
std::unordered_map<LWOOBJID, nejlika::AdditionalItemData> additionalItemData;
|
||||
|
||||
std::unordered_map<LWOOBJID, nejlika::AdditionalEntityData> additionalEntityData;
|
||||
|
||||
std::unordered_map<LOT, nejlika::EntityTemplate> entityTemplates;
|
||||
|
||||
}
|
||||
|
||||
const std::unordered_map<ModifierNameType,std::vector<ModifierNameTemplate>>& nejlika::NejlikaData::GetModifierNameTemplates()
|
||||
{
|
||||
return modifierNameTemplates;
|
||||
}
|
||||
|
||||
const std::vector<nejlika::ModifierNameTemplate>& nejlika::NejlikaData::GetModifierNameTemplates(ModifierNameType type)
|
||||
{
|
||||
const auto it = modifierNameTemplates.find(type);
|
||||
|
||||
if (it != modifierNameTemplates.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
static const std::vector<nejlika::ModifierNameTemplate> empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
const std::optional<AdditionalItemData*> nejlika::NejlikaData::GetAdditionalItemData(LWOOBJID id) {
|
||||
const auto& it = additionalItemData.find(id);
|
||||
|
||||
if (it != additionalItemData.end()) {
|
||||
return std::optional<AdditionalItemData*>(&it->second);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::optional<AdditionalEntityData*> nejlika::NejlikaData::GetAdditionalEntityData(LWOOBJID id) {
|
||||
const auto& it = additionalEntityData.find(id);
|
||||
|
||||
if (it != additionalEntityData.end()) {
|
||||
return std::optional<AdditionalEntityData*>(&it->second);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::optional<EntityTemplate*> nejlika::NejlikaData::GetEntityTemplate(LOT lot) {
|
||||
const auto& it = entityTemplates.find(lot);
|
||||
|
||||
if (it != entityTemplates.end()) {
|
||||
return std::optional<EntityTemplate*>(&it->second);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void nejlika::NejlikaData::SetAdditionalItemData(LWOOBJID id, AdditionalItemData data) {
|
||||
additionalItemData[id] = data;
|
||||
}
|
||||
|
||||
void nejlika::NejlikaData::SetAdditionalEntityData(LWOOBJID id, AdditionalEntityData data) {
|
||||
additionalEntityData[id] = data;
|
||||
}
|
||||
|
||||
void nejlika::NejlikaData::UnsetAdditionalItemData(LWOOBJID id) {
|
||||
additionalItemData.erase(id);
|
||||
}
|
||||
|
||||
void nejlika::NejlikaData::UnsetAdditionalEntityData(LWOOBJID id) {
|
||||
additionalEntityData.erase(id);
|
||||
}
|
||||
|
||||
void nejlika::NejlikaData::LoadNejlikaData()
|
||||
{
|
||||
modifierNameTemplates.clear();
|
||||
|
||||
// Load data from json file
|
||||
const auto& filename = Game::config->GetValue("nejlika");
|
||||
|
||||
if (filename.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream file(filename);
|
||||
|
||||
if (!file.is_open())
|
||||
{
|
||||
LOG("Failed to open nejlika data file: %s", filename.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json json;
|
||||
|
||||
try
|
||||
{
|
||||
json = nlohmann::json::parse(file);
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
LOG("Failed to parse nejlika data file: %s", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json.contains("modifier-templates"))
|
||||
{
|
||||
LOG("nejlika data file does not contain modifier-templates");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& modifierTemplates = json["modifier-templates"];
|
||||
|
||||
for (const auto& value : modifierTemplates)
|
||||
{
|
||||
auto modifierTemplate = ModifierNameTemplate(value);
|
||||
|
||||
modifierNameTemplates[modifierTemplate.GetType()].push_back(modifierTemplate);
|
||||
}
|
||||
|
||||
LOG("Loaded %d modifier templates", modifierNameTemplates.size());
|
||||
|
||||
if (json.contains("entity-templates"))
|
||||
{
|
||||
const auto& entityTemplatesArray = json["entity-templates"];
|
||||
|
||||
for (const auto& value : entityTemplatesArray)
|
||||
{
|
||||
auto entityTemplate = EntityTemplate(value);
|
||||
|
||||
entityTemplates[entityTemplate.GetLOT()] = entityTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
LOG("Loaded %d entity templates", entityTemplates.size());
|
||||
}
|
||||
|
34
dGame/NejlikaData.h
Normal file
34
dGame/NejlikaData.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "ModifierNameTemplate.h"
|
||||
#include "EntityTemplate.h"
|
||||
#include "AdditionalItemData.h"
|
||||
#include "AdditionalEntityData.h"
|
||||
|
||||
namespace nejlika::NejlikaData
|
||||
{
|
||||
|
||||
const std::unordered_map<ModifierNameType, std::vector<ModifierNameTemplate>>& GetModifierNameTemplates();
|
||||
|
||||
const std::vector<ModifierNameTemplate>& GetModifierNameTemplates(ModifierNameType type);
|
||||
|
||||
const std::optional<AdditionalItemData*> GetAdditionalItemData(LWOOBJID id);
|
||||
|
||||
const std::optional<AdditionalEntityData*> GetAdditionalEntityData(LWOOBJID id);
|
||||
|
||||
const std::optional<EntityTemplate*> GetEntityTemplate(LOT lot);
|
||||
|
||||
void SetAdditionalItemData(LWOOBJID id, AdditionalItemData data);
|
||||
|
||||
void SetAdditionalEntityData(LWOOBJID id, AdditionalEntityData data);
|
||||
|
||||
void UnsetAdditionalItemData(LWOOBJID id);
|
||||
|
||||
void UnsetAdditionalEntityData(LWOOBJID id);
|
||||
|
||||
void LoadNejlikaData();
|
||||
|
||||
}
|
426
dGame/NejlikaHooks.cpp
Normal file
426
dGame/NejlikaHooks.cpp
Normal file
@ -0,0 +1,426 @@
|
||||
#include "NejlikaHooks.h"
|
||||
|
||||
#include "SlashCommandHandler.h"
|
||||
|
||||
#include <InventoryComponent.h>
|
||||
#include <Item.h>
|
||||
#include <ChatPackets.h>
|
||||
#include <Amf3.h>
|
||||
#include <iomanip>
|
||||
#include <dConfig.h>
|
||||
#include <magic_enum.hpp>
|
||||
#include <GeneralUtils.h>
|
||||
#include <LevelProgressionComponent.h>
|
||||
#include <DestroyableComponent.h>
|
||||
#include <unordered_set>
|
||||
#include <BaseCombatAIComponent.h>
|
||||
#include <PlayerManager.h>
|
||||
#include <eGameMessageType.h>
|
||||
#include <dServer.h>
|
||||
|
||||
#include "NejlikaData.h"
|
||||
|
||||
using namespace nejlika;
|
||||
using namespace nejlika::NejlikaData;
|
||||
|
||||
void nejlika::NejlikaHooks::InstallHooks()
|
||||
{
|
||||
Command itemDescriptionCommand{
|
||||
.help = "Special UI command, does nothing when used in chat.",
|
||||
.info = "Special UI command, does nothing when used in chat.",
|
||||
.aliases = {"d"},
|
||||
.handle = ItemDescription,
|
||||
.requiredLevel = eGameMasterLevel::CIVILIAN
|
||||
};
|
||||
SlashCommandHandler::RegisterCommand(itemDescriptionCommand);
|
||||
|
||||
LoadNejlikaData();
|
||||
|
||||
InventoryComponent::OnItemCreated += [](InventoryComponent* component, Item* item) {
|
||||
const auto& itemType = static_cast<eItemType>(item->GetInfo().itemType);
|
||||
|
||||
static const std::unordered_set<eItemType> listOfHandledItems {
|
||||
eItemType::HAT,
|
||||
eItemType::CHEST,
|
||||
eItemType::LEGS,
|
||||
eItemType::NECK,
|
||||
eItemType::LEFT_HAND,
|
||||
eItemType::RIGHT_HAND
|
||||
};
|
||||
|
||||
if (listOfHandledItems.find(itemType) == listOfHandledItems.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->GetLot() == 6086) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* levelProgressionComponent = component->GetParent()->GetComponent<LevelProgressionComponent>();
|
||||
|
||||
if (!levelProgressionComponent) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto additionalData = AdditionalItemData(item);
|
||||
|
||||
LOG("Rolling modifiers for item: %d", item->GetLot());
|
||||
|
||||
additionalData.RollModifiers(item, levelProgressionComponent->GetLevel());
|
||||
|
||||
SetAdditionalItemData(item->GetId(), additionalData);
|
||||
};
|
||||
|
||||
EntityManager::OnEntityCreated += [](Entity* entity) {
|
||||
auto* destroyable = entity->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (!destroyable) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetAdditionalEntityData(entity->GetObjectID(), AdditionalEntityData(entity->GetObjectID(), entity->GetLOT()));
|
||||
|
||||
auto additionalDataOpt = GetAdditionalEntityData(entity->GetObjectID());
|
||||
|
||||
if (!additionalDataOpt.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& additionalData = *additionalDataOpt.value();
|
||||
|
||||
additionalData.ApplyToEntity();
|
||||
};
|
||||
|
||||
EntityManager::OnEntityDestroyed += [](Entity* entity) {
|
||||
UnsetAdditionalEntityData(entity->GetObjectID());
|
||||
};
|
||||
|
||||
InventoryComponent::OnItemLoaded += [](InventoryComponent* component, Item* item) {
|
||||
SetAdditionalItemData(item->GetId(), AdditionalItemData(item));
|
||||
};
|
||||
|
||||
InventoryComponent::OnItemDestroyed += [](InventoryComponent* component, Item* item) {
|
||||
UnsetAdditionalItemData(item->GetId());
|
||||
};
|
||||
|
||||
InventoryComponent::OnItemEquipped += [](InventoryComponent* component, Item* item) {
|
||||
std::cout << "Item equipped: " << item->GetId() << std::endl;
|
||||
|
||||
const auto entityDataOpt = GetAdditionalEntityData(component->GetParent()->GetObjectID());
|
||||
|
||||
if (!entityDataOpt.has_value()) {
|
||||
std::cout << "No entity data found for entity." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
auto& entityData = *entityDataOpt.value();
|
||||
|
||||
entityData.ApplyToEntity();
|
||||
|
||||
const auto itemDataOpt = GetAdditionalItemData(item->GetId());
|
||||
|
||||
if (!itemDataOpt.has_value()) {
|
||||
std::cout << "No item data found for item." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& itemData = *itemDataOpt.value();
|
||||
|
||||
const auto itemId = item->GetId();
|
||||
|
||||
std::cout << "Sending effects for item: " << itemId << " with " << itemData.GetModifierInstances().size() << " modifiers." << std::endl;
|
||||
|
||||
for (const auto& modifier : itemData.GetModifierInstances()) {
|
||||
const auto effectID = modifier.GetEffectID();
|
||||
const auto effectType = modifier.GetEffectType();
|
||||
|
||||
component->GetParent()->AddCallbackTimer(0.1f, [itemId, effectID, effectType]() {
|
||||
std::cout << "Sending effect: " << effectID << " - " << effectType << std::endl;
|
||||
GameMessages::SendPlayFXEffect(
|
||||
itemId,
|
||||
static_cast<int32_t>(effectID),
|
||||
GeneralUtils::UTF8ToUTF16(effectType),
|
||||
std::to_string(GeneralUtils::GenerateRandomNumber<uint32_t>())
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
InventoryComponent::OnItemUnequipped += [](InventoryComponent* component, Item* item) {
|
||||
const auto entityDataOpt = GetAdditionalEntityData(component->GetParent()->GetObjectID());
|
||||
|
||||
if (!entityDataOpt.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& entityData = *entityDataOpt.value();
|
||||
|
||||
entityData.ApplyToEntity();
|
||||
};
|
||||
|
||||
DestroyableComponent::OnDamageCalculation += [](Entity* damaged, LWOOBJID offender, uint32_t skillID, uint32_t& damage) {
|
||||
std::cout << "Calculating damage with skill: " << skillID << std::endl;
|
||||
|
||||
const auto damagedEntityOpt = GetAdditionalEntityData(damaged->GetObjectID());
|
||||
|
||||
if (!damagedEntityOpt.has_value()) {
|
||||
std::cout << "No entity data found for damaged entity." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
auto& damagedEntity = *damagedEntityOpt.value();
|
||||
|
||||
const auto offenderEntityOpt = GetAdditionalEntityData(offender);
|
||||
|
||||
if (!offenderEntityOpt.has_value()) {
|
||||
std::cout << "No entity data found for offender entity." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
auto& offenderEntity = *offenderEntityOpt.value();
|
||||
|
||||
auto* baseCombatAIComponent = damaged->GetComponent<BaseCombatAIComponent>();
|
||||
|
||||
if (baseCombatAIComponent) {
|
||||
baseCombatAIComponent->SetThreat(offender, 1);
|
||||
}
|
||||
|
||||
damagedEntity.CheckForRescale(&offenderEntity);
|
||||
offenderEntity.CheckForRescale(&damagedEntity);
|
||||
|
||||
int32_t level = offenderEntity.GetLevel();
|
||||
|
||||
auto* offfendEntity = Game::entityManager->GetEntity(offender);
|
||||
|
||||
if (offfendEntity == nullptr) {
|
||||
std::cout << "Offender entity not found." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
auto* levelProgressionComponent = offfendEntity->GetComponent<LevelProgressionComponent>();
|
||||
|
||||
if (levelProgressionComponent) {
|
||||
level = levelProgressionComponent->GetLevel();
|
||||
}
|
||||
|
||||
LOT itemLot = 0;
|
||||
LWOOBJID itemId = 0;
|
||||
|
||||
auto* inventoryComponent = offfendEntity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent) {
|
||||
const auto& skills = inventoryComponent->GetSkills();
|
||||
|
||||
std::cout << "Found " << skills.size() << " skills." << std::endl;
|
||||
|
||||
// omg...
|
||||
for (const auto& [slot, skill] : skills) {
|
||||
std::cout << "Found skill: " << skill << std::endl;
|
||||
|
||||
if (skill != skillID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& equipped = inventoryComponent->GetEquippedItems();
|
||||
|
||||
for (const auto& [equippedSlot, itemDetails] : equipped) {
|
||||
std::cout << "Found equipped item: " << itemDetails.lot << std::endl;
|
||||
|
||||
const auto info = Inventory::FindItemComponent(itemDetails.lot);
|
||||
|
||||
const auto itemBehaviorSlot = InventoryComponent::FindBehaviorSlot(static_cast<eItemType>(info.itemType));
|
||||
|
||||
std::cout << "Comparing slots: " << static_cast<int32_t>(itemBehaviorSlot) << " - " << static_cast<int32_t>(slot) << std::endl;
|
||||
|
||||
if (itemBehaviorSlot == slot) {
|
||||
itemLot = itemDetails.lot;
|
||||
itemId = itemDetails.id;
|
||||
|
||||
std::cout << "Found item: " << itemLot << std::endl;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto& skillTemplates = GetModifierNameTemplates(ModifierNameType::Skill);
|
||||
|
||||
const auto& skillTemplateIt = std::find_if(skillTemplates.begin(), skillTemplates.end(), [skillID](const auto& it) {
|
||||
return it.GetLOT() == skillID;
|
||||
});
|
||||
|
||||
std::vector<ModifierInstance> modifiers;
|
||||
|
||||
if (skillTemplateIt != skillTemplates.end()) {
|
||||
const auto& skillTemplate = *skillTemplateIt;
|
||||
|
||||
const auto skillModifiers = skillTemplate.GenerateModifiers(level);
|
||||
|
||||
modifiers.insert(modifiers.end(), skillModifiers.begin(), skillModifiers.end());
|
||||
}
|
||||
|
||||
std::unordered_set<ModifierType> damageTypes;
|
||||
|
||||
for (const auto& modifier : modifiers) {
|
||||
damageTypes.insert(modifier.GetType());
|
||||
}
|
||||
|
||||
const auto itemDataOpt = GetAdditionalItemData(itemId);
|
||||
|
||||
if (itemDataOpt.has_value()) {
|
||||
const auto& itemData = *itemDataOpt.value();
|
||||
|
||||
for (const auto& modifier : itemData.GetModifierInstances()) {
|
||||
damageTypes.insert(modifier.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the following: Offensive, Defensive, Health, Armor, Imagination
|
||||
damageTypes.erase(ModifierType::Offensive);
|
||||
damageTypes.erase(ModifierType::Defensive);
|
||||
damageTypes.erase(ModifierType::Health);
|
||||
damageTypes.erase(ModifierType::Armor);
|
||||
damageTypes.erase(ModifierType::Imagination);
|
||||
|
||||
uint32_t totalDamage = 0;
|
||||
|
||||
for (const auto& type : damageTypes) {
|
||||
float damageValue = offenderEntity.CalculateModifier(type, modifiers, level);
|
||||
|
||||
// Calculate resistance, can't go below 20% of the original damage
|
||||
const auto resistance = std::max(1 - (damagedEntity.CalculateResistance(type) / 100), 0.2f);
|
||||
|
||||
damageValue *= resistance;
|
||||
|
||||
totalDamage += static_cast<uint32_t>(damageValue);
|
||||
|
||||
std::cout << "Damage type: " << magic_enum::enum_name(type) << " - " << damageValue << std::endl << " Resistance: " << resistance << std::endl;
|
||||
std::cout << "Heath left: " << damaged->GetComponent<DestroyableComponent>()->GetHealth() << std::endl;
|
||||
}
|
||||
|
||||
// Get the offenders Offensive modifier
|
||||
auto offenderModifiers = offenderEntity.CalculateModifier(ModifierType::Offensive, level);
|
||||
|
||||
// Get the defenders Defensive modifier
|
||||
auto defensiveModifiers = damagedEntity.CalculateModifier(ModifierType::Defensive, level);
|
||||
|
||||
if (offenderModifiers == 0) offenderModifiers = 1;
|
||||
if (defensiveModifiers == 0) defensiveModifiers = 1;
|
||||
|
||||
auto ratio = offenderModifiers / defensiveModifiers;
|
||||
|
||||
// Ratio can not ge below 1.05
|
||||
ratio = std::max(ratio, 1.05f);
|
||||
|
||||
// Roll a number between 0 and ratio
|
||||
float roll = GeneralUtils::GenerateRandomNumber<size_t>() / static_cast<float>(std::numeric_limits<size_t>::max());
|
||||
|
||||
roll *= ratio;
|
||||
|
||||
std::cout << "Offensive: " << offenderModifiers << " Defensive: " << defensiveModifiers << " Ratio: " << ratio << " Roll: " << roll << std::endl;
|
||||
|
||||
// If the roll is above 1, the damage is increased by 1+roll, to a maximum of 5x the damage
|
||||
if (roll > 1) {
|
||||
roll = std::min(roll, 5.0f);
|
||||
totalDamage += static_cast<uint32_t>(totalDamage * roll);
|
||||
|
||||
GameMessages::SendPlayFXEffect(
|
||||
damaged->GetObjectID(),
|
||||
20041,
|
||||
u"onhit",
|
||||
std::to_string(GeneralUtils::GenerateRandomNumber<uint32_t>())
|
||||
);
|
||||
}
|
||||
|
||||
// Add a random +10% to the damage
|
||||
totalDamage += static_cast<uint32_t>(totalDamage * (GeneralUtils::GenerateRandomNumber<int32_t>(0, 10) / 100.0f));
|
||||
|
||||
damage = totalDamage;
|
||||
|
||||
if (offfendEntity->IsPlayer()) {
|
||||
offfendEntity->AddCallbackTimer(0.0f, [offfendEntity, skillID]() {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
const auto entity = offfendEntity->GetObjectID();
|
||||
|
||||
bitStream.Write(entity);
|
||||
bitStream.Write(eGameMessageType::MODIFY_SKILL_COOLDOWN);
|
||||
|
||||
bitStream.Write1();
|
||||
bitStream.Write<float>(-10.0f);
|
||||
bitStream.Write<int32_t>(static_cast<int32_t>(skillID));
|
||||
|
||||
LOG("Sending cooldown reduction for skill: %d", skillID);
|
||||
|
||||
SEND_PACKET_BROADCAST;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
void nejlika::NejlikaHooks::ItemDescription(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
if (splitArgs.size() < 2) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto requestId = GeneralUtils::TryParse<int32_t>(splitArgs[0]).value_or(-1);
|
||||
|
||||
if (requestId == -1) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Request ID: " << requestId << std::endl;
|
||||
|
||||
auto itemId = GeneralUtils::TryParse<LWOOBJID>(splitArgs[1]).value_or(LWOOBJID_EMPTY);
|
||||
|
||||
if (itemId == LWOOBJID_EMPTY) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID.");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto itemDataOpt = GetAdditionalItemData(itemId);
|
||||
|
||||
if (!itemDataOpt.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& itemDetails = *itemDataOpt.value();
|
||||
|
||||
const auto& modifiers = itemDetails.GetModifierInstances();
|
||||
const auto& names = itemDetails.GetModifierNames();
|
||||
|
||||
if (modifiers.empty() && names.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::stringstream name;
|
||||
std::stringstream desc;
|
||||
|
||||
name << "NAME";
|
||||
|
||||
desc << ModifierName::GenerateHtmlString(names) << "\n";
|
||||
|
||||
desc << ModifierInstance::GenerateHtmlString(modifiers);
|
||||
|
||||
std::cout << "Sending item name: " << name.str() << std::endl;
|
||||
std::cout << "Sending item desc: " << desc.str() << std::endl;
|
||||
|
||||
std::stringstream messageName;
|
||||
messageName << "desc" << requestId;
|
||||
|
||||
AMFArrayValue amfArgs;
|
||||
|
||||
amfArgs.Insert("t", true);
|
||||
amfArgs.Insert("d", desc.str());
|
||||
amfArgs.Insert("n", name.str());
|
||||
|
||||
GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, messageName.str(), amfArgs);
|
||||
|
||||
std::cout << "Sent item description." << std::endl;
|
||||
}
|
10
dGame/NejlikaHooks.h
Normal file
10
dGame/NejlikaHooks.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace nejlika::NejlikaHooks
|
||||
{
|
||||
|
||||
void InstallHooks();
|
||||
|
||||
void ItemDescription(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
|
||||
}
|
@ -18,6 +18,8 @@ void PlayerManager::AddPlayer(Entity* player) {
|
||||
|
||||
if (iter == m_Players.end()) {
|
||||
m_Players.push_back(player);
|
||||
|
||||
OnPlayerAdded(player);
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +29,8 @@ bool PlayerManager::RemovePlayer(Entity* player) {
|
||||
const bool toReturn = iter != m_Players.end();
|
||||
if (toReturn) {
|
||||
m_Players.erase(iter);
|
||||
|
||||
OnPlayerRemoved(player);
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define __PLAYERMANAGER__H__
|
||||
|
||||
#include "dCommonVars.h"
|
||||
#include "Observable.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
@ -20,6 +21,9 @@ namespace PlayerManager {
|
||||
Entity* GetPlayer(LWOOBJID playerID);
|
||||
|
||||
const std::vector<Entity*>& GetAllPlayers();
|
||||
|
||||
static Observable<Entity*> OnPlayerAdded;
|
||||
static Observable<Entity*> OnPlayerRemoved;
|
||||
};
|
||||
|
||||
#endif //!__PLAYERMANAGER__H__
|
||||
|
@ -36,6 +36,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id):
|
||||
m_Disabled = false;
|
||||
m_SkillEntries = {};
|
||||
m_SoftTimer = 5.0f;
|
||||
m_StunImmune = true;
|
||||
|
||||
//Grab the aggro information from BaseCombatAI:
|
||||
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
|
||||
@ -369,9 +370,9 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
||||
|
||||
m_Timer = 0;
|
||||
|
||||
m_SkillTime = result.skillTime;
|
||||
m_SkillTime = result.skillTime / 2.0f;
|
||||
|
||||
entry.cooldown = entry.abilityCooldown + m_SkillTime;
|
||||
entry.cooldown = 0.1f; //entry.abilityCooldown + m_SkillTime;
|
||||
|
||||
m_SkillEntries[i] = entry;
|
||||
|
||||
@ -619,6 +620,10 @@ void BaseCombatAIComponent::SetThreat(LWOOBJID offender, float threat) {
|
||||
m_DirtyThreat = true;
|
||||
}
|
||||
|
||||
const std::map<LWOOBJID, float>& BaseCombatAIComponent::GetThreats() const {
|
||||
return m_ThreatEntries;
|
||||
}
|
||||
|
||||
const NiPoint3& BaseCombatAIComponent::GetStartPosition() const {
|
||||
return m_StartPosition;
|
||||
}
|
||||
@ -679,7 +684,7 @@ void BaseCombatAIComponent::OnAggro() {
|
||||
return;
|
||||
}
|
||||
|
||||
m_MovementAI->SetHaltDistance(m_AttackRadius);
|
||||
m_MovementAI->SetHaltDistance(0);
|
||||
|
||||
NiPoint3 targetPos = target->GetPosition();
|
||||
NiPoint3 currentPos = m_MovementAI->GetParent()->GetPosition();
|
||||
@ -713,7 +718,7 @@ void BaseCombatAIComponent::OnTether() {
|
||||
return;
|
||||
}
|
||||
|
||||
m_MovementAI->SetHaltDistance(m_AttackRadius);
|
||||
m_MovementAI->SetHaltDistance(0);
|
||||
|
||||
NiPoint3 targetPos = target->GetPosition();
|
||||
NiPoint3 currentPos = m_MovementAI->ApproximateLocation();
|
||||
|
@ -114,6 +114,12 @@ public:
|
||||
*/
|
||||
void SetThreat(LWOOBJID offender, float threat);
|
||||
|
||||
/**
|
||||
* Get all threats for this entity
|
||||
* @return all threats for this entity
|
||||
*/
|
||||
const std::map<LWOOBJID, float>& GetThreats() const;
|
||||
|
||||
/**
|
||||
* Gets the position where the entity spawned
|
||||
* @return the position where the entity spawned
|
||||
|
@ -38,8 +38,7 @@
|
||||
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
|
||||
Implementation<bool, const Entity*> DestroyableComponent::IsEnemyImplentation;
|
||||
Implementation<bool, const Entity*> DestroyableComponent::IsFriendImplentation;
|
||||
Observable<Entity*, LWOOBJID, uint32_t, uint32_t&> DestroyableComponent::OnDamageCalculation;
|
||||
|
||||
DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
|
||||
m_iArmor = 0;
|
||||
@ -421,7 +420,6 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
|
||||
}
|
||||
|
||||
bool DestroyableComponent::IsEnemy(const Entity* other) const {
|
||||
if (IsEnemyImplentation.ExecuteWithDefault(other, false)) return true;
|
||||
if (m_Parent->IsPlayer() && other->IsPlayer()) {
|
||||
auto* thisCharacterComponent = m_Parent->GetComponent<CharacterComponent>();
|
||||
if (!thisCharacterComponent) return false;
|
||||
@ -444,7 +442,6 @@ bool DestroyableComponent::IsEnemy(const Entity* other) const {
|
||||
}
|
||||
|
||||
bool DestroyableComponent::IsFriend(const Entity* other) const {
|
||||
if (IsFriendImplentation.ExecuteWithDefault(other, false)) return true;
|
||||
const auto* otherDestroyableComponent = other->GetComponent<DestroyableComponent>();
|
||||
if (otherDestroyableComponent != nullptr) {
|
||||
for (const auto enemyFaction : m_EnemyFactionIDs) {
|
||||
@ -575,6 +572,8 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
|
||||
return;
|
||||
}
|
||||
|
||||
OnDamageCalculation(m_Parent, source, skillID, damage);
|
||||
|
||||
// If this entity has damage reduction, reduce the damage to a minimum of 1
|
||||
if (m_DamageReduction > 0 && damage > 0) {
|
||||
if (damage > m_DamageReduction) {
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include "Entity.h"
|
||||
#include "Component.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "Implementation.h"
|
||||
#include "Observable.h"
|
||||
|
||||
namespace CppScripts {
|
||||
class Script;
|
||||
@ -464,8 +464,8 @@ public:
|
||||
// handle hardcode mode drops
|
||||
void DoHardcoreModeDrops(const LWOOBJID source);
|
||||
|
||||
static Implementation<bool, const Entity*> IsEnemyImplentation;
|
||||
static Implementation<bool, const Entity*> IsFriendImplentation;
|
||||
// Damaged entity, offender, skillID, damage
|
||||
static Observable<Entity*, LWOOBJID, uint32_t, uint32_t&> OnDamageCalculation;
|
||||
|
||||
private:
|
||||
/**
|
||||
|
@ -38,6 +38,12 @@
|
||||
#include "CDObjectSkillsTable.h"
|
||||
#include "CDSkillBehaviorTable.h"
|
||||
|
||||
Observable<InventoryComponent*, Item*> InventoryComponent::OnItemCreated;
|
||||
Observable<InventoryComponent*, Item*> InventoryComponent::OnItemDestroyed;
|
||||
Observable<InventoryComponent*, Item*> InventoryComponent::OnItemEquipped;
|
||||
Observable<InventoryComponent*, Item*> InventoryComponent::OnItemUnequipped;
|
||||
Observable<InventoryComponent*, Item*> InventoryComponent::OnItemLoaded;
|
||||
|
||||
InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) {
|
||||
this->m_Dirty = true;
|
||||
this->m_Equipped = {};
|
||||
@ -287,6 +293,8 @@ void InventoryComponent::AddItem(
|
||||
}
|
||||
auto* item = new Item(lot, inventory, slot, size, {}, parent, showFlyingLoot, isModMoveAndEquip, subKey, false, lootSourceType);
|
||||
|
||||
OnItemCreated(this, item);
|
||||
|
||||
isModMoveAndEquip = false;
|
||||
}
|
||||
|
||||
@ -571,6 +579,8 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) {
|
||||
}
|
||||
|
||||
itemElement = itemElement->NextSiblingElement();
|
||||
|
||||
OnItemLoaded(this, item);
|
||||
}
|
||||
|
||||
bag = bag->NextSiblingElement();
|
||||
@ -849,6 +859,8 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) {
|
||||
|
||||
EquipScripts(item);
|
||||
|
||||
OnItemEquipped(this, item);
|
||||
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
}
|
||||
|
||||
@ -879,6 +891,8 @@ void InventoryComponent::UnEquipItem(Item* item) {
|
||||
|
||||
UnequipScripts(item);
|
||||
|
||||
OnItemUnequipped(this, item);
|
||||
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
|
||||
// Trigger property event
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "eInventoryType.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "eLootSourceType.h"
|
||||
#include "Observable.h"
|
||||
|
||||
class Entity;
|
||||
class ItemSet;
|
||||
@ -374,6 +375,12 @@ public:
|
||||
|
||||
~InventoryComponent() override;
|
||||
|
||||
static Observable<InventoryComponent*, Item*> OnItemCreated;
|
||||
static Observable<InventoryComponent*, Item*> OnItemDestroyed;
|
||||
static Observable<InventoryComponent*, Item*> OnItemEquipped;
|
||||
static Observable<InventoryComponent*, Item*> OnItemUnequipped;
|
||||
static Observable<InventoryComponent*, Item*> OnItemLoaded;
|
||||
|
||||
private:
|
||||
/**
|
||||
* All the inventory this entity possesses
|
||||
|
@ -212,6 +212,8 @@ void Inventory::AddManagedItem(Item* item) {
|
||||
items.insert_or_assign(id, item);
|
||||
|
||||
free--;
|
||||
|
||||
component->OnItemLoaded(component, item);
|
||||
}
|
||||
|
||||
void Inventory::RemoveManagedItem(Item* item) {
|
||||
@ -226,6 +228,8 @@ void Inventory::RemoveManagedItem(Item* item) {
|
||||
items.erase(id);
|
||||
|
||||
free++;
|
||||
|
||||
component->OnItemDestroyed(component, item);
|
||||
}
|
||||
|
||||
eInventoryType Inventory::FindInventoryTypeForLot(const LOT lot) {
|
||||
|
24765
dGame/json.hpp
Normal file
24765
dGame/json.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -81,6 +81,8 @@
|
||||
#include "eLoginResponse.h"
|
||||
#include "SlashCommandHandler.h"
|
||||
|
||||
#include "NejlikaHooks.h"
|
||||
|
||||
namespace Game {
|
||||
Logger* logger = nullptr;
|
||||
dServer* server = nullptr;
|
||||
@ -315,6 +317,8 @@ int main(int argc, char** argv) {
|
||||
// Register slash commands if not in zone 0
|
||||
if (zoneID != 0) SlashCommandHandler::Startup();
|
||||
|
||||
nejlika::NejlikaHooks::InstallHooks();
|
||||
|
||||
Game::logger->Flush(); // once immediately before the main loop
|
||||
while (true) {
|
||||
Metrics::StartMeasurement(MetricVariable::Frame);
|
||||
|
Loading…
Reference in New Issue
Block a user