Refactor and combat changes

This commit is contained in:
wincent 2024-06-02 09:53:56 +02:00
parent 0bf5ee02e4
commit d5b2278dc5
43 changed files with 27298 additions and 386 deletions

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

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

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

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

View File

@ -7,7 +7,19 @@ set(DGAME_SOURCES "Character.cpp"
"TradingManager.cpp"
"User.cpp"
"UserManager.cpp"
"Nejlika.cpp")
"ModifierTemplate.cpp"
"ModifierInstance.cpp"
"ModifierRarity.cpp"
"ModifierType.cpp"
"ModifierScale.cpp"
"ModifierName.cpp"
"ModifierNameTemplate.cpp"
"NejlikaData.cpp"
"AdditionalItemData.cpp"
"EntityTemplate.cpp"
"AdditionalEntityData.cpp"
"NejlikaHooks.cpp"
)
include_directories(
${PROJECT_SOURCE_DIR}/dScripts

View File

@ -26,6 +26,9 @@
#include "GhostComponent.h"
#include <ranges>
Observable<Entity*> EntityManager::OnEntityCreated;
Observable<Entity*> EntityManager::OnEntityDestroyed;
// Configure which zones have ghosting disabled, mostly small worlds.
std::vector<LWOMAPID> EntityManager::m_GhostingExcludedZones = {
// Small zones
@ -138,6 +141,9 @@ Entity* EntityManager::CreateEntity(EntityInfo info, User* user, Entity* parentE
m_SpawnPoints.insert_or_assign(GeneralUtils::UTF16ToWTF8(spawnName), entity->GetObjectID());
}
// Notify observers that a new entity has been created
OnEntityCreated(entity);
return entity;
}
@ -161,6 +167,9 @@ void EntityManager::DestroyEntity(Entity* entity) {
DestructEntity(entity);
}
// Notify observers that an entity is about to be destroyed
OnEntityDestroyed(entity);
// Delete this entity at the end of the frame
ScheduleForDeletion(id);
}

View File

@ -7,6 +7,7 @@
#include <unordered_map>
#include "dCommonVars.h"
#include "Observable.h"
class Entity;
class EntityInfo;
@ -72,6 +73,9 @@ public:
const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; };
const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; };
static Observable<Entity*> OnEntityCreated;
static Observable<Entity*> OnEntityDestroyed;
private:
void SerializeEntities();
void KillEntities();

88
dGame/EntityTemplate.cpp Normal file
View 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
View 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
View File

@ -0,0 +1,14 @@
#pragma once
#include <cstdint>
namespace nejlika
{
enum class ModifierCategory : uint8_t
{
Player = 0 << 0,
Pet = 1 << 0
};
}

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

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

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

@ -0,0 +1,14 @@
#pragma once
#include <cstdint>
namespace nejlika
{
enum class ModifierOperator : uint8_t
{
Additive,
Multiplicative
};
}

31
dGame/ModifierRarity.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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);
}

View File

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

View File

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

@ -0,0 +1,10 @@
#pragma once
namespace nejlika::NejlikaHooks
{
void InstallHooks();
void ItemDescription(Entity* entity, const SystemAddress& sysAddr, const std::string args);
}

View File

