Added upgrades

This commit is contained in:
wincent 2024-06-02 15:43:35 +02:00
parent d5b2278dc5
commit 364bcf822a
17 changed files with 531 additions and 14 deletions

View File

@ -7,6 +7,9 @@
#include <InventoryComponent.h>
#include <BaseCombatAIComponent.h>
#include <TeamManager.h>
#include <Item.h>
using namespace nejlika;
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, ModifierOperator op, bool resistance) const
{
@ -86,9 +89,27 @@ float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, std::v
float multiplicative = CalculateModifier(type, additionalModifiers, ModifierOperator::Multiplicative, false);
std::cout << "Scaler: " << scaler << " Additive: " << additive << " Multiplicative: " << multiplicative << std::endl;
static const std::unordered_set<ModifierType> damageTypes = {
ModifierType::Slashing,
ModifierType::Piercing,
ModifierType::Bludgeoning,
ModifierType::Fire,
ModifierType::Cold,
ModifierType::Lightning,
ModifierType::Corruption,
ModifierType::Psychic
};
return (scaler + additive) * (1 + multiplicative / 100);
if (damageTypes.contains(type)) {
additive += CalculateModifier(ModifierType::Damage, additionalModifiers, ModifierOperator::Additive, false);
multiplicative += CalculateModifier(ModifierType::Damage, additionalModifiers, ModifierOperator::Multiplicative, false);
}
float total = (scaler + additive) * (1 + multiplicative / 100);
std::cout << "Scaler: " << scaler << " Additive: " << additive << " Multiplicative: " << multiplicative << " Total: " << total << std::endl;
return total;
}
float nejlika::AdditionalEntityData::CalculateResistance(ModifierType type) const
@ -96,6 +117,44 @@ float nejlika::AdditionalEntityData::CalculateResistance(ModifierType type) cons
return CalculateModifier(type, ModifierOperator::Multiplicative, true);
}
std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems(UpgradeTriggerType triggerType) {
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return {};
}
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return {};
}
std::vector<ModifierInstance> result;
for (const auto& itemID : upgradeItems) {
auto* item = inventoryComponent->FindItemById(itemID);
if (item == nullptr) {
continue;
}
const auto upgradeDataOpt = NejlikaData::GetUpgradeTemplate(item->GetLot());
if (!upgradeDataOpt.has_value()) {
continue;
}
const auto& upgradeData = *upgradeDataOpt.value();
const auto modifiers = upgradeData.Trigger(item->GetCount(), triggerType, id);
result.insert(result.end(), modifiers.begin(), modifiers.end());
}
return result;
}
void nejlika::AdditionalEntityData::RollStandardModifiers(int32_t level) {
standardModifiers.clear();

View File

@ -7,6 +7,9 @@
#include "ModifierInstance.h"
#include "EntityTemplate.h"
#include "UpgradeTriggerType.h"
#include <unordered_set>
namespace nejlika
{
@ -40,6 +43,14 @@ public:
LOT GetLOT() const { return lot; }
const std::unordered_set<LWOOBJID>& GetUpgradeItems() const { return upgradeItems; }
void AddUpgradeItem(LWOOBJID id) { upgradeItems.insert(id); }
void RemoveUpgradeItem(LWOOBJID id) { upgradeItems.erase(id); }
std::vector<ModifierInstance> TriggerUpgradeItems(UpgradeTriggerType triggerType);
private:
void RollStandardModifiers(int32_t level);
@ -47,6 +58,7 @@ private:
std::vector<ModifierInstance> standardModifiers;
std::vector<ModifierInstance> activeModifiers;
std::unordered_set<LWOOBJID> upgradeItems;
LWOOBJID id;
LOT lot;

View File

@ -19,6 +19,8 @@ set(DGAME_SOURCES "Character.cpp"
"EntityTemplate.cpp"
"AdditionalEntityData.cpp"
"NejlikaHooks.cpp"
"UpgradeTemplate.cpp"
"UpgradeEffect.cpp"
)
include_directories(

View File

@ -3,8 +3,25 @@
nejlika::ModifierScale::ModifierScale(const nlohmann::json & json)
{
level = json["level"].get<int32_t>();
min = json["min"].get<float>();
max = json["max"].get<float>();
if (json.contains("min")) {
min = json["min"].get<float>();
}
else {
min = 0.0f;
}
if (json.contains("max")) {
max = json["max"].get<float>();
}
else {
max = 0.0f;
}
if (json.contains("value")) {
min = json["value"].get<float>();
max = json["value"].get<float>();
}
}
nlohmann::json nejlika::ModifierScale::ToJson() const

View File

@ -26,6 +26,8 @@ enum class ModifierType : uint8_t
Psychic,
Damage,
Invalid
};

View File

@ -20,6 +20,8 @@ std::unordered_map<LWOOBJID, nejlika::AdditionalEntityData> additionalEntityData
std::unordered_map<LOT, nejlika::EntityTemplate> entityTemplates;
std::unordered_map<LOT, nejlika::UpgradeTemplate> upgradeTemplates;
}
const std::unordered_map<ModifierNameType,std::vector<ModifierNameTemplate>>& nejlika::NejlikaData::GetModifierNameTemplates()
@ -69,6 +71,16 @@ const std::optional<EntityTemplate*> nejlika::NejlikaData::GetEntityTemplate(LOT
return std::nullopt;
}
const std::optional<UpgradeTemplate*> nejlika::NejlikaData::GetUpgradeTemplate(LOT lot) {
const auto& it = upgradeTemplates.find(lot);
if (it != upgradeTemplates.end()) {
return std::optional<UpgradeTemplate*>(&it->second);
}
return std::nullopt;
}
void nejlika::NejlikaData::SetAdditionalItemData(LWOOBJID id, AdditionalItemData data) {
additionalItemData[id] = data;
}
@ -147,5 +159,17 @@ void nejlika::NejlikaData::LoadNejlikaData()
}
LOG("Loaded %d entity templates", entityTemplates.size());
if (json.contains("upgrade-templates"))
{
const auto& upgradeTemplatesArray = json["upgrade-templates"];
for (const auto& value : upgradeTemplatesArray)
{
auto upgradeTemplate = UpgradeTemplate(value);
upgradeTemplates[upgradeTemplate.GetLot()] = upgradeTemplate;
}
}
}

