mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-12 19:28:21 +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"
|
"TradingManager.cpp"
|
||||||
"User.cpp"
|
"User.cpp"
|
||||||
"UserManager.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(
|
include_directories(
|
||||||
${PROJECT_SOURCE_DIR}/dScripts
|
${PROJECT_SOURCE_DIR}/dScripts
|
||||||
|
@ -26,6 +26,9 @@
|
|||||||
#include "GhostComponent.h"
|
#include "GhostComponent.h"
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
|
|
||||||
|
Observable<Entity*> EntityManager::OnEntityCreated;
|
||||||
|
Observable<Entity*> EntityManager::OnEntityDestroyed;
|
||||||
|
|
||||||
// Configure which zones have ghosting disabled, mostly small worlds.
|
// Configure which zones have ghosting disabled, mostly small worlds.
|
||||||
std::vector<LWOMAPID> EntityManager::m_GhostingExcludedZones = {
|
std::vector<LWOMAPID> EntityManager::m_GhostingExcludedZones = {
|
||||||
// Small zones
|
// 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());
|
m_SpawnPoints.insert_or_assign(GeneralUtils::UTF16ToWTF8(spawnName), entity->GetObjectID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify observers that a new entity has been created
|
||||||
|
OnEntityCreated(entity);
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +167,9 @@ void EntityManager::DestroyEntity(Entity* entity) {
|
|||||||
DestructEntity(entity);
|
DestructEntity(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify observers that an entity is about to be destroyed
|
||||||
|
OnEntityDestroyed(entity);
|
||||||
|
|
||||||
// Delete this entity at the end of the frame
|
// Delete this entity at the end of the frame
|
||||||
ScheduleForDeletion(id);
|
ScheduleForDeletion(id);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "dCommonVars.h"
|
#include "dCommonVars.h"
|
||||||
|
#include "Observable.h"
|
||||||
|
|
||||||
class Entity;
|
class Entity;
|
||||||
class EntityInfo;
|
class EntityInfo;
|
||||||
@ -72,6 +73,9 @@ public:
|
|||||||
const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; };
|
const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; };
|
||||||
const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; };
|
const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; };
|
||||||
|
|
||||||
|
static Observable<Entity*> OnEntityCreated;
|
||||||
|
static Observable<Entity*> OnEntityDestroyed;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SerializeEntities();
|
void SerializeEntities();
|
||||||
void KillEntities();
|
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()) {
|
if (iter == m_Players.end()) {
|
||||||
m_Players.push_back(player);
|
m_Players.push_back(player);
|
||||||
|
|
||||||
|
OnPlayerAdded(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +29,8 @@ bool PlayerManager::RemovePlayer(Entity* player) {
|
|||||||
const bool toReturn = iter != m_Players.end();
|
const bool toReturn = iter != m_Players.end();
|
||||||
if (toReturn) {
|
if (toReturn) {
|
||||||
m_Players.erase(iter);
|
m_Players.erase(iter);
|
||||||
|
|
||||||
|
OnPlayerRemoved(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
return toReturn;
|
return toReturn;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#define __PLAYERMANAGER__H__
|
#define __PLAYERMANAGER__H__
|
||||||
|
|
||||||
#include "dCommonVars.h"
|
#include "dCommonVars.h"
|
||||||
|
#include "Observable.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@ -20,6 +21,9 @@ namespace PlayerManager {
|
|||||||
Entity* GetPlayer(LWOOBJID playerID);
|
Entity* GetPlayer(LWOOBJID playerID);
|
||||||
|
|
||||||
const std::vector<Entity*>& GetAllPlayers();
|
const std::vector<Entity*>& GetAllPlayers();
|
||||||
|
|
||||||
|
static Observable<Entity*> OnPlayerAdded;
|
||||||
|
static Observable<Entity*> OnPlayerRemoved;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //!__PLAYERMANAGER__H__
|
#endif //!__PLAYERMANAGER__H__
|
||||||
|
@ -36,6 +36,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id):
|
|||||||
m_Disabled = false;
|
m_Disabled = false;
|
||||||
m_SkillEntries = {};
|
m_SkillEntries = {};
|
||||||
m_SoftTimer = 5.0f;
|
m_SoftTimer = 5.0f;
|
||||||
|
m_StunImmune = true;
|
||||||
|
|
||||||
//Grab the aggro information from BaseCombatAI:
|
//Grab the aggro information from BaseCombatAI:
|
||||||
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
|
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
|
||||||
@ -369,9 +370,9 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
|||||||
|
|
||||||
m_Timer = 0;
|
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;
|
m_SkillEntries[i] = entry;
|
||||||
|
|
||||||
@ -619,6 +620,10 @@ void BaseCombatAIComponent::SetThreat(LWOOBJID offender, float threat) {
|
|||||||
m_DirtyThreat = true;
|
m_DirtyThreat = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::map<LWOOBJID, float>& BaseCombatAIComponent::GetThreats() const {
|
||||||
|
return m_ThreatEntries;
|
||||||
|
}
|
||||||
|
|
||||||
const NiPoint3& BaseCombatAIComponent::GetStartPosition() const {
|
const NiPoint3& BaseCombatAIComponent::GetStartPosition() const {
|
||||||
return m_StartPosition;
|
return m_StartPosition;
|
||||||
}
|
}
|
||||||
@ -679,7 +684,7 @@ void BaseCombatAIComponent::OnAggro() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_MovementAI->SetHaltDistance(m_AttackRadius);
|
m_MovementAI->SetHaltDistance(0);
|
||||||
|
|
||||||
NiPoint3 targetPos = target->GetPosition();
|
NiPoint3 targetPos = target->GetPosition();
|
||||||
NiPoint3 currentPos = m_MovementAI->GetParent()->GetPosition();
|
NiPoint3 currentPos = m_MovementAI->GetParent()->GetPosition();
|
||||||
@ -713,7 +718,7 @@ void BaseCombatAIComponent::OnTether() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_MovementAI->SetHaltDistance(m_AttackRadius);
|
m_MovementAI->SetHaltDistance(0);
|
||||||
|
|
||||||
NiPoint3 targetPos = target->GetPosition();
|
NiPoint3 targetPos = target->GetPosition();
|
||||||
NiPoint3 currentPos = m_MovementAI->ApproximateLocation();
|
NiPoint3 currentPos = m_MovementAI->ApproximateLocation();
|
||||||
|
@ -114,6 +114,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
void SetThreat(LWOOBJID offender, float threat);
|
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
|
* Gets the position where the entity spawned
|
||||||
* @return the position where the entity spawned
|
* @return the position where the entity spawned
|
||||||
|
@ -38,8 +38,7 @@
|
|||||||
|
|
||||||
#include "CDComponentsRegistryTable.h"
|
#include "CDComponentsRegistryTable.h"
|
||||||
|
|
||||||
Implementation<bool, const Entity*> DestroyableComponent::IsEnemyImplentation;
|
Observable<Entity*, LWOOBJID, uint32_t, uint32_t&> DestroyableComponent::OnDamageCalculation;
|
||||||
Implementation<bool, const Entity*> DestroyableComponent::IsFriendImplentation;
|
|
||||||
|
|
||||||
DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
|
DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
|
||||||
m_iArmor = 0;
|
m_iArmor = 0;
|
||||||
@ -421,7 +420,6 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool DestroyableComponent::IsEnemy(const Entity* other) const {
|
bool DestroyableComponent::IsEnemy(const Entity* other) const {
|
||||||
if (IsEnemyImplentation.ExecuteWithDefault(other, false)) return true;
|
|
||||||
if (m_Parent->IsPlayer() && other->IsPlayer()) {
|
if (m_Parent->IsPlayer() && other->IsPlayer()) {
|
||||||
auto* thisCharacterComponent = m_Parent->GetComponent<CharacterComponent>();
|
auto* thisCharacterComponent = m_Parent->GetComponent<CharacterComponent>();
|
||||||
if (!thisCharacterComponent) return false;
|
if (!thisCharacterComponent) return false;
|
||||||
@ -444,7 +442,6 @@ bool DestroyableComponent::IsEnemy(const Entity* other) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool DestroyableComponent::IsFriend(const Entity* other) const {
|
bool DestroyableComponent::IsFriend(const Entity* other) const {
|
||||||
if (IsFriendImplentation.ExecuteWithDefault(other, false)) return true;
|
|
||||||
const auto* otherDestroyableComponent = other->GetComponent<DestroyableComponent>();
|
const auto* otherDestroyableComponent = other->GetComponent<DestroyableComponent>();
|
||||||
if (otherDestroyableComponent != nullptr) {
|
if (otherDestroyableComponent != nullptr) {
|
||||||
for (const auto enemyFaction : m_EnemyFactionIDs) {
|
for (const auto enemyFaction : m_EnemyFactionIDs) {
|
||||||
@ -575,6 +572,8 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnDamageCalculation(m_Parent, source, skillID, damage);
|
||||||
|
|
||||||
// If this entity has damage reduction, reduce the damage to a minimum of 1
|
// If this entity has damage reduction, reduce the damage to a minimum of 1
|
||||||
if (m_DamageReduction > 0 && damage > 0) {
|
if (m_DamageReduction > 0 && damage > 0) {
|
||||||
if (damage > m_DamageReduction) {
|
if (damage > m_DamageReduction) {
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "Component.h"
|
#include "Component.h"
|
||||||
#include "eReplicaComponentType.h"
|
#include "eReplicaComponentType.h"
|
||||||
#include "Implementation.h"
|
#include "Observable.h"
|
||||||
|
|
||||||
namespace CppScripts {
|
namespace CppScripts {
|
||||||
class Script;
|
class Script;
|
||||||
@ -464,8 +464,8 @@ public:
|
|||||||
// handle hardcode mode drops
|
// handle hardcode mode drops
|
||||||
void DoHardcoreModeDrops(const LWOOBJID source);
|
void DoHardcoreModeDrops(const LWOOBJID source);
|
||||||
|
|
||||||
static Implementation<bool, const Entity*> IsEnemyImplentation;
|
// Damaged entity, offender, skillID, damage
|
||||||
static Implementation<bool, const Entity*> IsFriendImplentation;
|
static Observable<Entity*, LWOOBJID, uint32_t, uint32_t&> OnDamageCalculation;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
|
@ -38,6 +38,12 @@
|
|||||||
#include "CDObjectSkillsTable.h"
|
#include "CDObjectSkillsTable.h"
|
||||||
#include "CDSkillBehaviorTable.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) {
|
InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) {
|
||||||
this->m_Dirty = true;
|
this->m_Dirty = true;
|
||||||
this->m_Equipped = {};
|
this->m_Equipped = {};
|
||||||
@ -287,6 +293,8 @@ void InventoryComponent::AddItem(
|
|||||||
}
|
}
|
||||||
auto* item = new Item(lot, inventory, slot, size, {}, parent, showFlyingLoot, isModMoveAndEquip, subKey, false, lootSourceType);
|
auto* item = new Item(lot, inventory, slot, size, {}, parent, showFlyingLoot, isModMoveAndEquip, subKey, false, lootSourceType);
|
||||||
|
|
||||||
|
OnItemCreated(this, item);
|
||||||
|
|
||||||
isModMoveAndEquip = false;
|
isModMoveAndEquip = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -571,6 +579,8 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
itemElement = itemElement->NextSiblingElement();
|
itemElement = itemElement->NextSiblingElement();
|
||||||
|
|
||||||
|
OnItemLoaded(this, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
bag = bag->NextSiblingElement();
|
bag = bag->NextSiblingElement();
|
||||||
@ -849,6 +859,8 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) {
|
|||||||
|
|
||||||
EquipScripts(item);
|
EquipScripts(item);
|
||||||
|
|
||||||
|
OnItemEquipped(this, item);
|
||||||
|
|
||||||
Game::entityManager->SerializeEntity(m_Parent);
|
Game::entityManager->SerializeEntity(m_Parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -879,6 +891,8 @@ void InventoryComponent::UnEquipItem(Item* item) {
|
|||||||
|
|
||||||
UnequipScripts(item);
|
UnequipScripts(item);
|
||||||
|
|
||||||
|
OnItemUnequipped(this, item);
|
||||||
|
|
||||||
Game::entityManager->SerializeEntity(m_Parent);
|
Game::entityManager->SerializeEntity(m_Parent);
|
||||||
|
|
||||||
// Trigger property event
|
// Trigger property event
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "eInventoryType.h"
|
#include "eInventoryType.h"
|
||||||
#include "eReplicaComponentType.h"
|
#include "eReplicaComponentType.h"
|
||||||
#include "eLootSourceType.h"
|
#include "eLootSourceType.h"
|
||||||
|
#include "Observable.h"
|
||||||
|
|
||||||
class Entity;
|
class Entity;
|
||||||
class ItemSet;
|
class ItemSet;
|
||||||
@ -374,6 +375,12 @@ public:
|
|||||||
|
|
||||||
~InventoryComponent() override;
|
~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:
|
private:
|
||||||
/**
|
/**
|
||||||
* All the inventory this entity possesses
|
* All the inventory this entity possesses
|
||||||
|
@ -212,6 +212,8 @@ void Inventory::AddManagedItem(Item* item) {
|
|||||||
items.insert_or_assign(id, item);
|
items.insert_or_assign(id, item);
|
||||||
|
|
||||||
free--;
|
free--;
|
||||||
|
|
||||||
|
component->OnItemLoaded(component, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Inventory::RemoveManagedItem(Item* item) {
|
void Inventory::RemoveManagedItem(Item* item) {
|
||||||
@ -226,6 +228,8 @@ void Inventory::RemoveManagedItem(Item* item) {
|
|||||||
items.erase(id);
|
items.erase(id);
|
||||||
|
|
||||||
free++;
|
free++;
|
||||||
|
|
||||||
|
component->OnItemDestroyed(component, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
eInventoryType Inventory::FindInventoryTypeForLot(const LOT lot) {
|
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 "eLoginResponse.h"
|
||||||
#include "SlashCommandHandler.h"
|
#include "SlashCommandHandler.h"
|
||||||
|
|
||||||
|
#include "NejlikaHooks.h"
|
||||||
|
|
||||||
namespace Game {
|
namespace Game {
|
||||||
Logger* logger = nullptr;
|
Logger* logger = nullptr;
|
||||||
dServer* server = nullptr;
|
dServer* server = nullptr;
|
||||||
@ -315,6 +317,8 @@ int main(int argc, char** argv) {
|
|||||||
// Register slash commands if not in zone 0
|
// Register slash commands if not in zone 0
|
||||||
if (zoneID != 0) SlashCommandHandler::Startup();
|
if (zoneID != 0) SlashCommandHandler::Startup();
|
||||||
|
|
||||||
|
nejlika::NejlikaHooks::InstallHooks();
|
||||||
|
|
||||||
Game::logger->Flush(); // once immediately before the main loop
|
Game::logger->Flush(); // once immediately before the main loop
|
||||||
while (true) {
|
while (true) {
|
||||||
Metrics::StartMeasurement(MetricVariable::Frame);
|
Metrics::StartMeasurement(MetricVariable::Frame);
|
||||||
|
Loading…
Reference in New Issue
Block a user