Updated how upgrade adds skills

This commit is contained in:
wincent 2024-06-08 17:31:22 +02:00
parent 87613f287f
commit 756dc4e44f
21 changed files with 775 additions and 80 deletions

View File

@ -7,8 +7,11 @@
#include <InventoryComponent.h>
#include <BaseCombatAIComponent.h>
#include <TeamManager.h>
#include <ControllablePhysicsComponent.h>
#include <Item.h>
#include <queue>
using namespace nejlika;
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, ModifierOperator op, bool resistance) const
@ -117,6 +120,11 @@ float nejlika::AdditionalEntityData::CalculateResistance(ModifierType type) cons
return CalculateModifier(type, ModifierOperator::Multiplicative, true);
}
float nejlika::AdditionalEntityData::CalculateMultiplier(ModifierType type) const
{
return 1 + CalculateModifier(type, ModifierOperator::Multiplicative, false);
}
std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems(UpgradeTriggerType triggerType) {
auto* entity = Game::entityManager->GetEntity(id);
@ -155,6 +163,99 @@ std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems
return result;
}
void nejlika::AdditionalEntityData::InitializeSkills() {
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return;
}
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return;
}
struct entry {
LWOOBJID id;
int32_t priority;
entry(LWOOBJID id, int32_t priority) : id(id), priority(priority) {}
};
std::vector<entry> items;
for (const auto& itemID : upgradeItems) {
auto* item = inventoryComponent->FindItemById(itemID);
if (item == nullptr) {
continue;
}
const auto priority = item->GetSlot();
items.push_back(entry(itemID, priority));
}
std::sort(items.begin(), items.end(), [](const entry& a, const entry& b) {
return a.priority < b.priority;
});
for (const auto& item : items) {
AddSkills(item.id);
}
}
void nejlika::AdditionalEntityData::AddSkills(LWOOBJID item) {
if (!upgradeItems.contains(item)) {
return;
}
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return;
}
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return;
}
auto* itemData = inventoryComponent->FindItemById(item);
if (itemData == nullptr) {
return;
}
const auto upgradeDataOpt = NejlikaData::GetUpgradeTemplate(itemData->GetLot());
if (!upgradeDataOpt.has_value()) {
return;
}
const auto& upgradeData = *upgradeDataOpt.value();
LOG("Adding skills for item %i", id);
upgradeData.AddSkills(id);
}
void nejlika::AdditionalEntityData::RemoveSkills(LOT lot) {
const auto upgradeDataOpt = NejlikaData::GetUpgradeTemplate(lot);
if (!upgradeDataOpt.has_value()) {
return;
}
const auto& upgradeData = *upgradeDataOpt.value();
LOG("Removing skills for item %i", id);
upgradeData.RemoveSkills(id);
}
void nejlika::AdditionalEntityData::RollStandardModifiers(int32_t level) {
standardModifiers.clear();
@ -252,6 +353,11 @@ void nejlika::AdditionalEntityData::ApplyToEntity() {
destroyable->SetImagination(destroyable->GetMaxImagination());
}
if (entity->IsPlayer()) {
auto* controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>();
if (controllablePhysicsComponent) controllablePhysicsComponent->SetSpeedMultiplier(CalculateMultiplier(ModifierType::Speed));
}
initialized = true;
}

View File

@ -33,6 +33,14 @@ public:
float CalculateResistance(ModifierType type) const;
/**
* @brief Calculate the multiplier for a given modifier type. With a base value of 1.0.
*
* @param type The modifier type.
* @return The multiplier.
*/
float CalculateMultiplier(ModifierType type) const;
void ApplyToEntity();
void CheckForRescale(AdditionalEntityData* other);
@ -51,6 +59,11 @@ public:
std::vector<ModifierInstance> TriggerUpgradeItems(UpgradeTriggerType triggerType);
void InitializeSkills();
void AddSkills(LWOOBJID item);
void RemoveSkills(LOT lot);
private:
void RollStandardModifiers(int32_t level);

View File