View File

@ -7,6 +7,7 @@
#include "EntityTemplate.h"
#include "AdditionalItemData.h"
#include "AdditionalEntityData.h"
#include "UpgradeTemplate.h"
namespace nejlika::NejlikaData
{
@ -21,6 +22,8 @@ const std::optional<AdditionalEntityData*> GetAdditionalEntityData(LWOOBJID id);
const std::optional<EntityTemplate*> GetEntityTemplate(LOT lot);
const std::optional<UpgradeTemplate*> GetUpgradeTemplate(LOT lot);
void SetAdditionalItemData(LWOOBJID id, AdditionalItemData data);
void SetAdditionalEntityData(LWOOBJID id, AdditionalEntityData data);

View File

@ -17,6 +17,7 @@
#include <PlayerManager.h>
#include <eGameMessageType.h>
#include <dServer.h>
#include <Item.h>
#include "NejlikaData.h"
@ -39,6 +40,7 @@ void nejlika::NejlikaHooks::InstallHooks()
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,
@ -51,7 +53,9 @@ void nejlika::NejlikaHooks::InstallHooks()
if (listOfHandledItems.find(itemType) == listOfHandledItems.end()) {
return;
}
*/
// No to the Thinking Hat
if (item->GetLot() == 6086) {
return;
}
@ -69,6 +73,24 @@ void nejlika::NejlikaHooks::InstallHooks()
additionalData.RollModifiers(item, levelProgressionComponent->GetLevel());
SetAdditionalItemData(item->GetId(), additionalData);
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;
}
auto& upgradeTemplate = *upgradeTemplateOpt.value();
entityData.AddUpgradeItem(item->GetId());
};
EntityManager::OnEntityCreated += [](Entity* entity) {
@ -89,6 +111,29 @@ void nejlika::NejlikaHooks::InstallHooks()
auto& additionalData = *additionalDataOpt.value();
additionalData.ApplyToEntity();
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (!inventoryComponent) {
return;
}
// Loop through all items and check if they are upgrade items
const auto& inventories = inventoryComponent->GetInventories();
for (const auto& [type, inventory] : inventories) {
for (const auto& [id, item] : inventory->GetItems()) {
const auto upgradeTemplateOpt = GetUpgradeTemplate(item->GetLot());
if (!upgradeTemplateOpt.has_value()) {
continue;
}
const auto& upgradeTemplate = *upgradeTemplateOpt.value();
additionalData.AddUpgradeItem(id);
}
}
};
EntityManager::OnEntityDestroyed += [](Entity* entity) {
@ -101,6 +146,38 @@ void nejlika::NejlikaHooks::InstallHooks()
InventoryComponent::OnItemDestroyed += [](InventoryComponent* component, Item* item) {
UnsetAdditionalItemData(item->GetId());
auto entityDataOpt = GetAdditionalEntityData(component->GetParent()->GetObjectID());
if (!entityDataOpt.has_value()) {
return;
}
auto& entityData = *entityDataOpt.value();
entityData.RemoveUpgradeItem(item->GetId());
};
LevelProgressionComponent::OnLevelUp += [](LevelProgressionComponent* component) {
auto* parent = component->GetParent();
auto entityDataOpt = GetAdditionalEntityData(parent->GetObjectID());
if (!entityDataOpt.has_value()) {
return;
}
auto& entityData = *entityDataOpt.value();
entityData.ApplyToEntity();
auto* inventoryComponent = parent->GetComponent<InventoryComponent>();
if (!inventoryComponent) {
return;
}
inventoryComponent->AddItem(2097253, 1, eLootSourceType::LEVEL_REWARD);
};
InventoryComponent::OnItemEquipped += [](InventoryComponent* component, Item* item) {
@ -260,6 +337,11 @@ void nejlika::NejlikaHooks::InstallHooks()
modifiers.insert(modifiers.end(), skillModifiers.begin(), skillModifiers.end());
}
// Upgrades
const auto upgradeModifiers = offenderEntity.TriggerUpgradeItems(UpgradeTriggerType::OnHit);
modifiers.insert(modifiers.end(), upgradeModifiers.begin(), upgradeModifiers.end());
std::unordered_set<ModifierType> damageTypes;
for (const auto& modifier : modifiers) {
@ -275,13 +357,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::Invalid);
uint32_t totalDamage = 0;
@ -291,11 +374,12 @@ void nejlika::NejlikaHooks::InstallHooks()
// Calculate resistance, can't go below 20% of the original damage
const auto resistance = std::max(1 - (damagedEntity.CalculateResistance(type) / 100), 0.2f);
damageValue *= resistance;
float reductedDamage = damageValue * resistance;
totalDamage += static_cast<uint32_t>(damageValue);
totalDamage += static_cast<uint32_t>(reductedDamage);
std::cout << "Damage type: " << magic_enum::enum_name(type) << " - " << damageValue << std::endl << " Resistance: " << resistance << std::endl;
std::cout << "Damage type: " << magic_enum::enum_name(type) << " - " << damageValue << std::endl;
std::cout << "Resistance: " << resistance << " - " << reductedDamage << std::endl;
std::cout << "Heath left: " << damaged->GetComponent<DestroyableComponent>()->GetHealth() << std::endl;
}
@ -325,12 +409,23 @@ void nejlika::NejlikaHooks::InstallHooks()
roll = std::min(roll, 5.0f);
totalDamage += static_cast<uint32_t>(totalDamage * roll);
const auto effectName = std::to_string(GeneralUtils::GenerateRandomNumber<uint32_t>());
const auto damagedID = damaged->GetObjectID();
GameMessages::SendPlayFXEffect(
damaged->GetObjectID(),
20041,
u"onhit",
std::to_string(GeneralUtils::GenerateRandomNumber<uint32_t>())
damagedID,
1531,
u"create",
effectName
);
damaged->AddCallbackTimer(0.5f, [damaged, effectName] () {
GameMessages::SendStopFXEffect(
damaged,
true,
effectName
);
});
}
// Add a random +10% to the damage