@ -18,6 +18,8 @@ void PlayerManager::AddPlayer(Entity* player) {
if (iter == m_Players.end()) {
m_Players.push_back(player);
OnPlayerAdded(player);
}
}
@ -27,6 +29,8 @@ bool PlayerManager::RemovePlayer(Entity* player) {
const bool toReturn = iter != m_Players.end();
if (toReturn) {
m_Players.erase(iter);
OnPlayerRemoved(player);
}
return toReturn;

View File

@ -2,6 +2,7 @@
#define __PLAYERMANAGER__H__
#include "dCommonVars.h"
#include "Observable.h"
#include <string>
@ -20,6 +21,9 @@ namespace PlayerManager {
Entity* GetPlayer(LWOOBJID playerID);
const std::vector<Entity*>& GetAllPlayers();
static Observable<Entity*> OnPlayerAdded;
static Observable<Entity*> OnPlayerRemoved;
};
#endif //!__PLAYERMANAGER__H__

View File

@ -36,6 +36,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id):
m_Disabled = false;
m_SkillEntries = {};
m_SoftTimer = 5.0f;
m_StunImmune = true;
//Grab the aggro information from BaseCombatAI:
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
@ -369,9 +370,9 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
m_Timer = 0;
m_SkillTime = result.skillTime;
m_SkillTime = result.skillTime / 2.0f;
entry.cooldown = entry.abilityCooldown + m_SkillTime;
entry.cooldown = 0.1f; //entry.abilityCooldown + m_SkillTime;
m_SkillEntries[i] = entry;
@ -619,6 +620,10 @@ void BaseCombatAIComponent::SetThreat(LWOOBJID offender, float threat) {
m_DirtyThreat = true;
}
const std::map<LWOOBJID, float>& BaseCombatAIComponent::GetThreats() const {
return m_ThreatEntries;
}
const NiPoint3& BaseCombatAIComponent::GetStartPosition() const {
return m_StartPosition;
}
@ -679,7 +684,7 @@ void BaseCombatAIComponent::OnAggro() {
return;
}
m_MovementAI->SetHaltDistance(m_AttackRadius);
m_MovementAI->SetHaltDistance(0);
NiPoint3 targetPos = target->GetPosition();
NiPoint3 currentPos = m_MovementAI->GetParent()->GetPosition();
@ -713,7 +718,7 @@ void BaseCombatAIComponent::OnTether() {
return;
}
m_MovementAI->SetHaltDistance(m_AttackRadius);
m_MovementAI->SetHaltDistance(0);
NiPoint3 targetPos = target->GetPosition();
NiPoint3 currentPos = m_MovementAI->ApproximateLocation();

View File