@ -21,6 +21,7 @@ set(DGAME_SOURCES "Character.cpp"
"NejlikaHooks.cpp"
"UpgradeTemplate.cpp"
"UpgradeEffect.cpp"
"Lookup.cpp"
)
include_directories(

View File

@ -97,6 +97,7 @@
#include "CDZoneTableTable.h"
Observable<Entity*, const PositionUpdate&> Entity::OnPlayerPositionUpdate;
Observable<Entity*> Entity::OnReadyForUpdates;
Entity::Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser, Entity* parentEntity) {
m_ObjectID = objectID;
@ -1780,6 +1781,12 @@ void Entity::SetOwnerOverride(const LWOOBJID value) {
m_OwnerOverride = value;
}
void Entity::SetPlayerReadyForUpdates() {
m_PlayerIsReadyForUpdates = true;
OnReadyForUpdates(this);
}
bool Entity::GetIsGhostingCandidate() const {
return m_IsGhostingCandidate;
}

View File

@ -117,7 +117,7 @@ public:
void SetOwnerOverride(LWOOBJID value);
void SetPlayerReadyForUpdates() { m_PlayerIsReadyForUpdates = true; }
void SetPlayerReadyForUpdates();
void SetObservers(int8_t value);
@ -304,6 +304,8 @@ public:
* @brief The observable for player entity position updates.
*/
static Observable<Entity*, const PositionUpdate&> OnPlayerPositionUpdate;
static Observable<Entity*> OnReadyForUpdates;
protected:
LWOOBJID m_ObjectID;

181
dGame/Lookup.cpp Normal file
View File

@ -0,0 +1,181 @@
#include "Lookup.h"
#include <fstream>
#include "json.hpp"
#include <iostream>
using namespace nejlika;
Lookup::Lookup(const std::filesystem::path& lookup)
{
m_Lookup = lookup;
// Check if the file exists.
if (!std::filesystem::exists(lookup))
{
// Empty lookup
return;
}
// Read the json file
std::ifstream file(lookup);
std::string json((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
// Parse the json
auto doc = nlohmann::json::parse(json);
// The document is a map, so we can iterate over it.
for (const auto& [key, v] : doc.items()) {
if (v.is_number_integer())
{
m_LookupMap[key] = v.get<int64_t>();
continue;
}
if (!v.is_object()) {
std::stringstream ss;
ss << "Invalid value for key \"" << key << "\" in lookup.";
throw std::runtime_error(ss.str());
}
// Get the data
auto data = v.get<nlohmann::json>();
// Get the id
auto id = data["id"].get<int64_t>();
m_LookupMap[key] = id;
// Get the metadata
auto metadata = data["metadata"].get<std::string>();
m_Metadata[key] = metadata;
}
}
nejlika::Lookup::Lookup(const Lookup &other)
{
m_Lookup = other.m_Lookup;
m_LookupMap = other.m_LookupMap;
m_Metadata = other.m_Metadata;
m_CoreSymbols = other.m_CoreSymbols;
}
id Lookup::GetValue(const name& symbol) const
{
id value;
if (IsCoreSymbol(symbol, value)) {
return value;
}
const auto& it = m_LookupMap.find(symbol);
if (it == m_LookupMap.end())
{
std::stringstream ss;
ss << "Symbol \"" << symbol << "\" does not exist in the lookup.";
throw std::runtime_error(ss.str());
}
return it->second;
}
bool nejlika::Lookup::Exists(const name &symbol) const
{
return IsCoreSymbol(symbol) || (m_CoreSymbols.find(symbol) != m_CoreSymbols.end()) || (m_LookupMap.find(symbol) != m_LookupMap.end());
}
bool nejlika::Lookup::Exists(id value) const
{
for (const auto& [k, v] : m_LookupMap)
{
if (v == value)
{
return true;
}
}
return false;
}
const std::string& Lookup::GetMetadata(const name& symbol) const
{
const auto& it = m_Metadata.find(symbol);
if (it == m_Metadata.end())
{
static std::string empty;
return empty;
}
return it->second;
}
const std::filesystem::path& Lookup::GetLookup() const
{
return m_Lookup;
}
const std::unordered_map<name, id> &nejlika::Lookup::GetMap() const
{
return m_LookupMap;
}
bool nejlika::Lookup::IsCoreSymbol(const name &symbol, id &value) const
{
try {
value = std::stoi(symbol);
return true;
} catch (...) {
// cont...
}
if (!symbol.starts_with(core_prefix)) {
return false;
}
// Check in the core symbols
const auto& it = m_CoreSymbols.find(symbol);
if (it != m_CoreSymbols.end())
{
value = it->second;
return true;
}
// In the format "core:<value>"
try {
value = std::stoi(symbol.substr(core_prefix.size() + 1));
} catch (...) {
return false;
}
return true;
}
bool nejlika::Lookup::IsCoreSymbol(const name &symbol)
{
// Check if it can be converted to an integer
try {
[[maybe_unused]] auto value = std::stoi(symbol);
return true;
} catch (...) {
// cont...
}
return symbol.starts_with(core_prefix);
}

117
dGame/Lookup.h Normal file
View File

@ -0,0 +1,117 @@
#pragma once
#include <filesystem>
#include <functional>
#include <cstdint>
#include <unordered_map>
#include <unordered_set>
namespace nejlika {
typedef std::string name;
typedef int id;
/**
* @brief A one-way mapping between symbols (names) and their corresponding numerical values.
*
* This is an active lookup, meaning that it is possible to add new symbols and their values to the lookup.
*
* A method is provided to wait for a symbol to be added to the lookup.
*/
class Lookup
{
public:
inline static const name core_prefix = "lego-universe";
/**
* @brief Constructs a Lookup object with the specified lookup file path.
*
* @param lookup The path to the lookup file.
* @throw If the lookup file could not be parsed.
*/
Lookup(const std::filesystem::path& lookup);
Lookup(const Lookup& other);
Lookup() = default;
/**
* @brief Gets the value of the specified symbol.
*
* @param symbol The symbol to get the value of.
* @return The value of the specified symbol.
* @throw If the specified symbol does not exist.
*/
id GetValue(const name& symbol) const;
/**
* @brief Checks whether the specified symbol exists.
*
* @param symbol The symbol to check.
* @return Whether the specified symbol exists.
*/
bool Exists(const name& symbol) const;
/**
* @brief Checks whether any symbol has the specified value.
*
* @param value The value to check.
* @return Whether any symbol has the specified value.
*/
bool Exists(id value) const;
/**
* @brief Gets the metadata of a specified symbol.
*
* @param symbol The symbol to get metadata of.
* @return The metadata of the specified symbol or an empty string if the symbol does not exist.
*/
const std::string& GetMetadata(const name& symbol) const;
/**
* @brief Gets the path to the lookup file.
*
* @return The path to the lookup file.
*/
const std::filesystem::path& GetLookup() const;
/**
* @brief Gets the map of all symbols and their values.
*
* @return The map of all symbols and their values.
*/
const std::unordered_map<name, id>& GetMap() const;
/**
* @brief Checks whether the specified symbol is a core symbol.
*
* @param symbol The symbol to check.
* @param value The value of the core symbol.
* @return Whether the specified symbol is a core symbol.
*/
bool IsCoreSymbol(const name& symbol, id& value) const;
/**
* @brief Checks whether the specified symbol is a core symbol.
*
* A symbol is considered a core symbol if it is either:
* a number;
* or a string starting with the core_prefix.
*
* @param symbol The symbol to check.
* @return Whether the specified symbol is a core symbol.
*/
static bool IsCoreSymbol(const name& symbol);
private:
std::filesystem::path m_Lookup;
std::unordered_map<name, id> m_LookupMap;
std::unordered_map<name, std::string> m_Metadata;
std::unordered_map<name, id> m_CoreSymbols;
};
} // namespace nejlika

View File

@ -24,10 +24,16 @@ enum class ModifierType : uint8_t
Lightning,
Corruption,
Elemental,
Psychic,
Damage,
Speed,
AttackSpeed,
Invalid
};

View File

@ -4,6 +4,7 @@
#include "dConfig.h"
#include "json.hpp"
#include "Logger.h"
#include "Lookup.h"
#include <fstream>
@ -22,6 +23,13 @@ std::unordered_map<LOT, nejlika::EntityTemplate> entityTemplates;
std::unordered_map<LOT, nejlika::UpgradeTemplate> upgradeTemplates;
nejlika::Lookup lookup;
}
const nejlika::Lookup& nejlika::NejlikaData::GetLookup()
{
return lookup;
}
const std::unordered_map<ModifierNameType,std::vector<ModifierNameTemplate>>& nejlika::NejlikaData::GetModifierNameTemplates()
@ -171,5 +179,12 @@ void nejlika::NejlikaData::LoadNejlikaData()
upgradeTemplates[upgradeTemplate.GetLot()] = upgradeTemplate;
}
}
const auto& lookupFile = Game::config->GetValue("lookup");
if (!lookupFile.empty())
{
lookup = Lookup(lookupFile);
}
}

