#include "NejlikaHooks.h" #include "SlashCommandHandler.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "NejlikaData.h" using namespace nejlika; using namespace nejlika::NejlikaData; void nejlika::NejlikaHooks::InstallHooks() { Command itemDescriptionCommand{ .help = "Special UI command, does nothing when used in chat.", .info = "Special UI command, does nothing when used in chat.", .aliases = {"d"}, .handle = ItemDescription, .requiredLevel = eGameMasterLevel::CIVILIAN }; SlashCommandHandler::RegisterCommand(itemDescriptionCommand); LoadNejlikaData(); InventoryComponent::OnItemCreated += [](InventoryComponent* component, Item* item) { const auto& itemType = static_cast(item->GetInfo().itemType); /* static const std::unordered_set 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; } auto* levelProgressionComponent = component->GetParent()->GetComponent(); if (!levelProgressionComponent) { return; } auto additionalData = AdditionalItemData(item); LOG("Rolling modifiers for item: %d", item->GetLot()); 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()); entityData.TriggerUpgradeItems(UpgradeTriggerType::Equip); }; EntityManager::OnEntityCreated += [](Entity* entity) { auto* destroyable = entity->GetComponent(); if (!destroyable) { return; } SetAdditionalEntityData(entity->GetObjectID(), AdditionalEntityData(entity->GetObjectID(), entity->GetLOT())); auto additionalDataOpt = GetAdditionalEntityData(entity->GetObjectID()); if (!additionalDataOpt.has_value()) { return; } auto& additionalData = *additionalDataOpt.value(); additionalData.ApplyToEntity(); auto* inventoryComponent = entity->GetComponent(); 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); } } additionalData.TriggerUpgradeItems(UpgradeTriggerType::Equip); }; EntityManager::OnEntityDestroyed += [](Entity* entity) { UnsetAdditionalEntityData(entity->GetObjectID()); }; InventoryComponent::OnItemLoaded += [](InventoryComponent* component, Item* item) { SetAdditionalItemData(item->GetId(), AdditionalItemData(item)); }; 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(); if (!inventoryComponent) { return; } inventoryComponent->AddItem(2097253, 1, eLootSourceType::LEVEL_REWARD); }; InventoryComponent::OnItemEquipped += [](InventoryComponent* component, Item* item) { std::cout << "Item equipped: " << item->GetId() << std::endl; const auto entityDataOpt = GetAdditionalEntityData(component->GetParent()->GetObjectID()); if (!entityDataOpt.has_value()) { std::cout << "No entity data found for entity." << std::endl; return; } auto& entityData = *entityDataOpt.value(); entityData.TriggerUpgradeItems(UpgradeTriggerType::Equip); entityData.ApplyToEntity(); const auto itemDataOpt = GetAdditionalItemData(item->GetId()); if (!itemDataOpt.has_value()) { std::cout << "No item data found for item." << std::endl; return; } const auto& itemData = *itemDataOpt.value(); const auto itemId = item->GetId(); std::cout << "Sending effects for item: " << itemId << " with " << itemData.GetModifierInstances().size() << " modifiers." << std::endl; for (const auto& modifier : itemData.GetModifierInstances()) { const auto effectID = modifier.GetEffectID(); const auto effectType = modifier.GetEffectType(); component->GetParent()->AddCallbackTimer(0.1f, [itemId, effectID, effectType]() { std::cout << "Sending effect: " << effectID << " - " << effectType << std::endl; GameMessages::SendPlayFXEffect( itemId, static_cast(effectID), GeneralUtils::UTF8ToUTF16(effectType), std::to_string(GeneralUtils::GenerateRandomNumber()) ); }); } }; InventoryComponent::OnItemUnequipped += [](InventoryComponent* component, Item* item) { const auto entityDataOpt = GetAdditionalEntityData(component->GetParent()->GetObjectID()); if (!entityDataOpt.has_value()) { return; } auto& entityData = *entityDataOpt.value(); entityData.TriggerUpgradeItems(UpgradeTriggerType::UnEquip); entityData.ApplyToEntity(); }; DestroyableComponent::OnDamageCalculation += [](Entity* damaged, LWOOBJID offender, uint32_t skillID, uint32_t& damage) { std::cout << "Calculating damage with skill: " << skillID << std::endl; const auto damagedEntityOpt = GetAdditionalEntityData(damaged->GetObjectID()); if (!damagedEntityOpt.has_value()) { std::cout << "No entity data found for damaged entity." << std::endl; return; } auto& damagedEntity = *damagedEntityOpt.value(); const auto offenderEntityOpt = GetAdditionalEntityData(offender); if (!offenderEntityOpt.has_value()) { std::cout << "No entity data found for offender entity." << std::endl; return; } auto& offenderEntity = *offenderEntityOpt.value(); auto* baseCombatAIComponent = damaged->GetComponent(); if (baseCombatAIComponent) { baseCombatAIComponent->SetThreat(offender, 1); } damagedEntity.CheckForRescale(&offenderEntity); offenderEntity.CheckForRescale(&damagedEntity); int32_t level = offenderEntity.GetLevel(); auto* offfendEntity = Game::entityManager->GetEntity(offender); if (offfendEntity == nullptr) { std::cout << "Offender entity not found." << std::endl; return; } auto* levelProgressionComponent = offfendEntity->GetComponent(); if (levelProgressionComponent) { level = levelProgressionComponent->GetLevel(); } LOT itemLot = 0; LWOOBJID itemId = 0; auto* inventoryComponent = offfendEntity->GetComponent(); if (inventoryComponent) { const auto& skills = inventoryComponent->GetSkills(); std::cout << "Found " << skills.size() << " skills." << std::endl; // omg... for (const auto& [slot, skill] : skills) { std::cout << "Found skill: " << skill << std::endl; if (skill != skillID) { continue; } const auto& equipped = inventoryComponent->GetEquippedItems(); for (const auto& [equippedSlot, itemDetails] : equipped) { std::cout << "Found equipped item: " << itemDetails.lot << std::endl; const auto info = Inventory::FindItemComponent(itemDetails.lot); const auto itemBehaviorSlot = InventoryComponent::FindBehaviorSlot(static_cast(info.itemType)); std::cout << "Comparing slots: " << static_cast(itemBehaviorSlot) << " - " << static_cast(slot) << std::endl; if (itemBehaviorSlot == slot) { itemLot = itemDetails.lot; itemId = itemDetails.id; std::cout << "Found item: " << itemLot << std::endl; break; } } } } 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 modifiers; if (skillTemplateIt != skillTemplates.end()) { const auto& skillTemplate = *skillTemplateIt; const auto skillModifiers = skillTemplate.GenerateModifiers(level); 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 damageTypes; for (const auto& modifier : modifiers) { damageTypes.insert(modifier.GetType()); } const auto itemDataOpt = GetAdditionalItemData(itemId); if (itemDataOpt.has_value()) { const auto& itemData = *itemDataOpt.value(); for (const auto& modifier : itemData.GetModifierInstances()) { 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; for (const auto& type : damageTypes) { float damageValue = offenderEntity.CalculateModifier(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); float reductedDamage = damageValue * resistance; totalDamage += static_cast(reductedDamage); 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()->GetHealth() << std::endl; } // Get the offenders Offensive modifier auto offenderModifiers = offenderEntity.CalculateModifier(ModifierType::Offensive, level); // Get the defenders Defensive modifier auto defensiveModifiers = damagedEntity.CalculateModifier(ModifierType::Defensive, level); if (offenderModifiers == 0) offenderModifiers = 1; if (defensiveModifiers == 0) defensiveModifiers = 1; auto ratio = offenderModifiers / defensiveModifiers; // Ratio can not ge below 1.05 ratio = std::max(ratio, 1.05f); // Roll a number between 0 and ratio float roll = GeneralUtils::GenerateRandomNumber() / static_cast(std::numeric_limits::max()); roll *= ratio; std::cout << "Offensive: " << offenderModifiers << " Defensive: " << defensiveModifiers << " Ratio: " << ratio << " Roll: " << roll << std::endl; // If the roll is above 1, the damage is increased by 1+roll, to a maximum of 5x the damage if (roll > 1) { roll = std::min(roll, 5.0f); totalDamage += static_cast(totalDamage * roll); const auto effectName = std::to_string(GeneralUtils::GenerateRandomNumber()); const auto damagedID = damaged->GetObjectID(); GameMessages::SendPlayFXEffect( damagedID, 1531, u"create", effectName ); damaged->AddCallbackTimer(0.5f, [damaged, effectName] () { GameMessages::SendStopFXEffect( damaged, true, effectName ); }); } // Add a random +10% to the damage totalDamage += static_cast(totalDamage * (GeneralUtils::GenerateRandomNumber(0, 10) / 100.0f)); damage = totalDamage; if (offfendEntity->IsPlayer()) { offfendEntity->AddCallbackTimer(0.0f, [offfendEntity, skillID]() { CBITSTREAM; CMSGHEADER; const auto entity = offfendEntity->GetObjectID(); bitStream.Write(entity); bitStream.Write(eGameMessageType::MODIFY_SKILL_COOLDOWN); bitStream.Write1(); bitStream.Write(-10.0f); bitStream.Write(static_cast(skillID)); LOG("Sending cooldown reduction for skill: %d", skillID); SEND_PACKET_BROADCAST; }); } }; } void nejlika::NejlikaHooks::ItemDescription(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto splitArgs = GeneralUtils::SplitString(args, ' '); if (splitArgs.size() < 2) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid arguments."); return; } auto requestId = GeneralUtils::TryParse(splitArgs[0]).value_or(-1); if (requestId == -1) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID."); return; } std::cout << "Request ID: " << requestId << std::endl; auto itemId = GeneralUtils::TryParse(splitArgs[1]).value_or(LWOOBJID_EMPTY); if (itemId == LWOOBJID_EMPTY) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid item ID."); return; } const auto itemDataOpt = GetAdditionalItemData(itemId); if (!itemDataOpt.has_value()) { return; } auto& itemDetails = *itemDataOpt.value(); const auto& modifiers = itemDetails.GetModifierInstances(); const auto& names = itemDetails.GetModifierNames(); if (modifiers.empty() && names.empty()) { return; } std::stringstream name; std::stringstream desc; name << "NAME"; desc << ModifierName::GenerateHtmlString(names) << "\n"; desc << ModifierInstance::GenerateHtmlString(modifiers); std::cout << "Sending item name: " << name.str() << std::endl; std::cout << "Sending item desc: " << desc.str() << std::endl; std::stringstream messageName; messageName << "desc" << requestId; AMFArrayValue amfArgs; amfArgs.Insert("t", true); amfArgs.Insert("d", desc.str()); amfArgs.Insert("n", name.str()); GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, messageName.str(), amfArgs); std::cout << "Sent item description." << std::endl; }