@ -114,6 +114,12 @@ public:
*/
void SetThreat(LWOOBJID offender, float threat);
/**
* Get all threats for this entity
* @return all threats for this entity
*/
const std::map<LWOOBJID, float>& GetThreats() const;
/**
* Gets the position where the entity spawned
* @return the position where the entity spawned

View File

@ -38,8 +38,7 @@
#include "CDComponentsRegistryTable.h"
Implementation<bool, const Entity*> DestroyableComponent::IsEnemyImplentation;
Implementation<bool, const Entity*> DestroyableComponent::IsFriendImplentation;
Observable<Entity*, LWOOBJID, uint32_t, uint32_t&> DestroyableComponent::OnDamageCalculation;
DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
m_iArmor = 0;
@ -421,7 +420,6 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
}
bool DestroyableComponent::IsEnemy(const Entity* other) const {
if (IsEnemyImplentation.ExecuteWithDefault(other, false)) return true;
if (m_Parent->IsPlayer() && other->IsPlayer()) {
auto* thisCharacterComponent = m_Parent->GetComponent<CharacterComponent>();
if (!thisCharacterComponent) return false;
@ -444,7 +442,6 @@ bool DestroyableComponent::IsEnemy(const Entity* other) const {
}
bool DestroyableComponent::IsFriend(const Entity* other) const {
if (IsFriendImplentation.ExecuteWithDefault(other, false)) return true;
const auto* otherDestroyableComponent = other->GetComponent<DestroyableComponent>();
if (otherDestroyableComponent != nullptr) {
for (const auto enemyFaction : m_EnemyFactionIDs) {
@ -575,6 +572,8 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
return;
}
OnDamageCalculation(m_Parent, source, skillID, damage);
// If this entity has damage reduction, reduce the damage to a minimum of 1
if (m_DamageReduction > 0 && damage > 0) {
if (damage > m_DamageReduction) {

View File

@ -7,7 +7,7 @@
#include "Entity.h"
#include "Component.h"
#include "eReplicaComponentType.h"
#include "Implementation.h"
#include "Observable.h"
namespace CppScripts {
class Script;
@ -464,8 +464,8 @@ public:
// handle hardcode mode drops
void DoHardcoreModeDrops(const LWOOBJID source);
static Implementation<bool, const Entity*> IsEnemyImplentation;
static Implementation<bool, const Entity*> IsFriendImplentation;
// Damaged entity, offender, skillID, damage
static Observable<Entity*, LWOOBJID, uint32_t, uint32_t&> OnDamageCalculation;
private:
/**

View File

@ -38,6 +38,12 @@
#include "CDObjectSkillsTable.h"
#include "CDSkillBehaviorTable.h"
Observable<InventoryComponent*, Item*> InventoryComponent::OnItemCreated;
Observable<InventoryComponent*, Item*> InventoryComponent::OnItemDestroyed;
Observable<InventoryComponent*, Item*> InventoryComponent::OnItemEquipped;
Observable<InventoryComponent*, Item*> InventoryComponent::OnItemUnequipped;
Observable<InventoryComponent*, Item*> InventoryComponent::OnItemLoaded;
InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) {
this->m_Dirty = true;
this->m_Equipped = {};
@ -287,6 +293,8 @@ void InventoryComponent::AddItem(
}
auto* item = new Item(lot, inventory, slot, size, {}, parent, showFlyingLoot, isModMoveAndEquip, subKey, false, lootSourceType);
OnItemCreated(this, item);
isModMoveAndEquip = false;
}
@ -571,6 +579,8 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) {
}
itemElement = itemElement->NextSiblingElement();
OnItemLoaded(this, item);
}
bag = bag->NextSiblingElement();
@ -849,6 +859,8 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) {
EquipScripts(item);
OnItemEquipped(this, item);
Game::entityManager->SerializeEntity(m_Parent);
}
@ -879,6 +891,8 @@ void InventoryComponent::UnEquipItem(Item* item) {
UnequipScripts(item);
OnItemUnequipped(this, item);
Game::entityManager->SerializeEntity(m_Parent);
// Trigger property event

View File

@ -22,6 +22,7 @@
#include "eInventoryType.h"
#include "eReplicaComponentType.h"
#include "eLootSourceType.h"
#include "Observable.h"
class Entity;
class ItemSet;
@ -374,6 +375,12 @@ public:
~InventoryComponent() override;
static Observable<InventoryComponent*, Item*> OnItemCreated;
static Observable<InventoryComponent*, Item*> OnItemDestroyed;
static Observable<InventoryComponent*, Item*> OnItemEquipped;
static Observable<InventoryComponent*, Item*> OnItemUnequipped;
static Observable<InventoryComponent*, Item*> OnItemLoaded;
private:
/**
* All the inventory this entity possesses

View File

@ -212,6 +212,8 @@ void Inventory::AddManagedItem(Item* item) {
items.insert_or_assign(id, item);
free--;
component->OnItemLoaded(component, item);
}
void Inventory::RemoveManagedItem(Item* item) {
@ -226,6 +228,8 @@ void Inventory::RemoveManagedItem(Item* item) {
items.erase(id);
free++;
component->OnItemDestroyed(component, item);
}
eInventoryType Inventory::FindInventoryTypeForLot(const LOT lot) {

24765
dGame/json.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -81,6 +81,8 @@
#include "eLoginResponse.h"
#include "SlashCommandHandler.h"
#include "NejlikaHooks.h"
namespace Game {
Logger* logger = nullptr;
dServer* server = nullptr;
@ -315,6 +317,8 @@ int main(int argc, char** argv) {
// Register slash commands if not in zone 0
if (zoneID != 0) SlashCommandHandler::Startup();
nejlika::NejlikaHooks::InstallHooks();
Game::logger->Flush(); // once immediately before the main loop
while (true) {
Metrics::StartMeasurement(MetricVariable::Frame);