View File

@ -8,6 +8,7 @@
#include "AdditionalItemData.h"
#include "AdditionalEntityData.h"
#include "UpgradeTemplate.h"
#include "Lookup.h"
namespace nejlika::NejlikaData
{
@ -34,4 +35,6 @@ void UnsetAdditionalEntityData(LWOOBJID id);
void LoadNejlikaData();
const Lookup& GetLookup();
}

View File

@ -18,6 +18,9 @@
#include <eGameMessageType.h>
#include <dServer.h>
#include <Item.h>
#include <SkillComponent.h>
#include <MissionComponent.h>
#include <eMissionState.h>
#include "NejlikaData.h"
@ -91,8 +94,7 @@ void nejlika::NejlikaHooks::InstallHooks()
auto& upgradeTemplate = *upgradeTemplateOpt.value();
entityData.AddUpgradeItem(item->GetId());
entityData.TriggerUpgradeItems(UpgradeTriggerType::Equip);
entityData.AddSkills(item->GetId());
};
EntityManager::OnEntityCreated += [](Entity* entity) {
@ -137,7 +139,26 @@ void nejlika::NejlikaHooks::InstallHooks()
}
}
additionalData.TriggerUpgradeItems(UpgradeTriggerType::Equip);
additionalData.InitializeSkills();
};
Entity::OnReadyForUpdates += [](Entity* entity) {
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:tertiary"), BehaviorSlot::Neck);
auto* missionComponent = entity->GetComponent<MissionComponent>();
if (missionComponent) {
if (missionComponent->GetMissionState(1732) != eMissionState::COMPLETE) {
missionComponent->CompleteMission(1732, true, false);
}
}
};
EntityManager::OnEntityDestroyed += [](Entity* entity) {
@ -149,17 +170,18 @@ void nejlika::NejlikaHooks::InstallHooks()
};
InventoryComponent::OnItemDestroyed += [](InventoryComponent* component, Item* item) {
UnsetAdditionalItemData(item->GetId());
auto entityDataOpt = GetAdditionalEntityData(component->GetParent()->GetObjectID());
if (!entityDataOpt.has_value()) {
UnsetAdditionalItemData(item->GetId());
return;
}
auto& entityData = *entityDataOpt.value();
entityData.RemoveUpgradeItem(item->GetId());
entityData.RemoveSkills(item->GetLot());
UnsetAdditionalItemData(item->GetId());
};
LevelProgressionComponent::OnLevelUp += [](LevelProgressionComponent* component) {
@ -181,7 +203,7 @@ void nejlika::NejlikaHooks::InstallHooks()
return;
}
inventoryComponent->AddItem(2097253, 1, eLootSourceType::LEVEL_REWARD);
inventoryComponent->AddItem(NejlikaData::GetLookup().GetValue("intro:upgrades:level-token"), 1, eLootSourceType::MODERATION);
};
InventoryComponent::OnItemEquipped += [](InventoryComponent* component, Item* item) {
@ -243,6 +265,147 @@ void nejlika::NejlikaHooks::InstallHooks()
entityData.ApplyToEntity();
};
SkillComponent::OnSkillCast += [](SkillComponent* skillComponent, uint32_t skillID, bool success) {
std::cout << "Skill cast: " << skillID << " - " << success << std::endl;
auto* inventoryComponent = skillComponent->GetParent()->GetComponent<InventoryComponent>();
if (!inventoryComponent) {
return;
}
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
{
/*
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);
}
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);
});
};
DestroyableComponent::OnDamageCalculation += [](Entity* damaged, LWOOBJID offender, uint32_t skillID, uint32_t& damage) {
std::cout << "Calculating damage with skill: " << skillID << std::endl;
@ -299,7 +462,14 @@ void nejlika::NejlikaHooks::InstallHooks()
std::cout << "Found " << skills.size() << " skills." << std::endl;
// omg...
for (const auto& [slot, skill] : skills) {
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) {
@ -372,6 +542,8 @@ void nejlika::NejlikaHooks::InstallHooks()
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;
@ -441,8 +613,10 @@ void nejlika::NejlikaHooks::InstallHooks()
damage = totalDamage;
auto attackSpeed = offenderEntity.CalculateModifier(ModifierType::AttackSpeed, modifiers, level);
if (offfendEntity->IsPlayer()) {
offfendEntity->AddCallbackTimer(0.0f, [offfendEntity, skillID]() {
offfendEntity->AddCallbackTimer(0.0f, [offfendEntity, skillID, attackSpeed]() {
CBITSTREAM;
CMSGHEADER;
@ -452,7 +626,7 @@ void nejlika::NejlikaHooks::InstallHooks()
bitStream.Write(eGameMessageType::MODIFY_SKILL_COOLDOWN);
bitStream.Write1();
bitStream.Write<float>(-10.0f);
bitStream.Write<float>(attackSpeed);
bitStream.Write<int32_t>(static_cast<int32_t>(skillID));
LOG("Sending cooldown reduction for skill: %d", skillID);

View File

@ -63,14 +63,6 @@ nlohmann::json nejlika::UpgradeEffect::ToJson() const
json["grant-skill-id"] = equipSkillID;
}
if (equipSkillSlot != BehaviorSlot::Invalid) {
json["grant-skill-slot"] = magic_enum::enum_name(equipSkillSlot);
}
if (unequipSkill) {
json["unequip-skill"] = true;
}
return json;
}
@ -130,14 +122,6 @@ void nejlika::UpgradeEffect::Load(const nlohmann::json& json)
if (json.contains("grant-skill-id")) {
equipSkillID = json["grant-skill-id"].get<int32_t>();
}
if (json.contains("grant-skill-slot")) {
equipSkillSlot = magic_enum::enum_cast<BehaviorSlot>(json["grant-skill-slot"].get<std::string>()).value_or(BehaviorSlot::Invalid);
}
if (json.contains("unequip-skill")) {
unequipSkill = json["unequip-skill"].get<bool>();
}
}
float nejlika::UpgradeEffect::CalculateChance(int32_t level) const {
@ -227,16 +211,6 @@ void nejlika::UpgradeEffect::OnTrigger(LWOOBJID origin) const {
if (!inventory) {
return;
}
if (equipSkillID != 0) {
std::cout << "Granting skill: " << equipSkillID << " to entity: " << origin << " in slot: " << magic_enum::enum_name(equipSkillSlot) << std::endl;
inventory->SetSkill(equipSkillSlot, equipSkillID);
}
if (unequipSkill) {
std::cout << "Unequipping skill from entity: " << origin << " in slot: " << magic_enum::enum_name(equipSkillSlot) << std::endl;
inventory->ResetSkill(equipSkillSlot);
}
}
std::vector<ModifierInstance> nejlika::UpgradeEffect::Trigger(const std::vector<UpgradeEffect>& modifiers, int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin) {
@ -279,3 +253,47 @@ std::vector<ModifierInstance> nejlika::UpgradeEffect::Trigger(const std::vector<
return result;
}
void nejlika::UpgradeEffect::AddSkill(LWOOBJID origin) const {
auto* entity = Game::entityManager->GetEntity(origin);
if (!entity) {
return;
}
auto* inventory = entity->GetComponent<InventoryComponent>();
if (!inventory) {
return;
}
if (triggerType != UpgradeTriggerType::Active) {
return;
}
if (equipSkillID != 0) {
inventory->SetSkill(equipSkillID);
}
}
void nejlika::UpgradeEffect::RemoveSkill(LWOOBJID origin) const {
auto* entity = Game::entityManager->GetEntity(origin);
if (!entity) {
return;
}
auto* inventory = entity->GetComponent<InventoryComponent>();
if (!inventory) {
return;
}
if (triggerType != UpgradeTriggerType::Active) {
return;
}
if (equipSkillID != 0) {
inventory->UnsetSkill(equipSkillID);
}
}

View File

@ -35,6 +35,9 @@ public:
UpgradeTriggerType GetTriggerType() const { return triggerType; }
void AddSkill(LWOOBJID origin) const;
void RemoveSkill(LWOOBJID origin) const;
private:
struct UpgradeScale
{
@ -46,8 +49,6 @@ private:
std::vector<UpgradeTriggerCondition> conditions;
UpgradeTriggerType triggerType;
int32_t equipSkillID = 0;
bool unequipSkill = false;
BehaviorSlot equipSkillSlot = BehaviorSlot::Invalid;
std::vector<ModifierTemplate> modifiers;
int32_t effectID = 0;
std::string effectType = "";

View File

@ -46,4 +46,15 @@ std::vector<ModifierInstance> nejlika::UpgradeTemplate::Trigger(int32_t level, U
return UpgradeEffect::Trigger(passives, level, triggerType, origin);
}
void nejlika::UpgradeTemplate::AddSkills(LWOOBJID origin) const {
for (const auto& passive : passives) {
passive.AddSkill(origin);
}
}
void nejlika::UpgradeTemplate::RemoveSkills(LWOOBJID origin) const {
for (const auto& passive : passives) {
passive.RemoveSkill(origin);
}
}

View File

@ -29,6 +29,9 @@ public:
std::vector<ModifierInstance> Trigger(int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin) const;
void AddSkills(LWOOBJID origin) const;
void RemoveSkills(LWOOBJID origin) const;
private:
std::string name = "";
int32_t lot = 0;

View File

@ -10,6 +10,7 @@ enum class UpgradeTriggerType
OnHit,
Equip,
UnEquip,
Active
};
}

View File

@ -516,7 +516,7 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID
Database::Get()->UpdateLastLoggedInCharacter(playerID);
uint32_t zoneID = character->GetZoneID();
if (zoneID == LWOZONEID_INVALID) zoneID = 1000; //Send char to VE
if (zoneID == LWOZONEID_INVALID) zoneID = 1100; //Send char to AG, skip VE
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, zoneID, character->GetZoneClone(), false, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", character->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);

View File

@ -1127,15 +1127,31 @@ void InventoryComponent::AddItemSkills(const LOT lot) {
const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
if (slot == BehaviorSlot::Invalid) {
if (slot != BehaviorSlot::Primary) {
return;
}
const auto skill = FindSkill(lot);
const auto index = m_Skills.find(slot);
const auto skill = FindSkill(lot);
if (index != m_Skills.end()) {
const auto old = index->second;
SetSkill(slot, skill);
if (!old.empty()) {
const auto firstElem = *old.begin();
GameMessages::SendRemoveSkill(m_Parent, firstElem);
}
}
m_Skills.erase(slot);
if (skill != 0) {
m_Skills.insert_or_assign(slot, std::set<uint32_t>{ skill });
GameMessages::SendAddSkill(m_Parent, skill, slot);
}
}
void InventoryComponent::RemoveItemSkills(const LOT lot) {
@ -1143,7 +1159,7 @@ void InventoryComponent::RemoveItemSkills(const LOT lot) {
const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
if (slot == BehaviorSlot::Invalid) {
if (slot != BehaviorSlot::Primary) {
return;
}
@ -1155,12 +1171,16 @@ void InventoryComponent::RemoveItemSkills(const LOT lot) {
const auto old = index->second;
GameMessages::SendRemoveSkill(m_Parent, old);
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, 1);
m_Skills.insert_or_assign(BehaviorSlot::Primary, std::set<uint32_t>{ 1 });
GameMessages::SendAddSkill(m_Parent, 1, BehaviorSlot::Primary);
}
@ -1608,40 +1628,46 @@ bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId) {
bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) {
if (skillId == 0) return false;
const auto index = m_Skills.find(slot);
if (index != m_Skills.end()) {
const auto old = index->second;
GameMessages::SendRemoveSkill(m_Parent, old);
if (index == m_Skills.end()) {
m_Skills.insert_or_assign(slot, std::set<uint32_t>{ skillId });
} else {
auto& existing = index->second;
existing.insert(skillId);
}
GameMessages::SendAddSkill(m_Parent, skillId, slot);
m_Skills.insert_or_assign(slot, skillId);
return true;
}
void InventoryComponent::UnsetSkill(BehaviorSlot slot) {
const auto index = m_Skills.find(slot);
if (index == m_Skills.end()) return;
const auto old = index->second;
GameMessages::SendRemoveSkill(m_Parent, old);
m_Skills.erase(slot);
}
void InventoryComponent::ResetSkill(BehaviorSlot slot) {
const auto index = m_Skills.find(slot);
if (index == m_Skills.end()) return;
const auto old = index->second;
GameMessages::SendRemoveSkill(m_Parent, old);
m_Skills.erase(slot);
// Loop through all equipped items and find the first item that can be equipped in the slot
for (const auto& pair : m_Equipped) {
const auto item = pair.second;
const auto info = Inventory::FindItemComponent(item.lot);
const auto behaviorSlot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
if (behaviorSlot == slot) {
SetSkill(slot, FindSkill(item.lot));
return;
}
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);
}

View File

@ -368,12 +368,12 @@ public:
*/
void UnequipScripts(Item* unequippedItem);
std::map<BehaviorSlot, uint32_t> GetSkills(){ return m_Skills; };
const std::map<BehaviorSlot, std::set<uint32_t>>& GetSkills(){ return m_Skills; };
bool SetSkill(int slot, uint32_t skillId);
bool SetSkill(int32_t slot, uint32_t skillId);
bool SetSkill(BehaviorSlot slot, uint32_t skillId);
void UnsetSkill(BehaviorSlot slot);
void ResetSkill(BehaviorSlot slot);
void UnsetSkill(uint32_t skillId);
void SetSkill(uint32_t skillId);
~InventoryComponent() override;
@ -389,10 +389,12 @@ private:
*/
std::map<eInventoryType, Inventory*> m_Inventories;
std::map<BehaviorSlot, uint32_t> m_ActivatorSkills;
/**
* The skills that this entity currently has active
*/
std::map<BehaviorSlot, uint32_t> m_Skills;
std::map<BehaviorSlot, std::set<uint32_t>> m_Skills;
/**
* The pets this entity has, mapped by object ID and pet info

View File

@ -31,6 +31,8 @@ ProjectileSyncEntry::ProjectileSyncEntry() {
std::unordered_map<uint32_t, uint32_t> SkillComponent::m_skillBehaviorCache = {};
Observable<SkillComponent*, uint32_t, bool> 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());
@ -48,6 +50,8 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s
context->ExecuteUpdates();
OnSkillCast(this, skillID, !context->failed);
return !context->failed;
}

View File

@ -14,6 +14,7 @@
#include "Entity.h"
#include "Logger.h"
#include "eReplicaComponentType.h"
#include "Observable.h"
struct ProjectileSyncEntry {
LWOOBJID id = LWOOBJID_EMPTY;
@ -184,6 +185,9 @@ public:
*/
uint32_t GetUniqueSkillId();
// SkillComponent, SkillID, Success
static Observable<SkillComponent*, uint32_t, bool> OnSkillCast;
private:
/**
* All of the active skills mapped by their unique ID.