142
dGame/UpgradeEffect.cpp Normal file
View File

@ -0,0 +1,142 @@
#include "UpgradeEffect.h"
#include "GeneralUtils.h"
#include "GameMessages.h"
#include <magic_enum.hpp>
#include <iostream>
using namespace nejlika;
nejlika::UpgradeEffect::UpgradeEffect(const nlohmann::json& json)
{
Load(json);
}
nlohmann::json nejlika::UpgradeEffect::ToJson() const
{
nlohmann::json json;
json["trigger-type"] = static_cast<int32_t>(triggerType);
nlohmann::json modifiersJson = nlohmann::json::array();
for (const auto& modifier : modifiers) {
modifiersJson.push_back(modifier.ToJson());
}
json["modifiers"] = modifiersJson;
if (!chance.empty()) {
nlohmann::json chanceJson = nlohmann::json::array();
for (const auto& scale : chance) {
chanceJson.push_back({
{"level", scale.level},
{"value", scale.value}
});
}
json["chance"] = chanceJson;
}
if (effectID != 0) {
json["effect-id"] = effectID;
}
if (!effectType.empty()) {
json["effect-type"] = effectType;
}
return json;
}
std::vector<ModifierInstance> nejlika::UpgradeEffect::GenerateModifiers(int32_t level) const
{
std::vector<ModifierInstance> result;
for (const auto& modifier : modifiers) {
auto instances = modifier.GenerateModifiers(level);
result.insert(result.end(), instances.begin(), instances.end());
}
return result;
}
void nejlika::UpgradeEffect::Load(const nlohmann::json& json)
{
triggerType = magic_enum::enum_cast<UpgradeTriggerType>(json["trigger-type"].get<std::string>()).value_or(UpgradeTriggerType::OnHit);
modifiers.clear();
for (const auto& modifier : json["modifiers"]) {
ModifierTemplate effect(modifier);
modifiers.push_back(effect);
}
if (json.contains("chance")) {
chance.clear();
for (const auto& scale : json["chance"]) {
chance.push_back({
scale["level"].get<int32_t>(),
scale["value"].get<float>()
});
}
}
if (json.contains("effect-id")) {
effectID = json["effect-id"].get<int32_t>();
}
if (json.contains("effect-type")) {
effectType = json["effect-type"].get<std::string>();
}
}
float nejlika::UpgradeEffect::CalculateChance(int32_t level) const {
if (chance.empty()) {
return 1;
}
// Find the highest level that is less than or equal to the given level
float value = 0;
for (const auto& scale : chance) {
if (scale.level <= level) {
value = scale.value;
}
}
return value;
}
std::vector<ModifierInstance> nejlika::UpgradeEffect::Trigger(const std::vector<UpgradeEffect>& modifiers, int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin) {
std::vector<ModifierInstance> result;
for (const auto& modifier : modifiers) {
if (modifier.GetTriggerType() != triggerType) {
continue;
}
float chanceRoll = GeneralUtils::GenerateRandomNumber<float>(0, 1);
if (chanceRoll > modifier.CalculateChance(level)) {
continue;
}
auto instances = modifier.GenerateModifiers(level);
result.insert(result.end(), instances.begin(), instances.end());
GameMessages::SendPlayFXEffect(
origin,
modifier.effectID,
GeneralUtils::UTF8ToUTF16(modifier.effectType),
std::to_string(GeneralUtils::GenerateRandomNumber<size_t>())
);
}
return result;
}

