mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-21 21:17:25 +00:00
Updated how skills and upgrades work, more gui stuff
This commit is contained in:
parent
5e3312850c
commit
3178a702a7
@ -33,7 +33,7 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, Modifi
|
||||
return total;
|
||||
}
|
||||
|
||||
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const {
|
||||
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const {
|
||||
float total = 0;
|
||||
|
||||
for (const auto& modifier : additionalModifiers) {
|
||||
@ -71,8 +71,6 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, int32_
|
||||
|
||||
float multiplicative = CalculateModifier(type, ModifierOperator::Multiplicative, false);
|
||||
|
||||
std::cout << "Scaler: " << scaler << " Additive: " << additive << " Multiplicative: " << multiplicative << std::endl;
|
||||
|
||||
return (scaler + additive) * (1 + multiplicative / 100);
|
||||
}
|
||||
|
||||
@ -80,7 +78,7 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type) const
|
||||
return CalculateModifier(type, level);
|
||||
}
|
||||
|
||||
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers, int32_t level) const
|
||||
float nejlika::AdditionalEntityData::CalculateFinalModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, int32_t level) const
|
||||
{
|
||||
const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
|
||||
|
||||
@ -106,6 +104,42 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::v
|
||||
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();
|
||||
@ -118,8 +152,6 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::v
|
||||
|
||||
float total = (scaler + additive) * (1 + multiplicative / 100);
|
||||
|
||||
std::cout << "Scaler: " << scaler << " Additive: " << additive << " Multiplicative: " << multiplicative << " Total: " << total << std::endl;
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
@ -132,7 +164,7 @@ float nejlika::AdditionalEntityData::CalculateResistance(ModifierType type) cons
|
||||
|
||||
float nejlika::AdditionalEntityData::CalculateMultiplier(ModifierType type) const
|
||||
{
|
||||
return 1 + CalculateModifier(type, ModifierOperator::Multiplicative, false);
|
||||
return 100 + CalculateModifier(type, ModifierOperator::Multiplicative, false);
|
||||
}
|
||||
|
||||
std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems(UpgradeTriggerType triggerType, const TriggerParameters& params) {
|
||||
@ -270,6 +302,53 @@ void nejlika::AdditionalEntityData::RemoveSkills(LOT lot) {
|
||||
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();
|
||||
|
||||
@ -296,8 +375,40 @@ 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);
|
||||
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 {
|
||||
@ -402,7 +513,15 @@ void nejlika::AdditionalEntityData::ApplyToEntity() {
|
||||
|
||||
const auto& itemModifiers = itemData.GetModifierInstances();
|
||||
|
||||
activeModifiers.insert(activeModifiers.end(), itemModifiers.begin(), itemModifiers.end());
|
||||
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) {
|
||||
@ -431,11 +550,11 @@ void nejlika::AdditionalEntityData::ApplyToEntity() {
|
||||
}
|
||||
}
|
||||
|
||||
destroyable->SetMaxHealth(static_cast<int32_t>(CalculateModifier(ModifierType::Health, level)));
|
||||
destroyable->SetMaxArmor(static_cast<int32_t>(CalculateModifier(ModifierType::Armor, level)));
|
||||
if (!entity->IsPlayer()) {
|
||||
destroyable->SetMaxImagination(static_cast<int32_t>(CalculateModifier(ModifierType::Imagination, level)));
|
||||
}
|
||||
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;
|
||||
@ -444,15 +563,17 @@ void nejlika::AdditionalEntityData::ApplyToEntity() {
|
||||
destroyable->SetHealth(destroyable->GetMaxHealth());
|
||||
destroyable->SetArmor(destroyable->GetMaxArmor());
|
||||
|
||||
if (!entity->IsPlayer()) {
|
||||
//if (!entity->IsPlayer()) {
|
||||
destroyable->SetImagination(destroyable->GetMaxImagination());
|
||||
}
|
||||
//}
|
||||
|
||||
if (entity->IsPlayer()) {
|
||||
auto* controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>();
|
||||
if (controllablePhysicsComponent) controllablePhysicsComponent->SetSpeedMultiplier(CalculateMultiplier(ModifierType::Speed));
|
||||
if (controllablePhysicsComponent) controllablePhysicsComponent->SetSpeedMultiplier(CalculateMultiplier(ModifierType::Speed) / 100.0f);
|
||||
}
|
||||
|
||||
TriggerPassiveRegeneration();
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
|
@ -24,18 +24,18 @@ public:
|
||||
|
||||
float CalculateModifier(ModifierType type, ModifierOperator op, bool resistance) const;
|
||||
|
||||
float CalculateModifier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const;
|
||||
float CalculateModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const;
|
||||
|
||||
float CalculateModifier(ModifierType type, int32_t level) const;
|
||||
|
||||
float CalculateModifier(ModifierType type) const;
|
||||
|
||||
float CalculateModifier(ModifierType type, std::vector<ModifierInstance>& additionalModifiers, int32_t level) const;
|
||||
float 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 1.0.
|
||||
* @brief Calculate the multiplier for a given modifier type. With a base value of 100 (%).
|
||||
*
|
||||
* @param type The modifier type.
|
||||
* @return The multiplier.
|
||||
@ -43,13 +43,13 @@ public:
|
||||
float CalculateMultiplier(ModifierType type) const;
|
||||
|
||||
/**
|
||||
* @brief Calculate the multiplier for a given modifier type. With a base value of 1.0.
|
||||
* @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, std::vector<ModifierInstance>& additionalModifiers) const;
|
||||
float CalculateMultiplier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers) const;
|
||||
|
||||
/**
|
||||
* @brief Calculate damage conversation mapping.
|
||||
@ -85,9 +85,15 @@ public:
|
||||
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;
|
||||
|
@ -210,18 +210,31 @@ void nejlika::AdditionalItemData::RollModifiers(Item* item, int32_t level) {
|
||||
|
||||
const auto& itemTemplateVec = NejlikaData::GetModifierNameTemplates(ModifierNameType::Object);
|
||||
|
||||
const auto itemTemplateIt = std::find_if(itemTemplateVec.begin(), itemTemplateVec.end(), [item](const auto& it) {
|
||||
return it.GetLOT() == static_cast<int32_t>(item->GetLot());
|
||||
});
|
||||
std::vector<const ModifierNameTemplate*> availableObjects;
|
||||
|
||||
if (itemTemplateIt != itemTemplateVec.end()) {
|
||||
const auto& itemTemplate = *itemTemplateIt;
|
||||
for (const auto& itemTemplate : itemTemplateVec) {
|
||||
if (itemTemplate.GetMinLevel() > level || itemTemplate.GetMaxLevel() < level) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto itemModifiers = itemTemplate.GenerateModifiers(level);
|
||||
if (itemTemplate.GetLOT() != static_cast<int32_t>(item->GetLot())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
modifierInstances.insert(modifierInstances.end(), itemModifiers.begin(), itemModifiers.end());
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ set(DGAME_SOURCES "Character.cpp"
|
||||
"UpgradeTemplate.cpp"
|
||||
"UpgradeEffect.cpp"
|
||||
"Lookup.cpp"
|
||||
"NejlikaHelpers.cpp"
|
||||
)
|
||||
|
||||
include_directories(
|
||||
|
@ -67,7 +67,8 @@ std::vector<nejlika::ModifierInstance> nejlika::EntityTemplate::GenerateModifier
|
||||
ModifierCategory::Player,
|
||||
0,
|
||||
"",
|
||||
ModifierType::Invalid
|
||||
ModifierType::Invalid,
|
||||
""
|
||||
);
|
||||
|
||||
modifiers.push_back(modifier);
|
||||
|
@ -51,11 +51,26 @@ std::string nejlika::ModifierInstance::GenerateHtmlString(const std::vector<Modi
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -67,32 +82,89 @@ std::string nejlika::ModifierInstance::GenerateHtmlString(const std::vector<Modi
|
||||
}
|
||||
|
||||
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 << "<font color=\"#FFFFFF\">";
|
||||
|
||||
ss << magic_enum::enum_name<ModifierType>(modifier.first) << ": ";
|
||||
ss << ((modifier.second > 0) ? (math.first == ModifierOperator::Multiplicative ? "+" : "") : "-");
|
||||
|
||||
ss << ((modifier.second > 0) ? "+" : "-");
|
||||
|
||||
ss << std::fixed << std::setprecision(0) << std::abs(modifier.second);
|
||||
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();
|
||||
}
|
@ -16,8 +16,11 @@ class ModifierInstance
|
||||
{
|
||||
public:
|
||||
ModifierInstance(
|
||||
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) {}
|
||||
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.
|
||||
@ -59,6 +62,8 @@ public:
|
||||
|
||||
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; }
|
||||
@ -75,6 +80,8 @@ public:
|
||||
|
||||
void SetEffectType(const std::string& effectType) { this->effectType = effectType; }
|
||||
|
||||
void SetUpgradeName(const std::string& upgradeName) { this->upgradeName = upgradeName; }
|
||||
|
||||
private:
|
||||
ModifierType type;
|
||||
ModifierType convertTo;
|
||||
@ -84,6 +91,7 @@ private:
|
||||
ModifierCategory category;
|
||||
uint32_t effectID;
|
||||
std::string effectType;
|
||||
std::string upgradeName;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -57,7 +57,14 @@ nejlika::ModifierNameTemplate::ModifierNameTemplate(const nlohmann::json & json)
|
||||
{
|
||||
for (const auto& modifier : json["modifiers"])
|
||||
{
|
||||
modifiers.push_back(ModifierTemplate(modifier));
|
||||
const auto modifierTemplate = ModifierTemplate(modifier);
|
||||
|
||||
if (modifierTemplate.GetTypes().empty() || modifierTemplate.GetTypes().at(0) == ModifierType::Invalid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
modifiers.push_back(modifierTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,6 +75,10 @@ nejlika::ModifierNameTemplate::ModifierNameTemplate(const nlohmann::json & json)
|
||||
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
|
||||
{
|
||||
|
@ -4,6 +4,10 @@ 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>();
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include <magic_enum.hpp>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
using namespace nejlika;
|
||||
|
||||
@ -133,6 +135,11 @@ nejlika::ModifierTemplate::ModifierTemplate(const nlohmann::json& config) {
|
||||
category = ModifierCategory::Player;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.contains("skill"))
|
||||
{
|
||||
upgradeName = config["skill"].get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json nejlika::ModifierTemplate::ToJson() const {
|
||||
@ -191,6 +198,12 @@ nlohmann::json nejlika::ModifierTemplate::ToJson() const {
|
||||
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;
|
||||
}
|
||||
@ -249,9 +262,24 @@ std::string nejlika::ModifierTemplate::GenerateHtmlString(const std::vector<Modi
|
||||
// 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) {
|
||||
if (type == ModifierType::Invalid || type == ModifierType::SkillModifier) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -299,42 +327,128 @@ std::string nejlika::ModifierTemplate::GenerateHtmlString(const std::vector<Modi
|
||||
}
|
||||
|
||||
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 << "<font color=\"#FFFFFF\">";
|
||||
|
||||
ss << magic_enum::enum_name<ModifierType>(modifier.first) << ": ";
|
||||
ss << ((modifier.second.first > 0) ? (math.first == ModifierOperator::Multiplicative ? "+" : "") : "-");
|
||||
|
||||
ss << ((modifier.second.first > 0) ? "+" : "-");
|
||||
|
||||
ss << std::fixed << std::setprecision(0) << std::abs(modifier.second.first);
|
||||
ss << std::fixed << std::setprecision(1) << 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);
|
||||
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();
|
||||
}
|
||||
|
||||
@ -354,12 +468,13 @@ std::optional<ModifierInstance> nejlika::ModifierTemplate::GenerateModifier(Modi
|
||||
power++;
|
||||
}
|
||||
|
||||
return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType, convertTo);
|
||||
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()) {
|
||||
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;
|
||||
}
|
||||
@ -369,7 +484,18 @@ std::optional<ModifierInstance> nejlika::ModifierTemplate::GenerateModifier(Modi
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
float value = GeneralUtils::GenerateRandomNumber<float>(scale.GetMin(), scale.GetMax());
|
||||
float value = 0;
|
||||
|
||||
return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType, convertTo);
|
||||
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);
|
||||
}
|
||||
|
@ -59,6 +59,8 @@ public:
|
||||
|
||||
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; }
|
||||
@ -75,6 +77,8 @@ public:
|
||||
|
||||
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.
|
||||
*
|
||||
@ -98,6 +102,7 @@ private:
|
||||
bool isResistance;
|
||||
uint32_t effectID;
|
||||
std::string effectType;
|
||||
std::string upgradeName;
|
||||
|
||||
};
|
||||
|
||||
|
@ -53,6 +53,27 @@ const std::unordered_map<ModifierType, std::string> nameMap = {
|
||||
{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 = {
|
||||
@ -115,6 +136,16 @@ const std::unordered_set<ModifierType> isOverTimeMap = {
|
||||
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)
|
||||
@ -179,3 +210,7 @@ const ModifierType nejlika::GetDurationType(ModifierType type) {
|
||||
const bool nejlika::IsOverTimeType(ModifierType type) {
|
||||
return isOverTimeMap.find(type) != isOverTimeMap.end();
|
||||
}
|
||||
|
||||
const bool nejlika::IsDurationType(ModifierType type) {
|
||||
return isDurationType.find(type) != isDurationType.end();
|
||||
}
|
||||
|
@ -58,9 +58,20 @@ enum class ModifierType : uint8_t
|
||||
|
||||
Slow,
|
||||
ArmorPiercing,
|
||||
ReducedStunDuration,
|
||||
SkillCooldownReduction,
|
||||
SkillRecharge,
|
||||
BlockRecovery,
|
||||
BlockChance,
|
||||
Block,
|
||||
HealthRegen,
|
||||
ImaginationRegen,
|
||||
AttacksPerSecond,
|
||||
ImaginationCost,
|
||||
MainWeaponDamage,
|
||||
Stun,
|
||||
|
||||
CriticalDamage,
|
||||
ChanceToBlock,
|
||||
HealthDrain,
|
||||
|
||||
Invalid
|
||||
@ -76,6 +87,8 @@ 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);
|
||||
|
@ -117,18 +117,39 @@ void nejlika::NejlikaData::LoadNejlikaData()
|
||||
modifierNameTemplates.clear();
|
||||
|
||||
// Load data from json file
|
||||
const auto& filename = Game::config->GetValue("nejlika");
|
||||
const auto& directory_name = Game::config->GetValue("nejlika");
|
||||
|
||||
if (filename.empty())
|
||||
if (directory_name.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream file(filename);
|
||||
// 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", filename.c_str());
|
||||
LOG("Failed to open nejlika data file: %s", path.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -144,19 +165,21 @@ void nejlika::NejlikaData::LoadNejlikaData()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json.contains("modifier-templates"))
|
||||
if (json.contains("modifier-templates"))
|
||||
{
|
||||
LOG("nejlika data file does not contain modifier-templates");
|
||||
return;
|
||||
}
|
||||
const auto& modifierTemplates = json["modifier-templates"];
|
||||
|
||||
const auto& modifierTemplates = json["modifier-templates"];
|
||||
for (const auto& value : modifierTemplates)
|
||||
{
|
||||
auto modifierTemplate = ModifierNameTemplate(value);
|
||||
|
||||
for (const auto& value : modifierTemplates)
|
||||
{
|
||||
auto modifierTemplate = ModifierNameTemplate(value);
|
||||
if (modifierTemplate.GetModifiers().empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
modifierNameTemplates[modifierTemplate.GetType()].push_back(modifierTemplate);
|
||||
modifierNameTemplates[modifierTemplate.GetType()].push_back(modifierTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
LOG("Loaded %d modifier templates", modifierNameTemplates.size());
|
||||
@ -186,5 +209,7 @@ void nejlika::NejlikaData::LoadNejlikaData()
|
||||
upgradeTemplates[upgradeTemplate.GetLot()] = upgradeTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
LOG("Loaded %d upgrade templates", upgradeTemplates.size());
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,8 @@ 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);
|
||||
|
||||
}
|
@ -22,11 +22,16 @@
|
||||
#include <MissionComponent.h>
|
||||
#include <eMissionState.h>
|
||||
|
||||
#include "NejlikaHelpers.h"
|
||||
#include "NejlikaData.h"
|
||||
|
||||
using namespace nejlika;
|
||||
using namespace nejlika::NejlikaData;
|
||||
|
||||
namespace {
|
||||
std::unordered_map<LWOOBJID, std::unordered_map<uint32_t, std::vector<ModifierInstance>>> uniqueSkillModifiers;
|
||||
}
|
||||
|
||||
void nejlika::NejlikaHooks::InstallHooks() {
|
||||
Command itemDescriptionCommand{
|
||||
.help = "Special UI command, does nothing when used in chat.",
|
||||
@ -37,26 +42,38 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
};
|
||||
SlashCommandHandler::RegisterCommand(itemDescriptionCommand);
|
||||
|
||||
Command modificationssCommand{
|
||||
.help = "Displays active modifications.",
|
||||
.info = "Displays active modifications.",
|
||||
.aliases = {"stats"},
|
||||
.handle = [](Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
auto entityDataOpt = GetAdditionalEntityData(entity->GetObjectID());
|
||||
|
||||
if (!entityDataOpt.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& entityData = *entityDataOpt.value();
|
||||
|
||||
std::vector<ModifierInstance> modifiers = entityData.GetActiveModifiers();
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "Active modifications: " << std::endl;
|
||||
|
||||
ss << ModifierInstance::GenerateHtmlString(modifiers);
|
||||
|
||||
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(ss.str()));
|
||||
},
|
||||
.requiredLevel = eGameMasterLevel::CIVILIAN
|
||||
};
|
||||
SlashCommandHandler::RegisterCommand(modificationssCommand);
|
||||
|
||||
LoadNejlikaData();
|
||||
|
||||
InventoryComponent::OnItemCreated += [](InventoryComponent* component, Item* item) {
|
||||
const auto& itemType = static_cast<eItemType>(item->GetInfo().itemType);
|
||||
|
||||
/*
|
||||
static const std::unordered_set<eItemType> listOfHandledItems {
|
||||
eItemType::HAT,
|
||||
eItemType::CHEST,
|
||||
eItemType::LEGS,
|
||||
eItemType::NECK,
|
||||
eItemType::LEFT_HAND,
|
||||
eItemType::RIGHT_HAND
|
||||
};
|
||||
|
||||
if (listOfHandledItems.find(itemType) == listOfHandledItems.end()) {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
// No to the Thinking Hat
|
||||
if (item->GetLot() == 6086) {
|
||||
return;
|
||||
@ -133,11 +150,10 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
|
||||
auto& additionalData = *additionalDataOpt.value();
|
||||
|
||||
additionalData.ApplyToEntity();
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (!inventoryComponent) {
|
||||
additionalData.ApplyToEntity();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -158,6 +174,8 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
}
|
||||
}
|
||||
|
||||
additionalData.ApplyToEntity();
|
||||
|
||||
additionalData.InitializeSkills();
|
||||
};
|
||||
|
||||
@ -166,8 +184,6 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
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:tertiary"), BehaviorSlot::Neck);
|
||||
|
||||
auto* missionComponent = entity->GetComponent<MissionComponent>();
|
||||
@ -300,7 +316,7 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
entityData.ApplyToEntity();
|
||||
};
|
||||
|
||||
SkillComponent::OnSkillCast += [](SkillComponent* skillComponent, uint32_t skillID, bool success) {
|
||||
SkillComponent::OnSkillCast += [](SkillComponent* skillComponent, uint32_t skillID, bool& success, uint32_t skillUID) {
|
||||
std::cout << "Skill cast: " << skillID << " - " << success << std::endl;
|
||||
|
||||
auto* inventoryComponent = skillComponent->GetParent()->GetComponent<InventoryComponent>();
|
||||
@ -312,17 +328,155 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
auto* entity = skillComponent->GetParent();
|
||||
|
||||
auto skills = inventoryComponent->GetSkills();
|
||||
|
||||
const auto primaryTrigger = NejlikaData::GetLookup().GetValue("intro:skills:proxy:main");
|
||||
const auto secondaryTrigger = NejlikaData::GetLookup().GetValue("intro:skills:proxy:secondary");
|
||||
const auto tertiaryTrigger = NejlikaData::GetLookup().GetValue("intro:skills:proxy:tertiary");
|
||||
|
||||
if (skillID == primaryTrigger || skillID == secondaryTrigger || skillID == tertiaryTrigger) {
|
||||
} else {
|
||||
if (skillID == tertiaryTrigger) {
|
||||
inventoryComponent->RotateSkills();
|
||||
}
|
||||
|
||||
const auto entityDataOpt = GetAdditionalEntityData(entity->GetObjectID());
|
||||
|
||||
if (!entityDataOpt.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
inventoryComponent->RotateSkills();
|
||||
auto& entityData = *entityDataOpt.value();
|
||||
|
||||
const auto& skillTemplates = GetModifierNameTemplates(ModifierNameType::Skill);
|
||||
|
||||
const auto& skillTemplateIt = std::find_if(skillTemplates.begin(), skillTemplates.end(), [skillID](const auto& it) {
|
||||
return it.GetLOT() == skillID;
|
||||
});
|
||||
|
||||
std::vector<ModifierInstance> modifiers;
|
||||
|
||||
if (skillTemplateIt != skillTemplates.end()) {
|
||||
const auto& skillTemplate = *skillTemplateIt;
|
||||
|
||||
const auto skillModifiers = skillTemplate.GenerateModifiers(entityData.GetLevel());
|
||||
|
||||
modifiers.insert(modifiers.end(), skillModifiers.begin(), skillModifiers.end());
|
||||
}
|
||||
|
||||
LOT itemLot = 0;
|
||||
LWOOBJID itemId = 0;
|
||||
BehaviorSlot itemSlot = BehaviorSlot::Invalid;
|
||||
|
||||
if (inventoryComponent) {
|
||||
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));
|
||||
|
||||
itemLot = itemDetails.lot;
|
||||
itemId = itemDetails.id;
|
||||
itemSlot = itemBehaviorSlot;
|
||||
|
||||
std::cout << "Found item: " << itemLot << std::endl;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TriggerParameters params;
|
||||
params.SkillID = skillID;
|
||||
params.SelectedBehaviorSlot = itemSlot;
|
||||
|
||||
// Upgrades
|
||||
const auto upgradeModifiers = entityData.TriggerUpgradeItems(UpgradeTriggerType::OnCast, params);
|
||||
|
||||
auto unionModifiersUpgradeModifiers = modifiers;
|
||||
unionModifiersUpgradeModifiers.insert(unionModifiersUpgradeModifiers.end(), upgradeModifiers.begin(), upgradeModifiers.end());
|
||||
|
||||
const auto& imaginationCost = entityData.CalculateFinalModifier(ModifierType::ImaginationCost, unionModifiersUpgradeModifiers, entityData.GetLevel());
|
||||
|
||||
auto* destroyable = entity->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (destroyable != nullptr) {
|
||||
if (destroyable->GetImagination() < imaginationCost) {
|
||||
NejlikaHelpers::RenderDamageText("Insufficient imagination!", entity, entity, 4.0f);
|
||||
}
|
||||
else {
|
||||
destroyable->SetImagination(destroyable->GetImagination() - imaginationCost);
|
||||
|
||||
Game::entityManager->SerializeEntity(entity);
|
||||
|
||||
// Insert into the unique skill modifiers
|
||||
const auto& uniqueSkillModifiersIt = uniqueSkillModifiers.find(entity->GetObjectID());
|
||||
|
||||
if (uniqueSkillModifiersIt != uniqueSkillModifiers.end()) {
|
||||
auto& uniqueSkillModifiersMap = uniqueSkillModifiersIt->second;
|
||||
|
||||
uniqueSkillModifiersMap[skillID] = upgradeModifiers;
|
||||
}
|
||||
else {
|
||||
uniqueSkillModifiers[entity->GetObjectID()] = { { skillID, upgradeModifiers } };
|
||||
}
|
||||
|
||||
entity->AddCallbackTimer(10.0f, [entity, skillID]() {
|
||||
const auto& uniqueSkillModifiersIt = uniqueSkillModifiers.find(entity->GetObjectID());
|
||||
|
||||
if (uniqueSkillModifiersIt != uniqueSkillModifiers.end()) {
|
||||
auto& uniqueSkillModifiersMap = uniqueSkillModifiersIt->second;
|
||||
|
||||
const auto& uniqueSkillModifiersSkillIt = uniqueSkillModifiersMap.find(skillID);
|
||||
|
||||
if (uniqueSkillModifiersSkillIt != uniqueSkillModifiersMap.end()) {
|
||||
uniqueSkillModifiersMap.erase(uniqueSkillModifiersSkillIt);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
modifiers.insert(modifiers.end(), upgradeModifiers.begin(), upgradeModifiers.end());
|
||||
}
|
||||
}
|
||||
|
||||
float attackSpeed = 0;
|
||||
|
||||
if (itemSlot == BehaviorSlot::Primary) {
|
||||
auto attacksPerSecond = entityData.CalculateFinalModifier(ModifierType::AttacksPerSecond, modifiers, entityData.GetLevel());
|
||||
attacksPerSecond = std::max(attacksPerSecond, 1.0f);
|
||||
const auto attackSpeedMod = entityData.CalculateMultiplier(ModifierType::AttackSpeed, modifiers);
|
||||
attackSpeed = (1.0f / attacksPerSecond) * ((attackSpeedMod / 100.0f)) / 2.0f; // 2.0f to account for animation times
|
||||
LOG("Attack speed: %f, attacks per second: %f, attack speed mod: %f", attackSpeed, attacksPerSecond, attackSpeedMod);
|
||||
}
|
||||
else {
|
||||
const auto skillRecharge = entityData.CalculateFinalModifier(ModifierType::SkillRecharge, modifiers, entityData.GetLevel());
|
||||
const auto skillCooldownMod = entityData.CalculateMultiplier(ModifierType::SkillCooldownReduction, modifiers);
|
||||
attackSpeed = skillRecharge * (skillCooldownMod / 100.0f);
|
||||
LOG("Skill recharge: %f, skill cooldown mod: %f", skillRecharge, skillCooldownMod);
|
||||
}
|
||||
|
||||
if (entity->IsPlayer()) {
|
||||
entity->AddCallbackTimer(0.0f, [entity, skillID, attackSpeed]() {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
const auto objectID = entity->GetObjectID();
|
||||
|
||||
bitStream.Write(objectID);
|
||||
bitStream.Write(eGameMessageType::MODIFY_SKILL_COOLDOWN);
|
||||
|
||||
bitStream.Write1();
|
||||
bitStream.Write<float>(attackSpeed);
|
||||
bitStream.Write<int32_t>(static_cast<int32_t>(skillID));
|
||||
|
||||
LOG("Sending cooldown reduction for skill: %d: %f", skillID, attackSpeed);
|
||||
|
||||
SEND_PACKET_BROADCAST;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
DestroyableComponent::OnDamageCalculation += [](Entity* damaged, LWOOBJID offender, uint32_t skillID, uint32_t& damage) {
|
||||
@ -403,14 +557,24 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ModifierInstance> modifiers;
|
||||
|
||||
if (itemSlot == BehaviorSlot::Primary) {
|
||||
const auto mainWeaponDamage = offenderEntity.CalculateMainWeaponDamage();
|
||||
|
||||
for (const auto& modifier : mainWeaponDamage) {
|
||||
std::cout << "Main weapon damage: " << magic_enum::enum_name(modifier.GetType()) << " - " << modifier.GetValue() << std::endl;
|
||||
}
|
||||
|
||||
modifiers.insert(modifiers.end(), mainWeaponDamage.begin(), mainWeaponDamage.end());
|
||||
}
|
||||
|
||||
const auto& skillTemplates = GetModifierNameTemplates(ModifierNameType::Skill);
|
||||
|
||||
const auto& skillTemplateIt = std::find_if(skillTemplates.begin(), skillTemplates.end(), [skillID](const auto& it) {
|
||||
return it.GetLOT() == skillID;
|
||||
});
|
||||
|
||||
std::vector<ModifierInstance> modifiers;
|
||||
|
||||
if (skillTemplateIt != skillTemplates.end()) {
|
||||
const auto& skillTemplate = *skillTemplateIt;
|
||||
|
||||
@ -428,6 +592,20 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
|
||||
modifiers.insert(modifiers.end(), upgradeModifiers.begin(), upgradeModifiers.end());
|
||||
|
||||
const auto& uniqueSkillModifiersIt = uniqueSkillModifiers.find(offender);
|
||||
|
||||
if (uniqueSkillModifiersIt != uniqueSkillModifiers.end()) {
|
||||
const auto& uniqueSkillModifiersMap = uniqueSkillModifiersIt->second;
|
||||
|
||||
const auto& uniqueSkillModifiersSkillIt = uniqueSkillModifiersMap.find(skillID);
|
||||
|
||||
if (uniqueSkillModifiersSkillIt != uniqueSkillModifiersMap.end()) {
|
||||
const auto& uniqueSkillModifiersVec = uniqueSkillModifiersSkillIt->second;
|
||||
|
||||
modifiers.insert(modifiers.end(), uniqueSkillModifiersVec.begin(), uniqueSkillModifiersVec.end());
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<ModifierType> damageTypes;
|
||||
|
||||
for (const auto& modifier : modifiers) {
|
||||
@ -444,14 +622,28 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t totalDamage = 0;
|
||||
int32_t totalDamage = 0;
|
||||
|
||||
std::unordered_map<ModifierType, std::pair<float, float>> durationTypes;
|
||||
std::unordered_map<ModifierType, float> tmpDamageValues;
|
||||
|
||||
const auto mainWeaponDamageModifier = (offenderEntity.CalculateMultiplier(ModifierType::MainWeaponDamage, modifiers) - 100.0f) / 100.0f;
|
||||
|
||||
if (mainWeaponDamageModifier > 0.0f) {
|
||||
const auto mainWeaponDamage = offenderEntity.CalculateMainWeaponDamage();
|
||||
|
||||
for (auto modifier : mainWeaponDamage) {
|
||||
const auto damageValue = modifier.GetValue();
|
||||
|
||||
modifier.SetValue(damageValue * mainWeaponDamageModifier);
|
||||
|
||||
modifiers.push_back(modifier);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& type : damageTypes) {
|
||||
if (nejlika::IsOverTimeType(type)) {
|
||||
float damageValue = offenderEntity.CalculateModifier(type, modifiers, level);
|
||||
float damageValue = offenderEntity.CalculateFinalModifier(type, modifiers, level);
|
||||
|
||||
// Calculate resistance, can't go below 20% of the original damage
|
||||
const auto resistance = std::max(1 - (damagedEntity.CalculateResistance(type) / 100), 0.2f);
|
||||
@ -460,7 +652,7 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
|
||||
const auto durationType = nejlika::GetDurationType(type);
|
||||
|
||||
const auto duration = offenderEntity.CalculateModifier(durationType, modifiers, level);
|
||||
const auto duration = offenderEntity.CalculateFinalModifier(durationType, modifiers, level);
|
||||
|
||||
const auto durationResistance = std::max(1 - (damagedEntity.CalculateResistance(durationType) / 100), 0.2f);
|
||||
|
||||
@ -475,7 +667,7 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
continue;
|
||||
}
|
||||
|
||||
float damageValue = offenderEntity.CalculateModifier(type, modifiers, level);
|
||||
float damageValue = offenderEntity.CalculateFinalModifier(type, modifiers, level);
|
||||
|
||||
tmpDamageValues[type] = damageValue;
|
||||
}
|
||||
@ -485,7 +677,7 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
|
||||
std::unordered_map<ModifierType, float> finalDamageValues;
|
||||
|
||||
for (const auto& [typeA, typeBMap] : converationMap) {
|
||||
/*for (const auto& [typeA, typeBMap] : converationMap) {
|
||||
const auto& typeAValue = tmpDamageValues.find(typeA);
|
||||
|
||||
if (typeAValue == tmpDamageValues.end()) {
|
||||
@ -508,6 +700,13 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
finalDamageValues[typeA] += typeAValueFloat - convertedValue;
|
||||
finalDamageValues[typeB] += typeBValueFloat + convertedValue;
|
||||
}
|
||||
}*/
|
||||
|
||||
// Add the remaining values
|
||||
for (const auto& [type, value] : tmpDamageValues) {
|
||||
if (finalDamageValues.find(type) == finalDamageValues.end()) {
|
||||
finalDamageValues[type] = value;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [type, damage] : finalDamageValues) {
|
||||
@ -516,7 +715,7 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
|
||||
float reductedDamage = damage * resistance;
|
||||
|
||||
totalDamage += static_cast<uint32_t>(reductedDamage);
|
||||
totalDamage += static_cast<int32_t>(reductedDamage);
|
||||
|
||||
std::cout << "Damage type: " << magic_enum::enum_name(type) << " - " << damage << std::endl;
|
||||
std::cout << "Resistance: " << resistance << " - " << reductedDamage << std::endl;
|
||||
@ -581,21 +780,25 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
if (damageMultiplier > 1.0f) {
|
||||
isCritical = true;
|
||||
|
||||
damageMultiplier *= offenderEntity.CalculateMultiplier(ModifierType::CriticalDamage, modifiers);
|
||||
damageMultiplier *= (offenderEntity.CalculateMultiplier(ModifierType::CriticalDamage, modifiers) / 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (isHit) {
|
||||
// Add a random +5% to the damage
|
||||
totalDamage += static_cast<uint32_t>(totalDamage * (GeneralUtils::GenerateRandomNumber<int32_t>(0, 5) / 100.0f));
|
||||
totalDamage += static_cast<int32_t>(totalDamage * (GeneralUtils::GenerateRandomNumber<int32_t>(0, 5) / 100.0f));
|
||||
|
||||
damage = totalDamage;
|
||||
} else {
|
||||
damage = totalDamage = 0;
|
||||
}
|
||||
|
||||
if (totalDamage < 0 || damage < 0) {
|
||||
totalDamage = damage = 0;
|
||||
}
|
||||
|
||||
if (isCritical) {
|
||||
totalDamage = static_cast<uint32_t>(totalDamage * damageMultiplier);
|
||||
totalDamage = static_cast<int32_t>(totalDamage * damageMultiplier);
|
||||
|
||||
const auto effectName = std::to_string(GeneralUtils::GenerateRandomNumber<uint32_t>());
|
||||
const auto damagedID = damaged->GetObjectID();
|
||||
@ -616,26 +819,15 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
});
|
||||
}
|
||||
|
||||
auto attackSpeed = offenderEntity.CalculateModifier(ModifierType::AttackSpeed, modifiers, level);
|
||||
if (damage == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (offfendEntity->IsPlayer()) {
|
||||
offfendEntity->AddCallbackTimer(0.0f, [offfendEntity, skillID, attackSpeed]() {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
const auto stunDuration = offenderEntity.CalculateFinalModifier(ModifierType::Stun, modifiers, level);
|
||||
|
||||
const auto entity = offfendEntity->GetObjectID();
|
||||
|
||||
bitStream.Write(entity);
|
||||
bitStream.Write(eGameMessageType::MODIFY_SKILL_COOLDOWN);
|
||||
|
||||
bitStream.Write1();
|
||||
bitStream.Write<float>(attackSpeed);
|
||||
bitStream.Write<int32_t>(static_cast<int32_t>(skillID));
|
||||
|
||||
LOG("Sending cooldown reduction for skill: %d", skillID);
|
||||
|
||||
SEND_PACKET_BROADCAST;
|
||||
});
|
||||
if (baseCombatAIComponent && stunDuration > 0) {
|
||||
LOG("Stunning for %f seconds", stunDuration);
|
||||
baseCombatAIComponent->Stun(stunDuration, true);
|
||||
}
|
||||
|
||||
// Apply over time damage.
|
||||
@ -651,7 +843,7 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto damagePerTick = static_cast<int32_t>(damageDuration.first / duration);
|
||||
const auto damagePerTick = static_cast<int32_t>(damageDuration.first);
|
||||
|
||||
auto* destroyable = damaged->GetComponent<DestroyableComponent>();
|
||||
|
||||
@ -661,54 +853,20 @@ void nejlika::NejlikaHooks::InstallHooks() {
|
||||
|
||||
for (size_t i = 0; i < duration; i++)
|
||||
{
|
||||
damaged->AddCallbackTimer(i * 0.5f, [offender, damaged, damagePerTick]() {
|
||||
damaged->AddCallbackTimer(i * 0.5f, [type, offender, damaged, damagePerTick]() {
|
||||
|
||||
auto* offenderEntity = Game::entityManager->GetEntity(offender);
|
||||
|
||||
auto* destroyable = damaged->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (!destroyable) {
|
||||
return;
|
||||
}
|
||||
|
||||
destroyable->Damage(offender, damagePerTick, 0, true, true);
|
||||
destroyable->Damage(damagePerTick, offender, 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);
|
||||
}*/
|
||||
};
|
||||
}
|
||||
|
||||
@ -744,34 +902,110 @@ void nejlika::NejlikaHooks::ItemDescription(Entity* entity, const SystemAddress&
|
||||
std::stringstream name;
|
||||
std::stringstream desc;
|
||||
|
||||
if (itemId == LWOOBJID_EMPTY) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID.");
|
||||
const auto& upgradeItemOpt = NejlikaData::GetUpgradeTemplate(lot);
|
||||
|
||||
if (upgradeItemOpt.has_value()) {
|
||||
const auto& upgradeItem = *upgradeItemOpt.value();
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (!inventoryComponent) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto amount = inventoryComponent->GetLotCount(lot);
|
||||
|
||||
if (itemId == LWOOBJID_EMPTY) {
|
||||
amount++;
|
||||
}
|
||||
|
||||
const auto& modifiers = upgradeItem.GenerateModifiers(amount);
|
||||
|
||||
name << "<font color=\"#D0AB62\">NAME</font>";
|
||||
|
||||
desc << "DESC" << "\n\n";
|
||||
|
||||
desc << "<font color=\"#D0AB62\">Level " << amount << "/" << upgradeItem.GetMaxLevel() << "</font>\n";
|
||||
|
||||
desc << ModifierInstance::GenerateHtmlString(modifiers);
|
||||
|
||||
const auto& passives = upgradeItem.GetPassives();
|
||||
|
||||
desc << UpgradeEffect::GenerateHtmlString(passives, amount);
|
||||
} else if (itemId == LWOOBJID_EMPTY) {
|
||||
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);
|
||||
});
|
||||
auto* levelProgressionComponent = entity->GetComponent<LevelProgressionComponent>();
|
||||
|
||||
if (itemTemplateIt == itemTemplateVec.end()) {
|
||||
name << "<font color=\"#D0AB62\">NAME</font>";
|
||||
desc << "DESC";
|
||||
} else {
|
||||
const auto& itemTemplate = *itemTemplateIt;
|
||||
if (!levelProgressionComponent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& modifiers = itemTemplate.GetModifiers();
|
||||
std::vector<const nejlika::ModifierNameTemplate*> availableTemplates;
|
||||
const nejlika::ModifierNameTemplate* lowestLevelTemplate = nullptr;
|
||||
std::vector<const nejlika::ModifierNameTemplate*> lowestLevelTemplates;
|
||||
|
||||
// Get the entity level
|
||||
auto* levelProgressionComponent = entity->GetComponent<LevelProgressionComponent>();
|
||||
for (const auto& itemTemplate : itemTemplateVec) {
|
||||
auto level = static_cast<int32_t>(levelProgressionComponent->GetLevel());
|
||||
|
||||
if (!levelProgressionComponent) {
|
||||
return;
|
||||
if (itemTemplate.GetLOT() != static_cast<int32_t>(lot)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto level = levelProgressionComponent->GetLevel();
|
||||
if (lowestLevelTemplate == nullptr) {
|
||||
lowestLevelTemplate = &itemTemplate;
|
||||
}
|
||||
else if (lowestLevelTemplate->GetMinLevel() > level) {
|
||||
lowestLevelTemplate = &itemTemplate;
|
||||
lowestLevelTemplates.clear();
|
||||
}
|
||||
else if (lowestLevelTemplate->GetMinLevel() == level) {
|
||||
lowestLevelTemplates.push_back(&itemTemplate);
|
||||
}
|
||||
|
||||
name << "<font color=\"#D0AB62\">NAME</font>";
|
||||
desc << ModifierTemplate::GenerateHtmlString(modifiers, level);
|
||||
if (itemTemplate.GetMinLevel() > level || itemTemplate.GetMaxLevel() < level) {
|
||||
continue;
|
||||
}
|
||||
|
||||
availableTemplates.push_back(&itemTemplate);
|
||||
}
|
||||
|
||||
// Add the lowest level template if no other template was found
|
||||
if (availableTemplates.empty() && lowestLevelTemplate != nullptr) {
|
||||
availableTemplates.push_back(lowestLevelTemplate);
|
||||
|
||||
// Add all templates with the same level
|
||||
availableTemplates.insert(availableTemplates.end(), lowestLevelTemplates.begin(), lowestLevelTemplates.end());
|
||||
}
|
||||
|
||||
name << "<font color=\"#D0AB62\">NAME</font>";
|
||||
|
||||
if (availableTemplates.empty()) {
|
||||
desc << "DESC";
|
||||
} else {
|
||||
if (availableTemplates.size() > 1) {
|
||||
desc << "<font color=\"#D0AB62\">One of:</font>\n";
|
||||
}
|
||||
for (size_t i = 0; i < availableTemplates.size(); i++) {
|
||||
const auto& itemTemplate = *availableTemplates[i];
|
||||
|
||||
const auto& modifiers = itemTemplate.GetModifiers();
|
||||
|
||||
// Get the entity level
|
||||
auto* levelProgressionComponent = entity->GetComponent<LevelProgressionComponent>();
|
||||
|
||||
if (!levelProgressionComponent) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto level = std::max(static_cast<int32_t>(levelProgressionComponent->GetLevel()), itemTemplate.GetMinLevel());
|
||||
|
||||
desc << ModifierTemplate::GenerateHtmlString(modifiers, level);
|
||||
|
||||
if (i < availableTemplates.size() - 1) {
|
||||
desc << "\n<font color=\"#D0AB62\">or</font>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto itemDataOpt = GetAdditionalItemData(itemId);
|
||||
|
@ -306,3 +306,50 @@ void nejlika::UpgradeEffect::RemoveSkill(LWOOBJID origin) const {
|
||||
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();
|
||||
}
|
||||
|
@ -39,6 +39,15 @@ public:
|
||||
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
|
||||
{
|
||||
|
@ -10,7 +10,8 @@ enum class UpgradeTriggerType
|
||||
OnHit,
|
||||
Equip,
|
||||
UnEquip,
|
||||
Active
|
||||
Active,
|
||||
OnCast
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -767,8 +767,8 @@ void BaseCombatAIComponent::SetTetherSpeed(float value) {
|
||||
m_TetherSpeed = value;
|
||||
}
|
||||
|
||||
void BaseCombatAIComponent::Stun(const float time) {
|
||||
if (m_StunImmune || m_StunTime > time) {
|
||||
void BaseCombatAIComponent::Stun(const float time, const bool ignoreImmunity) {
|
||||
if ((!ignoreImmunity && m_StunImmune) || m_StunTime > time) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -187,8 +187,9 @@ public:
|
||||
/**
|
||||
* 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 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
|
||||
|
@ -675,7 +675,7 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
|
||||
|
||||
if (m_Parent->IsPlayer()) {
|
||||
m_Parent->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
} else if (attacker->IsPlayer()) {
|
||||
} else if (attacker != nullptr && attacker->IsPlayer()) {
|
||||
attacker->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ ProjectileSyncEntry::ProjectileSyncEntry() {
|
||||
|
||||
std::unordered_map<uint32_t, uint32_t> SkillComponent::m_skillBehaviorCache = {};
|
||||
|
||||
Observable<SkillComponent*, uint32_t, bool> SkillComponent::OnSkillCast;
|
||||
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) {
|
||||
auto* context = new BehaviorContext(this->m_Parent->GetObjectID());
|
||||
@ -50,7 +50,11 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s
|
||||
|
||||
context->ExecuteUpdates();
|
||||
|
||||
OnSkillCast(this, skillID, !context->failed);
|
||||
bool success = !context->failed;
|
||||
|
||||
OnSkillCast(this, skillID, success, skillUid);
|
||||
|
||||
context->failed = !success;
|
||||
|
||||
return !context->failed;
|
||||
}
|
||||
|
@ -185,8 +185,8 @@ public:
|
||||
*/
|
||||
uint32_t GetUniqueSkillId();
|
||||
|
||||
// SkillComponent, SkillID, Success
|
||||
static Observable<SkillComponent*, uint32_t, bool> OnSkillCast;
|
||||
// SkillComponent, SkillID, Success, skillUID
|
||||
static Observable<SkillComponent*, uint32_t, bool&, uint32_t> OnSkillCast;
|
||||
|
||||
private:
|
||||
/**
|
||||
|
@ -977,7 +977,7 @@ void GameMessages::SendResurrect(Entity* entity) {
|
||||
|
||||
int32_t imaginationToRestore = levelComponent->GetLevel() >= 45 ? 20 : 6;
|
||||
if (imaginationToRestore > destroyableComponent->GetMaxImagination()) imaginationToRestore = destroyableComponent->GetMaxImagination();
|
||||
destroyableComponent->SetImagination(imaginationToRestore);
|
||||
destroyableComponent->SetImagination(destroyableComponent->GetMaxImagination());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user