#include "../common/evolving_items.h" #include "../common/events/player_event_logs.h" #include "../common/global_define.h" #include "client.h" #include "string_ids.h" #include "worldserver.h" extern WorldServer worldserver; extern QueryServ* QServ; const std::string SUB_TYPE_DELIMITER = "."; void Client::DoEvolveItemToggle(const EQApplicationPacket *app) { const auto in = reinterpret_cast(app->pBuffer); auto item = CharacterEvolvingItemsRepository::FindOne(database, in->unique_id); LogEvolveItemDetail( "Character ID [{}] requested to set evolve item with unique id [{}] to status [{}]", CharacterID(), in->unique_id, in->activated ); if (!item.id) { LogEvolveItemDetail( "Character ID [{}] toggle evolve item unique id [{}] failed", CharacterID(), in->unique_id); return; } item.activated = in->activated; const auto inst = GetInv().GetItem(GetInv().HasItem(item.item_id)); inst->SetEvolveActivated(item.activated ? true : false); CharacterEvolvingItemsRepository::ReplaceOne(database, item); SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, item); } void Client::SendEvolvingPacket(const int8 action, const CharacterEvolvingItemsRepository::CharacterEvolvingItems &item) { auto out = std::make_unique(OP_EvolveItem, sizeof(EvolveItemToggle)); const auto data = reinterpret_cast(out->pBuffer); LogEvolveItemDetail( "Character ID [{}] requested info for evolving item with unique id [{}] status [{}] " "percentage [{}]", CharacterID(), item.id, item.activated, item.progression ); data->action = action; data->unique_id = item.id; data->percentage = item.progression; data->activated = item.activated; QueuePacket(out.get()); LogEvolveItem( "Sent evolve item with unique id [{}] status [{}] percentage [{}] to Character ID " "[{}]", data->unique_id, data->activated, data->percentage, CharacterID() ); } void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob) { std::vector queue{}; for (auto &[key, inst]: GetInv().GetWorn()) { LogEvolveItemDetail( "CharacterID [{}] found equipped item ID [{}]", CharacterID(), inst->GetID()); if (!inst->IsEvolving() || !inst->GetEvolveActivated()) { LogEvolveItemDetail( "CharacterID [{}], item ID [{}] not an evolving item.", CharacterID(), inst->GetID() ); continue; } if (inst->GetTimers().contains("evolve") && !inst->GetTimers().at("evolve").Check(false)) { LogEvolveItemDetail( "CharacterID [{}], item ID [{}] timer not yet expired. [{}] secs remaining.", CharacterID(), inst->GetID(), inst->GetTimers().at("evolve").GetRemainingTime() / 1000); continue; } if (!EvolvingItemsManager::Instance()->GetEvolvingItemsCache().contains(inst->GetID())) { LogEvolveItem( "Character ID {} has an evolving item that is not found in the db. Please check your " "items_evolving_details table for item id {}", CharacterID(), inst->GetID() ); continue; } auto const type = EvolvingItemsManager::Instance()->GetEvolvingItemsCache().at(inst->GetID()).type; auto const sub_type = EvolvingItemsManager::Instance()->GetEvolvingItemsCache().at(inst->GetID()).sub_type; LogEvolveItemDetail( "CharacterID [{}] item id [{}] type {} sub_type {} is Evolving. Continue processing...", CharacterID(), inst->GetID(), type, sub_type ); auto sub_types = Strings::Split(sub_type, SUB_TYPE_DELIMITER); auto has_sub_type = [&](uint32_t type) { return Strings::Contains(sub_types, std::to_string(type)); }; switch (type) { case EvolvingItems::Types::AMOUNT_OF_EXP: { LogEvolveItemDetail("Type [{}] Processing sub_type", type); // Determine the evolve amount based on sub_type conditions int evolve_amount = 0; if (has_sub_type(EvolvingItems::SubTypes::ALL_EXP) || (has_sub_type(EvolvingItems::SubTypes::GROUP_EXP) && IsGrouped())) { evolve_amount = exp * RuleR(EvolvingItems, PercentOfGroupExperience) / 100; } else if (has_sub_type(EvolvingItems::SubTypes::ALL_EXP) || (has_sub_type(EvolvingItems::SubTypes::RAID_EXP) && IsRaidGrouped())) { evolve_amount = exp * RuleR(EvolvingItems, PercentOfRaidExperience) / 100; } else if (has_sub_type(EvolvingItems::SubTypes::ALL_EXP) || has_sub_type(EvolvingItems::SubTypes::SOLO_EXP)) { evolve_amount = exp * RuleR(EvolvingItems, PercentOfSoloExperience) / 100; } inst->SetEvolveAddToCurrentAmount(evolve_amount); inst->CalculateEvolveProgression(); auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression( database, inst->GetEvolveUniqueID(), inst->GetEvolveCurrentAmount(), inst->GetEvolveProgression() ); if (!e.id) { break; } SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e); LogEvolveItem( "Processing Complete for item id [{1}] Type 1 Amount of EXP - SubType [{0}] - " "Assigned [{2}] of exp to [{1}]", sub_type, inst->GetID(), exp * 0.001 ); if (inst->GetEvolveProgression() >= 100) { queue.push_back(inst); } break; } case EvolvingItems::Types::SPECIFIC_MOB_RACE: { LogEvolveItemDetail("Type [{}] Processing sub type", type); if (mob && has_sub_type(mob->GetRace())) { LogEvolveItemDetail("Sub_Type [{}] Processing Item", sub_type); inst->SetEvolveAddToCurrentAmount(1); inst->CalculateEvolveProgression(); auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression( database, inst->GetEvolveUniqueID(), inst->GetEvolveCurrentAmount(), inst->GetEvolveProgression() ); if (!e.id) { break; } SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e); LogEvolveItem( "Processing Complete for item id [{1}] Type 3 Specific Mob Race - SubType " "[{0}] " "- Increased count by 1 for [{1}]", sub_type, inst->GetID() ); } if (inst->GetEvolveProgression() >= 100) { queue.push_back(inst); } break; } case EvolvingItems::Types::SPECIFIC_ZONE_ID: { LogEvolveItemDetail("Type [{}] Processing sub type", type); if (mob && has_sub_type(mob->GetZoneID())) { LogEvolveItemDetail("Sub_Type [{}] Processing Item", sub_type); inst->SetEvolveAddToCurrentAmount(1); inst->CalculateEvolveProgression(); auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression( database, inst->GetEvolveUniqueID(), inst->GetEvolveCurrentAmount(), inst->GetEvolveProgression() ); if (!e.id) { break; } SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e); LogEvolveItem( "Processing Complete for item id [{1}] Type 4 Specific Zone ID - SubType " "[{0}] " "- Increased count by 1 for [{1}]", sub_type, inst->GetID() ); } if (inst->GetEvolveProgression() >= 100) { queue.push_back(inst); } break; } case EvolvingItems::Types::NUMBER_OF_KILLS: { LogEvolveItemDetail("Type [{}] Processing sub type", type); if (mob) { if (mob->GetLevel() >= Strings::ToUnsignedInt(sub_types.front()) || Strings::ToUnsignedInt(sub_types.front()) == 0 ) { LogEvolveItemDetail("Sub_Type [{}] Processing Item", sub_type); inst->SetEvolveAddToCurrentAmount(1); inst->CalculateEvolveProgression(); auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression( database, inst->GetEvolveUniqueID(), inst->GetEvolveCurrentAmount(), inst->GetEvolveProgression() ); if (!e.id) { break; } SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e); LogEvolveItem( "Processing Complete for item id [{1}] Type 4 Specific Zone ID - SubType " "[{0}] " "- Increased count by 1 for [{1}]", sub_type, inst->GetID() ); } } if (inst->GetEvolveProgression() >= 100) { queue.push_back(inst); } break; } default: { } } } if (!queue.empty()) { for (auto const &i: queue) { DoEvolveCheckProgression(*i); } } } void Client::DoEvolveItemDisplayFinalResult(const EQApplicationPacket *app) { const auto in = reinterpret_cast(app->pBuffer); const uint32 item_id = static_cast(in->unique_id & 0xFFFFFFFF); if (item_id == 0) { LogEvolveItem("Error - Item ID of final evolve item is blank."); return; } std::unique_ptr const inst(database.CreateItem(item_id)); if (!inst) { return; } LogEvolveItemDetail( "Character ID [{}] requested to view final evolve item id [{}] for evolve item id [{}]", CharacterID(), item_id, EvolvingItemsManager::Instance()->GetFirstItemInLoreGroupByItemID(item_id) ); inst->SetEvolveProgression(100); LogEvolveItemDetail( "Sending final result for item id [{}] to Character ID [{}]", item_id, CharacterID() ); SendItemPacket(0, inst.get(), ItemPacketViewLink); } bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst) { if (!inst) { return false; } if (inst.GetEvolveProgression() < 100 || inst.GetEvolveLvl() == inst.GetMaxEvolveLvl()) { return false; } const auto new_item_id = EvolvingItemsManager::Instance()->GetNextEvolveItemID(inst); if (!new_item_id) { return false; } std::unique_ptr const new_inst(database.CreateItem(new_item_id)); if (!new_inst) { return false; } if (RuleB(EvolvingItems, EnableParcelMerchants) && !RuleB(EvolvingItems, DestroyAugmentsOnEvolve) && inst.IsAugmented() ) { auto const augs = inst.GetAugmentIDs(); std::vector parcels; int32 next_slot = FindNextFreeParcelSlotUsingMemory(); for (auto const &item_id: augs) { if (!item_id) { continue; } if (next_slot == INVALID_INDEX) { break; } CharacterParcelsRepository::CharacterParcels p{}; p.char_id = CharacterID(); p.from_name = "Evolving Item Sub-System"; p.note = fmt::format( "System automatically removed from {} which recently evolved.", inst.GetItem()->Name ); p.slot_id = next_slot; p.sent_date = time(nullptr); p.item_id = item_id; p.quantity = 1; if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::PARCEL_SEND)) { PlayerEvent::ParcelSend e{}; e.from_player_name = p.from_name; e.to_player_name = GetCleanName(); e.item_id = p.item_id; e.quantity = 1; e.sent_date = p.sent_date; RecordPlayerEventLog(PlayerEvent::PARCEL_SEND, e); } parcels.push_back(p); m_parcels.emplace(p.slot_id, p); next_slot = FindNextFreeParcelSlotUsingMemory(); } CharacterParcelsRepository::InsertMany(database, parcels); SendParcelStatus(); SendParcelIconStatus(); } CheckItemDiscoverability(new_inst->GetID()); PlayerEvent::EvolveItem e{}; RemoveItemBySerialNumber(inst.GetSerialNumber()); EvolvingItemsManager::Instance()->LoadPlayerEvent(inst, e); e.status = "Evolved Item due to obtaining progression - Old Evolve Item removed from inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); PushItemOnCursor(*new_inst, true); EvolvingItemsManager::Instance()->LoadPlayerEvent(*new_inst, e); e.status = "Evolved Item due to obtaining progression - New Evolve Item placed in inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); MessageString(Chat::Yellow, EVOLVE_ITEM_EVOLVED, inst.GetItem()->Name); LogEvolveItem( "Evolved item id [{}] into item id [{}] for Character ID [{}]", inst.GetID(), new_inst->GetID(), CharacterID()); return true; } void Client::SendEvolveXPTransferWindow() { auto out = std::make_unique(OP_EvolveItem, sizeof(EvolveItemToggle)); const auto data = reinterpret_cast(out->pBuffer); data->action = 1; QueuePacket(out.get()); } void Client::SendEvolveXPWindowDetails(const EQApplicationPacket *app) { const auto in = reinterpret_cast(app->pBuffer); const auto item_1_slot = GetInv().HasEvolvingItem(in->item1_unique_id, 1, invWherePersonal | invWhereWorn | invWhereCursor); const auto item_2_slot = GetInv().HasEvolvingItem(in->item2_unique_id, 1, invWherePersonal | invWhereWorn | invWhereCursor); if (item_1_slot == INVALID_INDEX || item_2_slot == INVALID_INDEX) { return; } const auto inst_from = GetInv().GetItem(item_1_slot); const auto inst_to = GetInv().GetItem(item_2_slot); if (!inst_from || !inst_to) { return; } const auto results = EvolvingItemsManager::Instance()->DetermineTransferResults(*inst_from, *inst_to); if (!results.item_from_id || !results.item_to_id) { SendEvolveTransferResults(*inst_from, *inst_to, *inst_from, *inst_to, 0, 0); return; } std::unique_ptr const inst_from_new(database.CreateItem(results.item_from_id)); std::unique_ptr const inst_to_new(database.CreateItem(results.item_to_id)); if (!inst_from_new || !inst_to_new) { SendEvolveTransferResults(*inst_from, *inst_to, *inst_from, *inst_to, 0, 0); return; } inst_from_new->SetEvolveCurrentAmount(results.item_from_current_amount); inst_from_new->CalculateEvolveProgression(); inst_to_new->SetEvolveCurrentAmount(results.item_to_current_amount); inst_to_new->CalculateEvolveProgression(); SendEvolveTransferResults( *inst_from, *inst_to, *inst_from_new, *inst_to_new, results.compatibility, results.max_transfer_level); } void Client::DoEvolveTransferXP(const EQApplicationPacket *app) { const auto in = reinterpret_cast(app->pBuffer); const auto item_1_slot = GetInv().HasEvolvingItem(in->item1_unique_id, 1, invWherePersonal | invWhereWorn | invWhereCursor); const auto item_2_slot = GetInv().HasEvolvingItem(in->item2_unique_id, 1, invWherePersonal | invWhereWorn | invWhereCursor); if (item_1_slot == INVALID_INDEX || item_2_slot == INVALID_INDEX) { return; } const auto inst_from = GetInv().GetItem(item_1_slot); const auto inst_to = GetInv().GetItem(item_2_slot); if (!inst_from || !inst_to) { Message(Chat::Red, "Transfer Failed. Incompatible Items."); LogEvolveItem("Transfer Failed for Character ID [{}]", CharacterID()); return; } const auto results = EvolvingItemsManager::Instance()->DetermineTransferResults(*inst_from, *inst_to); if (!results.item_from_id || !results.item_to_id) { Message(Chat::Red, "Transfer Failed. Incompatible Items."); LogEvolveItem("Transfer Failed for Character ID [{}]", CharacterID()); return; } std::unique_ptr const inst_from_new(database.CreateItem(results.item_from_id)); std::unique_ptr const inst_to_new(database.CreateItem(results.item_to_id)); if (!inst_from_new || !inst_to_new) { Message(Chat::Red, "Transfer Failed. Incompatible Items."); LogEvolveItem("Transfer Failed for Character ID [{}]", CharacterID()); return; } inst_from_new->SetEvolveCurrentAmount(results.item_from_current_amount); inst_from_new->CalculateEvolveProgression(); inst_to_new->SetEvolveCurrentAmount(results.item_to_current_amount); inst_to_new->CalculateEvolveProgression(); PlayerEvent::EvolveItem e{}; RemoveItemBySerialNumber(inst_from->GetSerialNumber()); EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_from, e); e.status = "Transfer XP - Original FROM Evolve Item removed from inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); PushItemOnCursor(*inst_from_new, true); EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_from_new, e); e.status = "Transfer XP - Updated FROM item placed in inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); RemoveItemBySerialNumber(inst_to->GetSerialNumber()); EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_to, e); e.status = "Transfer XP - Original TO Evolve Item removed from inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); PushItemOnCursor(*inst_to_new, true); EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_to_new, e); e.status = "Transfer XP - Updated TO Evolve item placed in inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); LogEvolveItem( "Evolve Transfer XP resulted in evolved item id [{}] into item id [{}] for Character ID " "[{}]", inst_to->GetID(), inst_to_new->GetID(), CharacterID() ); } void Client::SendEvolveTransferResults( const EQ::ItemInstance &inst_from, const EQ::ItemInstance &inst_to, const EQ::ItemInstance &inst_from_new, const EQ::ItemInstance &inst_to_new, const uint32 compatibility, const uint32 max_transfer_level) { std::stringstream ss; cereal::BinaryOutputArchive ar(ss); EvolveXPWindowSend e{}; e.action = EvolvingItems::Actions::TRANSFER_WINDOW_DETAILS; e.compatibility = compatibility; e.item1_unique_id = inst_from.GetEvolveUniqueID(); e.item2_unique_id = inst_to.GetEvolveUniqueID(); e.max_transfer_level = max_transfer_level; e.item1_present = 1; e.item2_present = 1; e.serialize_item_1 = inst_from_new.Serialize(0); e.serialize_item_2 = inst_to_new.Serialize(0); { ar(e); } uint32 packet_size = sizeof(EvolveItemMessaging) + ss.str().length(); std::unique_ptr out(new EQApplicationPacket(OP_EvolveItem, packet_size)); const auto data = reinterpret_cast(out->pBuffer); data->action = EvolvingItems::Actions::TRANSFER_WINDOW_DETAILS; memcpy(data->serialized_data, ss.str().data(), ss.str().length()); QueuePacket(out.get()); ss.str(""); ss.clear(); }