46
dGame/UpgradeEffect.h Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include "ModifierTemplate.h"
#include "UpgradeTriggerType.h"
#include <dCommonVars.h>
namespace nejlika
{
class UpgradeEffect
{
public:
UpgradeEffect(const nlohmann::json& json);
nlohmann::json ToJson() const;
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
void Load(const nlohmann::json& json);
float CalculateChance(int32_t level) const;
static std::vector<ModifierInstance> Trigger(const std::vector<UpgradeEffect>& modifiers, int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin);
// Getters
const std::vector<ModifierTemplate>& GetModifiers() const { return modifiers; }
UpgradeTriggerType GetTriggerType() const { return triggerType; }
private:
struct UpgradeScale
{
int32_t level;
float value;
};
std::vector<UpgradeScale> chance;
UpgradeTriggerType triggerType;
std::vector<ModifierTemplate> modifiers;
int32_t effectID = 0;
std::string effectType = "";
};
}

49
dGame/UpgradeTemplate.cpp Normal file
View File

@ -0,0 +1,49 @@
#include "UpgradeTemplate.h"
using namespace nejlika;
nejlika::UpgradeTemplate::UpgradeTemplate(const nlohmann::json& json)
{
Load(json);
}
nlohmann::json nejlika::UpgradeTemplate::ToJson() const
{
nlohmann::json json;
json["name"] = name;
json["lot"] = lot;
json["max-level"] = maxLevel;
nlohmann::json passivesJson = nlohmann::json::array();
for (const auto& passive : passives) {
passivesJson.push_back(passive.ToJson());
}
json["passives"] = passivesJson;
return json;
}
void nejlika::UpgradeTemplate::Load(const nlohmann::json& json)
{
name = json["name"].get<std::string>();
lot = json["lot"].get<int32_t>();
maxLevel = json["max-level"].contains("max-level") ? json["max-level"].get<int32_t>() : 1;
passives.clear();
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 {
level = std::min(level, maxLevel);
return UpgradeEffect::Trigger(passives, level, triggerType, origin);
}

40
dGame/UpgradeTemplate.h Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include "json.hpp"
#include "UpgradeEffect.h"
namespace nejlika
{
class UpgradeTemplate
{
public:
UpgradeTemplate() = default;
UpgradeTemplate(const nlohmann::json& json);
nlohmann::json ToJson() const;
void Load(const nlohmann::json& json);
const std::string& GetName() const { return name; }
int32_t GetLot() const { return lot; }
int32_t GetMaxLevel() const { return maxLevel; }
const std::vector<UpgradeEffect>& GetPassives() const { return passives; }
std::vector<ModifierInstance> Trigger(int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin) const;
private:
std::string name = "";
int32_t lot = 0;
int32_t maxLevel = 0;
std::vector<UpgradeEffect> passives;
};
}

View File

@ -0,0 +1,13 @@
#pragma once
#include <cstdint>
namespace nejlika
{
enum class UpgradeTriggerType
{
OnHit,
};
}

View File

@ -24,7 +24,13 @@ void HealBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bit_strea
return;
}
destroyable->Heal(this->m_health);
auto maxHealth = destroyable->GetMaxHealth();
// 1 health is 5% of the max health, minimum of 5 health
auto health = static_cast<int32_t>(maxHealth * 0.05f) * this->m_health;
health = std::max(5u, health);
destroyable->Heal(health);
}

