mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-12-17 20:14:31 -06:00
Compare commits
14 Commits
fix-empty-
...
nejlika
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
328743f1e1 | ||
|
|
3178a702a7 | ||
|
|
5e3312850c | ||
|
|
756dc4e44f | ||
|
|
87613f287f | ||
|
|
ac1b00fdaa | ||
|
|
364bcf822a | ||
|
|
d5b2278dc5 | ||
|
|
0bf5ee02e4 | ||
|
|
c2ad76ee66 | ||
|
|
c6528d444a | ||
|
|
78425aacb1 | ||
|
|
b8d610987f | ||
|
|
7845131649 |
92
dCommon/Implementation.h
Normal file
92
dCommon/Implementation.h
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#ifndef __IMPLEMENTATION_H__
|
||||||
|
#define __IMPLEMENTATION_H__
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A way to defer the implementation of an action.
|
||||||
|
*
|
||||||
|
* @tparam R The result of the action.
|
||||||
|
* @tparam T The types of the arguments that the implementation requires.
|
||||||
|
*/
|
||||||
|
template <typename R, typename... T>
|
||||||
|
class Implementation {
|
||||||
|
public:
|
||||||
|
typedef std::function<std::optional<R>(T...)> ImplementationFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the implementation of the action.
|
||||||
|
*
|
||||||
|
* @param implementation The implementation of the action.
|
||||||
|
*/
|
||||||
|
void SetImplementation(const ImplementationFunction& implementation) {
|
||||||
|
this->implementation = implementation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clears the implementation of the action.
|
||||||
|
*/
|
||||||
|
void ClearImplementation() {
|
||||||
|
implementation.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if the implementation is set.
|
||||||
|
*
|
||||||
|
* @return true If the implementation is set.
|
||||||
|
* @return false If the implementation is not set.
|
||||||
|
*/
|
||||||
|
bool IsSet() const {
|
||||||
|
return implementation.has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Executes the implementation if it is set.
|
||||||
|
*
|
||||||
|
* @param args The arguments to pass to the implementation.
|
||||||
|
* @return std::optional<R> The optional result of the implementation. If the result is not set, it indicates that the default action should be taken.
|
||||||
|
*/
|
||||||
|
std::optional<R> Execute(T... args) const {
|
||||||
|
return IsSet() ? implementation.value()(args...) : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Exectues the implementation if it is set, otherwise returns a default value.
|
||||||
|
*
|
||||||
|
* @param args The arguments to pass to the implementation.
|
||||||
|
* @param defaultValue The default value to return if the implementation is not set or should not be deferred.
|
||||||
|
*/
|
||||||
|
R ExecuteWithDefault(T... args, const R& defaultValue) const {
|
||||||
|
return Execute(args...).value_or(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* = operator overload.
|
||||||
|
*/
|
||||||
|
Implementation& operator=(const Implementation& other) {
|
||||||
|
implementation = other.implementation;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* = operator overload.
|
||||||
|
*/
|
||||||
|
Implementation& operator=(const ImplementationFunction& implementation) {
|
||||||
|
this->implementation = implementation;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* () operator overload.
|
||||||
|
*/
|
||||||
|
std::optional<R> operator()(T... args) {
|
||||||
|
return !IsSet() ? std::nullopt : implementation(args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<ImplementationFunction> implementation;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //!__IMPLEMENTATION_H__
|
||||||
73
dCommon/Observable.h
Normal file
73
dCommon/Observable.h
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#ifndef __OBSERVABLE_H__
|
||||||
|
#define __OBSERVABLE_H__
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief An event which can be observed by multiple observers.
|
||||||
|
*
|
||||||
|
* @tparam T The types of the arguments to be passed to the observers.
|
||||||
|
*/
|
||||||
|
template <typename... T>
|
||||||
|
class Observable {
|
||||||
|
public:
|
||||||
|
typedef std::function<void(T...)> Observer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adds an observer to the event.
|
||||||
|
*
|
||||||
|
* @param observer The observer to add.
|
||||||
|
*/
|
||||||
|
void AddObserver(const Observer& observer) {
|
||||||
|
observers.push_back(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes an observer from the event.
|
||||||
|
*
|
||||||
|
* @param observer The observer to remove.
|
||||||
|
*/
|
||||||
|
void RemoveObserver(const Observer& observer) {
|
||||||
|
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Notifies all observers of the event.
|
||||||
|
*
|
||||||
|
* @param args The arguments to pass to the observers.
|
||||||
|
*/
|
||||||
|
void Notify(T... args) {
|
||||||
|
for (const auto& observer : observers) {
|
||||||
|
observer(args...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* += operator overload.
|
||||||
|
*/
|
||||||
|
Observable& operator+=(const Observer& observer) {
|
||||||
|
AddObserver(observer);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -= operator overload.
|
||||||
|
*/
|
||||||
|
Observable& operator-=(const Observer& observer) {
|
||||||
|
RemoveObserver(observer);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* () operator overload.
|
||||||
|
*/
|
||||||
|
void operator()(T... args) {
|
||||||
|
Notify(args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Observer> observers;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //!__OBSERVABLE_H__
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "dConfig.h"
|
#include "dConfig.h"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "BinaryPathFinder.h"
|
#include "BinaryPathFinder.h"
|
||||||
#include "GeneralUtils.h"
|
#include "GeneralUtils.h"
|
||||||
|
|||||||
677
dGame/AdditionalEntityData.cpp
Normal file
677
dGame/AdditionalEntityData.cpp
Normal file
@@ -0,0 +1,677 @@
|
|||||||
|
#include "AdditionalEntityData.h"
|
||||||
|
|
||||||
|
#include "NejlikaData.h"
|
||||||
|
|
||||||
|
#include <DestroyableComponent.h>
|
||||||
|
#include <LevelProgressionComponent.h>
|
||||||
|
#include <InventoryComponent.h>
|
||||||
|
#include <BaseCombatAIComponent.h>
|
||||||
|
#include <TeamManager.h>
|
||||||
|
#include <ControllablePhysicsComponent.h>
|
||||||
|
#include <Item.h>
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
using namespace nejlika;
|
||||||
|
|
||||||
|
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, ModifierOperator op, bool resistance) const
|
||||||
|
{
|
||||||
|
float total = 0;
|
||||||
|
|
||||||
|
for (const auto& modifier : activeModifiers) {
|
||||||
|
if (modifier.GetConvertTo() != ModifierType::Invalid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifier.GetType() != type || modifier.GetOperator() != op || modifier.IsResistance() != resistance) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
total += modifier.GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const {
|
||||||
|
float total = 0;
|
||||||
|
|
||||||
|
for (const auto& modifier : additionalModifiers) {
|
||||||
|
if (modifier.GetConvertTo() != ModifierType::Invalid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return (scaler + additive) * (1 + multiplicative / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type) const {
|
||||||
|
return CalculateModifier(type, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
float nejlika::AdditionalEntityData::CalculateFinalModifier(ModifierType type, const 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);
|
||||||
|
|
||||||
|
static const std::unordered_set<ModifierType> elementalDamage = {
|
||||||
|
ModifierType::Fire,
|
||||||
|
ModifierType::Cold,
|
||||||
|
ModifierType::Lightning
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type == ModifierType::Health) {
|
||||||
|
if (lot == 1) additive += 25;
|
||||||
|
additive += CalculateModifier(ModifierType::Physique, additionalModifiers, ModifierOperator::Additive, false) * 2.5f;
|
||||||
|
additive += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 1.0f;
|
||||||
|
additive += CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false) * 1.0f;
|
||||||
|
}
|
||||||
|
else if (type == ModifierType::Imagination) {
|
||||||
|
additive += CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false) * 2.0f;
|
||||||
|
}
|
||||||
|
else if (type == ModifierType::Seperation || type == ModifierType::InternalDisassembly || type == ModifierType::Physical) {
|
||||||
|
multiplicative += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 0.33f;
|
||||||
|
}
|
||||||
|
else if (type == ModifierType::Pierce) {
|
||||||
|
multiplicative += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 0.285f;
|
||||||
|
}
|
||||||
|
else if (nejlika::IsOverTimeType(type) || nejlika::IsNormalDamageType(type)) {
|
||||||
|
multiplicative += CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false) * 0.33f;
|
||||||
|
}
|
||||||
|
else if (type == ModifierType::ImaginationRegen) {
|
||||||
|
const auto spirit = CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false);
|
||||||
|
additive += spirit * 0.01f + 1;
|
||||||
|
multiplicative += spirit * 0.25f;
|
||||||
|
}
|
||||||
|
else if (type == ModifierType::HealthRegen) {
|
||||||
|
const auto physique = CalculateModifier(ModifierType::Physique, additionalModifiers, ModifierOperator::Additive, false);
|
||||||
|
additive += physique * 0.04f;
|
||||||
|
}
|
||||||
|
else if (type == ModifierType::Defensive) {
|
||||||
|
additive += CalculateModifier(ModifierType::Physique, additionalModifiers, ModifierOperator::Additive, false) * 0.5f;
|
||||||
|
if (lot == 1) additive += level * 10;
|
||||||
|
}
|
||||||
|
else if (type == ModifierType::Offensive) {
|
||||||
|
additive += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 0.5f;
|
||||||
|
if (lot == 1) additive += level * 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elementalDamage.contains(type)) {
|
||||||
|
additive += CalculateModifier(ModifierType::Elemental, additionalModifiers, ModifierOperator::Additive, false) / elementalDamage.size();
|
||||||
|
multiplicative += CalculateModifier(ModifierType::Elemental, additionalModifiers, ModifierOperator::Multiplicative, false) / elementalDamage.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nejlika::IsNormalDamageType(type) || nejlika::IsOverTimeType(type)) {
|
||||||
|
additive += CalculateModifier(ModifierType::Damage, additionalModifiers, ModifierOperator::Additive, false);
|
||||||
|
multiplicative += CalculateModifier(ModifierType::Damage, additionalModifiers, ModifierOperator::Multiplicative, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
float total = (scaler + additive) * (1 + multiplicative / 100);
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
float nejlika::AdditionalEntityData::CalculateResistance(ModifierType type) const
|
||||||
|
{
|
||||||
|
type = nejlika::GetResistanceType(type);
|
||||||
|
|
||||||
|
return CalculateModifier(type, ModifierOperator::Multiplicative, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
float nejlika::AdditionalEntityData::CalculateMultiplier(ModifierType type) const
|
||||||
|
{
|
||||||
|
return 100 + CalculateModifier(type, ModifierOperator::Multiplicative, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems(UpgradeTriggerType triggerType, const TriggerParameters& params) {
|
||||||
|
auto* entity = Game::entityManager->GetEntity(id);
|
||||||
|
|
||||||
|
if (entity == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||||
|
|
||||||
|
if (inventoryComponent == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> result;
|
||||||
|
|
||||||
|
for (const auto& itemID : upgradeItems) {
|
||||||
|
auto* item = inventoryComponent->FindItemById(itemID);
|
||||||
|
|
||||||
|
if (item == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto upgradeDataOpt = NejlikaData::GetUpgradeTemplate(item->GetLot());
|
||||||
|
|
||||||
|
if (!upgradeDataOpt.has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& upgradeData = *upgradeDataOpt.value();
|
||||||
|
|
||||||
|
const auto modifiers = upgradeData.Trigger(item->GetCount(), triggerType, id, params);
|
||||||
|
|
||||||
|
result.insert(result.end(), modifiers.begin(), modifiers.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems(UpgradeTriggerType triggerType) {
|
||||||
|
return TriggerUpgradeItems(triggerType, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void nejlika::AdditionalEntityData::InitializeSkills() {
|
||||||
|
auto* entity = Game::entityManager->GetEntity(id);
|
||||||
|
|
||||||
|
if (entity == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||||
|
|
||||||
|
if (inventoryComponent == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct entry {
|
||||||
|
LWOOBJID id;
|
||||||
|
int32_t priority;
|
||||||
|
|
||||||
|
entry(LWOOBJID id, int32_t priority) : id(id), priority(priority) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<entry> items;
|
||||||
|
|
||||||
|
for (const auto& itemID : upgradeItems) {
|
||||||
|
auto* item = inventoryComponent->FindItemById(itemID);
|
||||||
|
|
||||||
|
if (item == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto priority = item->GetSlot();
|
||||||
|
|
||||||
|
items.push_back(entry(itemID, priority));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(items.begin(), items.end(), [](const entry& a, const entry& b) {
|
||||||
|
return a.priority < b.priority;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const auto& item : items) {
|
||||||
|
AddSkills(item.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void nejlika::AdditionalEntityData::AddSkills(LWOOBJID item) {
|
||||||
|
if (!upgradeItems.contains(item)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* entity = Game::entityManager->GetEntity(id);
|
||||||
|
|
||||||
|
if (entity == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||||
|
|
||||||
|
if (inventoryComponent == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* itemData = inventoryComponent->FindItemById(item);
|
||||||
|
|
||||||
|
if (itemData == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto upgradeDataOpt = NejlikaData::GetUpgradeTemplate(itemData->GetLot());
|
||||||
|
|
||||||
|
if (!upgradeDataOpt.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& upgradeData = *upgradeDataOpt.value();
|
||||||
|
|
||||||
|
LOG("Adding skills for item %i", id);
|
||||||
|
|
||||||
|
upgradeData.AddSkills(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nejlika::AdditionalEntityData::RemoveSkills(LOT lot) {
|
||||||
|
const auto upgradeDataOpt = NejlikaData::GetUpgradeTemplate(lot);
|
||||||
|
|
||||||
|
if (!upgradeDataOpt.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& upgradeData = *upgradeDataOpt.value();
|
||||||
|
|
||||||
|
LOG("Removing skills for item %i", id);
|
||||||
|
|
||||||
|
upgradeData.RemoveSkills(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> nejlika::AdditionalEntityData::CalculateMainWeaponDamage() {
|
||||||
|
auto* entity = Game::entityManager->GetEntity(id);
|
||||||
|
|
||||||
|
if (entity == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||||
|
|
||||||
|
if (inventoryComponent == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Item* mainWeapon = nullptr;
|
||||||
|
|
||||||
|
for (const auto& [location, item] : inventoryComponent->GetEquippedItems()) {
|
||||||
|
if (location == "special_r") {
|
||||||
|
mainWeapon = inventoryComponent->FindItemById(item.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainWeapon == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto additionalItemDataOpt = NejlikaData::GetAdditionalItemData(mainWeapon->GetId());
|
||||||
|
|
||||||
|
if (!additionalItemDataOpt.has_value()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& additionalItemData = *additionalItemDataOpt.value();
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> result;
|
||||||
|
|
||||||
|
for (const auto& modifier : additionalItemData.GetModifierInstances()) {
|
||||||
|
if (nejlika::IsNormalDamageType(modifier.GetType()) || nejlika::IsOverTimeType(modifier.GetType()) || nejlika::IsDurationType(modifier.GetType())) {
|
||||||
|
if (modifier.GetOperator() == ModifierOperator::Additive && modifier.GetUpgradeName().empty()) {
|
||||||
|
result.push_back(modifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::TriggerPassiveRegeneration() {
|
||||||
|
auto* entity = Game::entityManager->GetEntity(id);
|
||||||
|
|
||||||
|
if (entity == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* destroyable = entity->GetComponent<DestroyableComponent>();
|
||||||
|
|
||||||
|
if (destroyable == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto healthRegen = CalculateFinalModifier(ModifierType::HealthRegen, {}, level);
|
||||||
|
const auto imaginationRegen = CalculateFinalModifier(ModifierType::ImaginationRegen, {}, level);
|
||||||
|
|
||||||
|
if (healthRegen > 0) {
|
||||||
|
destroyable->SetHealth(std::min(destroyable->GetHealth() + static_cast<int32_t>(healthRegen), static_cast<int32_t>(destroyable->GetMaxHealth())));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imaginationRegen > 0) {
|
||||||
|
destroyable->SetImagination(std::min(destroyable->GetImagination() + static_cast<int32_t>(imaginationRegen), static_cast<int32_t>(destroyable->GetMaxImagination())));
|
||||||
|
}
|
||||||
|
|
||||||
|
Game::entityManager->SerializeEntity(entity);
|
||||||
|
|
||||||
|
// Trigger it again in 1 second
|
||||||
|
entity->AddCallbackTimer(1.0f, [this]() {
|
||||||
|
TriggerPassiveRegeneration();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
float nejlika::AdditionalEntityData::CalculateMultiplier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers) const {
|
||||||
|
return 100 + CalculateModifier(type, additionalModifiers, ModifierOperator::Multiplicative, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<ModifierType, std::unordered_map<ModifierType, float>> nejlika::AdditionalEntityData::CalculateDamageConversion(std::vector<ModifierInstance>& additionalModifiers) const {
|
||||||
|
std::unordered_map<ModifierType, std::unordered_map<ModifierType, float>> conversion;
|
||||||
|
|
||||||
|
for (const auto& modifier : activeModifiers) {
|
||||||
|
if (modifier.GetConvertTo() == ModifierType::Invalid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
conversion[modifier.GetType()][modifier.GetConvertTo()] += modifier.GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& modifier : additionalModifiers) {
|
||||||
|
if (modifier.GetConvertTo() == ModifierType::Invalid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
conversion[modifier.GetType()][modifier.GetConvertTo()] += modifier.GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third pass: adjust bidirectional conversions
|
||||||
|
auto copy = conversion; // Create a copy to iterate over
|
||||||
|
for (const auto& [type, convertMap] : copy) {
|
||||||
|
for (const auto& [convertTo, value] : convertMap) {
|
||||||
|
if (conversion[convertTo][type] > 0) {
|
||||||
|
if (value > conversion[convertTo][type]) {
|
||||||
|
conversion[type][convertTo] -= conversion[convertTo][type];
|
||||||
|
conversion[convertTo][type] = 0; // Ensure no negative values
|
||||||
|
} else {
|
||||||
|
conversion[convertTo][type] -= value;
|
||||||
|
conversion[type][convertTo] = 0; // Ensure no negative values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fourth pass: if a type converts to multiple types, and the sum of the conversion values is greater than 100, normalize the values
|
||||||
|
for (const auto& [type, convertMap] : conversion) {
|
||||||
|
float sum = 0;
|
||||||
|
for (const auto& [convertTo, value] : convertMap) {
|
||||||
|
sum += value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sum > 100) {
|
||||||
|
for (const auto& [convertTo, value] : convertMap) {
|
||||||
|
conversion[type][convertTo] = value / sum * 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversion;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
for (const auto& modifier : itemModifiers) {
|
||||||
|
if (nejlika::IsNormalDamageType(modifier.GetType()) || nejlika::IsOverTimeType(modifier.GetType()) || nejlika::IsDurationType(modifier.GetType())) {
|
||||||
|
if (modifier.GetOperator() == ModifierOperator::Additive && modifier.GetUpgradeName().empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activeModifiers.push_back(modifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& upgradeItem : upgradeItems) {
|
||||||
|
auto* item = inventoryComponent->FindItemById(upgradeItem);
|
||||||
|
|
||||||
|
if (item == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG("Applying upgrade item %i", item->GetLot());
|
||||||
|
|
||||||
|
const auto itemDataOpt = NejlikaData::GetUpgradeTemplate(item->GetLot());
|
||||||
|
|
||||||
|
if (!itemDataOpt.has_value()) {
|
||||||
|
LOG("Upgrade item %i has no data", item->GetLot());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& itemData = *itemDataOpt.value();
|
||||||
|
|
||||||
|
const auto& itemModifiers = itemData.GenerateModifiers(item->GetCount());
|
||||||
|
|
||||||
|
LOG("Upgrade item %i has %i modifiers with level %i", item->GetLot(), itemModifiers.size(), item->GetCount());
|
||||||
|
|
||||||
|
activeModifiers.insert(activeModifiers.end(), itemModifiers.begin(), itemModifiers.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyable->SetMaxHealth(static_cast<int32_t>(CalculateFinalModifier(ModifierType::Health, {}, level)));
|
||||||
|
destroyable->SetMaxArmor(static_cast<int32_t>(CalculateFinalModifier(ModifierType::Armor, {}, level)));
|
||||||
|
//if (!entity->IsPlayer()) {
|
||||||
|
destroyable->SetMaxImagination(static_cast<int32_t>(CalculateFinalModifier(ModifierType::Imagination, {}, level)));
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyable->SetHealth(destroyable->GetMaxHealth());
|
||||||
|
destroyable->SetArmor(destroyable->GetMaxArmor());
|
||||||
|
|
||||||
|
//if (!entity->IsPlayer()) {
|
||||||
|
destroyable->SetImagination(destroyable->GetMaxImagination());
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (entity->IsPlayer()) {
|
||||||
|
auto* controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>();
|
||||||
|
if (controllablePhysicsComponent) controllablePhysicsComponent->SetSpeedMultiplier(CalculateMultiplier(ModifierType::Speed) / 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TriggerPassiveRegeneration();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
109
dGame/AdditionalEntityData.h
Normal file
109
dGame/AdditionalEntityData.h
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Entity.h"
|
||||||
|
|
||||||
|
#include "ModifierInstance.h"
|
||||||
|
#include "EntityTemplate.h"
|
||||||
|
#include "UpgradeTriggerType.h"
|
||||||
|
#include "TriggerParameters.h"
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
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, const std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const;
|
||||||
|
|
||||||
|
float CalculateModifier(ModifierType type, int32_t level) const;
|
||||||
|
|
||||||
|
float CalculateModifier(ModifierType type) const;
|
||||||
|
|
||||||
|
float CalculateFinalModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, int32_t level) const;
|
||||||
|
|
||||||
|
float CalculateResistance(ModifierType type) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate the multiplier for a given modifier type. With a base value of 100 (%).
|
||||||
|
*
|
||||||
|
* @param type The modifier type.
|
||||||
|
* @return The multiplier.
|
||||||
|
*/
|
||||||
|
float CalculateMultiplier(ModifierType type) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate the multiplier for a given modifier type. With a base value of 100 (%).
|
||||||
|
*
|
||||||
|
* @param type The modifier type.
|
||||||
|
* @param additionalModifiers Additional modifiers to apply.
|
||||||
|
* @return The multiplier.
|
||||||
|
*/
|
||||||
|
float CalculateMultiplier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate damage conversation mapping.
|
||||||
|
*
|
||||||
|
* @param additionalModifiers Additional modifiers to apply.
|
||||||
|
*
|
||||||
|
* @return The damage conversion mapping.
|
||||||
|
*/
|
||||||
|
std::unordered_map<ModifierType, std::unordered_map<ModifierType, float>> CalculateDamageConversion(std::vector<ModifierInstance>& additionalModifiers) const;
|
||||||
|
|
||||||
|
void ApplyToEntity();
|
||||||
|
|
||||||
|
void CheckForRescale(AdditionalEntityData* other);
|
||||||
|
|
||||||
|
int32_t GetLevel() const { return level; }
|
||||||
|
|
||||||
|
LWOOBJID GetID() const { return id; }
|
||||||
|
|
||||||
|
LOT GetLOT() const { return lot; }
|
||||||
|
|
||||||
|
const std::unordered_set<LWOOBJID>& GetUpgradeItems() const { return upgradeItems; }
|
||||||
|
|
||||||
|
void AddUpgradeItem(LWOOBJID id) { upgradeItems.insert(id); }
|
||||||
|
|
||||||
|
void RemoveUpgradeItem(LWOOBJID id) { upgradeItems.erase(id); }
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> TriggerUpgradeItems(UpgradeTriggerType triggerType, const TriggerParameters& params);
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> TriggerUpgradeItems(UpgradeTriggerType triggerType);
|
||||||
|
|
||||||
|
void InitializeSkills();
|
||||||
|
|
||||||
|
void AddSkills(LWOOBJID item);
|
||||||
|
void RemoveSkills(LOT lot);
|
||||||
|
|
||||||
|
const std::vector<ModifierInstance>& GetActiveModifiers() const { return activeModifiers; }
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> CalculateMainWeaponDamage();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void RollStandardModifiers(int32_t level);
|
||||||
|
|
||||||
|
void TriggerPassiveRegeneration();
|
||||||
|
|
||||||
|
bool initialized = false;
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> standardModifiers;
|
||||||
|
std::vector<ModifierInstance> activeModifiers;
|
||||||
|
std::unordered_set<LWOOBJID> upgradeItems;
|
||||||
|
|
||||||
|
LWOOBJID id;
|
||||||
|
LOT lot;
|
||||||
|
|
||||||
|
int32_t level = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
240
dGame/AdditionalItemData.cpp
Normal file
240
dGame/AdditionalItemData.cpp
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
#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);
|
||||||
|
|
||||||
|
std::vector<const ModifierNameTemplate*> availableObjects;
|
||||||
|
|
||||||
|
for (const auto& itemTemplate : itemTemplateVec) {
|
||||||
|
if (itemTemplate.GetMinLevel() > level || itemTemplate.GetMaxLevel() < level) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemTemplate.GetLOT() != static_cast<int32_t>(item->GetLot())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
availableObjects.push_back(&itemTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableObjects.empty()) {
|
||||||
|
Save(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& itemTemplate = *availableObjects[GeneralUtils::GenerateRandomNumber<uint32_t>() % availableObjects.size()];
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,7 +6,24 @@ set(DGAME_SOURCES "Character.cpp"
|
|||||||
"TeamManager.cpp"
|
"TeamManager.cpp"
|
||||||
"TradingManager.cpp"
|
"TradingManager.cpp"
|
||||||
"User.cpp"
|
"User.cpp"
|
||||||
"UserManager.cpp")
|
"UserManager.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"
|
||||||
|
"UpgradeTemplate.cpp"
|
||||||
|
"UpgradeEffect.cpp"
|
||||||
|
"Lookup.cpp"
|
||||||
|
"NejlikaHelpers.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
${PROJECT_SOURCE_DIR}/dScripts
|
${PROJECT_SOURCE_DIR}/dScripts
|
||||||
|
|||||||
@@ -96,6 +96,9 @@
|
|||||||
#include "CDSkillBehaviorTable.h"
|
#include "CDSkillBehaviorTable.h"
|
||||||
#include "CDZoneTableTable.h"
|
#include "CDZoneTableTable.h"
|
||||||
|
|
||||||
|
Observable<Entity*, const PositionUpdate&> Entity::OnPlayerPositionUpdate;
|
||||||
|
Observable<Entity*> Entity::OnReadyForUpdates;
|
||||||
|
|
||||||
Entity::Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser, Entity* parentEntity) {
|
Entity::Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser, Entity* parentEntity) {
|
||||||
m_ObjectID = objectID;
|
m_ObjectID = objectID;
|
||||||
m_TemplateID = info.lot;
|
m_TemplateID = info.lot;
|
||||||
@@ -1778,6 +1781,12 @@ void Entity::SetOwnerOverride(const LWOOBJID value) {
|
|||||||
m_OwnerOverride = value;
|
m_OwnerOverride = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Entity::SetPlayerReadyForUpdates() {
|
||||||
|
m_PlayerIsReadyForUpdates = true;
|
||||||
|
|
||||||
|
OnReadyForUpdates(this);
|
||||||
|
}
|
||||||
|
|
||||||
bool Entity::GetIsGhostingCandidate() const {
|
bool Entity::GetIsGhostingCandidate() const {
|
||||||
return m_IsGhostingCandidate;
|
return m_IsGhostingCandidate;
|
||||||
}
|
}
|
||||||
@@ -2133,6 +2142,8 @@ void Entity::ProcessPositionUpdate(PositionUpdate& update) {
|
|||||||
Game::entityManager->QueueGhostUpdate(GetObjectID());
|
Game::entityManager->QueueGhostUpdate(GetObjectID());
|
||||||
|
|
||||||
if (updateChar) Game::entityManager->SerializeEntity(this);
|
if (updateChar) Game::entityManager->SerializeEntity(this);
|
||||||
|
|
||||||
|
OnPlayerPositionUpdate.Notify(this, update);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SystemAddress& Entity::GetSystemAddress() const {
|
const SystemAddress& Entity::GetSystemAddress() const {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "NiQuaternion.h"
|
#include "NiQuaternion.h"
|
||||||
#include "LDFFormat.h"
|
#include "LDFFormat.h"
|
||||||
#include "eKillType.h"
|
#include "eKillType.h"
|
||||||
|
#include "Observable.h"
|
||||||
|
|
||||||
namespace Loot {
|
namespace Loot {
|
||||||
class Info;
|
class Info;
|
||||||
@@ -116,7 +117,7 @@ public:
|
|||||||
|
|
||||||
void SetOwnerOverride(LWOOBJID value);
|
void SetOwnerOverride(LWOOBJID value);
|
||||||
|
|
||||||
void SetPlayerReadyForUpdates() { m_PlayerIsReadyForUpdates = true; }
|
void SetPlayerReadyForUpdates();
|
||||||
|
|
||||||
void SetObservers(int8_t value);
|
void SetObservers(int8_t value);
|
||||||
|
|
||||||
@@ -299,6 +300,13 @@ public:
|
|||||||
// Scale will only be communicated to the client when the construction packet is sent
|
// Scale will only be communicated to the client when the construction packet is sent
|
||||||
void SetScale(const float scale) { m_Scale = scale; };
|
void SetScale(const float scale) { m_Scale = scale; };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The observable for player entity position updates.
|
||||||
|
*/
|
||||||
|
static Observable<Entity*, const PositionUpdate&> OnPlayerPositionUpdate;
|
||||||
|
|
||||||
|
static Observable<Entity*> OnReadyForUpdates;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
LWOOBJID m_ObjectID;
|
LWOOBJID m_ObjectID;
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
90
dGame/EntityTemplate.cpp
Normal file
90
dGame/EntityTemplate.cpp
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#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,
|
||||||
|
"",
|
||||||
|
ModifierType::Invalid,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
181
dGame/Lookup.cpp
Normal file
181
dGame/Lookup.cpp
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
#include "Lookup.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "json.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace nejlika;
|
||||||
|
|
||||||
|
Lookup::Lookup(const std::filesystem::path& lookup)
|
||||||
|
{
|
||||||
|
m_Lookup = lookup;
|
||||||
|
|
||||||
|
// Check if the file exists.
|
||||||
|
if (!std::filesystem::exists(lookup))
|
||||||
|
{
|
||||||
|
// Empty lookup
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the json file
|
||||||
|
std::ifstream file(lookup);
|
||||||
|
|
||||||
|
std::string json((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||||
|
|
||||||
|
// Parse the json
|
||||||
|
auto doc = nlohmann::json::parse(json);
|
||||||
|
|
||||||
|
|
||||||
|
// The document is a map, so we can iterate over it.
|
||||||
|
for (const auto& [key, v] : doc.items()) {
|
||||||
|
if (v.is_number_integer())
|
||||||
|
{
|
||||||
|
m_LookupMap[key] = v.get<int64_t>();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!v.is_object()) {
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
ss << "Invalid value for key \"" << key << "\" in lookup.";
|
||||||
|
|
||||||
|
throw std::runtime_error(ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the data
|
||||||
|
auto data = v.get<nlohmann::json>();
|
||||||
|
|
||||||
|
// Get the id
|
||||||
|
auto id = data["id"].get<int64_t>();
|
||||||
|
|
||||||
|
m_LookupMap[key] = id;
|
||||||
|
|
||||||
|
// Get the metadata
|
||||||
|
auto metadata = data["metadata"].get<std::string>();
|
||||||
|
|
||||||
|
m_Metadata[key] = metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nejlika::Lookup::Lookup(const Lookup &other)
|
||||||
|
{
|
||||||
|
m_Lookup = other.m_Lookup;
|
||||||
|
m_LookupMap = other.m_LookupMap;
|
||||||
|
m_Metadata = other.m_Metadata;
|
||||||
|
m_CoreSymbols = other.m_CoreSymbols;
|
||||||
|
}
|
||||||
|
|
||||||
|
id Lookup::GetValue(const name& symbol) const
|
||||||
|
{
|
||||||
|
id value;
|
||||||
|
|
||||||
|
if (IsCoreSymbol(symbol, value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& it = m_LookupMap.find(symbol);
|
||||||
|
|
||||||
|
if (it == m_LookupMap.end())
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
ss << "Symbol \"" << symbol << "\" does not exist in the lookup.";
|
||||||
|
|
||||||
|
throw std::runtime_error(ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nejlika::Lookup::Exists(const name &symbol) const
|
||||||
|
{
|
||||||
|
return IsCoreSymbol(symbol) || (m_CoreSymbols.find(symbol) != m_CoreSymbols.end()) || (m_LookupMap.find(symbol) != m_LookupMap.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nejlika::Lookup::Exists(id value) const
|
||||||
|
{
|
||||||
|
for (const auto& [k, v] : m_LookupMap)
|
||||||
|
{
|
||||||
|
if (v == value)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& Lookup::GetMetadata(const name& symbol) const
|
||||||
|
{
|
||||||
|
const auto& it = m_Metadata.find(symbol);
|
||||||
|
|
||||||
|
if (it == m_Metadata.end())
|
||||||
|
{
|
||||||
|
static std::string empty;
|
||||||
|
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path& Lookup::GetLookup() const
|
||||||
|
{
|
||||||
|
return m_Lookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::unordered_map<name, id> &nejlika::Lookup::GetMap() const
|
||||||
|
{
|
||||||
|
return m_LookupMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nejlika::Lookup::IsCoreSymbol(const name &symbol, id &value) const
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
value = std::stoi(symbol);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
// cont...
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!symbol.starts_with(core_prefix)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check in the core symbols
|
||||||
|
const auto& it = m_CoreSymbols.find(symbol);
|
||||||
|
|
||||||
|
if (it != m_CoreSymbols.end())
|
||||||
|
{
|
||||||
|
value = it->second;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the format "core:<value>"
|
||||||
|
try {
|
||||||
|
value = std::stoi(symbol.substr(core_prefix.size() + 1));
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nejlika::Lookup::IsCoreSymbol(const name &symbol)
|
||||||
|
{
|
||||||
|
// Check if it can be converted to an integer
|
||||||
|
try {
|
||||||
|
[[maybe_unused]] auto value = std::stoi(symbol);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
// cont...
|
||||||
|
}
|
||||||
|
|
||||||
|
return symbol.starts_with(core_prefix);
|
||||||
|
}
|
||||||
117
dGame/Lookup.h
Normal file
117
dGame/Lookup.h
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <functional>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace nejlika {
|
||||||
|
|
||||||
|
typedef std::string name;
|
||||||
|
typedef int id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A one-way mapping between symbols (names) and their corresponding numerical values.
|
||||||
|
*
|
||||||
|
* This is an active lookup, meaning that it is possible to add new symbols and their values to the lookup.
|
||||||
|
*
|
||||||
|
* A method is provided to wait for a symbol to be added to the lookup.
|
||||||
|
*/
|
||||||
|
class Lookup
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline static const name core_prefix = "lego-universe";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a Lookup object with the specified lookup file path.
|
||||||
|
*
|
||||||
|
* @param lookup The path to the lookup file.
|
||||||
|
* @throw If the lookup file could not be parsed.
|
||||||
|
*/
|
||||||
|
Lookup(const std::filesystem::path& lookup);
|
||||||
|
|
||||||
|
Lookup(const Lookup& other);
|
||||||
|
|
||||||
|
Lookup() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the value of the specified symbol.
|
||||||
|
*
|
||||||
|
* @param symbol The symbol to get the value of.
|
||||||
|
* @return The value of the specified symbol.
|
||||||
|
* @throw If the specified symbol does not exist.
|
||||||
|
*/
|
||||||
|
id GetValue(const name& symbol) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether the specified symbol exists.
|
||||||
|
*
|
||||||
|
* @param symbol The symbol to check.
|
||||||
|
* @return Whether the specified symbol exists.
|
||||||
|
*/
|
||||||
|
bool Exists(const name& symbol) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether any symbol has the specified value.
|
||||||
|
*
|
||||||
|
* @param value The value to check.
|
||||||
|
* @return Whether any symbol has the specified value.
|
||||||
|
*/
|
||||||
|
bool Exists(id value) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the metadata of a specified symbol.
|
||||||
|
*
|
||||||
|
* @param symbol The symbol to get metadata of.
|
||||||
|
* @return The metadata of the specified symbol or an empty string if the symbol does not exist.
|
||||||
|
*/
|
||||||
|
const std::string& GetMetadata(const name& symbol) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the path to the lookup file.
|
||||||
|
*
|
||||||
|
* @return The path to the lookup file.
|
||||||
|
*/
|
||||||
|
const std::filesystem::path& GetLookup() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the map of all symbols and their values.
|
||||||
|
*
|
||||||
|
* @return The map of all symbols and their values.
|
||||||
|
*/
|
||||||
|
const std::unordered_map<name, id>& GetMap() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether the specified symbol is a core symbol.
|
||||||
|
*
|
||||||
|
* @param symbol The symbol to check.
|
||||||
|
* @param value The value of the core symbol.
|
||||||
|
* @return Whether the specified symbol is a core symbol.
|
||||||
|
*/
|
||||||
|
bool IsCoreSymbol(const name& symbol, id& value) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether the specified symbol is a core symbol.
|
||||||
|
*
|
||||||
|
* A symbol is considered a core symbol if it is either:
|
||||||
|
* a number;
|
||||||
|
* or a string starting with the core_prefix.
|
||||||
|
*
|
||||||
|
* @param symbol The symbol to check.
|
||||||
|
* @return Whether the specified symbol is a core symbol.
|
||||||
|
*/
|
||||||
|
static bool IsCoreSymbol(const name& symbol);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::filesystem::path m_Lookup;
|
||||||
|
|
||||||
|
std::unordered_map<name, id> m_LookupMap;
|
||||||
|
|
||||||
|
std::unordered_map<name, std::string> m_Metadata;
|
||||||
|
|
||||||
|
std::unordered_map<name, id> m_CoreSymbols;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nejlika
|
||||||
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
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
170
dGame/ModifierInstance.cpp
Normal file
170
dGame/ModifierInstance.cpp
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
#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);
|
||||||
|
convertTo = magic_enum::enum_cast<ModifierType>(config["convert-to"].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["convert-to"] = magic_enum::enum_name(convertTo);
|
||||||
|
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;
|
||||||
|
|
||||||
|
bool hasConvertTo = false;
|
||||||
|
bool hasSkillModifier = false;
|
||||||
|
|
||||||
|
for (const auto& modifier : modifiers) {
|
||||||
|
if (modifier.type == ModifierType::Invalid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifier.GetConvertTo() != ModifierType::Invalid)
|
||||||
|
{
|
||||||
|
hasConvertTo = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!modifier.GetUpgradeName().empty())
|
||||||
|
{
|
||||||
|
hasSkillModifier = true;
|
||||||
|
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) {
|
||||||
|
for (const auto& math : resistance.second) {
|
||||||
|
for (const auto& modifier : math.second) {
|
||||||
|
ss << "<font color=\"#FFFFFF\">";
|
||||||
|
|
||||||
|
ss << ((modifier.second > 0) ? (math.first == ModifierOperator::Multiplicative ? "+" : "") : "-");
|
||||||
|
|
||||||
|
ss << std::fixed << std::setprecision(1) << std::abs(modifier.second);
|
||||||
|
|
||||||
|
if (math.first == ModifierOperator::Multiplicative) {
|
||||||
|
ss << "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << "</font> <font color=\"#D0AB62\">";
|
||||||
|
|
||||||
|
ss << " " << nejlika::GetModifierTypeName(modifier.first);
|
||||||
|
|
||||||
|
if (resistance.first) {
|
||||||
|
// If the ss now ends with 'Damage' remove it
|
||||||
|
if (ss.str().substr(ss.str().size() - 7) == " Damage") {
|
||||||
|
ss.seekp(-7, std::ios_base::end);
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << " " << "Resistance";
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << "</font>\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSkillModifier)
|
||||||
|
{
|
||||||
|
for (const auto& modifier : modifiers) {
|
||||||
|
if (modifier.type != ModifierType::SkillModifier) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << "<font color=\"" << GetModifierTypeColor(modifier.type) << "\">";
|
||||||
|
|
||||||
|
ss << ((modifier.value > 0) ? "+" : "-");
|
||||||
|
|
||||||
|
ss << std::fixed << std::setprecision(0) << std::abs(modifier.value);
|
||||||
|
|
||||||
|
ss << " to ";
|
||||||
|
|
||||||
|
ss << modifier.GetUpgradeName();
|
||||||
|
|
||||||
|
ss << "</font>\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasConvertTo)
|
||||||
|
{
|
||||||
|
for (const auto& modifier : modifiers) {
|
||||||
|
if (modifier.GetConvertTo() == ModifierType::Invalid)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifier.type == ModifierType::Invalid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << "<font color=\"#FFFFFF\">";
|
||||||
|
|
||||||
|
// +xx/yy% of T1 converted to T2
|
||||||
|
ss << ((modifier.value > 0) ? "" : "-");
|
||||||
|
|
||||||
|
ss << std::fixed << std::setprecision(0) << std::abs(modifier.value);
|
||||||
|
|
||||||
|
ss << "%</font> <font color=\"#D0AB62\">";
|
||||||
|
|
||||||
|
ss << " of ";
|
||||||
|
|
||||||
|
ss << nejlika::GetModifierTypeName(modifier.type);
|
||||||
|
|
||||||
|
ss << " converted to ";
|
||||||
|
|
||||||
|
ss << nejlika::GetModifierTypeName(modifier.GetConvertTo());
|
||||||
|
|
||||||
|
ss << "</font>\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
97
dGame/ModifierInstance.h
Normal file
97
dGame/ModifierInstance.h
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#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, ModifierType convertTo,
|
||||||
|
const std::string& upgradeName
|
||||||
|
) : type(type), value(value), op(op), isResistance(isResistance), category(category), effectID(effectID), effectType(effectType), convertTo(convertTo),
|
||||||
|
upgradeName(upgradeName)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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; }
|
||||||
|
|
||||||
|
ModifierType GetConvertTo() const { return convertTo; }
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
std::string GetUpgradeName() const { return upgradeName; }
|
||||||
|
|
||||||
|
void SetType(ModifierType type) { this->type = type; }
|
||||||
|
|
||||||
|
void SetConvertTo(ModifierType convertTo) { this->convertTo = convertTo; }
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
void SetUpgradeName(const std::string& upgradeName) { this->upgradeName = upgradeName; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ModifierType type;
|
||||||
|
ModifierType convertTo;
|
||||||
|
float value;
|
||||||
|
ModifierOperator op;
|
||||||
|
bool isResistance;
|
||||||
|
ModifierCategory category;
|
||||||
|
uint32_t effectID;
|
||||||
|
std::string effectType;
|
||||||
|
std::string upgradeName;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
166
dGame/ModifierNameTemplate.cpp
Normal file
166
dGame/ModifierNameTemplate.cpp
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#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"])
|
||||||
|
{
|
||||||
|
const auto modifierTemplate = ModifierTemplate(modifier);
|
||||||
|
|
||||||
|
if (modifierTemplate.GetTypes().empty() || modifierTemplate.GetTypes().at(0) == ModifierType::Invalid)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiers.push_back(modifierTemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.contains("levels"))
|
||||||
|
{
|
||||||
|
auto levels = json["levels"];
|
||||||
|
|
||||||
|
if (levels.contains("min"))
|
||||||
|
{
|
||||||
|
minLevel = levels["min"].get<int32_t>();
|
||||||
|
|
||||||
|
const auto rasio = 1; //45.0f / 85.0f;
|
||||||
|
|
||||||
|
minLevel = std::max(1, static_cast<int32_t>(minLevel * rasio));
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
40
dGame/ModifierScale.cpp
Normal file
40
dGame/ModifierScale.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include "ModifierScale.h"
|
||||||
|
|
||||||
|
nejlika::ModifierScale::ModifierScale(const nlohmann::json & json)
|
||||||
|
{
|
||||||
|
level = json["level"].get<int32_t>();
|
||||||
|
|
||||||
|
const auto rasio = 1; //45.0f / 85.0f;
|
||||||
|
|
||||||
|
level = std::max(1, static_cast<int32_t>(level * rasio));
|
||||||
|
|
||||||
|
if (json.contains("min")) {
|
||||||
|
min = json["min"].get<float>();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
min = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.contains("max")) {
|
||||||
|
max = json["max"].get<float>();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
max = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.contains("value")) {
|
||||||
|
min = json["value"].get<float>();
|
||||||
|
max = json["value"].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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
501
dGame/ModifierTemplate.cpp
Normal file
501
dGame/ModifierTemplate.cpp
Normal file
@@ -0,0 +1,501 @@
|
|||||||
|
#include "ModifierTemplate.h"
|
||||||
|
|
||||||
|
#include <magic_enum.hpp>
|
||||||
|
#include <random>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
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("convert-to"))
|
||||||
|
{
|
||||||
|
convertTo = magic_enum::enum_cast<ModifierType>(config["convert-to"].get<std::string>()).value_or(ModifierType::Invalid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
convertTo = ModifierType::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.contains("scaling"))
|
||||||
|
{
|
||||||
|
const auto scaling = config["scaling"];
|
||||||
|
|
||||||
|
for (const auto& scaler : scaling)
|
||||||
|
{
|
||||||
|
scales.push_back(ModifierScale(scaler));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.contains("polynomial"))
|
||||||
|
{
|
||||||
|
const auto polynomialConfig = config["polynomial"];
|
||||||
|
|
||||||
|
for (const auto& term : polynomialConfig)
|
||||||
|
{
|
||||||
|
polynomial.push_back(term.get<float>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.contains("skill"))
|
||||||
|
{
|
||||||
|
upgradeName = config["skill"].get<std::string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scales.empty())
|
||||||
|
{
|
||||||
|
nlohmann::json scaling;
|
||||||
|
|
||||||
|
for (const auto& scale : scales)
|
||||||
|
{
|
||||||
|
scaling.push_back(scale.ToJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
config["scaling"] = scaling;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!polynomial.empty())
|
||||||
|
{
|
||||||
|
nlohmann::json polynomialConfig;
|
||||||
|
|
||||||
|
for (const auto& term : polynomial)
|
||||||
|
{
|
||||||
|
polynomialConfig.push_back(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
config["polynomial"] = polynomialConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
config["convert-to"] = magic_enum::enum_name(convertTo);
|
||||||
|
|
||||||
|
config["category"] = magic_enum::enum_name(category);
|
||||||
|
config["resistance"] = isResistance;
|
||||||
|
config["effect-id"] = effectID;
|
||||||
|
config["effect-type"] = effectType;
|
||||||
|
config["operator"] = magic_enum::enum_name(operatorType);
|
||||||
|
|
||||||
|
if (!upgradeName.empty())
|
||||||
|
{
|
||||||
|
config["skill"] = upgradeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::string nejlika::ModifierTemplate::GenerateHtmlString(const std::vector<ModifierTemplate>& modifiers, int32_t level) {
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
// target -> resistance -> op -> type -> (min, max)
|
||||||
|
std::unordered_map<ModifierCategory, std::unordered_map<bool, std::unordered_map<ModifierOperator, std::unordered_map<ModifierType, std::pair<float, float>>>>> modifierMap;
|
||||||
|
|
||||||
|
bool hasConvertTo = false;
|
||||||
|
bool hasSkillModifier = false;
|
||||||
|
|
||||||
|
for (const auto& modifier : modifiers) {
|
||||||
|
if (modifier.GetConvertTo() != ModifierType::Invalid)
|
||||||
|
{
|
||||||
|
hasConvertTo = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!modifier.GetUpgradeName().empty())
|
||||||
|
{
|
||||||
|
hasSkillModifier = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& type : modifier.types) {
|
||||||
|
if (type == ModifierType::Invalid || type == ModifierType::SkillModifier) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!modifier.polynomial.empty())
|
||||||
|
{
|
||||||
|
float value = 0.0f;
|
||||||
|
|
||||||
|
int32_t power = 0;
|
||||||
|
for (const auto& term : modifier.polynomial)
|
||||||
|
{
|
||||||
|
value += term * std::pow(level, power);
|
||||||
|
|
||||||
|
power++;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifierMap[modifier.category][modifier.isResistance][modifier.operatorType][type] = {value, value};
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 : modifier.scales) {
|
||||||
|
if (s.GetLevel() <= level && s.GetLevel() > scale.GetLevel()) {
|
||||||
|
scale = s;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifierMap[modifier.category][modifier.isResistance][modifier.operatorType][type] = {scale.GetMin(), scale.GetMax()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
for (const auto& math : resistance.second) {
|
||||||
|
for (const auto& modifier : math.second) {
|
||||||
|
ss << "<font color=\"#FFFFFF\">";
|
||||||
|
|
||||||
|
ss << ((modifier.second.first > 0) ? (math.first == ModifierOperator::Multiplicative ? "+" : "") : "-");
|
||||||
|
|
||||||
|
ss << std::fixed << std::setprecision(1) << std::abs(modifier.second.first);
|
||||||
|
|
||||||
|
if (modifier.second.first != modifier.second.second)
|
||||||
|
{
|
||||||
|
ss << "/";
|
||||||
|
|
||||||
|
ss << std::fixed << std::setprecision(1) << std::abs(modifier.second.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (math.first == ModifierOperator::Multiplicative) {
|
||||||
|
ss << "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << "</font> <font color=\"#D0AB62\">";
|
||||||
|
|
||||||
|
ss << " " << nejlika::GetModifierTypeName(modifier.first);
|
||||||
|
|
||||||
|
if (resistance.first) {
|
||||||
|
// If the ss now ends with 'Damage' remove it
|
||||||
|
if (ss.str().substr(ss.str().size() - 6) == "Damage") {
|
||||||
|
ss.seekp(-6, std::ios_base::end);
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << " " << "Resistance";
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << "</font>\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSkillModifier)
|
||||||
|
{
|
||||||
|
for (const auto& modifier : modifiers) {
|
||||||
|
for (const auto& type : modifier.types) {
|
||||||
|
if (type != ModifierType::SkillModifier) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& scalors = modifier.GetScales();
|
||||||
|
|
||||||
|
if (scalors.empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& m = scalors[0];
|
||||||
|
|
||||||
|
ss << "<font color=\"" << GetModifierTypeColor(type) << "\">";
|
||||||
|
|
||||||
|
ss << ((m.GetMin() > 0) ? "+" : "-");
|
||||||
|
|
||||||
|
ss << std::fixed << std::setprecision(0) << std::abs(m.GetMin());
|
||||||
|
|
||||||
|
ss << " to ";
|
||||||
|
|
||||||
|
ss << modifier.GetUpgradeName();
|
||||||
|
|
||||||
|
ss << "</font>\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasConvertTo)
|
||||||
|
{
|
||||||
|
for (const auto& modifier : modifiers) {
|
||||||
|
if (modifier.GetConvertTo() == ModifierType::Invalid)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& type : modifier.types) {
|
||||||
|
if (type == ModifierType::Invalid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& scalors = modifier.GetScales();
|
||||||
|
|
||||||
|
auto m = scalors[0];
|
||||||
|
|
||||||
|
for (const auto& s : scalors) {
|
||||||
|
if (s.GetLevel() <= level && s.GetLevel() > m.GetLevel()) {
|
||||||
|
m = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << "<font color=\"#FFFFFF\">";
|
||||||
|
|
||||||
|
// +xx/yy% of T1 converted to T2
|
||||||
|
ss << ((m.GetMin() > 0) ? "" : "-");
|
||||||
|
|
||||||
|
ss << std::fixed << std::setprecision(0) << std::abs(m.GetMin());
|
||||||
|
|
||||||
|
if (m.GetMin() != m.GetMax())
|
||||||
|
{
|
||||||
|
ss << "/";
|
||||||
|
|
||||||
|
ss << std::fixed << std::setprecision(0) << std::abs(m.GetMax());
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << "%</font> <font color=\"#D0AB62\">";
|
||||||
|
|
||||||
|
ss << " of ";
|
||||||
|
|
||||||
|
ss << nejlika::GetModifierTypeName(type);
|
||||||
|
|
||||||
|
ss << " converted to ";
|
||||||
|
|
||||||
|
ss << nejlika::GetModifierTypeName(modifier.GetConvertTo());
|
||||||
|
|
||||||
|
ss << "</font>\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ModifierInstance> nejlika::ModifierTemplate::GenerateModifier(ModifierType type, int32_t level) const {
|
||||||
|
ModifierScale scale;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
if (!polynomial.empty())
|
||||||
|
{
|
||||||
|
float value = 0.0f;
|
||||||
|
|
||||||
|
int32_t power = 0;
|
||||||
|
for (const auto& term : polynomial)
|
||||||
|
{
|
||||||
|
value += term * std::pow(level, power);
|
||||||
|
|
||||||
|
power++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType, convertTo, upgradeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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())) {
|
||||||
|
std::cout << "Found scale: " << s.GetMin() << " - " << s.GetMax() << " for level " << s.GetLevel() << std::endl;
|
||||||
|
scale = s;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
float value = 0;
|
||||||
|
|
||||||
|
if (scale.GetMax() == scale.GetMin())
|
||||||
|
{
|
||||||
|
value = scale.GetMin();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = (GeneralUtils::GenerateRandomNumber<uint32_t>(0, 100) / 100.0f) * (scale.GetMax() - scale.GetMin()) + scale.GetMin();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Generated modifier: " << value << " with level " << level << " for type: " << magic_enum::enum_name(type) << std::endl;
|
||||||
|
|
||||||
|
return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType, convertTo, upgradeName);
|
||||||
|
}
|
||||||
109
dGame/ModifierTemplate.h
Normal file
109
dGame/ModifierTemplate.h
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#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; }
|
||||||
|
|
||||||
|
ModifierType GetConvertTo() const { return convertTo; }
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
const std::string& GetUpgradeName() const { return upgradeName; }
|
||||||
|
|
||||||
|
void SetTypes(const std::vector<ModifierType>& types) { this->types = types; }
|
||||||
|
|
||||||
|
void SetConvertTo(ModifierType convertTo) { this->convertTo = convertTo; }
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
void SetUpgradeName(const std::string& upgradeName) { this->upgradeName = upgradeName; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate a HTML string representation of a set of modifier templates.
|
||||||
|
*
|
||||||
|
* @param modifiers The modifier templates to generate the HTML string for.
|
||||||
|
* @param level The level of the modifier templates.
|
||||||
|
* @return The HTML string.
|
||||||
|
*/
|
||||||
|
static std::string GenerateHtmlString(const std::vector<ModifierTemplate>& modifiers, int32_t level);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<ModifierInstance> GenerateModifier(ModifierType type, int32_t level) const;
|
||||||
|
|
||||||
|
std::vector<ModifierType> types;
|
||||||
|
ModifierType convertTo;
|
||||||
|
ModifierTemplateSelector selector;
|
||||||
|
std::vector<ModifierScale> scales;
|
||||||
|
std::vector<float> polynomial;
|
||||||
|
ModifierCategory category;
|
||||||
|
ModifierOperator operatorType;
|
||||||
|
bool isResistance;
|
||||||
|
uint32_t effectID;
|
||||||
|
std::string effectType;
|
||||||
|
std::string upgradeName;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
216
dGame/ModifierType.cpp
Normal file
216
dGame/ModifierType.cpp
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
#include "ModifierType.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
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::Physical, "#666666"},
|
||||||
|
{ModifierType::Pierce, "#4f4f4f"},
|
||||||
|
{ModifierType::Vitality, "#e84646"},
|
||||||
|
{ModifierType::Fire, "#ff0000"},
|
||||||
|
{ModifierType::Cold, "#94d0f2"},
|
||||||
|
{ModifierType::Lightning, "#00a2ff"},
|
||||||
|
{ModifierType::Corruption, "#3d00ad"},
|
||||||
|
{ModifierType::Psychic, "#4b0161"},
|
||||||
|
{ModifierType::Acid, "#00ff00"},
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::unordered_map<ModifierType, std::string> nameMap = {
|
||||||
|
{ModifierType::Health, "Health"},
|
||||||
|
{ModifierType::Armor, "Armor"},
|
||||||
|
{ModifierType::Imagination, "Imagination"},
|
||||||
|
{ModifierType::Offensive, "Offensive Ability"},
|
||||||
|
{ModifierType::Defensive, "Defensive Ability"},
|
||||||
|
{ModifierType::Physical, "Physical Damage"},
|
||||||
|
{ModifierType::Pierce, "Pierce Damage"},
|
||||||
|
{ModifierType::Vitality, "Vitality Damage"},
|
||||||
|
{ModifierType::Fire, "Fire Damage"},
|
||||||
|
{ModifierType::Cold, "Cold Damage"},
|
||||||
|
{ModifierType::Lightning, "Lightning Damage"},
|
||||||
|
{ModifierType::Corruption, "Corruption Damage"},
|
||||||
|
{ModifierType::Psychic, "Psychic Damage"},
|
||||||
|
{ModifierType::Acid, "Acid Damage"},
|
||||||
|
{ModifierType::InternalDisassembly, "Internal Disassembly"},
|
||||||
|
{ModifierType::InternalDisassemblyDuration, "Internal Disassembly Duration"},
|
||||||
|
{ModifierType::Burn, "Burn"},
|
||||||
|
{ModifierType::BurnDuration, "Burn Duration"},
|
||||||
|
{ModifierType::Frostburn, "Frostburn"},
|
||||||
|
{ModifierType::FrostburnDuration, "Frostburn Duration"},
|
||||||
|
{ModifierType::Poison, "Poison"},
|
||||||
|
{ModifierType::PoisonDuration, "Poison Duration"},
|
||||||
|
{ModifierType::Electrocute, "Electrocute"},
|
||||||
|
{ModifierType::ElectrocuteDuration, "Electrocute Duration"},
|
||||||
|
{ModifierType::VitalityDecay, "Vitality Decay"},
|
||||||
|
{ModifierType::VitalityDecayDuration, "Vitality Decay Duration"},
|
||||||
|
{ModifierType::Seperation, "Seperation"},
|
||||||
|
{ModifierType::SeperationDuration, "Seperation Duration"},
|
||||||
|
{ModifierType::Elemental, "Elemental Damage"},
|
||||||
|
{ModifierType::Damage, "Damage"},
|
||||||
|
{ModifierType::Speed, "Speed"},
|
||||||
|
{ModifierType::AttackSpeed, "Attack Speed"},
|
||||||
|
{ModifierType::BlockRecovery, "Block Recovery Speed"},
|
||||||
|
{ModifierType::BlockChance, "Chance to Block"},
|
||||||
|
{ModifierType::Block, "Damage to Block"},
|
||||||
|
{ModifierType::CriticalDamage, "Critical-hit Damage"},
|
||||||
|
{ModifierType::HealthDrain, "Damage converted to Health"},
|
||||||
|
{ModifierType::ArmorPiercing, "Armor Piercing"},
|
||||||
|
{ModifierType::ReducedStunDuration, "Reduced Stun Duration"},
|
||||||
|
{ModifierType::SkillCooldownReduction, "Skill Cooldown Reduction"},
|
||||||
|
{ModifierType::SkillRecharge, "Skill Recharge Time"},
|
||||||
|
{ModifierType::Slow, "Slow"},
|
||||||
|
{ModifierType::Physique, "Physique"},
|
||||||
|
{ModifierType::Cunning, "Cunning"},
|
||||||
|
{ModifierType::Spirit, "Imagination"},
|
||||||
|
{ModifierType::AttacksPerSecond, "Attacks per Second"},
|
||||||
|
{ModifierType::ImaginationCost, "Imagination Cost"},
|
||||||
|
{ModifierType::MainWeaponDamage, "Main Weapon Damage"},
|
||||||
|
{ModifierType::Stun, "Target Stun Duration"}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::unordered_map<ModifierType, ModifierType> resistanceMap = {
|
||||||
|
{ModifierType::Physical, ModifierType::Physical},
|
||||||
|
{ModifierType::Pierce, ModifierType::Pierce},
|
||||||
|
{ModifierType::Vitality, ModifierType::Vitality},
|
||||||
|
{ModifierType::Fire, ModifierType::Fire},
|
||||||
|
{ModifierType::Cold, ModifierType::Cold},
|
||||||
|
{ModifierType::Lightning, ModifierType::Lightning},
|
||||||
|
{ModifierType::Corruption, ModifierType::Corruption},
|
||||||
|
{ModifierType::Psychic, ModifierType::Psychic},
|
||||||
|
{ModifierType::Acid, ModifierType::Acid},
|
||||||
|
{ModifierType::InternalDisassembly, ModifierType::Physical},
|
||||||
|
{ModifierType::Burn, ModifierType::Fire},
|
||||||
|
{ModifierType::Frostburn, ModifierType::Cold},
|
||||||
|
{ModifierType::Poison, ModifierType::Acid},
|
||||||
|
{ModifierType::VitalityDecay, ModifierType::Vitality},
|
||||||
|
{ModifierType::Electrocute, ModifierType::Lightning},
|
||||||
|
{ModifierType::Seperation, ModifierType::Seperation}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::unordered_set<ModifierType> normalDamageTypes = {
|
||||||
|
ModifierType::Physical,
|
||||||
|
ModifierType::Pierce,
|
||||||
|
ModifierType::Vitality,
|
||||||
|
ModifierType::Fire,
|
||||||
|
ModifierType::Cold,
|
||||||
|
ModifierType::Lightning,
|
||||||
|
ModifierType::Corruption,
|
||||||
|
ModifierType::Psychic,
|
||||||
|
ModifierType::Acid
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::unordered_map<ModifierType, ModifierType> durationMap = {
|
||||||
|
{ModifierType::InternalDisassembly, ModifierType::InternalDisassemblyDuration},
|
||||||
|
{ModifierType::Burn, ModifierType::BurnDuration},
|
||||||
|
{ModifierType::Frostburn, ModifierType::FrostburnDuration},
|
||||||
|
{ModifierType::Poison, ModifierType::PoisonDuration},
|
||||||
|
{ModifierType::VitalityDecay, ModifierType::VitalityDecayDuration},
|
||||||
|
{ModifierType::Electrocute, ModifierType::ElectrocuteDuration},
|
||||||
|
{ModifierType::Seperation, ModifierType::SeperationDuration}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::unordered_map<ModifierType, ModifierType> overTimeMap = {
|
||||||
|
{ModifierType::Physical, ModifierType::InternalDisassembly},
|
||||||
|
{ModifierType::Fire, ModifierType::Burn},
|
||||||
|
{ModifierType::Cold, ModifierType::Frostburn},
|
||||||
|
{ModifierType::Poison, ModifierType::Poison},
|
||||||
|
{ModifierType::Vitality, ModifierType::VitalityDecay},
|
||||||
|
{ModifierType::Lightning, ModifierType::Electrocute}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::unordered_set<ModifierType> isOverTimeMap = {
|
||||||
|
ModifierType::InternalDisassembly,
|
||||||
|
ModifierType::Burn,
|
||||||
|
ModifierType::Frostburn,
|
||||||
|
ModifierType::Poison,
|
||||||
|
ModifierType::VitalityDecay,
|
||||||
|
ModifierType::Electrocute,
|
||||||
|
ModifierType::Seperation
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::unordered_set<ModifierType> isDurationType = {
|
||||||
|
ModifierType::InternalDisassemblyDuration,
|
||||||
|
ModifierType::BurnDuration,
|
||||||
|
ModifierType::FrostburnDuration,
|
||||||
|
ModifierType::PoisonDuration,
|
||||||
|
ModifierType::VitalityDecayDuration,
|
||||||
|
ModifierType::ElectrocuteDuration,
|
||||||
|
ModifierType::SeperationDuration
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& nejlika::GetModifierTypeName(ModifierType type) {
|
||||||
|
const auto name = nameMap.find(type);
|
||||||
|
|
||||||
|
if (name != nameMap.end()) {
|
||||||
|
return name->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::string invalid = "Invalid";
|
||||||
|
|
||||||
|
return invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModifierType nejlika::GetResistanceType(ModifierType type) {
|
||||||
|
const auto resistance = resistanceMap.find(type);
|
||||||
|
|
||||||
|
if (resistance != resistanceMap.end()) {
|
||||||
|
return resistance->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ModifierType::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool nejlika::IsNormalDamageType(ModifierType type) {
|
||||||
|
return normalDamageTypes.find(type) != normalDamageTypes.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModifierType nejlika::GetOverTimeType(ModifierType type) {
|
||||||
|
const auto overTime = overTimeMap.find(type);
|
||||||
|
|
||||||
|
if (overTime != overTimeMap.end()) {
|
||||||
|
return overTime->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ModifierType::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModifierType nejlika::GetDurationType(ModifierType type) {
|
||||||
|
const auto duration = durationMap.find(type);
|
||||||
|
|
||||||
|
if (duration != durationMap.end()) {
|
||||||
|
return duration->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ModifierType::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool nejlika::IsOverTimeType(ModifierType type) {
|
||||||
|
return isOverTimeMap.find(type) != isOverTimeMap.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool nejlika::IsDurationType(ModifierType type) {
|
||||||
|
return isDurationType.find(type) != isDurationType.end();
|
||||||
|
}
|
||||||
96
dGame/ModifierType.h
Normal file
96
dGame/ModifierType.h
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace nejlika
|
||||||
|
{
|
||||||
|
|
||||||
|
enum class ModifierType : uint8_t
|
||||||
|
{
|
||||||
|
Health,
|
||||||
|
Armor,
|
||||||
|
Imagination,
|
||||||
|
|
||||||
|
Physique,
|
||||||
|
Cunning,
|
||||||
|
Spirit,
|
||||||
|
|
||||||
|
Offensive,
|
||||||
|
Defensive,
|
||||||
|
|
||||||
|
// Normal Types
|
||||||
|
Physical,
|
||||||
|
Fire,
|
||||||
|
Cold,
|
||||||
|
Lightning,
|
||||||
|
Acid,
|
||||||
|
Vitality,
|
||||||
|
Pierce,
|
||||||
|
Corruption, // Aether
|
||||||
|
Psychic, // Chaos
|
||||||
|
|
||||||
|
// Duration Types
|
||||||
|
InternalDisassembly, // Internal Trauma
|
||||||
|
InternalDisassemblyDuration,
|
||||||
|
Burn,
|
||||||
|
BurnDuration,
|
||||||
|
Frostburn,
|
||||||
|
FrostburnDuration,
|
||||||
|
Poison,
|
||||||
|
PoisonDuration,
|
||||||
|
VitalityDecay,
|
||||||
|
VitalityDecayDuration,
|
||||||
|
Electrocute,
|
||||||
|
ElectrocuteDuration,
|
||||||
|
Seperation, // Bleeding
|
||||||
|
SeperationDuration,
|
||||||
|
|
||||||
|
// Special
|
||||||
|
Elemental, // Even split between Fire, Cold, Lightning
|
||||||
|
|
||||||
|
Damage,
|
||||||
|
|
||||||
|
Speed,
|
||||||
|
|
||||||
|
AttackSpeed,
|
||||||
|
SkillModifier,
|
||||||
|
|
||||||
|
Slow,
|
||||||
|
ArmorPiercing,
|
||||||
|
ReducedStunDuration,
|
||||||
|
SkillCooldownReduction,
|
||||||
|
SkillRecharge,
|
||||||
|
BlockRecovery,
|
||||||
|
BlockChance,
|
||||||
|
Block,
|
||||||
|
HealthRegen,
|
||||||
|
ImaginationRegen,
|
||||||
|
AttacksPerSecond,
|
||||||
|
ImaginationCost,
|
||||||
|
MainWeaponDamage,
|
||||||
|
Stun,
|
||||||
|
|
||||||
|
CriticalDamage,
|
||||||
|
HealthDrain,
|
||||||
|
|
||||||
|
Invalid
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string& GetModifierTypeColor(ModifierType type);
|
||||||
|
|
||||||
|
const std::string& GetModifierTypeName(ModifierType type);
|
||||||
|
|
||||||
|
const ModifierType GetResistanceType(ModifierType type);
|
||||||
|
|
||||||
|
const bool IsNormalDamageType(ModifierType type);
|
||||||
|
|
||||||
|
const bool IsOverTimeType(ModifierType type);
|
||||||
|
|
||||||
|
const bool IsDurationType(ModifierType type);
|
||||||
|
|
||||||
|
const ModifierType GetOverTimeType(ModifierType type);
|
||||||
|
|
||||||
|
const ModifierType GetDurationType(ModifierType type);
|
||||||
|
|
||||||
|
}
|
||||||
215
dGame/NejlikaData.cpp
Normal file
215
dGame/NejlikaData.cpp
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
#include "NejlikaData.h"
|
||||||
|
|
||||||
|
#include "Game.h"
|
||||||
|
#include "dConfig.h"
|
||||||
|
#include "json.hpp"
|
||||||
|
#include "Logger.h"
|
||||||
|
#include "Lookup.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;
|
||||||
|
|
||||||
|
std::unordered_map<LOT, nejlika::UpgradeTemplate> upgradeTemplates;
|
||||||
|
|
||||||
|
nejlika::Lookup lookup;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const nejlika::Lookup& nejlika::NejlikaData::GetLookup()
|
||||||
|
{
|
||||||
|
return lookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<UpgradeTemplate*> nejlika::NejlikaData::GetUpgradeTemplate(LOT lot) {
|
||||||
|
const auto& it = upgradeTemplates.find(lot);
|
||||||
|
|
||||||
|
if (it != upgradeTemplates.end()) {
|
||||||
|
return std::optional<UpgradeTemplate*>(&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()
|
||||||
|
{
|
||||||
|
const auto& lookupFile = Game::config->GetValue("lookup");
|
||||||
|
|
||||||
|
if (!lookupFile.empty())
|
||||||
|
{
|
||||||
|
lookup = Lookup(lookupFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
modifierNameTemplates.clear();
|
||||||
|
|
||||||
|
// Load data from json file
|
||||||
|
const auto& directory_name = Game::config->GetValue("nejlika");
|
||||||
|
|
||||||
|
if (directory_name.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through all files in the directory
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(directory_name))
|
||||||
|
{
|
||||||
|
if (!entry.is_regular_file())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It has to end on .mod.json
|
||||||
|
const auto& path = entry.path().string();
|
||||||
|
|
||||||
|
if (path.size() < 9 || path.substr(path.size() - 9) != ".mod.json")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadNejlikaDataFile(entry.path().string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void nejlika::NejlikaData::LoadNejlikaDataFile(const std::string& path) {
|
||||||
|
std::ifstream file(path);
|
||||||
|
|
||||||
|
if (!file.is_open())
|
||||||
|
{
|
||||||
|
LOG("Failed to open nejlika data file: %s", path.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"))
|
||||||
|
{
|
||||||
|
const auto& modifierTemplates = json["modifier-templates"];
|
||||||
|
|
||||||
|
for (const auto& value : modifierTemplates)
|
||||||
|
{
|
||||||
|
auto modifierTemplate = ModifierNameTemplate(value);
|
||||||
|
|
||||||
|
if (modifierTemplate.GetModifiers().empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
if (json.contains("upgrade-templates"))
|
||||||
|
{
|
||||||
|
const auto& upgradeTemplatesArray = json["upgrade-templates"];
|
||||||
|
|
||||||
|
for (const auto& value : upgradeTemplatesArray)
|
||||||
|
{
|
||||||
|
auto upgradeTemplate = UpgradeTemplate(value);
|
||||||
|
|
||||||
|
upgradeTemplates[upgradeTemplate.GetLot()] = upgradeTemplate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG("Loaded %d upgrade templates", upgradeTemplates.size());
|
||||||
|
}
|
||||||
|
|
||||||
42
dGame/NejlikaData.h
Normal file
42
dGame/NejlikaData.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ModifierNameTemplate.h"
|
||||||
|
#include "EntityTemplate.h"
|
||||||
|
#include "AdditionalItemData.h"
|
||||||
|
#include "AdditionalEntityData.h"
|
||||||
|
#include "UpgradeTemplate.h"
|
||||||
|
#include "Lookup.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);
|
||||||
|
|
||||||
|
const std::optional<UpgradeTemplate*> GetUpgradeTemplate(LOT lot);
|
||||||
|
|
||||||
|
void SetAdditionalItemData(LWOOBJID id, AdditionalItemData data);
|
||||||
|
|
||||||
|
void SetAdditionalEntityData(LWOOBJID id, AdditionalEntityData data);
|
||||||
|
|
||||||
|
void UnsetAdditionalItemData(LWOOBJID id);
|
||||||
|
|
||||||
|
void UnsetAdditionalEntityData(LWOOBJID id);
|
||||||
|
|
||||||
|
void LoadNejlikaData();
|
||||||
|
|
||||||
|
void LoadNejlikaDataFile(const std::string& path);
|
||||||
|
|
||||||
|
const Lookup& GetLookup();
|
||||||
|
|
||||||
|
}
|
||||||
33
dGame/NejlikaHelpers.cpp
Normal file
33
dGame/NejlikaHelpers.cpp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#include "NejlikaHelpers.h"
|
||||||
|
|
||||||
|
|
||||||
|
void nejlika::NejlikaHelpers::RenderDamageText(const std::string & text, Entity * attacker, Entity * damaged,
|
||||||
|
float scale, int32_t fontSize, int32_t colorR, int32_t colorG, int32_t colorB, int32_t colorA)
|
||||||
|
{
|
||||||
|
if (damaged == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto damagedPosition = damaged->GetPosition();
|
||||||
|
|
||||||
|
// Add a slight random offset to the damage position
|
||||||
|
damagedPosition.x += (rand() % 10 - 5) / 5.0f;
|
||||||
|
damagedPosition.y += (rand() % 10 - 5) / 5.0f;
|
||||||
|
damagedPosition.z += (rand() % 10 - 5) / 5.0f;
|
||||||
|
|
||||||
|
const auto& damageText = text;
|
||||||
|
|
||||||
|
std::stringstream damageUIMessage;
|
||||||
|
|
||||||
|
damageUIMessage << 0.0825 << ";" << 0.12 << ";" << damagedPosition.x << ";" << damagedPosition.y + 4.5f << ";" << damagedPosition.z << ";" << 0.1 << ";";
|
||||||
|
damageUIMessage << 200 * scale << ";" << 200 * scale << ";" << 0.5 << ";" << 1.0 << ";" << damageText << ";" << 4 << ";" << fontSize << ";" << colorR << ";" << colorG << ";" << colorB << ";";
|
||||||
|
damageUIMessage << colorA;
|
||||||
|
|
||||||
|
const auto damageUIStr = damageUIMessage.str();
|
||||||
|
|
||||||
|
if (damaged->IsPlayer()) {
|
||||||
|
damaged->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
|
} else if (attacker != nullptr && attacker->IsPlayer()) {
|
||||||
|
attacker->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
dGame/NejlikaHelpers.h
Normal file
9
dGame/NejlikaHelpers.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include <Entity.h>
|
||||||
|
|
||||||
|
namespace nejlika::NejlikaHelpers
|
||||||
|
{
|
||||||
|
|
||||||
|
void RenderDamageText(const std::string& text, Entity* attacker, Entity* damaged,
|
||||||
|
float scale = 1, int32_t fontSize = 4, int32_t colorR = 255, int32_t colorG = 255, int32_t colorB = 255, int32_t colorA = 0);
|
||||||
|
|
||||||
|
}
|
||||||
1048
dGame/NejlikaHooks.cpp
Normal file
1048
dGame/NejlikaHooks.cpp
Normal file
File diff suppressed because it is too large
Load Diff
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__
|
||||||
|
|||||||
16
dGame/TriggerParameters.h
Normal file
16
dGame/TriggerParameters.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include "BehaviorSlot.h"
|
||||||
|
|
||||||
|
namespace nejlika
|
||||||
|
{
|
||||||
|
|
||||||
|
class TriggerParameters
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int32_t SkillID = 0;
|
||||||
|
BehaviorSlot SelectedBehaviorSlot = BehaviorSlot::Invalid;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
355
dGame/UpgradeEffect.cpp
Normal file
355
dGame/UpgradeEffect.cpp
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
#include "UpgradeEffect.h"
|
||||||
|
|
||||||
|
#include "GeneralUtils.h"
|
||||||
|
#include "GameMessages.h"
|
||||||
|
#include "InventoryComponent.h"
|
||||||
|
|
||||||
|
#include <magic_enum.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace nejlika;
|
||||||
|
|
||||||
|
nejlika::UpgradeEffect::UpgradeEffect(const nlohmann::json& json)
|
||||||
|
{
|
||||||
|
Load(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json nejlika::UpgradeEffect::ToJson() const
|
||||||
|
{
|
||||||
|
nlohmann::json json;
|
||||||
|
|
||||||
|
json["trigger-type"] = static_cast<int32_t>(triggerType);
|
||||||
|
|
||||||
|
nlohmann::json modifiersJson = nlohmann::json::array();
|
||||||
|
|
||||||
|
for (const auto& modifier : modifiers) {
|
||||||
|
modifiersJson.push_back(modifier.ToJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
json["modifiers"] = modifiersJson;
|
||||||
|
|
||||||
|
if (!chance.empty()) {
|
||||||
|
nlohmann::json chanceJson = nlohmann::json::array();
|
||||||
|
|
||||||
|
for (const auto& scale : chance) {
|
||||||
|
chanceJson.push_back({
|
||||||
|
{"level", scale.level},
|
||||||
|
{"value", scale.value}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
json["chance"] = chanceJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effectID != 0) {
|
||||||
|
json["effect-id"] = effectID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!effectType.empty()) {
|
||||||
|
json["effect-type"] = effectType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!conditions.empty()) {
|
||||||
|
nlohmann::json conditionsJson = nlohmann::json::array();
|
||||||
|
|
||||||
|
for (const auto& condition : conditions) {
|
||||||
|
conditionsJson.push_back(magic_enum::enum_name(condition));
|
||||||
|
}
|
||||||
|
|
||||||
|
json["conditions"] = conditionsJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (equipSkillID != 0) {
|
||||||
|
json["grant-skill-id"] = equipSkillID;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> nejlika::UpgradeEffect::GenerateModifiers(int32_t level) const
|
||||||
|
{
|
||||||
|
std::vector<ModifierInstance> result;
|
||||||
|
|
||||||
|
for (const auto& modifier : modifiers) {
|
||||||
|
auto instances = modifier.GenerateModifiers(level);
|
||||||
|
|
||||||
|
result.insert(result.end(), instances.begin(), instances.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nejlika::UpgradeEffect::Load(const nlohmann::json& json)
|
||||||
|
{
|
||||||
|
triggerType = magic_enum::enum_cast<UpgradeTriggerType>(json["trigger-type"].get<std::string>()).value_or(UpgradeTriggerType::OnHit);
|
||||||
|
|
||||||
|
modifiers.clear();
|
||||||
|
|
||||||
|
if (json.contains("modifiers")){
|
||||||
|
for (const auto& modifier : json["modifiers"]) {
|
||||||
|
ModifierTemplate effect(modifier);
|
||||||
|
modifiers.push_back(effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.contains("chance")) {
|
||||||
|
chance.clear();
|
||||||
|
|
||||||
|
for (const auto& scale : json["chance"]) {
|
||||||
|
chance.push_back({
|
||||||
|
scale["level"].get<int32_t>(),
|
||||||
|
scale["value"].get<float>()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.contains("effect-id")) {
|
||||||
|
effectID = json["effect-id"].get<int32_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.contains("effect-type")) {
|
||||||
|
effectType = json["effect-type"].get<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.contains("conditions")) {
|
||||||
|
conditions.clear();
|
||||||
|
|
||||||
|
for (const auto& condition : json["conditions"]) {
|
||||||
|
conditions.push_back(magic_enum::enum_cast<UpgradeTriggerCondition>(condition.get<std::string>()).value_or(UpgradeTriggerCondition::None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.contains("grant-skill-id")) {
|
||||||
|
equipSkillID = json["grant-skill-id"].get<int32_t>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float nejlika::UpgradeEffect::CalculateChance(int32_t level) const {
|
||||||
|
if (chance.empty()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the highest level that is less than or equal to the given level
|
||||||
|
float value = 0;
|
||||||
|
|
||||||
|
for (const auto& scale : chance) {
|
||||||
|
if (scale.level <= level) {
|
||||||
|
value = scale.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nejlika::UpgradeEffect::CheckConditions(LWOOBJID origin, const TriggerParameters& params) const {
|
||||||
|
auto* entity = Game::entityManager->GetEntity(origin);
|
||||||
|
|
||||||
|
if (!entity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* inventory = entity->GetComponent<InventoryComponent>();
|
||||||
|
|
||||||
|
if (!inventory) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& skills = inventory->GetSkills();
|
||||||
|
const auto& equipped = inventory->GetEquippedItems();
|
||||||
|
|
||||||
|
for (const auto& condition : conditions) {
|
||||||
|
switch (condition) {
|
||||||
|
case UpgradeTriggerCondition::PrimaryAbility:
|
||||||
|
if (params.SelectedBehaviorSlot != BehaviorSlot::Primary) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UpgradeTriggerCondition::UseSkill:
|
||||||
|
if (params.SkillID != equipSkillID) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case UpgradeTriggerCondition::None:
|
||||||
|
break;
|
||||||
|
case UpgradeTriggerCondition::Unarmed:
|
||||||
|
if (equipped.contains("special_r")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UpgradeTriggerCondition::Melee:
|
||||||
|
if (!equipped.contains("special_r")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UpgradeTriggerCondition::TwoHanded:
|
||||||
|
{
|
||||||
|
if (!equipped.contains("special_r")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& weaponLot = equipped.at("special_r").lot;
|
||||||
|
|
||||||
|
const auto& info = Inventory::FindItemComponent(weaponLot);
|
||||||
|
|
||||||
|
if (!info.isTwoHanded) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UpgradeTriggerCondition::Shield:
|
||||||
|
if (!equipped.contains("special_l")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nejlika::UpgradeEffect::OnTrigger(LWOOBJID origin) const {
|
||||||
|
auto* entity = Game::entityManager->GetEntity(origin);
|
||||||
|
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* inventory = entity->GetComponent<InventoryComponent>();
|
||||||
|
|
||||||
|
if (!inventory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> nejlika::UpgradeEffect::Trigger(const std::vector<UpgradeEffect>& modifiers, int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin, const TriggerParameters& params) {
|
||||||
|
std::vector<ModifierInstance> result;
|
||||||
|
|
||||||
|
for (const auto& modifier : modifiers) {
|
||||||
|
if (modifier.GetTriggerType() != triggerType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!modifier.CheckConditions(origin, params)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float chanceRoll = GeneralUtils::GenerateRandomNumber<float>(0, 1);
|
||||||
|
|
||||||
|
if (chanceRoll > modifier.CalculateChance(level)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Triggering effect trigger type: " << magic_enum::enum_name(triggerType) << std::endl;
|
||||||
|
|
||||||
|
modifier.OnTrigger(origin);
|
||||||
|
|
||||||
|
auto instances = modifier.GenerateModifiers(level);
|
||||||
|
|
||||||
|
result.insert(result.end(), instances.begin(), instances.end());
|
||||||
|
|
||||||
|
if (modifier.effectID == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameMessages::SendPlayFXEffect(
|
||||||
|
origin,
|
||||||
|
modifier.effectID,
|
||||||
|
GeneralUtils::UTF8ToUTF16(modifier.effectType),
|
||||||
|
std::to_string(GeneralUtils::GenerateRandomNumber<size_t>())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nejlika::UpgradeEffect::AddSkill(LWOOBJID origin) const {
|
||||||
|
auto* entity = Game::entityManager->GetEntity(origin);
|
||||||
|
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* inventory = entity->GetComponent<InventoryComponent>();
|
||||||
|
|
||||||
|
if (!inventory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triggerType != UpgradeTriggerType::Active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (equipSkillID != 0) {
|
||||||
|
inventory->AddSkill(equipSkillID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void nejlika::UpgradeEffect::RemoveSkill(LWOOBJID origin) const {
|
||||||
|
auto* entity = Game::entityManager->GetEntity(origin);
|
||||||
|
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* inventory = entity->GetComponent<InventoryComponent>();
|
||||||
|
|
||||||
|
if (!inventory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triggerType != UpgradeTriggerType::Active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (equipSkillID != 0) {
|
||||||
|
inventory->RemoveSkill(equipSkillID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string nejlika::UpgradeEffect::GenerateHtmlString(const std::vector<UpgradeEffect>& effects, int32_t level) {
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
for (const auto& effect : effects) {
|
||||||
|
const auto chance = effect.CalculateChance(level);
|
||||||
|
|
||||||
|
|
||||||
|
for (const auto& condition : effect.conditions) {
|
||||||
|
if (condition == UpgradeTriggerCondition::None || condition == UpgradeTriggerCondition::UseSkill) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << "<font color=\"#D0AB62\">";
|
||||||
|
|
||||||
|
switch (condition) {
|
||||||
|
case UpgradeTriggerCondition::PrimaryAbility:
|
||||||
|
ss << "On main-hand attack";
|
||||||
|
break;
|
||||||
|
case UpgradeTriggerCondition::Melee:
|
||||||
|
ss << "Requires melee weapon";
|
||||||
|
break;
|
||||||
|
case UpgradeTriggerCondition::TwoHanded:
|
||||||
|
ss << "Requires two-handed weapon";
|
||||||
|
break;
|
||||||
|
case UpgradeTriggerCondition::Shield:
|
||||||
|
ss << "Requires a shield";
|
||||||
|
break;
|
||||||
|
case UpgradeTriggerCondition::Unarmed:
|
||||||
|
ss << "Requires unarmed attack";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << "</font>\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chance < 1.0f) {
|
||||||
|
ss << "<font color=\"#FFFFFF\">" << chance * 100 << "%</font><font color=\"#D0AB62\"> chance to trigger</font>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Level " << level << " chance: " << chance << std::endl;
|
||||||
|
|
||||||
|
ss << ModifierInstance::GenerateHtmlString(effect.GenerateModifiers(level));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
69
dGame/UpgradeEffect.h
Normal file
69
dGame/UpgradeEffect.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ModifierTemplate.h"
|
||||||
|
#include "UpgradeTriggerType.h"
|
||||||
|
#include "UpgradeTriggerCondition.h"
|
||||||
|
#include <InventoryComponent.h>
|
||||||
|
#include "TriggerParameters.h"
|
||||||
|
|
||||||
|
#include <dCommonVars.h>
|
||||||
|
|
||||||
|
namespace nejlika
|
||||||
|
{
|
||||||
|
|
||||||
|
class UpgradeEffect
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
UpgradeEffect(const nlohmann::json& json);
|
||||||
|
|
||||||
|
nlohmann::json ToJson() const;
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
|
||||||
|
|
||||||
|
void Load(const nlohmann::json& json);
|
||||||
|
|
||||||
|
float CalculateChance(int32_t level) const;
|
||||||
|
|
||||||
|
bool CheckConditions(LWOOBJID origin, const TriggerParameters& params) const;
|
||||||
|
|
||||||
|
void OnTrigger(LWOOBJID origin) const;
|
||||||
|
|
||||||
|
static std::vector<ModifierInstance> Trigger(const std::vector<UpgradeEffect>& modifiers, int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin, const TriggerParameters& params);
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
|
||||||
|
const std::vector<ModifierTemplate>& GetModifiers() const { return modifiers; }
|
||||||
|
|
||||||
|
UpgradeTriggerType GetTriggerType() const { return triggerType; }
|
||||||
|
|
||||||
|
void AddSkill(LWOOBJID origin) const;
|
||||||
|
void RemoveSkill(LWOOBJID origin) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate a HTML string representation of a set of upgrade effects.
|
||||||
|
*
|
||||||
|
* @param modifiers The upgrade effects to generate the HTML string for.
|
||||||
|
* @param level The level of the upgrade effects.
|
||||||
|
* @return The HTML string.
|
||||||
|
*/
|
||||||
|
static std::string GenerateHtmlString(const std::vector<UpgradeEffect>& effects, int32_t level);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct UpgradeScale
|
||||||
|
{
|
||||||
|
int32_t level;
|
||||||
|
float value;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<UpgradeScale> chance;
|
||||||
|
std::vector<UpgradeTriggerCondition> conditions;
|
||||||
|
UpgradeTriggerType triggerType;
|
||||||
|
int32_t equipSkillID = 0;
|
||||||
|
std::vector<ModifierTemplate> modifiers;
|
||||||
|
int32_t effectID = 0;
|
||||||
|
std::string effectType = "";
|
||||||
|
std::vector<ModifierTemplate> debuffs;
|
||||||
|
std::vector<UpgradeScale> debuffDuration;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
90
dGame/UpgradeTemplate.cpp
Normal file
90
dGame/UpgradeTemplate.cpp
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#include "UpgradeTemplate.h"
|
||||||
|
|
||||||
|
#include "NejlikaData.h"
|
||||||
|
|
||||||
|
using namespace nejlika;
|
||||||
|
|
||||||
|
nejlika::UpgradeTemplate::UpgradeTemplate(const nlohmann::json& json)
|
||||||
|
{
|
||||||
|
Load(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json nejlika::UpgradeTemplate::ToJson() const
|
||||||
|
{
|
||||||
|
nlohmann::json json;
|
||||||
|
|
||||||
|
json["name"] = name;
|
||||||
|
json["lot"] = lot;
|
||||||
|
json["max-level"] = maxLevel;
|
||||||
|
|
||||||
|
nlohmann::json passivesJson = nlohmann::json::array();
|
||||||
|
|
||||||
|
for (const auto& passive : passives) {
|
||||||
|
passivesJson.push_back(passive.ToJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
json["passives"] = passivesJson;
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nejlika::UpgradeTemplate::Load(const nlohmann::json& json)
|
||||||
|
{
|
||||||
|
name = json["name"].get<std::string>();
|
||||||
|
if (json["lot"].is_string()) {
|
||||||
|
lot = NejlikaData::GetLookup().GetValue(json["lot"].get<std::string>());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lot = json["lot"].get<int32_t>();
|
||||||
|
}
|
||||||
|
maxLevel = json.contains("max-level") ? json["max-level"].get<int32_t>() : 1;
|
||||||
|
|
||||||
|
passives.clear();
|
||||||
|
|
||||||
|
if (json.contains("modifiers")) {
|
||||||
|
for (const auto& modifier : json["modifiers"]) {
|
||||||
|
ModifierTemplate modTemplate(modifier);
|
||||||
|
modifiers.push_back(modTemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.contains("passives")) {
|
||||||
|
for (const auto& passive : json["passives"]) {
|
||||||
|
UpgradeEffect effect(passive);
|
||||||
|
passives.push_back(effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> nejlika::UpgradeTemplate::Trigger(int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin, const TriggerParameters& params) const {
|
||||||
|
level = std::min(level, maxLevel);
|
||||||
|
|
||||||
|
return UpgradeEffect::Trigger(passives, level, triggerType, origin, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> nejlika::UpgradeTemplate::GenerateModifiers(int32_t level) const {
|
||||||
|
level = std::min(level, maxLevel);
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> result;
|
||||||
|
|
||||||
|
for (const auto& modifier : modifiers) {
|
||||||
|
auto instances = modifier.GenerateModifiers(level);
|
||||||
|
|
||||||
|
result.insert(result.end(), instances.begin(), instances.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nejlika::UpgradeTemplate::AddSkills(LWOOBJID origin) const {
|
||||||
|
for (const auto& passive : passives) {
|
||||||
|
passive.AddSkill(origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void nejlika::UpgradeTemplate::RemoveSkills(LWOOBJID origin) const {
|
||||||
|
for (const auto& passive : passives) {
|
||||||
|
passive.RemoveSkill(origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
48
dGame/UpgradeTemplate.h
Normal file
48
dGame/UpgradeTemplate.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "json.hpp"
|
||||||
|
|
||||||
|
#include "UpgradeEffect.h"
|
||||||
|
#include "TriggerParameters.h"
|
||||||
|
|
||||||
|
namespace nejlika
|
||||||
|
{
|
||||||
|
|
||||||
|
class UpgradeTemplate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
UpgradeTemplate() = default;
|
||||||
|
|
||||||
|
UpgradeTemplate(const nlohmann::json& json);
|
||||||
|
|
||||||
|
nlohmann::json ToJson() const;
|
||||||
|
|
||||||
|
void Load(const nlohmann::json& json);
|
||||||
|
|
||||||
|
const std::string& GetName() const { return name; }
|
||||||
|
int32_t GetLot() const { return lot; }
|
||||||
|
int32_t GetMaxLevel() const { return maxLevel; }
|
||||||
|
const std::vector<UpgradeEffect>& GetPassives() const { return passives; }
|
||||||
|
const std::vector<ModifierTemplate>& GetModifiers() const { return modifiers; }
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> Trigger(int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin, const TriggerParameters& params) const;
|
||||||
|
|
||||||
|
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
|
||||||
|
|
||||||
|
void AddSkills(LWOOBJID origin) const;
|
||||||
|
void RemoveSkills(LWOOBJID origin) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name = "";
|
||||||
|
int32_t lot = 0;
|
||||||
|
int32_t maxLevel = 0;
|
||||||
|
std::vector<UpgradeEffect> passives;
|
||||||
|
std::vector<ModifierTemplate> modifiers;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
19
dGame/UpgradeTriggerCondition.h
Normal file
19
dGame/UpgradeTriggerCondition.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace nejlika
|
||||||
|
{
|
||||||
|
|
||||||
|
enum class UpgradeTriggerCondition
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Unarmed,
|
||||||
|
Melee,
|
||||||
|
TwoHanded,
|
||||||
|
Shield,
|
||||||
|
PrimaryAbility,
|
||||||
|
UseSkill
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
17
dGame/UpgradeTriggerType.h
Normal file
17
dGame/UpgradeTriggerType.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace nejlika
|
||||||
|
{
|
||||||
|
|
||||||
|
enum class UpgradeTriggerType
|
||||||
|
{
|
||||||
|
OnHit,
|
||||||
|
Equip,
|
||||||
|
UnEquip,
|
||||||
|
Active,
|
||||||
|
OnCast
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -516,7 +516,7 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID
|
|||||||
Database::Get()->UpdateLastLoggedInCharacter(playerID);
|
Database::Get()->UpdateLastLoggedInCharacter(playerID);
|
||||||
|
|
||||||
uint32_t zoneID = character->GetZoneID();
|
uint32_t zoneID = character->GetZoneID();
|
||||||
if (zoneID == LWOZONEID_INVALID) zoneID = 1000; //Send char to VE
|
if (zoneID == LWOZONEID_INVALID) zoneID = 1100; //Send char to AG, skip VE
|
||||||
|
|
||||||
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, zoneID, character->GetZoneClone(), false, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
|
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, zoneID, character->GetZoneClone(), false, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
|
||||||
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", character->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
|
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", character->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
|
||||||
|
|||||||
@@ -24,7 +24,13 @@ void HealBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bit_strea
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyable->Heal(this->m_health);
|
auto maxHealth = destroyable->GetMaxHealth();
|
||||||
|
|
||||||
|
// 1 health is 5% of the max health, minimum of 5 health
|
||||||
|
auto health = static_cast<int32_t>(maxHealth * 0.05f) * this->m_health;
|
||||||
|
health = std::max(5u, health);
|
||||||
|
|
||||||
|
destroyable->Heal(health);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -762,8 +767,8 @@ void BaseCombatAIComponent::SetTetherSpeed(float value) {
|
|||||||
m_TetherSpeed = value;
|
m_TetherSpeed = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseCombatAIComponent::Stun(const float time) {
|
void BaseCombatAIComponent::Stun(const float time, const bool ignoreImmunity) {
|
||||||
if (m_StunImmune || m_StunTime > time) {
|
if ((!ignoreImmunity && m_StunImmune) || m_StunTime > time) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -181,8 +187,9 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Stuns the entity for a certain amount of time, will not work if the entity is stun immune
|
* Stuns the entity for a certain amount of time, will not work if the entity is stun immune
|
||||||
* @param time the time to stun the entity, if stunnable
|
* @param time the time to stun the entity, if stunnable
|
||||||
|
* @param ignoreImmunity whether to ignore the stun immunity of the entity
|
||||||
*/
|
*/
|
||||||
void Stun(float time);
|
void Stun(float time, bool ignoreImmunity = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the radius that will cause this entity to get aggro'd, causing a target chase
|
* Gets the radius that will cause this entity to get aggro'd, causing a target chase
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
|
|
||||||
#include "CDComponentsRegistryTable.h"
|
#include "CDComponentsRegistryTable.h"
|
||||||
|
|
||||||
|
Observable<Entity*, LWOOBJID, uint32_t, uint32_t&> DestroyableComponent::OnDamageCalculation;
|
||||||
|
|
||||||
DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
|
DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
|
||||||
m_iArmor = 0;
|
m_iArmor = 0;
|
||||||
m_fMaxArmor = 0.0f;
|
m_fMaxArmor = 0.0f;
|
||||||
@@ -554,7 +556,7 @@ void DestroyableComponent::Repair(const uint32_t armor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32_t skillID, bool echo) {
|
void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32_t skillID, bool echo, bool isDamageOverTime) {
|
||||||
if (GetHealth() <= 0) {
|
if (GetHealth() <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -570,6 +572,10 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isDamageOverTime) {
|
||||||
|
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) {
|
||||||
@@ -637,6 +643,42 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
|
|||||||
cb(attacker);
|
cb(attacker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::stringstream damageUIMessage;
|
||||||
|
|
||||||
|
auto damagedPosition = m_Parent->GetPosition();
|
||||||
|
|
||||||
|
// Add a slight random offset to the damage position
|
||||||
|
damagedPosition.x += (rand() % 10 - 5) / 5.0f;
|
||||||
|
damagedPosition.y += (rand() % 10 - 5) / 5.0f;
|
||||||
|
damagedPosition.z += (rand() % 10 - 5) / 5.0f;
|
||||||
|
|
||||||
|
int colorR = 255;
|
||||||
|
int colorG = 255;
|
||||||
|
int colorB = 255;
|
||||||
|
int colorA = 0;
|
||||||
|
|
||||||
|
if (m_Parent->IsPlayer()) {
|
||||||
|
// Make the damage red
|
||||||
|
colorR = 0;
|
||||||
|
colorG = 255;
|
||||||
|
colorB = 0;
|
||||||
|
colorA = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto damageText = damage != 0 ? std::to_string(damage) : "Miss";
|
||||||
|
|
||||||
|
damageUIMessage << 0.0825 << ";" << 0.12 << ";" << damagedPosition.x << ";" << damagedPosition.y + 4.5f << ";" << damagedPosition.z << ";" << 0.1 << ";";
|
||||||
|
damageUIMessage << 200 << ";" << 200 << ";" << 0.5 << ";" << 1.0 << ";" << damageText << ";" << 4 << ";" << 4 << ";" << colorR << ";" << colorG << ";" << colorB << ";";
|
||||||
|
damageUIMessage << colorA;
|
||||||
|
|
||||||
|
const auto damageUIStr = damageUIMessage.str();
|
||||||
|
|
||||||
|
if (m_Parent->IsPlayer()) {
|
||||||
|
m_Parent->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
|
} else if (attacker != nullptr && attacker->IsPlayer()) {
|
||||||
|
attacker->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
|
}
|
||||||
|
|
||||||
if (health != 0) {
|
if (health != 0) {
|
||||||
auto* combatComponent = m_Parent->GetComponent<BaseCombatAIComponent>();
|
auto* combatComponent = m_Parent->GetComponent<BaseCombatAIComponent>();
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "Component.h"
|
#include "Component.h"
|
||||||
#include "eReplicaComponentType.h"
|
#include "eReplicaComponentType.h"
|
||||||
|
#include "Observable.h"
|
||||||
|
|
||||||
namespace CppScripts {
|
namespace CppScripts {
|
||||||
class Script;
|
class Script;
|
||||||
@@ -383,8 +384,9 @@ public:
|
|||||||
* @param source the attacker that caused this damage
|
* @param source the attacker that caused this damage
|
||||||
* @param skillID the skill that damaged this entity
|
* @param skillID the skill that damaged this entity
|
||||||
* @param echo whether or not to serialize the damage
|
* @param echo whether or not to serialize the damage
|
||||||
|
* @param isDamageOverTime whether or not this damage is over time
|
||||||
*/
|
*/
|
||||||
void Damage(uint32_t damage, LWOOBJID source, uint32_t skillID = 0, bool echo = true);
|
void Damage(uint32_t damage, LWOOBJID source, uint32_t skillID = 0, bool echo = true, bool isDamageOverTime = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Smashes this entity, notifying all clients
|
* Smashes this entity, notifying all clients
|
||||||
@@ -463,6 +465,9 @@ public:
|
|||||||
// handle hardcode mode drops
|
// handle hardcode mode drops
|
||||||
void DoHardcoreModeDrops(const LWOOBJID source);
|
void DoHardcoreModeDrops(const LWOOBJID source);
|
||||||
|
|
||||||
|
// Damaged entity, offender, skillID, damage
|
||||||
|
static Observable<Entity*, LWOOBJID, uint32_t, uint32_t&> OnDamageCalculation;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Whether or not the health should be serialized
|
* Whether or not the health should be serialized
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "InventoryComponent.h"
|
#include "InventoryComponent.h"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "Item.h"
|
#include "Item.h"
|
||||||
@@ -38,6 +39,13 @@
|
|||||||
#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;
|
||||||
|
Observable<InventoryComponent*, Item*> InventoryComponent::OnCountChanged;
|
||||||
|
|
||||||
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 +295,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 +581,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 +861,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 +893,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
|
||||||
@@ -1113,15 +1129,17 @@ void InventoryComponent::AddItemSkills(const LOT lot) {
|
|||||||
|
|
||||||
const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
|
const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
|
||||||
|
|
||||||
if (slot == BehaviorSlot::Invalid) {
|
if (slot != BehaviorSlot::Primary) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto index = m_Skills.find(slot);
|
if (m_PrimarySkill != 0) {
|
||||||
|
GameMessages::SendRemoveSkill(m_Parent, m_PrimarySkill);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_PrimarySkill = FindSkill(lot);
|
||||||
|
|
||||||
const auto skill = FindSkill(lot);
|
GameMessages::SendAddSkill(m_Parent, m_PrimarySkill, slot);
|
||||||
|
|
||||||
SetSkill(slot, skill);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
||||||
@@ -1129,27 +1147,15 @@ void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
|||||||
|
|
||||||
const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
|
const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
|
||||||
|
|
||||||
if (slot == BehaviorSlot::Invalid) {
|
if (slot != BehaviorSlot::Primary) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto index = m_Skills.find(slot);
|
GameMessages::SendRemoveSkill(m_Parent, m_PrimarySkill);
|
||||||
|
|
||||||
if (index == m_Skills.end()) {
|
m_PrimarySkill = 1;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto old = index->second;
|
GameMessages::SendAddSkill(m_Parent, m_PrimarySkill, BehaviorSlot::Primary);
|
||||||
|
|
||||||
GameMessages::SendRemoveSkill(m_Parent, old);
|
|
||||||
|
|
||||||
m_Skills.erase(slot);
|
|
||||||
|
|
||||||
if (slot == BehaviorSlot::Primary) {
|
|
||||||
m_Skills.insert_or_assign(BehaviorSlot::Primary, 1);
|
|
||||||
|
|
||||||
GameMessages::SendAddSkill(m_Parent, 1, BehaviorSlot::Primary);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InventoryComponent::TriggerPassiveAbility(PassiveAbilityTrigger trigger, Entity* target) {
|
void InventoryComponent::TriggerPassiveAbility(PassiveAbilityTrigger trigger, Entity* target) {
|
||||||
@@ -1326,6 +1332,71 @@ void InventoryComponent::SetNPCItems(const std::vector<LOT>& items) {
|
|||||||
Game::entityManager->SerializeEntity(m_Parent);
|
Game::entityManager->SerializeEntity(m_Parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InventoryComponent::AddSkill(uint32_t skillId) {
|
||||||
|
if (std::find(m_Skills.begin(), m_Skills.end(), skillId) != m_Skills.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Skills.push_back(skillId);
|
||||||
|
|
||||||
|
UpdateSkills();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InventoryComponent::RemoveSkill(uint32_t skillId) {
|
||||||
|
const auto index = std::find(m_Skills.begin(), m_Skills.end(), skillId);
|
||||||
|
|
||||||
|
if (index == m_Skills.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Skills.erase(index);
|
||||||
|
|
||||||
|
UpdateSkills();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void InventoryComponent::UpdateSkills() {
|
||||||
|
// There are two skills active at the same time. If the rotation index is greater than the amount of skills / 2, set it to the max number is can be.
|
||||||
|
// This is to prevent the rotation index from going out of bounds.
|
||||||
|
if (m_SkillRotationIndex * 2 >= m_Skills.size()) {
|
||||||
|
m_SkillRotationIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto startIndex = m_SkillRotationIndex * 2;
|
||||||
|
|
||||||
|
const auto activeSkillA = m_Skills.size() > startIndex ? m_Skills[startIndex] : 0;
|
||||||
|
const auto activeSkillB = m_Skills.size() > startIndex + 1 ? m_Skills[startIndex + 1] : 0;
|
||||||
|
|
||||||
|
std::cout << "Skill rotation index: " << m_SkillRotationIndex << " Active skills: " << activeSkillA << " " << activeSkillB << "\n";
|
||||||
|
|
||||||
|
GameMessages::SendRemoveSkill(m_Parent, m_ActiveSkills.first);
|
||||||
|
GameMessages::SendRemoveSkill(m_Parent, m_ActiveSkills.second);
|
||||||
|
|
||||||
|
m_ActiveSkills = { activeSkillA, activeSkillB };
|
||||||
|
|
||||||
|
if (activeSkillA != 0) {
|
||||||
|
GameMessages::SendAddSkill(m_Parent, activeSkillA, BehaviorSlot::Head);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeSkillB != 0) {
|
||||||
|
GameMessages::SendAddSkill(m_Parent, activeSkillB, BehaviorSlot::Offhand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InventoryComponent::RotateSkills() {
|
||||||
|
m_SkillRotationIndex++;
|
||||||
|
|
||||||
|
if (m_SkillRotationIndex * 2 >= m_Skills.size()) {
|
||||||
|
m_SkillRotationIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateSkills();
|
||||||
|
}
|
||||||
|
|
||||||
InventoryComponent::~InventoryComponent() {
|
InventoryComponent::~InventoryComponent() {
|
||||||
for (const auto& inventory : m_Inventories) {
|
for (const auto& inventory : m_Inventories) {
|
||||||
delete inventory.second;
|
delete inventory.second;
|
||||||
@@ -1580,26 +1651,4 @@ void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument& document) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId) {
|
|
||||||
BehaviorSlot behaviorSlot = BehaviorSlot::Invalid;
|
|
||||||
if (slot == 1) behaviorSlot = BehaviorSlot::Primary;
|
|
||||||
else if (slot == 2) behaviorSlot = BehaviorSlot::Offhand;
|
|
||||||
else if (slot == 3) behaviorSlot = BehaviorSlot::Neck;
|
|
||||||
else if (slot == 4) behaviorSlot = BehaviorSlot::Head;
|
|
||||||
else if (slot == 5) behaviorSlot = BehaviorSlot::Consumable;
|
|
||||||
else return false;
|
|
||||||
return SetSkill(behaviorSlot, skillId);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) {
|
|
||||||
if (skillId == 0) return false;
|
|
||||||
const auto index = m_Skills.find(slot);
|
|
||||||
if (index != m_Skills.end()) {
|
|
||||||
const auto old = index->second;
|
|
||||||
GameMessages::SendRemoveSkill(m_Parent, old);
|
|
||||||
}
|
|
||||||
|
|
||||||
GameMessages::SendAddSkill(m_Parent, skillId, slot);
|
|
||||||
m_Skills.insert_or_assign(slot, skillId);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -367,23 +368,42 @@ public:
|
|||||||
*/
|
*/
|
||||||
void UnequipScripts(Item* unequippedItem);
|
void UnequipScripts(Item* unequippedItem);
|
||||||
|
|
||||||
std::map<BehaviorSlot, uint32_t> GetSkills(){ return m_Skills; };
|
uint32_t GetPrimarySkill() const { return m_PrimarySkill; }
|
||||||
|
|
||||||
bool SetSkill(int slot, uint32_t skillId);
|
const std::vector<uint32_t>& GetSkills(){ return m_Skills; };
|
||||||
bool SetSkill(BehaviorSlot slot, uint32_t skillId);
|
|
||||||
|
bool AddSkill(uint32_t skillId);
|
||||||
|
bool RemoveSkill(uint32_t skillId);
|
||||||
|
void RotateSkills();
|
||||||
|
void UpdateSkills();
|
||||||
|
|
||||||
~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;
|
||||||
|
static Observable<InventoryComponent*, Item*> OnCountChanged;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* All the inventory this entity possesses
|
* All the inventory this entity possesses
|
||||||
*/
|
*/
|
||||||
std::map<eInventoryType, Inventory*> m_Inventories;
|
std::map<eInventoryType, Inventory*> m_Inventories;
|
||||||
|
|
||||||
|
std::map<BehaviorSlot, uint32_t> m_ActivatorSkills;
|
||||||
|
|
||||||
|
uint32_t m_PrimarySkill = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The skills that this entity currently has active
|
* The skills that this entity currently has active
|
||||||
*/
|
*/
|
||||||
std::map<BehaviorSlot, uint32_t> m_Skills;
|
std::vector<uint32_t> m_Skills;
|
||||||
|
|
||||||
|
std::pair<uint32_t, uint32_t> m_ActiveSkills;
|
||||||
|
|
||||||
|
uint32_t m_SkillRotationIndex = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The pets this entity has, mapped by object ID and pet info
|
* The pets this entity has, mapped by object ID and pet info
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#include "CDRewardsTable.h"
|
#include "CDRewardsTable.h"
|
||||||
|
|
||||||
|
Observable<LevelProgressionComponent*> LevelProgressionComponent::OnLevelUp;
|
||||||
|
|
||||||
LevelProgressionComponent::LevelProgressionComponent(Entity* parent) : Component(parent) {
|
LevelProgressionComponent::LevelProgressionComponent(Entity* parent) : Component(parent) {
|
||||||
m_Parent = parent;
|
m_Parent = parent;
|
||||||
m_Level = 1;
|
m_Level = 1;
|
||||||
@@ -80,6 +82,8 @@ void LevelProgressionComponent::HandleLevelUp() {
|
|||||||
}
|
}
|
||||||
// Tell the client we have finished sending level rewards.
|
// Tell the client we have finished sending level rewards.
|
||||||
if (rewardingItem) GameMessages::NotifyLevelRewards(m_Parent->GetObjectID(), m_Parent->GetSystemAddress(), m_Level, !rewardingItem);
|
if (rewardingItem) GameMessages::NotifyLevelRewards(m_Parent->GetObjectID(), m_Parent->GetSystemAddress(), m_Level, !rewardingItem);
|
||||||
|
|
||||||
|
OnLevelUp(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LevelProgressionComponent::SetRetroactiveBaseSpeed(){
|
void LevelProgressionComponent::SetRetroactiveBaseSpeed(){
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "Component.h"
|
#include "Component.h"
|
||||||
#include "eCharacterVersion.h"
|
#include "eCharacterVersion.h"
|
||||||
#include "eReplicaComponentType.h"
|
#include "eReplicaComponentType.h"
|
||||||
|
#include "Observable.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that handles level progression and serilization.
|
* Component that handles level progression and serilization.
|
||||||
@@ -81,6 +82,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
void SetRetroactiveBaseSpeed();
|
void SetRetroactiveBaseSpeed();
|
||||||
|
|
||||||
|
static Observable<LevelProgressionComponent*> OnLevelUp;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* whether the level is dirty
|
* whether the level is dirty
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ ProjectileSyncEntry::ProjectileSyncEntry() {
|
|||||||
|
|
||||||
std::unordered_map<uint32_t, uint32_t> SkillComponent::m_skillBehaviorCache = {};
|
std::unordered_map<uint32_t, uint32_t> SkillComponent::m_skillBehaviorCache = {};
|
||||||
|
|
||||||
|
Observable<SkillComponent*, uint32_t, bool&, uint32_t> SkillComponent::OnSkillCast;
|
||||||
|
|
||||||
bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t skillUid, RakNet::BitStream& bitStream, const LWOOBJID target, uint32_t skillID) {
|
bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t skillUid, RakNet::BitStream& bitStream, const LWOOBJID target, uint32_t skillID) {
|
||||||
auto* context = new BehaviorContext(this->m_Parent->GetObjectID());
|
auto* context = new BehaviorContext(this->m_Parent->GetObjectID());
|
||||||
|
|
||||||
@@ -48,6 +50,12 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s
|
|||||||
|
|
||||||
context->ExecuteUpdates();
|
context->ExecuteUpdates();
|
||||||
|
|
||||||
|
bool success = !context->failed;
|
||||||
|
|
||||||
|
OnSkillCast(this, skillID, success, skillUid);
|
||||||
|
|
||||||
|
context->failed = !success;
|
||||||
|
|
||||||
return !context->failed;
|
return !context->failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include "eReplicaComponentType.h"
|
#include "eReplicaComponentType.h"
|
||||||
|
#include "Observable.h"
|
||||||
|
|
||||||
struct ProjectileSyncEntry {
|
struct ProjectileSyncEntry {
|
||||||
LWOOBJID id = LWOOBJID_EMPTY;
|
LWOOBJID id = LWOOBJID_EMPTY;
|
||||||
@@ -184,6 +185,9 @@ public:
|
|||||||
*/
|
*/
|
||||||
uint32_t GetUniqueSkillId();
|
uint32_t GetUniqueSkillId();
|
||||||
|
|
||||||
|
// SkillComponent, SkillID, Success, skillUID
|
||||||
|
static Observable<SkillComponent*, uint32_t, bool&, uint32_t> OnSkillCast;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* All of the active skills mapped by their unique ID.
|
* All of the active skills mapped by their unique ID.
|
||||||
|
|||||||
@@ -973,11 +973,11 @@ void GameMessages::SendResurrect(Entity* entity) {
|
|||||||
if (levelComponent) {
|
if (levelComponent) {
|
||||||
int32_t healthToRestore = levelComponent->GetLevel() >= 45 ? 8 : 4;
|
int32_t healthToRestore = levelComponent->GetLevel() >= 45 ? 8 : 4;
|
||||||
if (healthToRestore > destroyableComponent->GetMaxHealth()) healthToRestore = destroyableComponent->GetMaxHealth();
|
if (healthToRestore > destroyableComponent->GetMaxHealth()) healthToRestore = destroyableComponent->GetMaxHealth();
|
||||||
destroyableComponent->SetHealth(healthToRestore);
|
destroyableComponent->SetHealth(destroyableComponent->GetMaxHealth());
|
||||||
|
|
||||||
int32_t imaginationToRestore = levelComponent->GetLevel() >= 45 ? 20 : 6;
|
int32_t imaginationToRestore = levelComponent->GetLevel() >= 45 ? 20 : 6;
|
||||||
if (imaginationToRestore > destroyableComponent->GetMaxImagination()) imaginationToRestore = destroyableComponent->GetMaxImagination();
|
if (imaginationToRestore > destroyableComponent->GetMaxImagination()) imaginationToRestore = destroyableComponent->GetMaxImagination();
|
||||||
destroyableComponent->SetImagination(imaginationToRestore);
|
destroyableComponent->SetImagination(destroyableComponent->GetMaxImagination());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ namespace {
|
|||||||
{ "userModelMod", "um" },
|
{ "userModelMod", "um" },
|
||||||
{ "userModelOpt", "uo" },
|
{ "userModelOpt", "uo" },
|
||||||
{ "reforgedLOT", "rl" },
|
{ "reforgedLOT", "rl" },
|
||||||
|
{ "modifiers", "mod" }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,6 +201,8 @@ void Item::SetCount(const uint32_t value, const bool silent, const bool disassem
|
|||||||
|
|
||||||
count = value;
|
count = value;
|
||||||
|
|
||||||
|
InventoryComponent::OnCountChanged(inventory->GetComponent(), this);
|
||||||
|
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
RemoveFromInventory();
|
RemoveFromInventory();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1347,23 +1347,17 @@ namespace DEVGMCommands {
|
|||||||
|
|
||||||
void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||||
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||||
if (splitArgs.size() < 2) return;
|
if (splitArgs.size() < 1) return;
|
||||||
|
|
||||||
auto* const inventoryComponent = entity->GetComponent<InventoryComponent>();
|
auto* const inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||||
if (inventoryComponent) {
|
if (inventoryComponent) {
|
||||||
const auto slot = GeneralUtils::TryParse<BehaviorSlot>(splitArgs[0]);
|
const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[0]);
|
||||||
if (!slot) {
|
if (!skillId) {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot.");
|
ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill.");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[1]);
|
if (inventoryComponent->AddSkill(skillId.value())) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully");
|
||||||
if (!skillId) {
|
else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed");
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill.");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if (inventoryComponent->SetSkill(slot.value(), skillId.value())) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully");
|
|
||||||
else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24765
dGame/json.hpp
Normal file
24765
dGame/json.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@
|
|||||||
#include "MessageIdentifiers.h"
|
#include "MessageIdentifiers.h"
|
||||||
#include "BitStream.h"
|
#include "BitStream.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
enum class eConnectionType : uint16_t;
|
enum class eConnectionType : uint16_t;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
4
thirdparty/CMakeLists.txt
vendored
4
thirdparty/CMakeLists.txt
vendored
@@ -23,6 +23,10 @@ if(NOT WIN32)
|
|||||||
target_include_directories(bcrypt PRIVATE "libbcrypt/include/bcrypt")
|
target_include_directories(bcrypt PRIVATE "libbcrypt/include/bcrypt")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
|
||||||
|
target_compile_definitions(bcrypt PRIVATE "_POSIX_C_SOURCE=200809L")
|
||||||
|
endif()
|
||||||
|
|
||||||
target_include_directories(bcrypt INTERFACE "libbcrypt/include")
|
target_include_directories(bcrypt INTERFACE "libbcrypt/include")
|
||||||
target_include_directories(bcrypt PRIVATE "libbcrypt/src")
|
target_include_directories(bcrypt PRIVATE "libbcrypt/src")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user