mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-22 05:27:19 +00:00
Refactor damage calculations and add additional modifiers
This commit is contained in:
parent
756dc4e44f
commit
5e3312850c
@ -19,6 +19,10 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, Modifi
|
||||
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;
|
||||
}
|
||||
@ -33,6 +37,10 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::v
|
||||
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;
|
||||
}
|
||||
@ -92,18 +100,18 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::v
|
||||
|
||||
float multiplicative = CalculateModifier(type, additionalModifiers, ModifierOperator::Multiplicative, false);
|
||||
|
||||
static const std::unordered_set<ModifierType> damageTypes = {
|
||||
ModifierType::Slashing,
|
||||
ModifierType::Piercing,
|
||||
ModifierType::Bludgeoning,
|
||||
static const std::unordered_set<ModifierType> elementalDamage = {
|
||||
ModifierType::Fire,
|
||||
ModifierType::Cold,
|
||||
ModifierType::Lightning,
|
||||
ModifierType::Corruption,
|
||||
ModifierType::Psychic
|
||||
ModifierType::Lightning
|
||||
};
|
||||
|
||||
if (damageTypes.contains(type)) {
|
||||
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);
|
||||
}
|
||||
@ -117,6 +125,8 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::v
|
||||
|
||||
float nejlika::AdditionalEntityData::CalculateResistance(ModifierType type) const
|
||||
{
|
||||
type = nejlika::GetResistanceType(type);
|
||||
|
||||
return CalculateModifier(type, ModifierOperator::Multiplicative, true);
|
||||
}
|
||||
|
||||
@ -125,7 +135,7 @@ float nejlika::AdditionalEntityData::CalculateMultiplier(ModifierType type) cons
|
||||
return 1 + CalculateModifier(type, ModifierOperator::Multiplicative, false);
|
||||
}
|
||||
|
||||
std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems(UpgradeTriggerType triggerType) {
|
||||
std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems(UpgradeTriggerType triggerType, const TriggerParameters& params) {
|
||||
auto* entity = Game::entityManager->GetEntity(id);
|
||||
|
||||
if (entity == nullptr) {
|
||||
@ -155,7 +165,7 @@ std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems
|
||||
|
||||
const auto& upgradeData = *upgradeDataOpt.value();
|
||||
|
||||
const auto modifiers = upgradeData.Trigger(item->GetCount(), triggerType, id);
|
||||
const auto modifiers = upgradeData.Trigger(item->GetCount(), triggerType, id, params);
|
||||
|
||||
result.insert(result.end(), modifiers.begin(), modifiers.end());
|
||||
}
|
||||
@ -163,6 +173,10 @@ std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems(UpgradeTriggerType triggerType) {
|
||||
return TriggerUpgradeItems(triggerType, {});
|
||||
}
|
||||
|
||||
void nejlika::AdditionalEntityData::InitializeSkills() {
|
||||
auto* entity = Game::entityManager->GetEntity(id);
|
||||
|
||||
@ -282,6 +296,62 @@ void nejlika::AdditionalEntityData::RollStandardModifiers(int32_t level) {
|
||||
}
|
||||
}
|
||||
|
||||
float nejlika::AdditionalEntityData::CalculateMultiplier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers) const {
|
||||
return 1 + 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);
|
||||
|
||||
@ -334,6 +404,31 @@ void nejlika::AdditionalEntityData::ApplyToEntity() {
|
||||
|
||||
activeModifiers.insert(activeModifiers.end(), itemModifiers.begin(), itemModifiers.end());
|
||||
}
|
||||
|
||||
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>(CalculateModifier(ModifierType::Health, level)));
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "ModifierInstance.h"
|
||||
#include "EntityTemplate.h"
|
||||
#include "UpgradeTriggerType.h"
|
||||
#include "TriggerParameters.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
@ -41,6 +42,24 @@ public:
|
||||
*/
|
||||
float CalculateMultiplier(ModifierType type) const;
|
||||
|
||||
/**
|
||||
* @brief Calculate the multiplier for a given modifier type. With a base value of 1.0.
|
||||
*
|
||||
* @param type The modifier type.
|
||||
* @param additionalModifiers Additional modifiers to apply.
|
||||
* @return The multiplier.
|
||||
*/
|
||||
float CalculateMultiplier(ModifierType type, 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);
|
||||
@ -57,6 +76,8 @@ public:
|
||||
|
||||
void RemoveUpgradeItem(LWOOBJID id) { upgradeItems.erase(id); }
|
||||
|
||||
std::vector<ModifierInstance> TriggerUpgradeItems(UpgradeTriggerType triggerType, const TriggerParameters& params);
|
||||
|
||||
std::vector<ModifierInstance> TriggerUpgradeItems(UpgradeTriggerType triggerType);
|
||||
|
||||
void InitializeSkills();
|
||||
|
@ -66,7 +66,8 @@ std::vector<nejlika::ModifierInstance> nejlika::EntityTemplate::GenerateModifier
|
||||
scaler.isResistance,
|
||||
ModifierCategory::Player,
|
||||
0,
|
||||
""
|
||||
"",
|
||||
ModifierType::Invalid
|
||||
);
|
||||
|
||||
modifiers.push_back(modifier);
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
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")) {
|
||||
@ -32,6 +33,7 @@ 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;
|
||||
|
@ -16,8 +16,8 @@ class ModifierInstance
|
||||
{
|
||||
public:
|
||||
ModifierInstance(
|
||||
ModifierType type, float value, ModifierOperator op, bool isResistance, ModifierCategory category, uint32_t effectID, const std::string& effectType
|
||||
) : type(type), value(value), op(op), isResistance(isResistance), category(category), effectID(effectID), effectType(effectType) {}
|
||||
ModifierType type, float value, ModifierOperator op, bool isResistance, ModifierCategory category, uint32_t effectID, const std::string& effectType, ModifierType convertTo
|
||||
) : type(type), value(value), op(op), isResistance(isResistance), category(category), effectID(effectID), effectType(effectType), convertTo(convertTo) {}
|
||||
|
||||
/**
|
||||
* @brief Construct a new Modifier Instance object from a json configuration.
|
||||
@ -45,6 +45,8 @@ public:
|
||||
|
||||
ModifierType GetType() const { return type; }
|
||||
|
||||
ModifierType GetConvertTo() const { return convertTo; }
|
||||
|
||||
float GetValue() const { return value; }
|
||||
|
||||
ModifierOperator GetOperator() const { return op; }
|
||||
@ -59,6 +61,8 @@ public:
|
||||
|
||||
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; }
|
||||
@ -73,6 +77,7 @@ public:
|
||||
|
||||
private:
|
||||
ModifierType type;
|
||||
ModifierType convertTo;
|
||||
float value;
|
||||
ModifierOperator op;
|
||||
bool isResistance;
|
||||
|
@ -59,17 +59,34 @@ nejlika::ModifierTemplate::ModifierTemplate(const nlohmann::json& config) {
|
||||
types = {};
|
||||
}
|
||||
|
||||
if (!config.contains("scaling"))
|
||||
if (config.contains("convert-to"))
|
||||
{
|
||||
throw std::runtime_error("Modifier template is missing scaling.");
|
||||
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"))
|
||||
{
|
||||
@ -144,6 +161,8 @@ nlohmann::json nejlika::ModifierTemplate::ToJson() const {
|
||||
}
|
||||
}
|
||||
|
||||
if (!scales.empty())
|
||||
{
|
||||
nlohmann::json scaling;
|
||||
|
||||
for (const auto& scale : scales)
|
||||
@ -152,6 +171,21 @@ nlohmann::json nejlika::ModifierTemplate::ToJson() const {
|
||||
}
|
||||
|
||||
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;
|
||||
@ -209,10 +243,120 @@ std::vector<ModifierInstance> nejlika::ModifierTemplate::GenerateModifiers(int32
|
||||
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;
|
||||
|
||||
for (const auto& modifier : modifiers) {
|
||||
for (const auto& type : modifier.types) {
|
||||
if (type == ModifierType::Invalid) {
|
||||
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) {
|
||||
|
||||
ss << "\n<font color=\"#D0AB62\">";
|
||||
|
||||
ss << ((resistance.first) ? "Resistances" : "Modifiers");
|
||||
|
||||
ss << ":</font>\n";
|
||||
|
||||
for (const auto& math : resistance.second) {
|
||||
for (const auto& modifier : math.second) {
|
||||
ss << "<font color=\"" << GetModifierTypeColor(modifier.first) << "\">";
|
||||
|
||||
ss << magic_enum::enum_name<ModifierType>(modifier.first) << ": ";
|
||||
|
||||
ss << ((modifier.second.first > 0) ? "+" : "-");
|
||||
|
||||
ss << std::fixed << std::setprecision(0) << std::abs(modifier.second.first);
|
||||
|
||||
if (modifier.second.first != modifier.second.second)
|
||||
{
|
||||
ss << "/";
|
||||
|
||||
ss << ((modifier.second.second > 0) ? "+" : "-");
|
||||
|
||||
ss << std::fixed << std::setprecision(0) << std::abs(modifier.second.second);
|
||||
}
|
||||
|
||||
if (math.first == ModifierOperator::Multiplicative) {
|
||||
ss << "%";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
@ -227,5 +371,5 @@ std::optional<ModifierInstance> nejlika::ModifierTemplate::GenerateModifier(Modi
|
||||
|
||||
float value = GeneralUtils::GenerateRandomNumber<float>(scale.GetMin(), scale.GetMax());
|
||||
|
||||
return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType);
|
||||
return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType, convertTo);
|
||||
}
|
||||
|
@ -45,6 +45,8 @@ public:
|
||||
|
||||
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; }
|
||||
@ -59,6 +61,8 @@ public:
|
||||
|
||||
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; }
|
||||
@ -71,12 +75,24 @@ public:
|
||||
|
||||
void SetEffectType(const std::string& effectType) { this->effectType = effectType; }
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "ModifierType.h"
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace nejlika;
|
||||
|
||||
@ -12,14 +13,106 @@ const std::unordered_map<ModifierType, std::string> colorMap = {
|
||||
{ModifierType::Imagination, "#0077FF"},
|
||||
{ModifierType::Offensive, "#71583B"},
|
||||
{ModifierType::Defensive, "#71583B"},
|
||||
{ModifierType::Slashing, "#666666"},
|
||||
{ModifierType::Piercing, "#4f4f4f"},
|
||||
{ModifierType::Bludgeoning, "#e84646"},
|
||||
{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::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"},
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
}
|
||||
@ -36,3 +129,53 @@ const std::string& nejlika::GetModifierTypeColor(ModifierType type)
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -12,31 +12,72 @@ enum class ModifierType : uint8_t
|
||||
Armor,
|
||||
Imagination,
|
||||
|
||||
Physique,
|
||||
Cunning,
|
||||
Spirit,
|
||||
|
||||
Offensive,
|
||||
Defensive,
|
||||
|
||||
Slashing,
|
||||
Piercing,
|
||||
Bludgeoning,
|
||||
|
||||
// Normal Types
|
||||
Physical,
|
||||
Fire,
|
||||
Cold,
|
||||
Lightning,
|
||||
Corruption,
|
||||
Acid,
|
||||
Vitality,
|
||||
Pierce,
|
||||
Corruption, // Aether
|
||||
Psychic, // Chaos
|
||||
|
||||
Elemental,
|
||||
// Duration Types
|
||||
InternalDisassembly, // Internal Trauma
|
||||
InternalDisassemblyDuration,
|
||||
Burn,
|
||||
BurnDuration,
|
||||
Frostburn,
|
||||
FrostburnDuration,
|
||||
Poison,
|
||||
PoisonDuration,
|
||||
VitalityDecay,
|
||||
VitalityDecayDuration,
|
||||
Electrocute,
|
||||
ElectrocuteDuration,
|
||||
Seperation, // Bleeding
|
||||
SeperationDuration,
|
||||
|
||||
Psychic,
|
||||
// Special
|
||||
Elemental, // Even split between Fire, Cold, Lightning
|
||||
|
||||
Damage,
|
||||
|
||||
Speed,
|
||||
|
||||
AttackSpeed,
|
||||
SkillModifier,
|
||||
|
||||
Slow,
|
||||
ArmorPiercing,
|
||||
|
||||
CriticalDamage,
|
||||
ChanceToBlock,
|
||||
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 ModifierType GetOverTimeType(ModifierType type);
|
||||
|
||||
const ModifierType GetDurationType(ModifierType type);
|
||||
|
||||
}
|
||||
|
@ -107,6 +107,13 @@ void nejlika::NejlikaData::UnsetAdditionalEntityData(LWOOBJID 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
|
||||
@ -179,12 +186,5 @@ void nejlika::NejlikaData::LoadNejlikaData()
|
||||
upgradeTemplates[upgradeTemplate.GetLot()] = upgradeTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
const auto& lookupFile = Game::config->GetValue("lookup");
|
||||
|
||||
if (!lookupFile.empty())
|
||||
{
|
||||
lookup = Lookup(lookupFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,8 +27,7 @@
|
||||
using namespace nejlika;
|
||||
using namespace nejlika::NejlikaData;
|
||||
|
||||
void nejlika::NejlikaHooks::InstallHooks()
|
||||
{
|
||||
void nejlika::NejlikaHooks::InstallHooks() {
|
||||
Command itemDescriptionCommand{
|
||||
.help = "Special UI command, does nothing when used in chat.",
|
||||
.info = "Special UI command, does nothing when used in chat.",
|
||||
@ -95,6 +94,26 @@ void nejlika::NejlikaHooks::InstallHooks()
|
||||
|
||||
entityData.AddUpgradeItem(item->GetId());
|
||||
entityData.AddSkills(item->GetId());
|
||||
|
||||
entityData.ApplyToEntity();
|
||||
};
|
||||
|
||||
InventoryComponent::OnCountChanged += [](InventoryComponent* component, Item* item) {
|
||||
auto entityDataOpt = GetAdditionalEntityData(component->GetParent()->GetObjectID());
|
||||
|
||||
if (!entityDataOpt.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& entityData = *entityDataOpt.value();
|
||||
|
||||
auto upgradeTemplateOpt = GetUpgradeTemplate(item->GetLot());
|
||||
|
||||
if (!upgradeTemplateOpt.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
entityData.ApplyToEntity();
|
||||
};
|
||||
|
||||
EntityManager::OnEntityCreated += [](Entity* entity) {
|
||||
@ -143,13 +162,12 @@ void nejlika::NejlikaHooks::InstallHooks()
|
||||
};
|
||||
|
||||
Entity::OnReadyForUpdates += [](Entity* entity) {
|
||||
if (!entity->IsPlayer())
|
||||
{
|
||||
if (!entity->IsPlayer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GameMessages::SendAddSkill(entity, NejlikaData::GetLookup().GetValue("intro:skills:proxy:main"), BehaviorSlot::Head);
|
||||
GameMessages::SendAddSkill(entity, NejlikaData::GetLookup().GetValue("intro:skills:proxy:secondary"), BehaviorSlot::Offhand);
|
||||
//GameMessages::SendAddSkill(entity, NejlikaData::GetLookup().GetValue("intro:skills:proxy:main"), BehaviorSlot::Head);
|
||||
//GameMessages::SendAddSkill(entity, NejlikaData::GetLookup().GetValue("intro:skills:proxy:secondary"), BehaviorSlot::Offhand);
|
||||
GameMessages::SendAddSkill(entity, NejlikaData::GetLookup().GetValue("intro:skills:proxy:tertiary"), BehaviorSlot::Neck);
|
||||
|
||||
auto* missionComponent = entity->GetComponent<MissionComponent>();
|
||||
@ -158,7 +176,24 @@ void nejlika::NejlikaHooks::InstallHooks()
|
||||
if (missionComponent->GetMissionState(1732) != eMissionState::COMPLETE) {
|
||||
missionComponent->CompleteMission(1732, true, false);
|
||||
}
|
||||
|
||||
if (missionComponent->GetMissionState(173) != eMissionState::COMPLETE) {
|
||||
missionComponent->CompleteMission(173, true, false);
|
||||
|
||||
auto* destroyable = entity->GetComponent<DestroyableComponent>();
|
||||
|
||||
destroyable->SetMaxImagination(6);
|
||||
destroyable->SetImagination(6);
|
||||
}
|
||||
}
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (!inventoryComponent) {
|
||||
return;
|
||||
}
|
||||
|
||||
inventoryComponent->UpdateSkills();
|
||||
};
|
||||
|
||||
EntityManager::OnEntityDestroyed += [](Entity* entity) {
|
||||
@ -203,7 +238,7 @@ void nejlika::NejlikaHooks::InstallHooks()
|
||||
return;
|
||||
}
|
||||
|
||||
inventoryComponent->AddItem(NejlikaData::GetLookup().GetValue("intro:upgrades:level-token"), 1, eLootSourceType::MODERATION);
|
||||
inventoryComponent->AddItem(NejlikaData::GetLookup().GetValue("intro:upgrades:level-token"), 3, eLootSourceType::MODERATION);
|
||||
};
|
||||
|
||||
InventoryComponent::OnItemEquipped += [](InventoryComponent* component, Item* item) {
|
||||
@ -283,127 +318,11 @@ void nejlika::NejlikaHooks::InstallHooks()
|
||||
const auto tertiaryTrigger = NejlikaData::GetLookup().GetValue("intro:skills:proxy:tertiary");
|
||||
|
||||
if (skillID == primaryTrigger || skillID == secondaryTrigger || skillID == tertiaryTrigger) {
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
const auto primarySkills = skills[BehaviorSlot::Primary];
|
||||
|
||||
// If the skillID is in the primary skills, ignore this
|
||||
if (primarySkills.contains(skillID)) {
|
||||
if (entity->HasVar(u"skill-cast") && entity->GetVar<size_t>(u"skill-cast") == 0) {
|
||||
GameMessages::SendAddSkill(entity, primaryTrigger, BehaviorSlot::Head);
|
||||
GameMessages::SendAddSkill(entity, secondaryTrigger, BehaviorSlot::Offhand);
|
||||
GameMessages::SendAddSkill(entity, tertiaryTrigger, BehaviorSlot::Neck);
|
||||
}
|
||||
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity->HasVar(u"skill-cast")) {
|
||||
entity->SetVar(u"skill-cast", static_cast<size_t>(0));
|
||||
}
|
||||
|
||||
if (entity->HasVar(u"skill-cast-slot")) {
|
||||
BehaviorSlot slot = static_cast<BehaviorSlot>(entity->GetVar<int32_t>(u"skill-cast-slot"));
|
||||
|
||||
auto primarySkills = inventoryComponent->GetSkills();
|
||||
|
||||
for (const auto& skill : primarySkills[slot]) {
|
||||
GameMessages::SendRemoveSkill(entity, skill);
|
||||
}
|
||||
}
|
||||
|
||||
LOG("Restoring triggers via skill");
|
||||
|
||||
// Restore the triggers
|
||||
GameMessages::SendAddSkill(entity, primaryTrigger, BehaviorSlot::Head);
|
||||
GameMessages::SendAddSkill(entity, secondaryTrigger, BehaviorSlot::Offhand);
|
||||
GameMessages::SendAddSkill(entity, tertiaryTrigger, BehaviorSlot::Neck);
|
||||
*/
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static const std::vector<BehaviorSlot> slotOrder = {
|
||||
BehaviorSlot::Head,
|
||||
BehaviorSlot::Offhand,
|
||||
BehaviorSlot::Neck
|
||||
};
|
||||
|
||||
std::set<uint32_t> selectedSkills;
|
||||
BehaviorSlot slot = BehaviorSlot::Invalid;
|
||||
|
||||
if (skillID == primaryTrigger) {
|
||||
selectedSkills = skills[BehaviorSlot::Head];
|
||||
slot = BehaviorSlot::Head;
|
||||
} else if (skillID == secondaryTrigger) {
|
||||
selectedSkills = skills[BehaviorSlot::Offhand];
|
||||
slot = BehaviorSlot::Offhand;
|
||||
} else if (skillID == tertiaryTrigger) {
|
||||
selectedSkills = skills[BehaviorSlot::Neck];
|
||||
slot = BehaviorSlot::Neck;
|
||||
}
|
||||
|
||||
if (selectedSkills.empty()) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
GameMessages::SendRemoveSkill(entity, primaryTrigger);
|
||||
GameMessages::SendRemoveSkill(entity, secondaryTrigger);
|
||||
GameMessages::SendRemoveSkill(entity, tertiaryTrigger);
|
||||
}
|
||||
|
||||
int32_t i = 0;
|
||||
for (const auto& skill : selectedSkills) {
|
||||
if (i >= 3) {
|
||||
break;
|
||||
}
|
||||
|
||||
GameMessages::SendAddSkill(entity, skill, slotOrder[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
const auto randomNumber = GeneralUtils::GenerateRandomNumber<size_t>();
|
||||
|
||||
entity->SetVar(u"skill-cast", randomNumber);
|
||||
entity->SetVar(u"skill-cast-slot", static_cast<int32_t>(slot));
|
||||
|
||||
entity->AddCallbackTimer(1.0f, [entity, randomNumber, primaryTrigger, secondaryTrigger, tertiaryTrigger]() {
|
||||
if (!entity->HasVar(u"skill-cast")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto currentRandom = entity->GetVar<size_t>(u"skill-cast");
|
||||
|
||||
if (currentRandom != randomNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
entity->SetVar(u"skill-cast", static_cast<size_t>(0));
|
||||
|
||||
LOG("Restoring triggers via timeout");
|
||||
|
||||
// Remove the skills
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (!inventoryComponent) {
|
||||
return;
|
||||
}
|
||||
|
||||
BehaviorSlot slot = static_cast<BehaviorSlot>(entity->GetVar<int32_t>(u"skill-cast-slot"));
|
||||
|
||||
auto primarySkills = inventoryComponent->GetSkills();
|
||||
|
||||
for (const auto& skill : primarySkills[slot]) {
|
||||
GameMessages::SendRemoveSkill(entity, skill);
|
||||
}
|
||||
|
||||
// Restore the triggers
|
||||
GameMessages::SendAddSkill(entity, primaryTrigger, BehaviorSlot::Head);
|
||||
GameMessages::SendAddSkill(entity, secondaryTrigger, BehaviorSlot::Offhand);
|
||||
GameMessages::SendAddSkill(entity, tertiaryTrigger, BehaviorSlot::Neck);
|
||||
});
|
||||
inventoryComponent->RotateSkills();
|
||||
};
|
||||
|
||||
DestroyableComponent::OnDamageCalculation += [](Entity* damaged, LWOOBJID offender, uint32_t skillID, uint32_t& damage) {
|
||||
@ -453,51 +372,36 @@ void nejlika::NejlikaHooks::InstallHooks()
|
||||
|
||||
LOT itemLot = 0;
|
||||
LWOOBJID itemId = 0;
|
||||
BehaviorSlot itemSlot = BehaviorSlot::Invalid;
|
||||
|
||||
auto* inventoryComponent = offfendEntity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent) {
|
||||
const auto& skills = inventoryComponent->GetSkills();
|
||||
|
||||
std::cout << "Found " << skills.size() << " skills." << std::endl;
|
||||
|
||||
// omg...
|
||||
for (const auto& [slot, skillSet] : skills) {
|
||||
if (skillSet.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& skill = *skillSet.begin();
|
||||
|
||||
std::cout << "Found skill: " << skill << std::endl;
|
||||
|
||||
if (skill != skillID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& equipped = inventoryComponent->GetEquippedItems();
|
||||
|
||||
// omg...
|
||||
for (const auto& [equippedSlot, itemDetails] : equipped) {
|
||||
std::cout << "Found equipped item: " << itemDetails.lot << std::endl;
|
||||
|
||||
const auto info = Inventory::FindItemComponent(itemDetails.lot);
|
||||
|
||||
const auto skill = InventoryComponent::FindSkill(itemDetails.lot);
|
||||
|
||||
if (skill != skillID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto itemBehaviorSlot = InventoryComponent::FindBehaviorSlot(static_cast<eItemType>(info.itemType));
|
||||
|
||||
std::cout << "Comparing slots: " << static_cast<int32_t>(itemBehaviorSlot) << " - " << static_cast<int32_t>(slot) << std::endl;
|
||||
|
||||
if (itemBehaviorSlot == slot) {
|
||||
itemLot = itemDetails.lot;
|
||||
itemId = itemDetails.id;
|
||||
itemSlot = itemBehaviorSlot;
|
||||
|
||||
std::cout << "Found item: " << itemLot << std::endl;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto& skillTemplates = GetModifierNameTemplates(ModifierNameType::Skill);
|
||||
|
||||
@ -515,8 +419,12 @@ void nejlika::NejlikaHooks::InstallHooks()
|
||||
modifiers.insert(modifiers.end(), skillModifiers.begin(), skillModifiers.end());
|
||||
}
|
||||
|
||||
TriggerParameters params;
|
||||
params.SkillID = skillID;
|
||||
params.SelectedBehaviorSlot = itemSlot;
|
||||
|
||||
// Upgrades
|
||||
const auto upgradeModifiers = offenderEntity.TriggerUpgradeItems(UpgradeTriggerType::OnHit);
|
||||
const auto upgradeModifiers = offenderEntity.TriggerUpgradeItems(UpgradeTriggerType::OnHit, params);
|
||||
|
||||
modifiers.insert(modifiers.end(), upgradeModifiers.begin(), upgradeModifiers.end());
|
||||
|
||||
@ -535,20 +443,14 @@ void nejlika::NejlikaHooks::InstallHooks()
|
||||
damageTypes.insert(modifier.GetType());
|
||||
}
|
||||
}
|
||||
// Remove the following: Offensive, Defensive, Health, Armor, Imagination
|
||||
damageTypes.erase(ModifierType::Offensive);
|
||||
damageTypes.erase(ModifierType::Defensive);
|
||||
damageTypes.erase(ModifierType::Health);
|
||||
damageTypes.erase(ModifierType::Armor);
|
||||
damageTypes.erase(ModifierType::Imagination);
|
||||
damageTypes.erase(ModifierType::Damage);
|
||||
damageTypes.erase(ModifierType::Speed);
|
||||
damageTypes.erase(ModifierType::AttackSpeed);
|
||||
damageTypes.erase(ModifierType::Invalid);
|
||||
|
||||
uint32_t totalDamage = 0;
|
||||
|
||||
std::unordered_map<ModifierType, std::pair<float, float>> durationTypes;
|
||||
std::unordered_map<ModifierType, float> tmpDamageValues;
|
||||
|
||||
for (const auto& type : damageTypes) {
|
||||
if (nejlika::IsOverTimeType(type)) {
|
||||
float damageValue = offenderEntity.CalculateModifier(type, modifiers, level);
|
||||
|
||||
// Calculate resistance, can't go below 20% of the original damage
|
||||
@ -556,9 +458,67 @@ void nejlika::NejlikaHooks::InstallHooks()
|
||||
|
||||
float reductedDamage = damageValue * resistance;
|
||||
|
||||
const auto durationType = nejlika::GetDurationType(type);
|
||||
|
||||
const auto duration = offenderEntity.CalculateModifier(durationType, modifiers, level);
|
||||
|
||||
const auto durationResistance = std::max(1 - (damagedEntity.CalculateResistance(durationType) / 100), 0.2f);
|
||||
|
||||
float reductedDuration = duration * durationResistance;
|
||||
|
||||
durationTypes[type] = std::make_pair(reductedDamage, reductedDuration);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!nejlika::IsNormalDamageType(type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float damageValue = offenderEntity.CalculateModifier(type, modifiers, level);
|
||||
|
||||
tmpDamageValues[type] = damageValue;
|
||||
}
|
||||
|
||||
// Type A -> Type B -> (0-100) how much of type A is converted to type B
|
||||
const auto converationMap = offenderEntity.CalculateDamageConversion(modifiers);
|
||||
|
||||
std::unordered_map<ModifierType, float> finalDamageValues;
|
||||
|
||||
for (const auto& [typeA, typeBMap] : converationMap) {
|
||||
const auto& typeAValue = tmpDamageValues.find(typeA);
|
||||
|
||||
if (typeAValue == tmpDamageValues.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& typeAValueFloat = typeAValue->second;
|
||||
|
||||
for (const auto& [typeB, conversion] : typeBMap) {
|
||||
const auto& typeBValue = tmpDamageValues.find(typeB);
|
||||
|
||||
if (typeBValue == tmpDamageValues.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& typeBValueFloat = typeBValue->second;
|
||||
|
||||
const auto convertedValue = typeAValueFloat * conversion;
|
||||
|
||||
finalDamageValues[typeA] += typeAValueFloat - convertedValue;
|
||||
finalDamageValues[typeB] += typeBValueFloat + convertedValue;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [type, damage] : finalDamageValues) {
|
||||
// Calculate resistance, can't go below 20% of the original damage
|
||||
const auto resistance = std::max(1 - (damagedEntity.CalculateResistance(type) / 100), 0.2f);
|
||||
|
||||
float reductedDamage = damage * resistance;
|
||||
|
||||
totalDamage += static_cast<uint32_t>(reductedDamage);
|
||||
|
||||
std::cout << "Damage type: " << magic_enum::enum_name(type) << " - " << damageValue << std::endl;
|
||||
std::cout << "Damage type: " << magic_enum::enum_name(type) << " - " << damage << std::endl;
|
||||
std::cout << "Resistance: " << resistance << " - " << reductedDamage << std::endl;
|
||||
std::cout << "Heath left: " << damaged->GetComponent<DestroyableComponent>()->GetHealth() << std::endl;
|
||||
}
|
||||
@ -572,22 +532,70 @@ void nejlika::NejlikaHooks::InstallHooks()
|
||||
if (offenderModifiers == 0) offenderModifiers = 1;
|
||||
if (defensiveModifiers == 0) defensiveModifiers = 1;
|
||||
|
||||
auto ratio = offenderModifiers / defensiveModifiers;
|
||||
// https://www.grimdawn.com/guide/gameplay/combat/#q20
|
||||
auto pth = ((
|
||||
((offenderModifiers / ((defensiveModifiers / 3.5) + offenderModifiers)) * 300) * 0.3
|
||||
) + (
|
||||
((((offenderModifiers * 3.25) + 10000) - (defensiveModifiers * 3.25)) / 100) * 0.7)
|
||||
) - 50;
|
||||
|
||||
// Ratio can not ge below 1.05
|
||||
ratio = std::max(ratio, 1.05f);
|
||||
if (pth < 60) pth = 60;
|
||||
|
||||
// Roll a number between 0 and ratio
|
||||
float roll = GeneralUtils::GenerateRandomNumber<size_t>() / static_cast<float>(std::numeric_limits<size_t>::max());
|
||||
float roll = GeneralUtils::GenerateRandomNumber<size_t>(0, std::max(static_cast<int32_t>(pth), 100));
|
||||
|
||||
roll *= ratio;
|
||||
bool isCritical = false;
|
||||
bool isHit = false;
|
||||
float damageMultiplier = 1.0f;
|
||||
|
||||
std::cout << "Offensive: " << offenderModifiers << " Defensive: " << defensiveModifiers << " Ratio: " << ratio << " Roll: " << roll << std::endl;
|
||||
if (roll > pth) {
|
||||
// Miss
|
||||
isHit = false;
|
||||
} else {
|
||||
// Hit
|
||||
isHit = true;
|
||||
|
||||
// If the roll is above 1, the damage is increased by 1+roll, to a maximum of 5x the damage
|
||||
if (roll > 1) {
|
||||
roll = std::min(roll, 5.0f);
|
||||
totalDamage += static_cast<uint32_t>(totalDamage * roll);
|
||||
if (pth >= 135) {
|
||||
if (roll <= 134) damageMultiplier = 1.5f;
|
||||
else if (roll <= 129) damageMultiplier = 1.4f;
|
||||
else if (roll <= 124) damageMultiplier = 1.3f;
|
||||
else if (roll <= 119) damageMultiplier = 1.2f;
|
||||
else if (roll <= 104) damageMultiplier = 1.1f;
|
||||
} else if (pth >= 130) {
|
||||
if (roll <= 129) damageMultiplier = 1.4f;
|
||||
else if (roll <= 124) damageMultiplier = 1.3f;
|
||||
else if (roll <= 119) damageMultiplier = 1.2f;
|
||||
else if (roll <= 104) damageMultiplier = 1.1f;
|
||||
} else if (pth >= 120) {
|
||||
if (roll <= 119) damageMultiplier = 1.3f;
|
||||
else if (roll <= 104) damageMultiplier = 1.2f;
|
||||
else if (roll <= 89) damageMultiplier = 1.1f;
|
||||
} else if (pth >= 105) {
|
||||
if (roll <= 104) damageMultiplier = 1.2f;
|
||||
else if (roll <= 89) damageMultiplier = 1.1f;
|
||||
} else if (pth >= 90) {
|
||||
if (roll <= 89) damageMultiplier = 1.1f;
|
||||
} else if (pth < 70) {
|
||||
damageMultiplier = pth / 70.0f;
|
||||
}
|
||||
|
||||
if (damageMultiplier > 1.0f) {
|
||||
isCritical = true;
|
||||
|
||||
damageMultiplier *= offenderEntity.CalculateMultiplier(ModifierType::CriticalDamage, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
if (isHit) {
|
||||
// Add a random +5% to the damage
|
||||
totalDamage += static_cast<uint32_t>(totalDamage * (GeneralUtils::GenerateRandomNumber<int32_t>(0, 5) / 100.0f));
|
||||
|
||||
damage = totalDamage;
|
||||
} else {
|
||||
damage = totalDamage = 0;
|
||||
}
|
||||
|
||||
if (isCritical) {
|
||||
totalDamage = static_cast<uint32_t>(totalDamage * damageMultiplier);
|
||||
|
||||
const auto effectName = std::to_string(GeneralUtils::GenerateRandomNumber<uint32_t>());
|
||||
const auto damagedID = damaged->GetObjectID();
|
||||
@ -599,7 +607,7 @@ void nejlika::NejlikaHooks::InstallHooks()
|
||||
effectName
|
||||
);
|
||||
|
||||
damaged->AddCallbackTimer(0.5f, [damaged, effectName] () {
|
||||
damaged->AddCallbackTimer(0.5f, [damaged, effectName]() {
|
||||
GameMessages::SendStopFXEffect(
|
||||
damaged,
|
||||
true,
|
||||
@ -608,11 +616,6 @@ void nejlika::NejlikaHooks::InstallHooks()
|
||||
});
|
||||
}
|
||||
|
||||
// Add a random +10% to the damage
|
||||
totalDamage += static_cast<uint32_t>(totalDamage * (GeneralUtils::GenerateRandomNumber<int32_t>(0, 10) / 100.0f));
|
||||
|
||||
damage = totalDamage;
|
||||
|
||||
auto attackSpeed = offenderEntity.CalculateModifier(ModifierType::AttackSpeed, modifiers, level);
|
||||
|
||||
if (offfendEntity->IsPlayer()) {
|
||||
@ -634,13 +637,85 @@ void nejlika::NejlikaHooks::InstallHooks()
|
||||
SEND_PACKET_BROADCAST;
|
||||
});
|
||||
}
|
||||
|
||||
// Apply over time damage.
|
||||
// Times are rounded to the nearest 0.5s
|
||||
for (const auto& [type, damageDuration] : durationTypes) {
|
||||
if (damageDuration.first == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto duration = static_cast<int32_t>(damageDuration.second * 2);
|
||||
|
||||
if (duration == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto damagePerTick = static_cast<int32_t>(damageDuration.first / duration);
|
||||
|
||||
auto* destroyable = damaged->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (!destroyable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < duration; i++)
|
||||
{
|
||||
damaged->AddCallbackTimer(i * 0.5f, [offender, damaged, damagePerTick]() {
|
||||
auto* destroyable = damaged->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (!destroyable) {
|
||||
return;
|
||||
}
|
||||
|
||||
destroyable->Damage(offender, damagePerTick, 0, true, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* Moved to DestroyableComponent
|
||||
std::stringstream damageUIMessage;
|
||||
|
||||
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;
|
||||
|
||||
int colorR = 255;
|
||||
int colorG = 255;
|
||||
int colorB = 255;
|
||||
int colorA = 0;
|
||||
|
||||
if (damaged->IsPlayer()) {
|
||||
// Make the damage red
|
||||
colorR = 0;
|
||||
colorG = 255;
|
||||
colorB = 0;
|
||||
colorA = 0;
|
||||
}
|
||||
|
||||
const auto damageText = isHit ? std::to_string(totalDamage) : "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 (damaged->IsPlayer()) {
|
||||
damaged->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
} else if (offfendEntity->IsPlayer()) {
|
||||
offfendEntity->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
}*/
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
void nejlika::NejlikaHooks::ItemDescription(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
if (splitArgs.size() < 2) {
|
||||
if (splitArgs.size() < 3) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid arguments.");
|
||||
return;
|
||||
}
|
||||
@ -656,34 +731,70 @@ void nejlika::NejlikaHooks::ItemDescription(Entity* entity, const SystemAddress&
|
||||
|
||||
auto itemId = GeneralUtils::TryParse<LWOOBJID>(splitArgs[1]).value_or(LWOOBJID_EMPTY);
|
||||
|
||||
if (itemId == LWOOBJID_EMPTY) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID.");
|
||||
auto lot = GeneralUtils::TryParse<LOT>(splitArgs[2]).value_or(0);
|
||||
|
||||
if (lot == 0) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Item ID: " << itemId << std::endl;
|
||||
std::cout << "Item LOT: " << lot << std::endl;
|
||||
|
||||
std::stringstream name;
|
||||
std::stringstream desc;
|
||||
|
||||
if (itemId == LWOOBJID_EMPTY) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID.");
|
||||
|
||||
const auto& itemTemplateVec = NejlikaData::GetModifierNameTemplates(ModifierNameType::Object);
|
||||
|
||||
const auto itemTemplateIt = std::find_if(itemTemplateVec.begin(), itemTemplateVec.end(), [lot](const auto& it) {
|
||||
return it.GetLOT() == static_cast<int32_t>(lot);
|
||||
});
|
||||
|
||||
if (itemTemplateIt == itemTemplateVec.end()) {
|
||||
name << "<font color=\"#D0AB62\">NAME</font>";
|
||||
desc << "DESC";
|
||||
} else {
|
||||
const auto& itemTemplate = *itemTemplateIt;
|
||||
|
||||
const auto& modifiers = itemTemplate.GetModifiers();
|
||||
|
||||
// Get the entity level
|
||||
auto* levelProgressionComponent = entity->GetComponent<LevelProgressionComponent>();
|
||||
|
||||
if (!levelProgressionComponent) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto level = levelProgressionComponent->GetLevel();
|
||||
|
||||
name << "<font color=\"#D0AB62\">NAME</font>";
|
||||
desc << ModifierTemplate::GenerateHtmlString(modifiers, level);
|
||||
}
|
||||
} else {
|
||||
const auto itemDataOpt = GetAdditionalItemData(itemId);
|
||||
|
||||
if (!itemDataOpt.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
name << "<font color=\"#D0AB62\">NAME</font>";
|
||||
desc << "DESC";
|
||||
} else {
|
||||
auto& itemDetails = *itemDataOpt.value();
|
||||
|
||||
const auto& modifiers = itemDetails.GetModifierInstances();
|
||||
const auto& names = itemDetails.GetModifierNames();
|
||||
|
||||
if (modifiers.empty() && names.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::stringstream name;
|
||||
std::stringstream desc;
|
||||
|
||||
name << "NAME";
|
||||
|
||||
desc << ModifierName::GenerateHtmlString(names) << "\n";
|
||||
name << "<font color=\"#D0AB62\">NAME</font>";
|
||||
desc << "DESC";
|
||||
} else {
|
||||
name << ModifierName::GenerateHtmlString(names);
|
||||
|
||||
desc << ModifierInstance::GenerateHtmlString(modifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Sending item name: " << name.str() << std::endl;
|
||||
std::cout << "Sending item desc: " << desc.str() << std::endl;
|
||||
|
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;
|
||||
};
|
||||
|
||||
}
|
@ -141,7 +141,7 @@ float nejlika::UpgradeEffect::CalculateChance(int32_t level) const {
|
||||
return value;
|
||||
}
|
||||
|
||||
bool nejlika::UpgradeEffect::CheckConditions(LWOOBJID origin) const {
|
||||
bool nejlika::UpgradeEffect::CheckConditions(LWOOBJID origin, const TriggerParameters& params) const {
|
||||
auto* entity = Game::entityManager->GetEntity(origin);
|
||||
|
||||
if (!entity) {
|
||||
@ -159,6 +159,15 @@ bool nejlika::UpgradeEffect::CheckConditions(LWOOBJID origin) const {
|
||||
|
||||
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:
|
||||
@ -213,7 +222,7 @@ void nejlika::UpgradeEffect::OnTrigger(LWOOBJID origin) const {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ModifierInstance> nejlika::UpgradeEffect::Trigger(const std::vector<UpgradeEffect>& modifiers, int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin) {
|
||||
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) {
|
||||
@ -221,7 +230,7 @@ std::vector<ModifierInstance> nejlika::UpgradeEffect::Trigger(const std::vector<
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!modifier.CheckConditions(origin)) {
|
||||
if (!modifier.CheckConditions(origin, params)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -272,7 +281,7 @@ void nejlika::UpgradeEffect::AddSkill(LWOOBJID origin) const {
|
||||
}
|
||||
|
||||
if (equipSkillID != 0) {
|
||||
inventory->SetSkill(equipSkillID);
|
||||
inventory->AddSkill(equipSkillID);
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,6 +303,6 @@ void nejlika::UpgradeEffect::RemoveSkill(LWOOBJID origin) const {
|
||||
}
|
||||
|
||||
if (equipSkillID != 0) {
|
||||
inventory->UnsetSkill(equipSkillID);
|
||||
inventory->RemoveSkill(equipSkillID);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "UpgradeTriggerType.h"
|
||||
#include "UpgradeTriggerCondition.h"
|
||||
#include <InventoryComponent.h>
|
||||
#include "TriggerParameters.h"
|
||||
|
||||
#include <dCommonVars.h>
|
||||
|
||||
@ -23,11 +24,11 @@ public:
|
||||
|
||||
float CalculateChance(int32_t level) const;
|
||||
|
||||
bool CheckConditions(LWOOBJID origin) 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);
|
||||
static std::vector<ModifierInstance> Trigger(const std::vector<UpgradeEffect>& modifiers, int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin, const TriggerParameters& params);
|
||||
|
||||
// Getters
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include "UpgradeTemplate.h"
|
||||
|
||||
#include "NejlikaData.h"
|
||||
|
||||
using namespace nejlika;
|
||||
|
||||
nejlika::UpgradeTemplate::UpgradeTemplate(const nlohmann::json& json)
|
||||
@ -29,21 +31,49 @@ nlohmann::json nejlika::UpgradeTemplate::ToJson() const
|
||||
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["max-level"].contains("max-level") ? json["max-level"].get<int32_t>() : 1;
|
||||
}
|
||||
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 {
|
||||
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);
|
||||
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 {
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "json.hpp"
|
||||
|
||||
#include "UpgradeEffect.h"
|
||||
#include "TriggerParameters.h"
|
||||
|
||||
namespace nejlika
|
||||
{
|
||||
@ -26,8 +27,11 @@ public:
|
||||
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;
|
||||
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;
|
||||
@ -37,6 +41,7 @@ private:
|
||||
int32_t lot = 0;
|
||||
int32_t maxLevel = 0;
|
||||
std::vector<UpgradeEffect> passives;
|
||||
std::vector<ModifierTemplate> modifiers;
|
||||
|
||||
};
|
||||
|
||||
|
@ -11,7 +11,9 @@ enum class UpgradeTriggerCondition
|
||||
Unarmed,
|
||||
Melee,
|
||||
TwoHanded,
|
||||
Shield
|
||||
Shield,
|
||||
PrimaryAbility,
|
||||
UseSkill
|
||||
};
|
||||
|
||||
}
|
@ -556,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) {
|
||||
return;
|
||||
}
|
||||
@ -572,7 +572,9 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDamageOverTime) {
|
||||
OnDamageCalculation(m_Parent, source, skillID, damage);
|
||||
}
|
||||
|
||||
// If this entity has damage reduction, reduce the damage to a minimum of 1
|
||||
if (m_DamageReduction > 0 && damage > 0) {
|
||||
@ -641,6 +643,42 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
|
||||
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->IsPlayer()) {
|
||||
attacker->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
}
|
||||
|
||||
if (health != 0) {
|
||||
auto* combatComponent = m_Parent->GetComponent<BaseCombatAIComponent>();
|
||||
|
||||
|
@ -384,8 +384,9 @@ public:
|
||||
* @param source the attacker that caused this damage
|
||||
* @param skillID the skill that damaged this entity
|
||||
* @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
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "InventoryComponent.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Entity.h"
|
||||
#include "Item.h"
|
||||
@ -43,6 +44,7 @@ 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) {
|
||||
this->m_Dirty = true;
|
||||
@ -1131,27 +1133,13 @@ void InventoryComponent::AddItemSkills(const LOT lot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto skill = FindSkill(lot);
|
||||
|
||||
const auto index = m_Skills.find(slot);
|
||||
|
||||
if (index != m_Skills.end()) {
|
||||
const auto old = index->second;
|
||||
|
||||
if (!old.empty()) {
|
||||
const auto firstElem = *old.begin();
|
||||
|
||||
GameMessages::SendRemoveSkill(m_Parent, firstElem);
|
||||
}
|
||||
if (m_PrimarySkill != 0) {
|
||||
GameMessages::SendRemoveSkill(m_Parent, m_PrimarySkill);
|
||||
}
|
||||
|
||||
m_Skills.erase(slot);
|
||||
m_PrimarySkill = FindSkill(lot);
|
||||
|
||||
if (skill != 0) {
|
||||
m_Skills.insert_or_assign(slot, std::set<uint32_t>{ skill });
|
||||
|
||||
GameMessages::SendAddSkill(m_Parent, skill, slot);
|
||||
}
|
||||
GameMessages::SendAddSkill(m_Parent, m_PrimarySkill, slot);
|
||||
}
|
||||
|
||||
void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
||||
@ -1163,27 +1151,11 @@ void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = m_Skills.find(slot);
|
||||
GameMessages::SendRemoveSkill(m_Parent, m_PrimarySkill);
|
||||
|
||||
if (index == m_Skills.end()) {
|
||||
return;
|
||||
}
|
||||
m_PrimarySkill = 1;
|
||||
|
||||
const auto old = index->second;
|
||||
|
||||
if (!old.empty()) {
|
||||
const auto firstElem = *old.begin();
|
||||
|
||||
GameMessages::SendRemoveSkill(m_Parent, firstElem);
|
||||
}
|
||||
|
||||
m_Skills.erase(slot);
|
||||
|
||||
if (slot == BehaviorSlot::Primary) {
|
||||
m_Skills.insert_or_assign(BehaviorSlot::Primary, std::set<uint32_t>{ 1 });
|
||||
|
||||
GameMessages::SendAddSkill(m_Parent, 1, BehaviorSlot::Primary);
|
||||
}
|
||||
GameMessages::SendAddSkill(m_Parent, m_PrimarySkill, BehaviorSlot::Primary);
|
||||
}
|
||||
|
||||
void InventoryComponent::TriggerPassiveAbility(PassiveAbilityTrigger trigger, Entity* target) {
|
||||
@ -1360,6 +1332,71 @@ void InventoryComponent::SetNPCItems(const std::vector<LOT>& items) {
|
||||
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() {
|
||||
for (const auto& inventory : m_Inventories) {
|
||||
delete inventory.second;
|
||||
@ -1614,60 +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()) {
|
||||
m_Skills.insert_or_assign(slot, std::set<uint32_t>{ skillId });
|
||||
} else {
|
||||
auto& existing = index->second;
|
||||
existing.insert(skillId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void InventoryComponent::UnsetSkill(uint32_t skillId) {
|
||||
for (auto& pair : m_Skills) {
|
||||
auto& skills = pair.second;
|
||||
skills.erase(skillId);
|
||||
}
|
||||
}
|
||||
|
||||
void InventoryComponent::SetSkill(uint32_t skillId) {
|
||||
UnsetSkill(skillId);
|
||||
|
||||
const auto& slotA = m_Skills.find(BehaviorSlot::Head);
|
||||
const auto& slotB = m_Skills.find(BehaviorSlot::Neck);
|
||||
const auto& slotC = m_Skills.find(BehaviorSlot::Offhand);
|
||||
|
||||
// Pick the first one which has less than 3 skills
|
||||
std::set<uint32_t>* slot = nullptr;
|
||||
|
||||
if (slotA == m_Skills.end() || slotA->second.size() < 3) {
|
||||
slot = &m_Skills[BehaviorSlot::Head];
|
||||
} else if (slotB == m_Skills.end() || slotB->second.size() < 3) {
|
||||
slot = &m_Skills[BehaviorSlot::Neck];
|
||||
} else if (slotC == m_Skills.end() || slotC->second.size() < 3) {
|
||||
slot = &m_Skills[BehaviorSlot::Offhand];
|
||||
}
|
||||
|
||||
if (slot == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
slot->insert(skillId);
|
||||
}
|
||||
|
||||
|
||||
|
@ -368,12 +368,14 @@ public:
|
||||
*/
|
||||
void UnequipScripts(Item* unequippedItem);
|
||||
|
||||
const std::map<BehaviorSlot, std::set<uint32_t>>& GetSkills(){ return m_Skills; };
|
||||
uint32_t GetPrimarySkill() const { return m_PrimarySkill; }
|
||||
|
||||
bool SetSkill(int32_t slot, uint32_t skillId);
|
||||
bool SetSkill(BehaviorSlot slot, uint32_t skillId);
|
||||
void UnsetSkill(uint32_t skillId);
|
||||
void SetSkill(uint32_t skillId);
|
||||
const std::vector<uint32_t>& GetSkills(){ return m_Skills; };
|
||||
|
||||
bool AddSkill(uint32_t skillId);
|
||||
bool RemoveSkill(uint32_t skillId);
|
||||
void RotateSkills();
|
||||
void UpdateSkills();
|
||||
|
||||
~InventoryComponent() override;
|
||||
|
||||
@ -382,6 +384,7 @@ public:
|
||||
static Observable<InventoryComponent*, Item*> OnItemEquipped;
|
||||
static Observable<InventoryComponent*, Item*> OnItemUnequipped;
|
||||
static Observable<InventoryComponent*, Item*> OnItemLoaded;
|
||||
static Observable<InventoryComponent*, Item*> OnCountChanged;
|
||||
|
||||
private:
|
||||
/**
|
||||
@ -391,10 +394,16 @@ private:
|
||||
|
||||
std::map<BehaviorSlot, uint32_t> m_ActivatorSkills;
|
||||
|
||||
uint32_t m_PrimarySkill = 0;
|
||||
|
||||
/**
|
||||
* The skills that this entity currently has active
|
||||
*/
|
||||
std::map<BehaviorSlot, std::set<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
|
||||
|
@ -201,6 +201,8 @@ void Item::SetCount(const uint32_t value, const bool silent, const bool disassem
|
||||
|
||||
count = value;
|
||||
|
||||
InventoryComponent::OnCountChanged(inventory->GetComponent(), this);
|
||||
|
||||
if (count == 0) {
|
||||
RemoveFromInventory();
|
||||
}
|
||||
|
@ -1347,26 +1347,20 @@ namespace DEVGMCommands {
|
||||
|
||||
void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
if (splitArgs.size() < 2) return;
|
||||
if (splitArgs.size() < 1) return;
|
||||
|
||||
auto* const inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
if (inventoryComponent) {
|
||||
const auto slot = GeneralUtils::TryParse<BehaviorSlot>(splitArgs[0]);
|
||||
if (!slot) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot.");
|
||||
return;
|
||||
} else {
|
||||
const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[1]);
|
||||
const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[0]);
|
||||
if (!skillId) {
|
||||
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");
|
||||
if (inventoryComponent->AddSkill(skillId.value())) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully");
|
||||
else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
|
Loading…
Reference in New Issue
Block a user