View File

@ -6,6 +6,8 @@
#include "CDRewardsTable.h"
Observable<LevelProgressionComponent*> LevelProgressionComponent::OnLevelUp;
LevelProgressionComponent::LevelProgressionComponent(Entity* parent) : Component(parent) {
m_Parent = parent;
m_Level = 1;
@ -80,6 +82,8 @@ void LevelProgressionComponent::HandleLevelUp() {
}
// Tell the client we have finished sending level rewards.
if (rewardingItem) GameMessages::NotifyLevelRewards(m_Parent->GetObjectID(), m_Parent->GetSystemAddress(), m_Level, !rewardingItem);
OnLevelUp(this);
}
void LevelProgressionComponent::SetRetroactiveBaseSpeed(){

View File

@ -5,6 +5,7 @@
#include "Component.h"
#include "eCharacterVersion.h"
#include "eReplicaComponentType.h"
#include "Observable.h"
/**
* Component that handles level progression and serilization.
@ -81,6 +82,8 @@ public:
*/
void SetRetroactiveBaseSpeed();
static Observable<LevelProgressionComponent*> OnLevelUp;
private:
/**
* whether the level is dirty

View File

@ -973,7 +973,7 @@ void GameMessages::SendResurrect(Entity* entity) {
if (levelComponent) {
int32_t healthToRestore = levelComponent->GetLevel() >= 45 ? 8 : 4;
if (healthToRestore > destroyableComponent->GetMaxHealth()) healthToRestore = destroyableComponent->GetMaxHealth();
destroyableComponent->SetHealth(healthToRestore);
destroyableComponent->SetHealth(destroyableComponent->GetMaxHealth());
int32_t imaginationToRestore = levelComponent->GetLevel() >= 45 ? 20 : 6;
if (imaginationToRestore > destroyableComponent->GetMaxImagination()) imaginationToRestore = destroyableComponent->GetMaxImagination();