#include "../common/eqemu_logsys.h" #include "../common/extprofile.h" #include "../common/rulesys.h" #include "../common/strings.h" #include "client.h" #include "corpse.h" #include "groups.h" #include "merc.h" #include "zone.h" #include "zonedb.h" #include "aura.h" #include "../common/repositories/blocked_spells_repository.h" #include "../common/repositories/character_tribute_repository.h" #include "../common/repositories/character_data_repository.h" #include "../common/repositories/character_disciplines_repository.h" #include "../common/repositories/npc_types_repository.h" #include "../common/repositories/character_bind_repository.h" #include "../common/repositories/character_pet_buffs_repository.h" #include "../common/repositories/character_pet_inventory_repository.h" #include "../common/repositories/character_pet_info_repository.h" #include "../common/repositories/character_buffs_repository.h" #include "../common/repositories/character_languages_repository.h" #include "../common/repositories/criteria/content_filter_criteria.h" #include "../common/repositories/spawn2_disabled_repository.h" #include "../common/repositories/character_leadership_abilities_repository.h" #include "../common/repositories/character_material_repository.h" #include "../common/repositories/character_memmed_spells_repository.h" #include "../common/repositories/character_spells_repository.h" #include "../common/repositories/character_skills_repository.h" #include "../common/repositories/character_potionbelt_repository.h" #include "../common/repositories/character_bandolier_repository.h" #include "../common/repositories/character_currency_repository.h" #include "../common/repositories/character_alternate_abilities_repository.h" #include "../common/repositories/character_auras_repository.h" #include "../common/repositories/character_alt_currency_repository.h" #include "../common/repositories/character_item_recast_repository.h" #include "../common/repositories/account_repository.h" #include "../common/repositories/respawn_times_repository.h" #include "../common/repositories/object_contents_repository.h" #include "../common/repositories/mercs_repository.h" #include "../common/repositories/merc_buffs_repository.h" #include "../common/repositories/merc_inventory_repository.h" #include "../common/repositories/merc_subtypes_repository.h" #include "../common/repositories/npc_types_tint_repository.h" #include "../common/repositories/merchantlist_temp_repository.h" #include "../common/repositories/character_exp_modifiers_repository.h" #include "../common/repositories/character_data_repository.h" #include "../common/repositories/character_corpses_repository.h" #include "../common/repositories/character_corpse_items_repository.h" #include "../common/repositories/zone_repository.h" #include #include #include extern Zone* zone; ZoneDatabase database; ZoneDatabase content_db; ZoneDatabase::ZoneDatabase() : SharedDatabase() { ZDBInitVars(); } ZoneDatabase::ZoneDatabase(const char* host, const char* user, const char* passwd, const char* database, uint32 port) : SharedDatabase(host, user, passwd, database, port) { ZDBInitVars(); } void ZoneDatabase::ZDBInitVars() { npc_spellseffects_cache = 0; npc_spellseffects_loadtried = 0; max_faction = 0; faction_array = nullptr; } ZoneDatabase::~ZoneDatabase() { if (npc_spellseffects_cache) { for (int x = 0; x <= npc_spellseffects_maxid; x++) { safe_delete_array(npc_spellseffects_cache[x]); } safe_delete_array(npc_spellseffects_cache); } safe_delete_array(npc_spellseffects_loadtried); if (faction_array != nullptr) { for (int x = 0; x <= max_faction; x++) { if (faction_array[x] != 0) safe_delete(faction_array[x]); } safe_delete_array(faction_array); } } bool ZoneDatabase::SaveZoneCFG(uint32 zone_id, uint16 instance_version, NewZone_Struct* zd) { const auto& l = ZoneRepository::GetWhere( *this, fmt::format( "`zoneidnumber` = {} AND `version` = {}", zone_id, instance_version ) ); if (l.empty()) { return false; } auto e = l.front(); e.underworld = zd->underworld; e.minclip = zd->minclip; e.maxclip = zd->maxclip; e.fog_minclip = zd->fog_minclip[0]; e.fog_maxclip = zd->fog_maxclip[0]; e.fog_blue = zd->fog_blue[0]; e.fog_red = zd->fog_red[0]; e.fog_green = zd->fog_green[0]; e.sky = zd->sky; e.ztype = zd->ztype; e.zone_exp_multiplier = zd->zone_exp_multiplier; e.safe_x = zd->safe_x; e.safe_y = zd->safe_y; e.safe_z = zd->safe_z; e.safe_heading = zd->safe_heading; return ZoneRepository::UpdateOne(*this, e); } void ZoneDatabase::UpdateRespawnTime(uint32 spawn2_id, uint16 instance_id, uint32 time_left) { timeval tv; gettimeofday(&tv, nullptr); uint32 current_time = tv.tv_sec; /* If we pass timeleft as 0 that means we clear from respawn time otherwise we update with a REPLACE INTO */ if (!time_left) { RespawnTimesRepository::DeleteWhere( *this, fmt::format( "`id` = {} AND `instance_id` = {}", spawn2_id, instance_id ) ); return; } RespawnTimesRepository::ReplaceOne( *this, RespawnTimesRepository::RespawnTimes{ .id = static_cast(spawn2_id), .start = static_cast(current_time), .duration = static_cast(time_left), .instance_id = static_cast(instance_id) } ); } //Gets the respawn time left in the database for the current spawn id uint32 ZoneDatabase::GetSpawnTimeLeft(uint32 spawn2_id, uint16 instance_id) { timeval tv; gettimeofday(&tv, nullptr); return RespawnTimesRepository::GetTimeRemaining(*this, spawn2_id, instance_id, tv.tv_sec); } void ZoneDatabase::UpdateSpawn2Status(uint32 id, uint8 new_status, uint32 instance_id) { auto spawns = Spawn2DisabledRepository::GetWhere( *this, fmt::format("spawn2_id = {} and instance_id = {}", id, instance_id) ); if (!spawns.empty()) { auto spawn = spawns[0]; // 1 = enabled 0 = disabled spawn.disabled = new_status ? 0 : 1; spawn.instance_id = instance_id; Spawn2DisabledRepository::UpdateOne(*this, spawn); return; } auto spawn = Spawn2DisabledRepository::NewEntity(); spawn.spawn2_id = id; spawn.instance_id = instance_id; spawn.disabled = new_status ? 0 : 1; Spawn2DisabledRepository::InsertOne(*this, spawn); } bool ZoneDatabase::SetSpecialAttkFlag(uint8 id, const char* flag) { std::string query = StringFormat("UPDATE npc_types SET npcspecialattks='%s' WHERE id = %i;", flag, id); auto results = QueryDatabase(query); if (!results.Success()) return false; return results.RowsAffected() != 0; } void ZoneDatabase::LoadWorldContainer(uint32 parent_id, EQ::ItemInstance* container) { if (!container) { return; } const auto& l = ObjectContentsRepository::GetWhere( database, fmt::format( "`parentid` = {}", parent_id ) ); for (const auto& e : l) { const uint32 augments[EQ::invaug::SOCKET_COUNT] = { e.augslot1, e.augslot2, e.augslot3, e.augslot4, e.augslot5, static_cast(e.augslot6) }; auto inst = database.CreateItem(e.itemid, e.charges); if (inst && inst->GetItem()->IsClassCommon()) { for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { if (augments[i]) { inst->PutAugment(&database, i, augments[i]); } } container->PutItem(e.bagidx, *inst); } safe_delete(inst); } } void ZoneDatabase::SaveWorldContainer(uint32 zone_id, uint32 parent_id, const EQ::ItemInstance* container) { if (!container) { return; } DeleteWorldContainer(parent_id, zone_id); for (uint8 index = EQ::invbag::SLOT_BEGIN; index <= EQ::invbag::SLOT_END; index++) { auto inst = container->GetItem(index); if (!inst) { continue; } uint32 augments[EQ::invaug::SOCKET_COUNT] = { 0, 0, 0, 0, 0, 0 }; if (inst->IsType(EQ::item::ItemClassCommon)) { for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { auto augment = inst->GetAugment(i); augments[i] = (augment && augment->GetItem()) ? augment->GetItem()->ID : 0; } } ObjectContentsRepository::ReplaceOne( database, ObjectContentsRepository::ObjectContents{ .zoneid = zone_id, .parentid = parent_id, .bagidx = index, .itemid = inst->GetItem()->ID, .charges = inst->GetCharges(), .droptime = std::time(nullptr), .augslot1 = augments[0], .augslot2 = augments[1], .augslot3 = augments[2], .augslot4 = augments[3], .augslot5 = augments[4], .augslot6 = static_cast(augments[5]) } ); } } void ZoneDatabase::DeleteWorldContainer(uint32 parent_id, uint32 zone_id) { ObjectContentsRepository::DeleteWhere( database, fmt::format( "`parentid` = {} AND `zoneid` = {}", parent_id, zone_id ) ); } Trader_Struct* ZoneDatabase::LoadTraderItem(uint32 char_id) { auto loadti = new Trader_Struct; memset(loadti,0,sizeof(Trader_Struct)); std::string query = StringFormat("SELECT * FROM trader WHERE char_id = %i ORDER BY slot_id LIMIT 80", char_id); auto results = QueryDatabase(query); if (!results.Success()) { LogTrading("Failed to load trader information!\n"); return loadti; } loadti->Code = BazaarTrader_ShowItems; for (auto& row = results.begin(); row != results.end(); ++row) { if (Strings::ToInt(row[5]) >= 80 || Strings::ToInt(row[4]) < 0) { LogTrading("Bad Slot number when trying to load trader information!\n"); continue; } loadti->Items[Strings::ToInt(row[5])] = Strings::ToInt(row[1]); loadti->ItemCost[Strings::ToInt(row[5])] = Strings::ToInt(row[4]); } return loadti; } TraderCharges_Struct* ZoneDatabase::LoadTraderItemWithCharges(uint32 char_id) { auto loadti = new TraderCharges_Struct; memset(loadti,0,sizeof(TraderCharges_Struct)); std::string query = StringFormat("SELECT * FROM trader WHERE char_id=%i ORDER BY slot_id LIMIT 80", char_id); auto results = QueryDatabase(query); if (!results.Success()) { LogTrading("Failed to load trader information!\n"); return loadti; } for (auto& row = results.begin(); row != results.end(); ++row) { if (Strings::ToInt(row[5]) >= 80 || Strings::ToInt(row[5]) < 0) { LogTrading("Bad Slot number when trying to load trader information!\n"); continue; } loadti->ItemID[Strings::ToInt(row[5])] = Strings::ToInt(row[1]); loadti->SerialNumber[Strings::ToInt(row[5])] = Strings::ToInt(row[2]); loadti->Charges[Strings::ToInt(row[5])] = Strings::ToInt(row[3]); loadti->ItemCost[Strings::ToInt(row[5])] = Strings::ToInt(row[4]); } return loadti; } EQ::ItemInstance* ZoneDatabase::LoadSingleTraderItem(uint32 CharID, int SerialNumber) { std::string query = StringFormat("SELECT * FROM trader WHERE char_id = %i AND serialnumber = %i " "ORDER BY slot_id LIMIT 80", CharID, SerialNumber); auto results = QueryDatabase(query); if (!results.Success()) return nullptr; if (results.RowCount() == 0) { LogTrading("Bad result from query\n"); fflush(stdout); return nullptr; } auto& row = results.begin(); int ItemID = Strings::ToInt(row[1]); int Charges = Strings::ToInt(row[3]); int Cost = Strings::ToInt(row[4]); const EQ::ItemData *item = database.GetItem(ItemID); if(!item) { LogTrading("Unable to create item\n"); fflush(stdout); return nullptr; } if (item->NoDrop == 0) return nullptr; EQ::ItemInstance* inst = database.CreateItem(item); if(!inst) { LogTrading("Unable to create item instance\n"); fflush(stdout); return nullptr; } inst->SetCharges(Charges); inst->SetSerialNumber(SerialNumber); inst->SetMerchantSlot(SerialNumber); inst->SetPrice(Cost); if(inst->IsStackable()) inst->SetMerchantCount(Charges); return inst; } void ZoneDatabase::SaveTraderItem(uint32 CharID, uint32 ItemID, uint32 SerialNumber, int32 Charges, uint32 ItemCost, uint8 Slot){ std::string query = StringFormat("REPLACE INTO trader VALUES(%i, %i, %i, %i, %i, %i)", CharID, ItemID, SerialNumber, Charges, ItemCost, Slot); auto results = QueryDatabase(query); if (!results.Success()) LogDebug("[CLIENT] Failed to save trader item: [{}] for char_id: [{}], the error was: [{}]\n", ItemID, CharID, results.ErrorMessage().c_str()); } void ZoneDatabase::UpdateTraderItemCharges(int CharID, uint32 SerialNumber, int32 Charges) { LogTrading("ZoneDatabase::UpdateTraderItemCharges([{}], [{}], [{}])", CharID, SerialNumber, Charges); std::string query = StringFormat("UPDATE trader SET charges = %i WHERE char_id = %i AND serialnumber = %i", Charges, CharID, SerialNumber); auto results = QueryDatabase(query); if (!results.Success()) LogDebug("[CLIENT] Failed to update charges for trader item: [{}] for char_id: [{}], the error was: [{}]\n", SerialNumber, CharID, results.ErrorMessage().c_str()); } void ZoneDatabase::UpdateTraderItemPrice(int CharID, uint32 ItemID, uint32 Charges, uint32 NewPrice) { LogTrading("ZoneDatabase::UpdateTraderPrice([{}], [{}], [{}], [{}])", CharID, ItemID, Charges, NewPrice); const EQ::ItemData *item = database.GetItem(ItemID); if(!item) return; if(NewPrice == 0) { LogTrading("Removing Trader items from the DB for CharID [{}], ItemID [{}]", CharID, ItemID); std::string query = StringFormat("DELETE FROM trader WHERE char_id = %i AND item_id = %i",CharID, ItemID); auto results = QueryDatabase(query); if (!results.Success()) LogDebug("[CLIENT] Failed to remove trader item(s): [{}] for char_id: [{}], the error was: [{}]\n", ItemID, CharID, results.ErrorMessage().c_str()); return; } if(!item->Stackable) { std::string query = StringFormat("UPDATE trader SET item_cost = %i " "WHERE char_id = %i AND item_id = %i AND charges=%i", NewPrice, CharID, ItemID, Charges); auto results = QueryDatabase(query); if (!results.Success()) LogDebug("[CLIENT] Failed to update price for trader item: [{}] for char_id: [{}], the error was: [{}]\n", ItemID, CharID, results.ErrorMessage().c_str()); return; } std::string query = StringFormat("UPDATE trader SET item_cost = %i " "WHERE char_id = %i AND item_id = %i", NewPrice, CharID, ItemID); auto results = QueryDatabase(query); if (!results.Success()) LogDebug("[CLIENT] Failed to update price for trader item: [{}] for char_id: [{}], the error was: [{}]\n", ItemID, CharID, results.ErrorMessage().c_str()); } void ZoneDatabase::DeleteTraderItem(uint32 char_id){ if(char_id==0) { const std::string query = "DELETE FROM trader"; auto results = QueryDatabase(query); if (!results.Success()) LogDebug("[CLIENT] Failed to delete all trader items data, the error was: [{}]\n", results.ErrorMessage().c_str()); return; } std::string query = StringFormat("DELETE FROM trader WHERE char_id = %i", char_id); auto results = QueryDatabase(query); if (!results.Success()) LogDebug("[CLIENT] Failed to delete trader item data for char_id: [{}], the error was: [{}]\n", char_id, results.ErrorMessage().c_str()); } void ZoneDatabase::DeleteTraderItem(uint32 CharID,uint16 SlotID) { std::string query = StringFormat("DELETE FROM trader WHERE char_id = %i And slot_id = %i", CharID, SlotID); auto results = QueryDatabase(query); if (!results.Success()) LogDebug("[CLIENT] Failed to delete trader item data for char_id: [{}], the error was: [{}]\n",CharID, results.ErrorMessage().c_str()); } void ZoneDatabase::DeleteBuyLines(uint32 CharID) { if(CharID==0) { const std::string query = "DELETE FROM buyer"; auto results = QueryDatabase(query); if (!results.Success()) LogDebug("[CLIENT] Failed to delete all buyer items data, the error was: [{}]\n",results.ErrorMessage().c_str()); return; } std::string query = StringFormat("DELETE FROM buyer WHERE charid = %i", CharID); auto results = QueryDatabase(query); if (!results.Success()) LogDebug("[CLIENT] Failed to delete buyer item data for charid: [{}], the error was: [{}]\n",CharID,results.ErrorMessage().c_str()); } void ZoneDatabase::AddBuyLine(uint32 CharID, uint32 BuySlot, uint32 ItemID, const char* ItemName, uint32 Quantity, uint32 Price) { std::string query = StringFormat("REPLACE INTO buyer VALUES(%i, %i, %i, \"%s\", %i, %i)", CharID, BuySlot, ItemID, ItemName, Quantity, Price); auto results = QueryDatabase(query); if (!results.Success()) LogDebug("[CLIENT] Failed to save buline item: [{}] for char_id: [{}], the error was: [{}]\n", ItemID, CharID, results.ErrorMessage().c_str()); } void ZoneDatabase::RemoveBuyLine(uint32 CharID, uint32 BuySlot) { std::string query = StringFormat("DELETE FROM buyer WHERE charid = %i AND buyslot = %i", CharID, BuySlot); auto results = QueryDatabase(query); if (!results.Success()) LogDebug("[CLIENT] Failed to delete buyslot [{}] for charid: [{}], the error was: [{}]\n", BuySlot, CharID, results.ErrorMessage().c_str()); } void ZoneDatabase::UpdateBuyLine(uint32 CharID, uint32 BuySlot, uint32 Quantity) { if(Quantity <= 0) { RemoveBuyLine(CharID, BuySlot); return; } std::string query = StringFormat("UPDATE buyer SET quantity = %i WHERE charid = %i AND buyslot = %i", Quantity, CharID, BuySlot); auto results = QueryDatabase(query); if (!results.Success()) LogDebug("[CLIENT] Failed to update quantity in buyslot [{}] for charid: [{}], the error was: [{}]\n", BuySlot, CharID, results.ErrorMessage().c_str()); } #define StructDist(in, f1, f2) (uint32(&in->f2)-uint32(&in->f1)) bool ZoneDatabase::LoadCharacterData(uint32 character_id, PlayerProfile_Struct* pp, ExtendedProfile_Struct* m_epp){ const auto& e = CharacterDataRepository::FindOne(database, character_id); if (!e.id) { return false; } strcpy(pp->name, e.name.c_str()); strcpy(pp->last_name, e.last_name.c_str()); strcpy(pp->title, e.title.c_str()); strcpy(pp->suffix, e.suffix.c_str()); pp->gender = e.gender; pp->race = e.race; pp->class_ = e.class_; pp->level = e.level; pp->deity = e.deity; pp->birthday = e.birthday; pp->lastlogin = e.last_login; pp->timePlayedMin = e.time_played; pp->pvp = e.pvp_status; pp->level2 = e.level2; pp->anon = e.anon; pp->gm = e.gm; pp->intoxication = e.intoxication; pp->haircolor = e.hair_color; pp->beardcolor = e.beard_color; pp->eyecolor1 = e.eye_color_1; pp->eyecolor2 = e.eye_color_2; pp->hairstyle = e.hair_style; pp->beard = e.beard; pp->ability_time_seconds = e.ability_time_seconds; pp->ability_number = e.ability_number; pp->ability_time_minutes = e.ability_time_minutes; pp->ability_time_hours = e.ability_time_hours; pp->exp = e.exp; pp->points = e.points; pp->mana = e.mana; pp->cur_hp = e.cur_hp; pp->STR = e.str; pp->STA = e.sta; pp->CHA = e.cha; pp->DEX = e.dex; pp->INT = e.int_; pp->AGI = e.agi; pp->WIS = e.wis; pp->face = e.face; pp->y = e.y; pp->x = e.x; pp->z = e.z; pp->heading = e.heading; pp->pvp2 = e.pvp2; pp->pvptype = e.pvp_type; pp->autosplit = e.autosplit_enabled; pp->zone_change_count = e.zone_change_count; pp->drakkin_heritage = e.drakkin_heritage; pp->drakkin_tattoo = e.drakkin_tattoo; pp->drakkin_details = e.drakkin_details; pp->toxicity = e.toxicity; pp->hunger_level = e.hunger_level; pp->thirst_level = e.thirst_level; pp->ability_up = e.ability_up; pp->zone_id = e.zone_id; pp->zoneInstance = e.zone_instance; pp->leadAAActive = e.leadership_exp_on; pp->ldon_points_guk = e.ldon_points_guk; pp->ldon_points_mir = e.ldon_points_mir; pp->ldon_points_mmc = e.ldon_points_mmc; pp->ldon_points_ruj = e.ldon_points_ruj; pp->ldon_points_tak = e.ldon_points_tak; pp->ldon_points_available = e.ldon_points_available; pp->tribute_time_remaining = e.tribute_time_remaining; pp->showhelm = e.show_helm; pp->career_tribute_points = e.career_tribute_points; pp->tribute_points = e.tribute_points; pp->tribute_active = e.tribute_active; pp->endurance = e.endurance; pp->group_leadership_exp = e.group_leadership_exp; pp->raid_leadership_exp = e.raid_leadership_exp; pp->group_leadership_points = e.group_leadership_points; pp->raid_leadership_points = e.raid_leadership_points; pp->air_remaining = e.air_remaining; pp->PVPKills = e.pvp_kills; pp->PVPDeaths = e.pvp_deaths; pp->PVPCurrentPoints = e.pvp_current_points; pp->PVPCareerPoints = e.pvp_career_points; pp->PVPBestKillStreak = e.pvp_best_kill_streak; pp->PVPWorstDeathStreak = e.pvp_worst_death_streak; pp->PVPCurrentKillStreak = e.pvp_current_kill_streak; pp->aapoints_spent = e.aa_points_spent; pp->expAA = e.aa_exp; pp->aapoints = e.aa_points; pp->groupAutoconsent = e.group_auto_consent; pp->raidAutoconsent = e.raid_auto_consent; pp->guildAutoconsent = e.guild_auto_consent; pp->RestTimer = e.RestTimer; m_epp->aa_effects = e.e_aa_effects; m_epp->perAA = e.e_percent_to_aa; m_epp->expended_aa = e.e_expended_aa_spent; m_epp->last_invsnapshot_time = e.e_last_invsnapshot; m_epp->next_invsnapshot_time = m_epp->last_invsnapshot_time + (RuleI(Character, InvSnapshotMinIntervalM) * 60); return true; } bool ZoneDatabase::LoadCharacterFactionValues(uint32 character_id, faction_map & val_list) { std::string query = StringFormat("SELECT `faction_id`, `current_value` FROM `faction_values` WHERE `char_id` = %i", character_id); auto results = database.QueryDatabase(query); for (auto& row = results.begin(); row != results.end(); ++row) { val_list[Strings::ToInt(row[0])] = Strings::ToInt(row[1]); } return true; } bool ZoneDatabase::LoadCharacterMemmedSpells(uint32 character_id, PlayerProfile_Struct* pp) { const auto& l = CharacterMemmedSpellsRepository::GetWhere( database, fmt::format( "`id` = {} ORDER BY `slot_id`", character_id ) ); for (int i = 0; i < EQ::spells::SPELL_GEM_COUNT; i++) { // Initialize Spells pp->mem_spells[i] = UINT32_MAX; } for (const auto& e : l) { if (e.slot_id < EQ::spells::SPELL_GEM_COUNT && IsValidSpell(e.spell_id)) { pp->mem_spells[e.slot_id] = e.spell_id; } } return true; } bool ZoneDatabase::LoadCharacterSpellBook(uint32 character_id, PlayerProfile_Struct* pp) { const auto& l = CharacterSpellsRepository::GetWhere( database, fmt::format( "`id` = {} ORDER BY `slot_id`", character_id ) ); memset(pp->spell_book, UINT8_MAX, (sizeof(uint32) * EQ::spells::SPELLBOOK_SIZE)); // We have the ability to block loaded spells by max id on a per-client basis.. // but, we do not have to ability to keep players from using older clients after // they have scribed spells on a newer one that exceeds the older one's limit. // Load them all so that server actions are valid..but, nix them in translators. for (const auto& e : l) { if (!EQ::ValueWithin(e.slot_id, 0, EQ::spells::SPELLBOOK_SIZE)) { continue; } if (!IsValidSpell(e.spell_id)) { continue; } pp->spell_book[e.slot_id] = e.spell_id; } return true; } bool ZoneDatabase::LoadCharacterLanguages(uint32 character_id, PlayerProfile_Struct* pp) { const auto& l = CharacterLanguagesRepository::GetWhere( database, fmt::format( "`id` = {} ORDER BY `lang_id`", character_id ) ); if (l.empty()) { return false; } for (int i = 0; i < MAX_PP_LANGUAGE; ++i) { // Initialize Languages pp->languages[i] = 0; } for (const auto& e : l) { if (EQ::ValueWithin(e.lang_id, Language::CommonTongue, Language::Unknown27)) { pp->languages[e.lang_id] = e.value; } } return true; } bool ZoneDatabase::LoadCharacterLeadershipAbilities(uint32 character_id, PlayerProfile_Struct* pp) { const auto& l = CharacterLeadershipAbilitiesRepository::GetWhere( database, fmt::format( "`id` = {}", character_id ) ); for (const auto& e : l) { pp->leader_abilities.ranks[e.slot] = e.rank_; } return true; } bool ZoneDatabase::LoadCharacterDisciplines(uint32 character_id, PlayerProfile_Struct* pp){ const auto& l = CharacterDisciplinesRepository::GetWhere( database, fmt::format( "`id` = {} ORDER BY `slot_id`", character_id ) ); if (l.empty()) { return false; } for (int slot_id = 0; slot_id < MAX_PP_DISCIPLINES; slot_id++) { // Initialize Disciplines pp->disciplines.values[slot_id] = 0; } for (const auto& e : l) { if (IsValidSpell(e.disc_id) && e.slot_id < MAX_PP_DISCIPLINES) { pp->disciplines.values[e.slot_id] = e.disc_id; } } return true; } bool ZoneDatabase::LoadCharacterSkills(uint32 character_id, PlayerProfile_Struct* pp) { const auto& l = CharacterSkillsRepository::GetWhere( *this, fmt::format( "`id` = {} ORDER BY `skill_id`", character_id ) ); for (int i = 0; i < MAX_PP_SKILL; ++i) { // Initialize Skills pp->skills[i] = 0; } for (const auto& e : l) { if (e.skill_id < MAX_PP_SKILL) { pp->skills[e.skill_id] = e.value; } } return true; } bool ZoneDatabase::LoadCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp) { const auto& e = CharacterCurrencyRepository::FindOne(*this, character_id); if (!e.id) { return false; } pp->platinum = e.platinum; pp->platinum_bank = e.platinum_bank; pp->platinum_cursor = e.platinum_cursor; pp->gold = e.gold; pp->gold_bank = e.gold_bank; pp->gold_cursor = e.gold_cursor; pp->silver = e.silver; pp->silver_bank = e.silver_bank; pp->silver_cursor = e.silver_cursor; pp->copper = e.copper; pp->copper_bank = e.copper_bank; pp->copper_cursor = e.copper_cursor; pp->currentRadCrystals = e.radiant_crystals; pp->careerRadCrystals = e.career_radiant_crystals; pp->currentEbonCrystals = e.ebon_crystals; pp->careerEbonCrystals = e.career_ebon_crystals; return true; } bool ZoneDatabase::LoadCharacterMaterialColor(uint32 character_id, PlayerProfile_Struct* pp) { const auto& l = CharacterMaterialRepository::GetWhere( database, fmt::format( "`id` = {} LIMIT 9", character_id ) ); for (const auto& e : l) { pp->item_tint.Slot[e.slot].Blue = e.blue; pp->item_tint.Slot[e.slot].Green = e.green; pp->item_tint.Slot[e.slot].Red = e.red; pp->item_tint.Slot[e.slot].UseTint = e.use_tint; } return true; } bool ZoneDatabase::LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp) { const auto& l = CharacterBandolierRepository::GetWhere( database, fmt::format( "`id` = {} LIMIT {}", character_id, EQ::profile::BANDOLIERS_SIZE ) ); for (int i = 0; i < EQ::profile::BANDOLIERS_SIZE; i++) { pp->bandoliers[i].Name[0] = '\0'; for (int si = 0; si < EQ::profile::BANDOLIER_ITEM_COUNT; si++) { pp->bandoliers[i].Items[si].ID = 0; pp->bandoliers[i].Items[si].Icon = 0; pp->bandoliers[i].Items[si].Name[0] = '\0'; } } for (const auto& e : l) { const auto* item_data = database.GetItem(e.item_id); if (item_data) { pp->bandoliers[e.bandolier_id].Items[e.bandolier_slot].ID = item_data->ID; pp->bandoliers[e.bandolier_id].Items[e.bandolier_slot].Icon = e.icon; strncpy( pp->bandoliers[e.bandolier_id].Items[e.bandolier_slot].Name, item_data->Name, 64 ); } else { pp->bandoliers[e.bandolier_id].Items[e.bandolier_slot].ID = 0; pp->bandoliers[e.bandolier_id].Items[e.bandolier_slot].Icon = 0; pp->bandoliers[e.bandolier_id].Items[e.bandolier_slot].Name[0] = '\0'; } strncpy(pp->bandoliers[e.bandolier_id].Name, e.bandolier_name.c_str(), 32); } return true; } void ZoneDatabase::LoadCharacterTribute(Client* c){ const auto& l = CharacterTributeRepository::GetWhere(database, fmt::format("character_id = {}", c->CharacterID())); for (auto& t : c->GetPP().tributes) { t.tier = 0; t.tribute = TRIBUTE_NONE; } auto i = 0; for (const auto& e : l) { if (e.tribute != TRIBUTE_NONE) { c->GetPP().tributes[i].tier = e.tier; c->GetPP().tributes[i].tribute = e.tribute; i++; } } } bool ZoneDatabase::LoadCharacterPotionBelt(uint32 character_id, PlayerProfile_Struct *pp) { const auto& l = CharacterPotionbeltRepository::GetWhere( database, fmt::format( "`id` = {} LIMIT {}", character_id, EQ::profile::POTION_BELT_SIZE ) ); for (int i = 0; i < EQ::profile::POTION_BELT_SIZE; i++) { // Initialize Potion Belt pp->potionbelt.Items[i].Icon = 0; pp->potionbelt.Items[i].ID = 0; pp->potionbelt.Items[i].Name[0] = '\0'; } for (const auto& e : l) { const auto* item_data = database.GetItem(e.item_id); if (!item_data) { continue; } pp->potionbelt.Items[e.potion_id].ID = item_data->ID; pp->potionbelt.Items[e.potion_id].Icon = e.icon; strncpy(pp->potionbelt.Items[e.potion_id].Name, item_data->Name, 64); } return true; } bool ZoneDatabase::LoadCharacterBindPoint(uint32 character_id, PlayerProfile_Struct *pp) { const auto& l = CharacterBindRepository::GetWhere( database, fmt::format( "`id` = {} LIMIT 5", character_id ) ); if (l.empty()) { return true; } for (const auto& e : l) { if (!EQ::ValueWithin(e.slot, 0, 4)) { continue; } pp->binds[e.slot].zone_id = e.zone_id; pp->binds[e.slot].instance_id = e.instance_id; pp->binds[e.slot].x = e.x; pp->binds[e.slot].y = e.y; pp->binds[e.slot].z = e.z; pp->binds[e.slot].heading = e.heading; } return true; } bool ZoneDatabase::SaveCharacterLanguage(uint32 character_id, uint32 language_id, uint32 value) { return CharacterLanguagesRepository::ReplaceOne( *this, CharacterLanguagesRepository::CharacterLanguages{ .id = character_id, .lang_id = static_cast(language_id), .value = static_cast(value) } ); } bool ZoneDatabase::SaveCharacterMaterialColor(uint32 character_id, uint8 slot_id, uint32 color) { const uint8 red = (color & 0x00FF0000) >> 16; const uint8 green = (color & 0x0000FF00) >> 8; const uint8 blue = (color & 0x000000FF); return CharacterMaterialRepository::ReplaceOne( *this, CharacterMaterialRepository::CharacterMaterial{ .id = character_id, .slot = slot_id, .blue = blue, .green = green, .red = red, .use_tint = UINT8_MAX, .color = color } ); } bool ZoneDatabase::SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value) { return CharacterSkillsRepository::ReplaceOne( *this, CharacterSkillsRepository::CharacterSkills{ .id = character_id, .skill_id = static_cast(skill_id), .value = static_cast(value) } ); } bool ZoneDatabase::SaveCharacterDiscipline(uint32 character_id, uint32 slot_id, uint32 disc_id) { return CharacterDisciplinesRepository::ReplaceOne( *this, CharacterDisciplinesRepository::CharacterDisciplines{ .id = character_id, .slot_id = static_cast(slot_id), .disc_id = static_cast(disc_id) } ); } void ZoneDatabase::SaveCharacterTribute(Client* c) { std::vector tributes = {}; CharacterTributeRepository::CharacterTribute tribute = {}; uint32 tribute_count = 0; for (auto& t : c->GetPP().tributes) { if (t.tribute != TRIBUTE_NONE) { tribute_count++; } } tributes.reserve(tribute_count); for (auto& t : c->GetPP().tributes) { if (t.tribute != TRIBUTE_NONE) { tribute.character_id = c->CharacterID(); tribute.tier = t.tier; tribute.tribute = t.tribute; tributes.emplace_back(tribute); } } CharacterTributeRepository::DeleteWhere(database, fmt::format("character_id = {}", c->CharacterID())); if (tribute_count > 0) { CharacterTributeRepository::InsertMany(database, tributes); } } bool ZoneDatabase::SaveCharacterBandolier( uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name ) { return CharacterBandolierRepository::ReplaceOne( *this, CharacterBandolierRepository::CharacterBandolier{ .id = character_id, .bandolier_id = bandolier_id, .bandolier_slot = bandolier_slot, .item_id = item_id, .icon = icon, .bandolier_name = bandolier_name } ); } bool ZoneDatabase::SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon) { return CharacterPotionbeltRepository::ReplaceOne( *this, CharacterPotionbeltRepository::CharacterPotionbelt{ .id = character_id, .potion_id = potion_id, .item_id = item_id, .icon = icon } ); } bool ZoneDatabase::SaveCharacterLeadershipAbilities(uint32 character_id, PlayerProfile_Struct* pp) { std::vector v; auto e = CharacterLeadershipAbilitiesRepository::NewEntity(); for (int slot_id = 0; slot_id < MAX_LEADERSHIP_AA_ARRAY; slot_id++) { if (pp->leader_abilities.ranks[slot_id] > 0) { e.id = character_id; e.slot = slot_id; e.rank_ = pp->leader_abilities.ranks[slot_id]; v.emplace_back(e); } } return CharacterLeadershipAbilitiesRepository::ReplaceMany(*this, v); } bool ZoneDatabase::SaveCharacterData( Client* c, PlayerProfile_Struct* pp, ExtendedProfile_Struct* m_epp ) { if (!c) { return false; } /* If this is ever zero - the client hasn't fully loaded and potentially crashed during zone */ if (c->AccountID() <= 0) { return false; } clock_t t = std::clock(); /* Function timer start */ auto e = CharacterDataRepository::FindOne(database, c->CharacterID()); if (!e.id) { return false; } e.id = c->CharacterID(); e.account_id = c->AccountID(); e.name = pp->name; e.last_name = pp->last_name; e.gender = pp->gender; e.race = pp->race; e.class_ = pp->class_; e.level = pp->level; e.deity = pp->deity; e.birthday = pp->birthday; e.last_login = pp->lastlogin; e.time_played = pp->timePlayedMin; e.pvp_status = pp->pvp; e.level2 = pp->level2; e.anon = pp->anon; e.gm = pp->gm; e.intoxication = pp->intoxication; e.hair_color = pp->haircolor; e.beard_color = pp->beardcolor; e.eye_color_1 = pp->eyecolor1; e.eye_color_2 = pp->eyecolor2; e.hair_style = pp->hairstyle; e.beard = pp->beard; e.ability_time_seconds = pp->ability_time_seconds; e.ability_number = pp->ability_number; e.ability_time_minutes = pp->ability_time_minutes; e.ability_time_hours = pp->ability_time_hours; e.title = pp->title; e.suffix = pp->suffix; e.exp = pp->exp; e.exp_enabled = c->IsEXPEnabled(); e.points = pp->points; e.mana = pp->mana; e.cur_hp = pp->cur_hp; e.str = pp->STR; e.sta = pp->STA; e.cha = pp->CHA; e.dex = pp->DEX; e.int_ = pp->INT; e.agi = pp->AGI; e.wis = pp->WIS; e.face = pp->face; e.y = pp->y; e.x = pp->x; e.z = pp->z; e.heading = pp->heading; e.pvp2 = pp->pvp2; e.pvp_type = pp->pvptype; e.autosplit_enabled = pp->autosplit; e.zone_change_count = pp->zone_change_count; e.drakkin_heritage = pp->drakkin_heritage; e.drakkin_tattoo = pp->drakkin_tattoo; e.drakkin_details = pp->drakkin_details; e.toxicity = pp->toxicity; e.hunger_level = pp->hunger_level; e.thirst_level = pp->thirst_level; e.ability_up = pp->ability_up; e.zone_id = pp->zone_id; e.zone_instance = pp->zoneInstance; e.leadership_exp_on = pp->leadAAActive; e.ldon_points_guk = pp->ldon_points_guk; e.ldon_points_mir = pp->ldon_points_mir; e.ldon_points_mmc = pp->ldon_points_mmc; e.ldon_points_ruj = pp->ldon_points_ruj; e.ldon_points_tak = pp->ldon_points_tak; e.ldon_points_available = pp->ldon_points_available; e.tribute_time_remaining = pp->tribute_time_remaining; e.show_helm = pp->showhelm; e.career_tribute_points = pp->career_tribute_points; e.tribute_points = pp->tribute_points; e.tribute_active = pp->tribute_active; e.endurance = pp->endurance; e.group_leadership_exp = pp->group_leadership_exp; e.raid_leadership_exp = pp->raid_leadership_exp; e.group_leadership_points = pp->group_leadership_points; e.raid_leadership_points = pp->raid_leadership_points; e.air_remaining = pp->air_remaining; e.pvp_kills = pp->PVPKills; e.pvp_deaths = pp->PVPDeaths; e.pvp_current_points = pp->PVPCurrentPoints; e.pvp_career_points = pp->PVPCareerPoints; e.pvp_best_kill_streak = pp->PVPBestKillStreak; e.pvp_worst_death_streak = pp->PVPWorstDeathStreak; e.pvp_current_kill_streak = pp->PVPCurrentKillStreak; e.aa_points_spent = pp->aapoints_spent; e.aa_exp = pp->expAA; e.aa_points = pp->aapoints; e.group_auto_consent = pp->groupAutoconsent; e.raid_auto_consent = pp->raidAutoconsent; e.guild_auto_consent = pp->guildAutoconsent; e.RestTimer = pp->RestTimer; e.e_aa_effects = m_epp->aa_effects; e.e_percent_to_aa = m_epp->perAA; e.e_expended_aa_spent = m_epp->expended_aa; e.e_last_invsnapshot = m_epp->last_invsnapshot_time; e.mailkey = c->GetMailKeyFull(); const int replaced = CharacterDataRepository::ReplaceOne(database, e); if (!replaced) { LogError("Failed to save character data for [{}] ID [{}].", c->GetCleanName(), c->CharacterID()); return false; } LogDebug( "ZoneDatabase::SaveCharacterData [{}], done Took [{}] seconds", c->CharacterID(), ((float)(std::clock() - t)) / CLOCKS_PER_SEC ); return true; } bool ZoneDatabase::SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp) { ZeroPlayerProfileCurrency(pp); auto e = CharacterCurrencyRepository::NewEntity(); return CharacterCurrencyRepository::ReplaceOne( *this, CharacterCurrencyRepository::CharacterCurrency{ .id = character_id, .platinum = static_cast(pp->platinum), .gold = static_cast(pp->gold), .silver = static_cast(pp->silver), .copper = static_cast(pp->copper), .platinum_bank = static_cast(pp->platinum_bank), .gold_bank = static_cast(pp->gold_bank), .silver_bank = static_cast(pp->silver_bank), .copper_bank = static_cast(pp->copper_bank), .platinum_cursor = static_cast(pp->platinum_cursor), .gold_cursor = static_cast(pp->gold_cursor), .silver_cursor = static_cast(pp->silver_cursor), .copper_cursor = static_cast(pp->copper_cursor), .radiant_crystals = pp->currentRadCrystals, .career_radiant_crystals = pp->careerRadCrystals, .ebon_crystals = pp->currentEbonCrystals, .career_ebon_crystals = pp->careerEbonCrystals } ); } bool ZoneDatabase::SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id){ if (!IsValidSpell(spell_id)) { return false; } return CharacterMemmedSpellsRepository::ReplaceOne( *this, CharacterMemmedSpellsRepository::CharacterMemmedSpells{ .id = character_id, .slot_id = static_cast(slot_id), .spell_id = static_cast(spell_id) } ); } bool ZoneDatabase::SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id) { if (!IsValidSpell(spell_id)) { return false; } return CharacterSpellsRepository::ReplaceOne( *this, CharacterSpellsRepository::CharacterSpells{ .id = character_id, .slot_id = static_cast(slot_id), .spell_id = static_cast(spell_id) } ); } bool ZoneDatabase::DeleteCharacterSpell(uint32 character_id, uint32 slot_id) { return CharacterSpellsRepository::DeleteWhere( *this, fmt::format( "`id` = {} AND `slot_id` = {}", character_id, slot_id ) ); } bool ZoneDatabase::DeleteCharacterDiscipline(uint32 character_id, uint32 slot_id) { return CharacterDisciplinesRepository::DeleteWhere( *this, fmt::format( "`id` = {} AND `slot_id` = {}", character_id, slot_id ) ); } bool ZoneDatabase::DeleteCharacterBandolier(uint32 character_id, uint32 bandolier_id) { return CharacterBandolierRepository::DeleteWhere( *this, fmt::format( "`id` = {} AND `bandolier_id` = {}", character_id, bandolier_id ) ); } bool ZoneDatabase::DeleteCharacterLeadershipAbilities(uint32 character_id) { return CharacterLeadershipAbilitiesRepository::DeleteOne(*this, character_id); } bool ZoneDatabase::DeleteCharacterAAs(uint32 character_id) { return CharacterAlternateAbilitiesRepository::DeleteWhere( *this, fmt::format( "`id` = {} AND `aa_id` NOT IN (SELECT a.first_rank_id FROM aa_ability a WHERE a.grant_only != 0)", character_id ) ); } bool ZoneDatabase::DeleteCharacterMaterialColor(uint32 character_id) { return CharacterMaterialRepository::DeleteOne(*this, character_id); } bool ZoneDatabase::DeleteCharacterMemorizedSpell(uint32 character_id, uint32 slot_id) { return CharacterMemmedSpellsRepository::DeleteWhere( *this, fmt::format( "`id` = {} AND `slot_id` = {}", character_id, slot_id ) ); } bool ZoneDatabase::NoRentExpired(const std::string& name) { const uint32 seconds = CharacterDataRepository::GetSecondsSinceLastLogin(*this, name); return seconds > 1800; } bool ZoneDatabase::SaveCharacterInvSnapshot(uint32 character_id) { uint32 time_index = time(nullptr); std::string query = StringFormat( "INSERT " "INTO" " `inventory_snapshots` " "(`time_index`," " `charid`," " `slotid`," " `itemid`," " `charges`," " `color`," " `augslot1`," " `augslot2`," " `augslot3`," " `augslot4`," " `augslot5`," " `augslot6`," " `instnodrop`," " `custom_data`," " `ornamenticon`," " `ornamentidfile`," " `ornament_hero_model`" ") " "SELECT" " %u," " `charid`," " `slotid`," " `itemid`," " `charges`," " `color`," " `augslot1`," " `augslot2`," " `augslot3`," " `augslot4`," " `augslot5`," " `augslot6`," " `instnodrop`," " `custom_data`," " `ornamenticon`," " `ornamentidfile`," " `ornament_hero_model` " "FROM" " `inventory` " "WHERE" " `charid` = %u", time_index, character_id ); auto results = database.QueryDatabase(query); LogInventory("[{}] ([{}])", character_id, (results.Success() ? "pass" : "fail")); return results.Success(); } int ZoneDatabase::CountCharacterInvSnapshots(uint32 character_id) { std::string query = StringFormat( "SELECT" " COUNT(*) " "FROM " "(" "SELECT * FROM" " `inventory_snapshots` a " "WHERE" " `charid` = %u " "GROUP BY" " `time_index`" ") b", character_id ); auto results = QueryDatabase(query); if (!results.Success()) return -1; auto& row = results.begin(); int64 count = Strings::ToBigInt(row[0]); if (count > 2147483647) return -2; if (count < 0) return -3; return count; } void ZoneDatabase::ClearCharacterInvSnapshots(uint32 character_id, bool from_now) { uint32 del_time = time(nullptr); if (!from_now) { del_time -= RuleI(Character, InvSnapshotHistoryD) * 86400; } std::string query = StringFormat( "DELETE " "FROM" " `inventory_snapshots` " "WHERE" " `charid` = %u " "AND" " `time_index` <= %lu", character_id, (unsigned long)del_time ); QueryDatabase(query); } void ZoneDatabase::ListCharacterInvSnapshots(uint32 character_id, std::list> &is_list) { std::string query = StringFormat( "SELECT" " `time_index`," " COUNT(*) " "FROM" " `inventory_snapshots` " "WHERE" " `charid` = %u " "GROUP BY" " `time_index` " "ORDER BY" " `time_index` " "DESC", character_id ); auto results = QueryDatabase(query); if (!results.Success()) return; for (auto row : results) is_list.emplace_back(std::pair(Strings::ToUnsignedInt(row[0]), Strings::ToInt(row[1]))); } bool ZoneDatabase::ValidateCharacterInvSnapshotTimestamp(uint32 character_id, uint32 timestamp) { if (!character_id || !timestamp) return false; std::string query = StringFormat( "SELECT" " * " "FROM" " `inventory_snapshots` " "WHERE" " `charid` = %u " "AND" " `time_index` = %u " "LIMIT 1", character_id, timestamp ); auto results = QueryDatabase(query); if (!results.Success() || results.RowCount() == 0) return false; return true; } void ZoneDatabase::ParseCharacterInvSnapshot(uint32 character_id, uint32 timestamp, std::list> &parse_list) { std::string query = StringFormat( "SELECT" " `slotid`," " `itemid` " "FROM" " `inventory_snapshots` " "WHERE" " `charid` = %u " "AND" " `time_index` = %u " "ORDER BY" " `slotid`", character_id, timestamp ); auto results = QueryDatabase(query); if (!results.Success()) return; for (auto row : results) parse_list.emplace_back(std::pair(Strings::ToInt(row[0]), Strings::ToUnsignedInt(row[1]))); } void ZoneDatabase::DivergeCharacterInvSnapshotFromInventory(uint32 character_id, uint32 timestamp, std::list> &compare_list) { std::string query = StringFormat( "SELECT" " slotid," " itemid " "FROM" " `inventory_snapshots` " "WHERE" " `time_index` = %u " "AND" " `charid` = %u " "AND" " `slotid` NOT IN " "(" "SELECT" " a.`slotid` " "FROM" " `inventory_snapshots` a " "JOIN" " `inventory` b " "USING" " (`slotid`, `itemid`) " "WHERE" " a.`time_index` = %u " "AND" " a.`charid` = %u " "AND" " b.`charid` = %u" ")", timestamp, character_id, timestamp, character_id, character_id ); auto results = QueryDatabase(query); if (!results.Success()) return; for (auto row : results) compare_list.emplace_back(std::pair(Strings::ToInt(row[0]), Strings::ToUnsignedInt(row[1]))); } void ZoneDatabase::DivergeCharacterInventoryFromInvSnapshot(uint32 character_id, uint32 timestamp, std::list> &compare_list) { std::string query = StringFormat( "SELECT" " `slotid`," " `itemid` " "FROM" " `inventory` " "WHERE" " `charid` = %u " "AND" " `slotid` NOT IN " "(" "SELECT" " a.`slotid` " "FROM" " `inventory` a " "JOIN" " `inventory_snapshots` b " "USING" " (`slotid`, `itemid`) " "WHERE" " b.`time_index` = %u " "AND" " b.`charid` = %u " "AND" " a.`charid` = %u" ")", character_id, timestamp, character_id, character_id ); auto results = QueryDatabase(query); if (!results.Success()) return; for (auto row : results) compare_list.emplace_back(std::pair(Strings::ToInt(row[0]), Strings::ToUnsignedInt(row[1]))); } bool ZoneDatabase::RestoreCharacterInvSnapshot(uint32 character_id, uint32 timestamp) { // we should know what we're doing by the time we call this function..but, // this is to prevent inventory deletions where no timestamp entries exists if (!ValidateCharacterInvSnapshotTimestamp(character_id, timestamp)) { LogError("called for id: [{}] without valid snapshot entries @ [{}]", character_id, timestamp); return false; } std::string query = StringFormat( "DELETE " "FROM" " `inventory` " "WHERE" " `charid` = %u", character_id ); auto results = database.QueryDatabase(query); if (!results.Success()) return false; query = StringFormat( "INSERT " "INTO" " `inventory` " "(`charid`," " `slotid`," " `itemid`," " `charges`," " `color`," " `augslot1`," " `augslot2`," " `augslot3`," " `augslot4`," " `augslot5`," " `augslot6`," " `instnodrop`," " `custom_data`," " `ornamenticon`," " `ornamentidfile`," " `ornament_hero_model`" ") " "SELECT" " `charid`," " `slotid`," " `itemid`," " `charges`," " `color`," " `augslot1`," " `augslot2`," " `augslot3`," " `augslot4`," " `augslot5`," " `augslot6`," " `instnodrop`," " `custom_data`," " `ornamenticon`," " `ornamentidfile`," " `ornament_hero_model` " "FROM" " `inventory_snapshots` " "WHERE" " `charid` = %u " "AND" " `time_index` = %u", character_id, timestamp ); results = database.QueryDatabase(query); LogInventory("[{}] snapshot for [{}] @ [{}]", (results.Success() ? "restored" : "failed to restore"), character_id, timestamp); return results.Success(); } const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load /*= false*/) { const NPCType *npc = nullptr; /* If there is a cached NPC entry, load it */ auto itr = zone->npctable.find(npc_type_id); if (itr != zone->npctable.end()) { return itr->second; } std::string filter = fmt::format("id = {}", npc_type_id); if (bulk_load) { LogDebug("Performing bulk NPC Types load"); filter = fmt::format( SQL( id IN ( select npcID from spawnentry where spawngroupID IN ( select spawngroupID from spawn2 where `zone` = '{}' and (`version` = {} OR `version` = -1) ) ) ), zone->GetShortName(), zone->GetInstanceVersion() ); } std::vector npc_ids; std::vector npc_faction_ids; std::vector loottable_ids; for (NpcTypesRepository::NpcTypes &n : NpcTypesRepository::GetWhere((Database &) content_db, filter)) { NPCType *t; t = new NPCType; memset(t, 0, sizeof *t); t->npc_id = n.id; strn0cpy(t->name, n.name.c_str(), 50); t->level = n.level; t->race = n.race; t->class_ = n.class_; t->max_hp = n.hp; t->current_hp = n.hp; t->Mana = n.mana; t->gender = n.gender; t->texture = n.texture; t->helmtexture = n.helmtexture; t->herosforgemodel = n.herosforgemodel; t->size = n.size; t->loottable_id = n.loottable_id; t->merchanttype = n.merchant_id; t->alt_currency_type = n.alt_currency_id; t->adventure_template = n.adventure_template_id; t->trap_template = n.trap_template; t->attack_speed = n.attack_speed; t->STR = n.STR; t->STA = n.STA; t->DEX = n.DEX; t->AGI = n.AGI; t->INT = n._INT; t->WIS = n.WIS; t->CHA = n.CHA; t->MR = n.MR; t->CR = n.CR; t->DR = n.DR; t->FR = n.FR; t->PR = n.PR; t->Corrup = n.Corrup; t->PhR = n.PhR; t->min_dmg = n.mindmg; t->max_dmg = n.maxdmg; t->attack_count = n.attack_count; if (!n.special_abilities.empty()) { strn0cpy(t->special_abilities, n.special_abilities.c_str(), 512); } else { t->special_abilities[0] = '\0'; } if (n.loottable_id > 0) { // check if we already have this loottable_id before inserting it if (std::find(loottable_ids.begin(), loottable_ids.end(), n.loottable_id) == loottable_ids.end()) { loottable_ids.emplace_back(n.loottable_id); } } t->npc_spells_id = n.npc_spells_id; t->npc_spells_effects_id = n.npc_spells_effects_id; t->d_melee_texture1 = n.d_melee_texture1; t->d_melee_texture2 = n.d_melee_texture2; strn0cpy(t->ammo_idfile, n.ammo_idfile.c_str(), 30); t->prim_melee_type = n.prim_melee_type; t->sec_melee_type = n.sec_melee_type; t->ranged_type = n.ranged_type; t->runspeed = n.runspeed; t->findable = n.findable != 0; t->is_quest_npc = n.isquest != 0; t->trackable = n.trackable != 0; t->hp_regen = n.hp_regen_rate; t->mana_regen = n.mana_regen_rate; // set default value for aggroradius t->aggroradius = (int32) n.aggroradius; if (t->aggroradius <= 0) { t->aggroradius = 70; } t->assistradius = (int32) n.assistradius; if (t->assistradius <= 0) { t->assistradius = t->aggroradius; } if (n.bodytype > 0) { t->bodytype = n.bodytype; } else { t->bodytype = 0; } // facial features t->npc_faction_id = n.npc_faction_id; t->luclinface = n.face; t->hairstyle = n.luclin_hairstyle; t->haircolor = n.luclin_haircolor; t->eyecolor1 = n.luclin_eyecolor; t->eyecolor2 = n.luclin_eyecolor2; t->beardcolor = n.luclin_beardcolor; t->beard = n.luclin_beard; t->drakkin_heritage = n.drakkin_heritage; t->drakkin_tattoo = n.drakkin_tattoo; t->drakkin_details = n.drakkin_details; if (t->npc_faction_id > 0) { if ( std::find( npc_faction_ids.begin(), npc_faction_ids.end(), t->npc_faction_id ) == npc_faction_ids.end() ) { npc_faction_ids.emplace_back(t->npc_faction_id); } } // armor tint uint32 armor_tint_id = n.armortint_id; t->armor_tint.Head.Color = (n.armortint_red & 0xFF) << 16; t->armor_tint.Head.Color |= (n.armortint_green & 0xFF) << 8; t->armor_tint.Head.Color |= (n.armortint_blue & 0xFF); t->armor_tint.Head.Color |= (t->armor_tint.Head.Color) ? (0xFF << 24) : 0; if (armor_tint_id != 0) { std::string armortint_query = StringFormat( "SELECT red1h, grn1h, blu1h, " "red2c, grn2c, blu2c, " "red3a, grn3a, blu3a, " "red4b, grn4b, blu4b, " "red5g, grn5g, blu5g, " "red6l, grn6l, blu6l, " "red7f, grn7f, blu7f, " "red8x, grn8x, blu8x, " "red9x, grn9x, blu9x " "FROM npc_types_tint WHERE id = %d", armor_tint_id ); auto armortint_results = QueryDatabase(armortint_query); if (!armortint_results.Success() || armortint_results.RowCount() == 0) { armor_tint_id = 0; } else { auto& armorTint_row = armortint_results.begin(); for (int index = EQ::textures::textureBegin; index <= EQ::textures::LastTexture; index++) { t->armor_tint.Slot[index].Color = Strings::ToInt(armorTint_row[index * 3]) << 16; t->armor_tint.Slot[index].Color |= Strings::ToInt(armorTint_row[index * 3 + 1]) << 8; t->armor_tint.Slot[index].Color |= Strings::ToInt(armorTint_row[index * 3 + 2]); t->armor_tint.Slot[index].Color |= (t->armor_tint.Slot[index].Color) ? (0xFF << 24) : 0; } } } // Try loading npc_types tint fields if armor tint is 0 or query failed to get results if (armor_tint_id == 0) { for (int index = EQ::textures::armorChest; index < EQ::textures::materialCount; index++) { t->armor_tint.Slot[index].Color = t->armor_tint.Slot[0].Color; // odd way to 'zero-out' the array... } } t->see_invis = n.see_invis; t->see_invis_undead = n.see_invis_undead != 0; // Set see_invis_undead flag if (!RuleB(NPC, DisableLastNames) && !n.lastname.empty()) { strn0cpy(t->lastname, n.lastname.c_str(), sizeof(t->lastname)); } t->qglobal = n.qglobal != 0; // qglobal t->AC = n.AC; t->npc_aggro = n.npc_aggro != 0; t->spawn_limit = n.spawn_limit; t->see_hide = n.see_hide != 0; t->see_improved_hide = n.see_improved_hide != 0; t->ATK = n.ATK; t->accuracy_rating = n.Accuracy; t->avoidance_rating = n.Avoidance; t->slow_mitigation = n.slow_mitigation; t->maxlevel = n.maxlevel; t->scalerate = n.scalerate; t->private_corpse = n.private_corpse != 0; t->unique_spawn_by_name = n.unique_spawn_by_name != 0; t->underwater = n.underwater != 0; t->emoteid = n.emoteid; t->spellscale = n.spellscale; t->healscale = n.healscale; t->no_target_hotkey = n.no_target_hotkey != 0; t->raid_target = n.raid_target != 0; t->attack_delay = n.attack_delay * 100; // TODO: fix DB t->light = (n.light & 0x0F); t->armtexture = n.armtexture; t->bracertexture = n.bracertexture; t->handtexture = n.handtexture; t->legtexture = n.legtexture; t->feettexture = n.feettexture; t->ignore_despawn = n.ignore_despawn != 0; t->show_name = n.show_name != 0; t->untargetable = n.untargetable != 0; t->charm_ac = n.charm_ac; t->charm_min_dmg = n.charm_min_dmg; t->charm_max_dmg = n.charm_max_dmg; t->charm_attack_delay = n.charm_attack_delay * 100; // TODO: fix DB t->charm_accuracy_rating = n.charm_accuracy_rating; t->charm_avoidance_rating = n.charm_avoidance_rating; t->charm_atk = n.charm_atk; t->skip_global_loot = n.skip_global_loot != 0; t->rare_spawn = n.rare_spawn != 0; t->stuck_behavior = n.stuck_behavior; t->use_model = n.model; t->flymode = n.flymode; t->always_aggro = n.always_aggro != 0; t->exp_mod = n.exp_mod; t->skip_auto_scale = false; // hardcoded here for now t->hp_regen_per_second = n.hp_regen_per_second; t->heroic_strikethrough = n.heroic_strikethrough; t->faction_amount = n.faction_amount; t->keeps_sold_items = n.keeps_sold_items; // If NPC with duplicate NPC id already in table, // free item we attempted to add. if (zone->npctable.find(t->npc_id) != zone->npctable.end()) { std::cerr << "Error loading duplicate NPC " << t->npc_id << std::endl; delete t; return nullptr; } zone->npctable[t->npc_id] = t; npc = t; // If NPC ID is not in npc_ids, add to vector if (!std::count(npc_ids.begin(), npc_ids.end(), t->npc_id)) { npc_ids.emplace_back(t->npc_id); } } DataBucket::BulkLoadEntities(DataBucketLoadType::NPC, npc_ids); if (!npc_faction_ids.empty()) { zone->LoadNPCFactions(npc_faction_ids); zone->LoadNPCFactionAssociations(npc_faction_ids); } zone->LoadLootTables(loottable_ids); return npc; } const NPCType* ZoneDatabase::GetMercenaryType(uint32 merc_npc_type_id, uint16 race_id, uint32 owner_level) { const uint32 merc_type_id = merc_npc_type_id * 100 + owner_level; auto i = zone->merctable.find(merc_type_id); if (i != zone->merctable.end()) { return i->second; } if (!merc_npc_type_id) { return nullptr; } const std::string& query = fmt::format( SQL( SELECT m_stats.merc_npc_type_id, '' AS name, m_stats.level, m_types.race_id, m_subtypes.class_id, m_stats.hp, m_stats.mana, 0 AS gender, m_armorinfo.texture, m_armorinfo.helmtexture, m_stats.attack_delay, m_stats.STR, m_stats.STA, m_stats.DEX, m_stats.AGI, m_stats._INT, m_stats.WIS, m_stats.CHA, m_stats.MR, m_stats.CR, m_stats.DR, m_stats.FR, m_stats.PR, m_stats.Corrup, m_stats.mindmg, m_stats.maxdmg, m_stats.attack_count, m_stats.special_abilities, m_weaponinfo.d_melee_texture1, m_weaponinfo.d_melee_texture2, m_weaponinfo.prim_melee_type, m_weaponinfo.sec_melee_type, m_stats.runspeed, m_stats.hp_regen_rate, m_stats.mana_regen_rate, 1 AS bodytype, m_armorinfo.armortint_id, m_armorinfo.armortint_red, m_armorinfo.armortint_green, m_armorinfo.armortint_blue, m_stats.AC, m_stats.ATK, m_stats.Accuracy, m_stats.statscale, m_stats.spellscale, m_stats.healscale FROM merc_stats m_stats INNER JOIN merc_armorinfo m_armorinfo ON m_stats.merc_npc_type_id = m_armorinfo.merc_npc_type_id AND m_armorinfo.minlevel <= m_stats.level AND m_armorinfo.maxlevel >= m_stats.level INNER JOIN merc_weaponinfo m_weaponinfo ON m_stats.merc_npc_type_id = m_weaponinfo.merc_npc_type_id AND m_weaponinfo.minlevel <= m_stats.level AND m_weaponinfo.maxlevel >= m_stats.level INNER JOIN merc_templates m_templates ON m_templates.merc_npc_type_id = m_stats.merc_npc_type_id INNER JOIN merc_types m_types ON m_templates.merc_type_id = m_types.merc_type_id INNER JOIN merc_subtypes m_subtypes ON m_templates.merc_subtype_id = m_subtypes.merc_subtype_id WHERE m_templates.merc_npc_type_id = {} AND m_types.race_id = {} AND m_stats.clientlevel = {} ), merc_npc_type_id, race_id, owner_level ); auto results = QueryDatabase(query); if (!results.Success() || !results.RowCount()) { return nullptr; } const NPCType* n = nullptr; auto row = results.begin(); NPCType* t = new NPCType; memset(t, 0, sizeof *t); t->npc_id = Strings::ToInt(row[0]); strn0cpy(t->name, row[1], sizeof(t->name)); t->level = Strings::ToInt(row[2]); t->race = Strings::ToInt(row[3]); t->class_ = Strings::ToInt(row[4]); t->max_hp = Strings::ToInt(row[5]); t->current_hp = t->max_hp; t->Mana = Strings::ToInt(row[6]); t->gender = Strings::ToInt(row[7]); t->texture = Strings::ToInt(row[8]); t->helmtexture = Strings::ToInt(row[9]); t->attack_delay = Strings::ToInt(row[10]) * 100; // TODO: fix DB t->STR = Strings::ToInt(row[11]); t->STA = Strings::ToInt(row[12]); t->DEX = Strings::ToInt(row[13]); t->AGI = Strings::ToInt(row[14]); t->INT = Strings::ToInt(row[15]); t->WIS = Strings::ToInt(row[16]); t->CHA = Strings::ToInt(row[17]); t->MR = Strings::ToInt(row[18]); t->CR = Strings::ToInt(row[19]); t->DR = Strings::ToInt(row[20]); t->FR = Strings::ToInt(row[21]); t->PR = Strings::ToInt(row[22]); t->Corrup = Strings::ToInt(row[23]); t->min_dmg = Strings::ToInt(row[24]); t->max_dmg = Strings::ToInt(row[25]); t->attack_count = Strings::ToInt(row[26]); if (row[27]) { strn0cpy(t->special_abilities, row[27], sizeof(t->special_abilities)); } else { t->special_abilities[0] = '\0'; } t->d_melee_texture1 = Strings::ToUnsignedInt(row[28]); t->d_melee_texture2 = Strings::ToUnsignedInt(row[29]); t->prim_melee_type = Strings::ToInt(row[30]); t->sec_melee_type = Strings::ToInt(row[31]); t->runspeed = Strings::ToFloat(row[32]); t->hp_regen = Strings::ToInt(row[33]); t->mana_regen = Strings::ToInt(row[34]); t->aggroradius = RuleI(Mercs, AggroRadius); t->bodytype = row[35] && strlen(row[35]) ? static_cast(Strings::ToUnsignedInt(row[35])) : 1; uint32 armor_tint_id = Strings::ToInt(row[36]); t->armor_tint.Slot[0].Color = (Strings::ToInt(row[37]) & 0xFF) << 16; t->armor_tint.Slot[0].Color |= (Strings::ToInt(row[38]) & 0xFF) << 8; t->armor_tint.Slot[0].Color |= (Strings::ToInt(row[39]) & 0xFF); t->armor_tint.Slot[0].Color |= (t->armor_tint.Slot[0].Color) ? (0xFF << 24) : 0; if (armor_tint_id == 0) { for (int index = EQ::textures::armorChest; index <= EQ::textures::LastTexture; index++) { t->armor_tint.Slot[index].Color = t->armor_tint.Slot[0].Color; } } else if (t->armor_tint.Slot[0].Color == 0) { const auto& e = NpcTypesTintRepository::FindOne(*this, armor_tint_id); if (!e.id) { armor_tint_id = 0; } else { t->armor_tint.Slot[EQ::textures::armorHead].Color = e.red1h << 16; t->armor_tint.Slot[EQ::textures::armorHead].Color = e.grn1h << 8; t->armor_tint.Slot[EQ::textures::armorHead].Color = e.blu1h; t->armor_tint.Slot[EQ::textures::armorChest].Color = e.red2c << 16; t->armor_tint.Slot[EQ::textures::armorChest].Color = e.grn2c << 8; t->armor_tint.Slot[EQ::textures::armorChest].Color = e.blu2c; t->armor_tint.Slot[EQ::textures::armorArms].Color = e.red3a << 16; t->armor_tint.Slot[EQ::textures::armorArms].Color = e.grn3a << 8; t->armor_tint.Slot[EQ::textures::armorArms].Color = e.blu3a; t->armor_tint.Slot[EQ::textures::armorWrist].Color = e.red4b << 16; t->armor_tint.Slot[EQ::textures::armorWrist].Color = e.grn4b << 8; t->armor_tint.Slot[EQ::textures::armorWrist].Color = e.blu4b; t->armor_tint.Slot[EQ::textures::armorHands].Color = e.red5g << 16; t->armor_tint.Slot[EQ::textures::armorHands].Color = e.grn5g << 8; t->armor_tint.Slot[EQ::textures::armorHands].Color = e.blu5g; t->armor_tint.Slot[EQ::textures::armorLegs].Color = e.red6l << 16; t->armor_tint.Slot[EQ::textures::armorLegs].Color = e.grn6l << 8; t->armor_tint.Slot[EQ::textures::armorLegs].Color = e.blu6l; t->armor_tint.Slot[EQ::textures::armorFeet].Color = e.red7f << 16; t->armor_tint.Slot[EQ::textures::armorFeet].Color = e.grn7f << 8; t->armor_tint.Slot[EQ::textures::armorFeet].Color = e.blu7f; t->armor_tint.Slot[EQ::textures::weaponPrimary].Color = e.red8x << 16; t->armor_tint.Slot[EQ::textures::weaponPrimary].Color = e.grn8x << 8; t->armor_tint.Slot[EQ::textures::weaponPrimary].Color = e.blu8x; t->armor_tint.Slot[EQ::textures::weaponSecondary].Color = e.red9x << 16; t->armor_tint.Slot[EQ::textures::weaponSecondary].Color = e.grn9x << 8; t->armor_tint.Slot[EQ::textures::weaponSecondary].Color = e.blu9x; } } t->AC = Strings::ToInt(row[40]); t->ATK = Strings::ToInt(row[41]); t->accuracy_rating = Strings::ToInt(row[42]); t->scalerate = Strings::ToInt(row[43]); t->spellscale = Strings::ToInt(row[44]); t->healscale = Strings::ToInt(row[45]); t->skip_global_loot = true; t->skip_auto_scale = true; // If Merc with duplicate NPC id already in table, // free item we attempted to add. if (zone->merctable.find(merc_type_id) != zone->merctable.end()) { delete t; return nullptr; } zone->merctable[merc_type_id] = t; n = t; return n; } bool ZoneDatabase::LoadMercenaryInfo(Client* c) { const auto& l = MercsRepository::GetWhere( *this, fmt::format( "`OwnerCharacterID` = {} ORDER BY `Slot`", c->CharacterID() ) ); if (l.empty()) { return false; } for (const auto& e : l) { if (e.Slot >= MAXMERCS) { continue; } auto& m = c->GetMercInfo(e.Slot); strn0cpy(m.merc_name, e.Name.c_str(), sizeof(m.merc_name)); m.mercid = e.MercID; m.slot = e.Slot; m.MercTemplateID = e.TemplateID; m.SuspendedTime = e.SuspendedTime; m.IsSuspended = e.IsSuspended; m.MercTimerRemaining = e.TimerRemaining; m.Gender = e.Gender; m.MercSize = e.MercSize; m.State = 5; m.Stance = e.StanceID; m.hp = e.HP; m.mana = e.Mana; m.endurance = e.Endurance; m.face = e.Face; m.luclinHairStyle = e.LuclinHairStyle; m.luclinHairColor = e.LuclinHairColor; m.luclinEyeColor = e.LuclinEyeColor; m.luclinEyeColor2 = e.LuclinEyeColor2; m.luclinBeardColor = e.LuclinBeardColor; m.luclinBeard = e.LuclinBeard; m.drakkinHeritage = e.DrakkinHeritage; m.drakkinTattoo = e.DrakkinTattoo; m.drakkinDetails = e.DrakkinDetails; } return true; } bool ZoneDatabase::LoadCurrentMercenary(Client* c) { const uint8 mercenary_slot = c->GetMercSlot(); if (mercenary_slot > MAXMERCS) { return false; } const auto& e = MercsRepository::GetMercenaryBySlot(*this, c); if (!e.MercID) { return false; } auto& m = c->GetMercInfo(mercenary_slot); strn0cpy(m.merc_name, e.Name.c_str(), sizeof(m.merc_name)); m.mercid = e.MercID; m.slot = e.Slot; m.MercTemplateID = e.TemplateID; m.SuspendedTime = e.SuspendedTime; m.IsSuspended = e.IsSuspended; m.MercTimerRemaining = e.TimerRemaining; m.Gender = e.Gender; m.MercSize = e.MercSize; m.State = e.StanceID; m.hp = e.HP; m.mana = e.Mana; m.endurance = e.Endurance; m.face = e.Face; m.luclinHairStyle = e.LuclinHairStyle; m.luclinHairColor = e.LuclinHairColor; m.luclinEyeColor = e.LuclinEyeColor; m.luclinEyeColor2 = e.LuclinEyeColor2; m.luclinBeardColor = e.LuclinBeardColor; m.luclinBeard = e.LuclinBeard; m.drakkinHeritage = e.DrakkinHeritage; m.drakkinTattoo = e.DrakkinTattoo; m.drakkinDetails = e.DrakkinDetails; return true; } bool ZoneDatabase::SaveMercenary(Merc* m) { Client* c = m->GetMercenaryOwner(); if (!c) { return false; } if (!m->GetMercenaryID()) { auto e = MercsRepository::NewEntity(); e.OwnerCharacterID = m->GetMercenaryCharacterID(); e.Slot = (c->GetNumberOfMercenaries() - 1); e.Name = m->GetCleanName(); e.TemplateID = m->GetMercenaryTemplateID(); e.SuspendedTime = c->GetMercInfo().SuspendedTime; e.IsSuspended = m->IsSuspended(); e.TimerRemaining = c->GetMercInfo().MercTimerRemaining; e.Gender = m->GetGender(); e.MercSize = m->GetSize(); e.StanceID = m->GetStance(); e.HP = m->GetHP(); e.Mana = m->GetMana(); e.Endurance = m->GetEndurance(); e.Face = m->GetLuclinFace(); e.LuclinHairStyle = m->GetHairStyle(); e.LuclinHairColor = m->GetHairColor(); e.LuclinEyeColor = m->GetEyeColor1(); e.LuclinEyeColor2 = m->GetEyeColor2(); e.LuclinBeardColor = m->GetBeardColor(); e.LuclinBeard = m->GetBeard(); e.DrakkinHeritage = m->GetDrakkinHeritage(); e.DrakkinTattoo = m->GetDrakkinTattoo(); e.DrakkinDetails = m->GetDrakkinDetails(); e = MercsRepository::InsertOne(*this, e); if (!e.MercID) { c->Message(Chat::Red, "Unable to save mercenary to the database."); return false; } m->SetMercID(e.MercID); m->UpdateMercInfo(c); database.SaveMercenaryBuffs(m); return true; } auto e = MercsRepository::FindOne(*this, m->GetMercenaryID()); e.OwnerCharacterID = m->GetMercenaryCharacterID(); e.Slot = (c->GetNumberOfMercenaries() - 1); e.Name = m->GetCleanName(); e.TemplateID = m->GetMercenaryTemplateID(); e.SuspendedTime = c->GetMercInfo().SuspendedTime; e.IsSuspended = m->IsSuspended(); e.TimerRemaining = c->GetMercInfo().MercTimerRemaining; e.Gender = m->GetGender(); e.MercSize = m->GetSize(); e.StanceID = m->GetStance(); e.HP = m->GetHP(); e.Mana = m->GetMana(); e.Endurance = m->GetEndurance(); e.Face = m->GetLuclinFace(); e.LuclinHairStyle = m->GetHairStyle(); e.LuclinHairColor = m->GetHairColor(); e.LuclinEyeColor = m->GetEyeColor1(); e.LuclinEyeColor2 = m->GetEyeColor2(); e.LuclinBeardColor = m->GetBeardColor(); e.LuclinBeard = m->GetBeard(); e.DrakkinHeritage = m->GetDrakkinHeritage(); e.DrakkinTattoo = m->GetDrakkinTattoo(); e.DrakkinDetails = m->GetDrakkinDetails(); const int updated = MercsRepository::UpdateOne(*this, e); if (!updated) { c->Message(Chat::Red, "Unable to save mercenary to the database."); return false; } m->UpdateMercInfo(c); database.SaveMercenaryBuffs(m); return true; } void ZoneDatabase::SaveMercenaryBuffs(Merc* m) { auto buffs = m->GetBuffs(); MercBuffsRepository::DeleteWhere( *this, fmt::format( "`MercID` = {}", m->GetMercenaryID() ) ); std::vector v; auto e = MercBuffsRepository::NewEntity(); for (uint32 slot_id = 0; slot_id <= BUFF_COUNT; slot_id++) { if (!IsValidSpell(buffs[slot_id].spellid)) { continue; } e.MercId = m->GetMercenaryID(); e.SpellId = buffs[slot_id].spellid; e.CasterLevel = buffs[slot_id].casterlevel; e.DurationFormula = spells[buffs[slot_id].spellid].buff_duration_formula; e.TicsRemaining = buffs[slot_id].ticsremaining; e.PoisonCounters = CalculatePoisonCounters(buffs[slot_id].spellid) > 0 ? buffs[slot_id].counters : 0; e.DiseaseCounters = CalculateDiseaseCounters(buffs[slot_id].spellid) > 0 ? buffs[slot_id].counters : 0; e.CurseCounters = CalculateCurseCounters(buffs[slot_id].spellid) > 0 ? buffs[slot_id].counters : 0; e.CorruptionCounters = CalculateCorruptionCounters(buffs[slot_id].spellid) > 0 ? buffs[slot_id].counters : 0; e.HitCount = buffs[slot_id].hit_number; e.MeleeRune = buffs[slot_id].melee_rune; e.MagicRune = buffs[slot_id].magic_rune; e.dot_rune = buffs[slot_id].dot_rune; e.caston_x = buffs[slot_id].caston_x; e.caston_y = buffs[slot_id].caston_y; e.caston_z = buffs[slot_id].caston_z; e.Persistent = buffs[slot_id].persistant_buff ? 1 : 0; e.ExtraDIChance = buffs[slot_id].ExtraDIChance; v.emplace_back(e); } if (!v.empty()) { MercBuffsRepository::InsertMany(*this, v); } } void ZoneDatabase::LoadMercenaryBuffs(Merc* m) { auto buffs = m->GetBuffs(); const int max_slots = m->GetMaxBuffSlots(); const auto& l = MercBuffsRepository::GetWhere( *this, fmt::format( "`MercID` = {}", m->GetMercenaryID() ) ); if (l.empty()) { return; } uint32 slot_id = 0; for (const auto& e : l) { if (slot_id == BUFF_COUNT) { break; } buffs[slot_id].spellid = e.SpellId; buffs[slot_id].casterlevel = e.CasterLevel; buffs[slot_id].ticsremaining = e.TicsRemaining; buffs[slot_id].hit_number = e.HitCount; buffs[slot_id].melee_rune = e.MeleeRune; buffs[slot_id].magic_rune = e.MagicRune; buffs[slot_id].dot_rune = e.dot_rune; buffs[slot_id].caston_x = e.caston_x; buffs[slot_id].caston_y = e.caston_y; buffs[slot_id].caston_z = e.caston_z; buffs[slot_id].casterid = 0; buffs[slot_id].ExtraDIChance = e.ExtraDIChance; buffs[slot_id].persistant_buff = e.Persistent; if (CalculatePoisonCounters(buffs[slot_id].spellid) > 0) { buffs[slot_id].counters = e.PoisonCounters; } if (CalculateDiseaseCounters(buffs[slot_id].spellid) > 0) { buffs[slot_id].counters = e.DiseaseCounters; } if (CalculateCurseCounters(buffs[slot_id].spellid) > 0) { buffs[slot_id].counters = e.CurseCounters; } if (CalculateCorruptionCounters(buffs[slot_id].spellid) > 0) { buffs[slot_id].counters = e.CorruptionCounters; } } MercBuffsRepository::DeleteWhere( *this, fmt::format( "`MercID` = {}", m->GetMercenaryID() ) ); } bool ZoneDatabase::DeleteMercenary(uint32 mercenary_id) { if (!mercenary_id) { return false; } MercBuffsRepository::DeleteWhere( *this, fmt::format( "`MercID` = {}", mercenary_id ) ); const int deleted = MercsRepository::DeleteOne(*this, mercenary_id); if (!deleted) { return false; } return true; } void ZoneDatabase::LoadMercenaryEquipment(Merc* m) { const int merc_subtype_id = MercSubtypesRepository::GetSubtype(*this, m->GetClass(), m->GetTierID()); const auto& l = MercInventoryRepository::GetWhere( *this, fmt::format( "`merc_subtype_id` = {} AND `min_level` <= {} AND `max_level` >= {}", merc_subtype_id, m->GetLevel(), m->GetLevel() ) ); if (l.empty()) { return; } uint32 item_count = 0; for (const auto& e: l) { if (item_count == EQ::invslot::EQUIPMENT_COUNT) { break; } if (!e.item_id) { continue; } m->AddItem(item_count, e.item_id); item_count++; } } void ZoneDatabase::SaveMerchantTemp( uint32 npc_id, uint32 slot_id, uint32 zone_id, uint32 instance_id, uint32 item_id, uint32 charges ) { auto e = MerchantlistTempRepository::NewEntity(); e.npcid = npc_id; e.slot = slot_id; e.zone_id = zone_id; e.instance_id = instance_id; e.itemid = item_id; e.charges = charges; MerchantlistTempRepository::ReplaceOne(*this, e); } void ZoneDatabase::DeleteMerchantTemp(uint32 npc_id, uint32 slot_id, uint32 zone_id, uint32 instance_id) { MerchantlistTempRepository::DeleteWhere( *this, fmt::format( "`npcid` = {} AND `slot` = {} AND `zone_id` = {} AND `instance_id` = {}", npc_id, slot_id, zone_id, instance_id ) ); } uint32 ZoneDatabase::GetZoneTimezone(uint32 zone_id, uint32 instance_version) { const auto& l = ZoneRepository::GetWhere( *this, fmt::format( "`zoneidnumber` = {} AND (`version` = {} OR `version` = 0) ORDER BY `version` DESC", zone_id, instance_version ) ); if (l.empty()) { return 0; } return l[0].id ? l[0].timezone : 0; } bool ZoneDatabase::SetZoneTimezone(uint32 zone_id, uint32 instance_version, uint32 timezone) { return ZoneRepository::SetTimeZone(*this, zone_id, instance_version, timezone); } void ZoneDatabase::RefreshGroupFromDB(Client *client){ if (!client) { return; } Group *group = client->GetGroup(); if (!group) { return; } auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupUpdate2_Struct)); GroupUpdate2_Struct* gu = (GroupUpdate2_Struct*)outapp->pBuffer; gu->action = groupActUpdate; strcpy(gu->yourname, client->GetName()); GetGroupLeadershipInfo(group->GetID(), gu->leadersname, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &gu->leader_aas); gu->NPCMarkerID = group->GetNPCMarkerID(); int index = 0; auto query = fmt::format( "SELECT name FROM group_id WHERE groupid = {}", group->GetID() ); auto results = QueryDatabase(query); if (results.Success()) { for (auto row : results) { if (index >= 6) { continue; } if (!strcmp(client->GetName(), row[0])) { continue; } strcpy(gu->membername[index], row[0]); index++; } } client->QueuePacket(outapp); safe_delete(outapp); if (client->ClientVersion() >= EQ::versions::ClientVersion::SoD) { group->NotifyMainTank(client, 1); group->NotifyPuller(client, 1); } group->NotifyMainAssist(client, 1); group->NotifyMarkNPC(client); group->NotifyAssistTarget(client); group->NotifyTankTarget(client); group->NotifyPullerTarget(client); group->SendMarkedNPCsToMember(client); } int64 ZoneDatabase::GetBlockedSpellsCount(uint32 zone_id) { return BlockedSpellsRepository::Count( *this, fmt::format( "zoneid = {} {}", zone_id, ContentFilterCriteria::apply() ) ); } bool ZoneDatabase::LoadBlockedSpells(int64 blocked_spells_count, ZoneSpellsBlocked* into, uint32 zone_id) { LogInfo("Loading Blocked Spells from database for {} ({}).", zone_store.GetZoneName(zone_id, true), zone_id); const auto& l = BlockedSpellsRepository::GetWhere( *this, fmt::format( "zoneid = {} {} ORDER BY id ASC", zone_id, ContentFilterCriteria::apply() ) ); if (l.empty()) { return true; } int64 i = 0; for (const auto& e : l) { if (i >= blocked_spells_count) { LogError( "Blocked spells count of {} exceeded for {} ({}).", blocked_spells_count, zone_store.GetZoneName(zone_id, true), zone_id ); break; } memset(&into[i], 0, sizeof(ZoneSpellsBlocked)); into[i].spellid = e.spellid; into[i].type = e.type; into[i].m_Location = glm::vec3(e.x, e.y, e.z); into[i].m_Difference = glm::vec3(e.x_diff, e.y_diff, e.z_diff); strn0cpy(into[i].message, e.message.c_str(), sizeof(into[i].message)); i++; } return true; } int ZoneDatabase::getZoneShutDownDelay(uint32 zoneID, uint32 version) { auto z = GetZoneVersionWithFallback(zoneID, version); return z ? z->shutdowndelay : RuleI(Zone, AutoShutdownDelay); } uint32 ZoneDatabase::GetKarma(uint32 account_id) { const auto& e = AccountRepository::FindOne(*this, account_id); return !e.id ? 0 : e.karma; } void ZoneDatabase::UpdateKarma(uint32 account_id, uint32 amount) { auto e = AccountRepository::FindOne(*this, account_id); if (!e.id) { return; } e.karma = amount; AccountRepository::UpdateOne(*this, e); } void ZoneDatabase::ListAllInstances(Client* client, uint32 character_id) { if (!client) { return; } std::string query = fmt::format( "SELECT instance_list.id, zone, version, start_time, duration, never_expires " "FROM instance_list JOIN instance_list_player " "ON instance_list.id = instance_list_player.id " "WHERE instance_list_player.charid = {}", character_id ); auto results = QueryDatabase(query); if (!results.Success()) { return; } auto character_name = database.GetCharNameByID(character_id); bool is_same_client = client->CharacterID() == character_id; if (character_name.empty()) { client->Message( Chat::White, fmt::format( "Character ID '{}' does not exist.", character_id ).c_str() ); return; } if (!results.RowCount()) { client->Message( Chat::White, fmt::format( "{} not in any Instances.", ( is_same_client ? "You are" : fmt::format( "{} ({}) is", character_name, character_id ) ) ).c_str() ); return; } client->Message( Chat::White, fmt::format( "{} in the following Instances.", ( is_same_client ? "You are" : fmt::format( "{} ({}) is", character_name, character_id ) ) ).c_str() ); uint32 instance_count = 0; for (auto row : results) { auto instance_id = Strings::ToUnsignedInt(row[0]); auto zone_id = Strings::ToUnsignedInt(row[1]); auto version = Strings::ToUnsignedInt(row[2]); auto start_time = Strings::ToUnsignedInt(row[3]); auto duration = Strings::ToUnsignedInt(row[4]); auto never_expires = Strings::ToInt(row[5]) ? true : false; std::string remaining_time_string = "Never"; timeval time_value; gettimeofday(&time_value, nullptr); auto current_time = time_value.tv_sec; auto remaining_time = ((start_time + duration) - current_time); if (!never_expires) { if (remaining_time > 0) { remaining_time_string = Strings::SecondsToTime(remaining_time); } else { remaining_time_string = "Already Expired"; } } client->Message( Chat::White, fmt::format("Instance {} | Zone: {} ({}){}", instance_id, ZoneLongName(zone_id), zone_id, ( version ? fmt::format( " Version: {}", version ) : "" ) ).c_str() ); client->Message( Chat::White, fmt::format( "Instance {} | Expires: {}", instance_id, remaining_time_string, remaining_time ).c_str() ); instance_count++; } client->Message( Chat::White, fmt::format( "{} in {} Instance{}.", ( is_same_client ? "You are" : fmt::format( "{} ({}) is", character_name, character_id ) ), instance_count, instance_count != 1 ? "s" : "" ).c_str() ); } void ZoneDatabase::QGlobalPurge() { const std::string query = "DELETE FROM quest_globals WHERE expdate < UNIX_TIMESTAMP()"; database.QueryDatabase(query); } void ZoneDatabase::LoadAltCurrencyValues(uint32 char_id, std::map ¤cy) { const auto& l = CharacterAltCurrencyRepository::GetWhere( *this, fmt::format( "`char_id` = {}", char_id ) ); if (l.empty()) { return; } for (const auto& e : l) { currency[e.currency_id] = e.amount; } } void ZoneDatabase::UpdateAltCurrencyValue(uint32 char_id, uint32 currency_id, uint32 value) { auto e = CharacterAltCurrencyRepository::NewEntity(); e.char_id = char_id; e.currency_id = currency_id; e.amount = value; CharacterAltCurrencyRepository::ReplaceOne(*this, e); } void ZoneDatabase::SaveBuffs(Client *client) { CharacterBuffsRepository::DeleteWhere( database, fmt::format( "`character_id` = {}", client->CharacterID() ) ); auto buffs = client->GetBuffs(); const int max_buff_slots = client->GetMaxBuffSlots(); std::vector v; auto e = CharacterBuffsRepository::NewEntity(); uint32 character_buff_count = 0; for (int slot_id = 0; slot_id < max_buff_slots; slot_id++) { if (!IsValidSpell(buffs[slot_id].spellid)) { continue; } character_buff_count++; } v.reserve(character_buff_count); for (int slot_id = 0; slot_id < max_buff_slots; slot_id++) { if (!IsValidSpell(buffs[slot_id].spellid)) { continue; } e.character_id = client->CharacterID(); e.slot_id = slot_id; e.spell_id = buffs[slot_id].spellid; e.caster_level = buffs[slot_id].casterlevel; e.caster_name = buffs[slot_id].caster_name; e.ticsremaining = buffs[slot_id].ticsremaining; e.counters = buffs[slot_id].counters; e.numhits = buffs[slot_id].hit_number; e.melee_rune = buffs[slot_id].melee_rune; e.magic_rune = buffs[slot_id].magic_rune; e.persistent = buffs[slot_id].persistant_buff; e.dot_rune = buffs[slot_id].dot_rune; e.caston_x = buffs[slot_id].caston_x; e.caston_y = buffs[slot_id].caston_y; e.caston_z = buffs[slot_id].caston_z; e.ExtraDIChance = buffs[slot_id].ExtraDIChance; e.instrument_mod = buffs[slot_id].instrument_mod; v.emplace_back(e); } if (!v.empty()) { CharacterBuffsRepository::ReplaceMany(database, v); } } void ZoneDatabase::LoadBuffs(Client *client) { auto buffs = client->GetBuffs(); int max_buff_slots = client->GetMaxBuffSlots(); for (int slot_id = 0; slot_id < max_buff_slots; ++slot_id) { buffs[slot_id].spellid = SPELL_UNKNOWN; } const auto& l = CharacterBuffsRepository::GetWhere( *this, fmt::format( "`character_id` = {}", client->CharacterID() ) ); if (l.empty()) { return; } for (const auto& e : l) { if (e.slot_id >= max_buff_slots) { continue; } if (!IsValidSpell(e.spell_id)) { continue; } Client* c = entity_list.GetClientByName(e.caster_name.c_str()); buffs[e.slot_id].spellid = e.spell_id; buffs[e.slot_id].casterlevel = e.caster_level; if (c) { buffs[e.slot_id].casterid = c->GetID(); buffs[e.slot_id].client = true; strncpy(buffs[e.slot_id].caster_name, c->GetName(), 64); } else { buffs[e.slot_id].casterid = 0; buffs[e.slot_id].client = false; strncpy(buffs[e.slot_id].caster_name, "", 64); } buffs[e.slot_id].ticsremaining = e.ticsremaining; buffs[e.slot_id].counters = e.counters; buffs[e.slot_id].hit_number = e.numhits; buffs[e.slot_id].melee_rune = e.melee_rune; buffs[e.slot_id].magic_rune = e.magic_rune; buffs[e.slot_id].persistant_buff = e.persistent ? true : false; buffs[e.slot_id].dot_rune = e.dot_rune; buffs[e.slot_id].caston_x = e.caston_x; buffs[e.slot_id].caston_y = e.caston_y; buffs[e.slot_id].caston_z = e.caston_z; buffs[e.slot_id].ExtraDIChance = e.ExtraDIChance; buffs[e.slot_id].RootBreakChance = 0; buffs[e.slot_id].virus_spread_time = 0; buffs[e.slot_id].UpdateClient = false; buffs[e.slot_id].instrument_mod = e.instrument_mod; } // We load up to the most our client supports max_buff_slots = EQ::spells::StaticLookup(client->ClientVersion())->LongBuffs; for (int slot_id = 0; slot_id < max_buff_slots; ++slot_id) { if (!IsValidSpell(buffs[slot_id].spellid)) { continue; } if (IsEffectInSpell(buffs[slot_id].spellid, SE_Charm)) { buffs[slot_id].spellid = SPELL_UNKNOWN; break; } if (IsEffectInSpell(buffs[slot_id].spellid, SE_Illusion)) { if (buffs[slot_id].persistant_buff) { break; } buffs[slot_id].spellid = SPELL_UNKNOWN; break; } } } void ZoneDatabase::SaveAuras(Client *c) { CharacterAurasRepository::DeleteOne(database, c->CharacterID()); std::vector v; auto e = CharacterAurasRepository::NewEntity(); const auto& auras = c->GetAuraMgr(); for (int slot_id = 0; slot_id < auras.count; ++slot_id) { Aura* a = auras.auras[slot_id].aura; if (a && a->AuraZones()) { e.id = c->CharacterID(); e.slot = slot_id; e.spell_id = a->GetAuraID(); v.emplace_back(e); } } if (!v.empty()) { CharacterAurasRepository::ReplaceMany(database, v); } } void ZoneDatabase::LoadAuras(Client *c) { const auto& l = CharacterAurasRepository::GetWhere( database, fmt::format( "`id` = {} ORDER BY `slot`", c->CharacterID() ) ); if (l.empty()) { return; } for (const auto& e : l) { c->MakeAura(e.spell_id); } } void ZoneDatabase::SavePetInfo(Client *client) { PetInfo* p = nullptr; std::vector pet_infos; auto pet_info = CharacterPetInfoRepository::NewEntity(); std::vector pet_buffs; auto pet_buff = CharacterPetBuffsRepository::NewEntity(); std::vector inventory; auto item = CharacterPetInventoryRepository::NewEntity(); for (int pet_info_type = PetInfoType::Current; pet_info_type <= PetInfoType::Suspended; pet_info_type++) { p = client->GetPetInfo(pet_info_type); if (!p) { continue; } pet_info.char_id = client->CharacterID(); pet_info.pet = pet_info_type; pet_info.petname = p->Name; pet_info.petpower = p->petpower; pet_info.spell_id = p->SpellID; pet_info.hp = p->HP; pet_info.mana = p->Mana; pet_info.size = p->size; pet_info.taunting = p->taunting ? 1 : 0; pet_infos.push_back(pet_info); uint32 pet_buff_count = 0; const uint32 max_slots = ( RuleI(Spells, MaxTotalSlotsPET) > PET_BUFF_COUNT ? PET_BUFF_COUNT : RuleI(Spells, MaxTotalSlotsPET) ); for (int slot_id = 0; slot_id < max_slots; slot_id++) { if (!IsValidSpell(p->Buffs[slot_id].spellid)) { continue; } pet_buff_count++; } pet_buffs.reserve(pet_buff_count); for (int slot_id = 0; slot_id < max_slots; slot_id++) { if (!IsValidSpell(p->Buffs[slot_id].spellid)) { continue; } pet_buff.char_id = client->CharacterID(); pet_buff.pet = pet_info_type; pet_buff.slot = slot_id; pet_buff.spell_id = p->Buffs[slot_id].spellid; pet_buff.caster_level = p->Buffs[slot_id].level; pet_buff.ticsremaining = p->Buffs[slot_id].duration; pet_buff.counters = p->Buffs[slot_id].counters; pet_buff.instrument_mod = p->Buffs[slot_id].bard_modifier; pet_buffs.push_back(pet_buff); } uint32 pet_inventory_count = 0; for ( int slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; slot_id++ ) { if (!p->Items[slot_id]) { continue; } pet_inventory_count++; } inventory.reserve(pet_inventory_count); for ( int slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; slot_id++ ) { if (!p->Items[slot_id]) { continue; } item.char_id = client->CharacterID(); item.pet = pet_info_type; item.slot = slot_id; item.item_id = p->Items[slot_id]; inventory.push_back(item); } } CharacterPetInfoRepository::DeleteWhere( database, fmt::format( "`char_id` = {}", client->CharacterID() ) ); if (!pet_infos.empty()) { CharacterPetInfoRepository::InsertMany(database, pet_infos); } CharacterPetBuffsRepository::DeleteWhere( database, fmt::format( "`char_id` = {}", client->CharacterID() ) ); if (!pet_buffs.empty()) { CharacterPetBuffsRepository::InsertMany(database, pet_buffs); } CharacterPetInventoryRepository::DeleteWhere( database, fmt::format( "`char_id` = {}", client->CharacterID() ) ); if (!inventory.empty()) { CharacterPetInventoryRepository::InsertMany(database, inventory); } } void ZoneDatabase::RemoveTempFactions(Client *client) { std::string query = StringFormat("DELETE FROM faction_values " "WHERE temp = 1 AND char_id = %u", client->CharacterID()); QueryDatabase(query); } void ZoneDatabase::UpdateItemRecast(uint32 character_id, uint32 recast_type, uint32 timestamp) { CharacterItemRecastRepository::ReplaceOne( *this, CharacterItemRecastRepository::CharacterItemRecast{ .id = character_id, .recast_type = recast_type, .timestamp = timestamp, } ); } void ZoneDatabase::DeleteItemRecast(uint32 character_id, uint32 recast_type) { CharacterItemRecastRepository::DeleteWhere( *this, fmt::format( "`id` = {} AND `recast_type` = {}", character_id, recast_type ) ); } void ZoneDatabase::LoadPetInfo(Client *client) { // Load current pet and suspended pet auto pet_info = client->GetPetInfo(PetInfoType::Current); auto suspended_pet_info = client->GetPetInfo(PetInfoType::Suspended); memset(pet_info, 0, sizeof(PetInfo)); memset(suspended_pet_info, 0, sizeof(PetInfo)); const auto& info = CharacterPetInfoRepository::GetWhere( database, fmt::format( "`char_id` = {}", client->CharacterID() ) ); if (info.empty()) { return; } PetInfo* p; for (const auto& e : info) { if (e.pet == PetInfoType::Current) { p = pet_info; } else if (e.pet == PetInfoType::Suspended) { p = suspended_pet_info; } else { continue; } strn0cpy(p->Name, e.petname.c_str(), sizeof(p->Name)); p->petpower = e.petpower; p->SpellID = e.spell_id; p->HP = e.hp; p->Mana = e.mana; p->size = e.size; p->taunting = e.taunting; } const auto& buffs = CharacterPetBuffsRepository::GetWhere( database, fmt::format( "`char_id` = {}", client->CharacterID() ) ); if (!buffs.empty()) { for (const auto& e : buffs) { if (e.pet == PetInfoType::Current) { p = pet_info; } else if (e.pet == PetInfoType::Suspended) { p = suspended_pet_info; } else { continue; } if (e.slot >= RuleI(Spells, MaxTotalSlotsPET)) { continue; } if (!IsValidSpell(e.spell_id)) { continue; } p->Buffs[e.slot].spellid = e.spell_id; p->Buffs[e.slot].level = e.caster_level; p->Buffs[e.slot].player_id = 0; p->Buffs[e.slot].effect_type = BuffEffectType::Buff; p->Buffs[e.slot].duration = e.ticsremaining; p->Buffs[e.slot].counters = e.counters; p->Buffs[e.slot].bard_modifier = e.instrument_mod; } } const auto& inventory = CharacterPetInventoryRepository::GetWhere( database, fmt::format( "`char_id` = {}", client->CharacterID() ) ); if (!inventory.empty()) { for (const auto& e : inventory) { if (e.pet == PetInfoType::Current) { p = pet_info; } else if (e.pet == PetInfoType::Suspended) { p = suspended_pet_info; } else { continue; } if (!EQ::ValueWithin(e.slot, EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END)) { continue; } p->Items[e.slot] = e.item_id; } } } bool ZoneDatabase::GetFactionData(FactionMods* fm, uint32 class_mod, uint32 race_mod, uint32 deity_mod, int32 faction_id) { if (faction_id <= 0 || faction_id > (int32) max_faction) return false; if (faction_array[faction_id] == 0){ return false; } fm->base = faction_array[faction_id]->base; fm->min = faction_array[faction_id]->min; // The lowest your personal earned faction can go - before race/class/deity adjustments. fm->max = faction_array[faction_id]->max; // The highest your personal earned faction can go - before race/class/deity adjustments. if(class_mod > 0) { char str[32]; sprintf(str, "c%u", class_mod); std::map::const_iterator iter = faction_array[faction_id]->mods.find(str); if(iter != faction_array[faction_id]->mods.end()) { fm->class_mod = iter->second; } else { fm->class_mod = 0; } } else { fm->class_mod = 0; } if(race_mod > 0) { char str[32]; sprintf(str, "r%u", race_mod); auto iter = faction_array[faction_id]->mods.find(str); if(iter != faction_array[faction_id]->mods.end()) { fm->race_mod = iter->second; } else { fm->race_mod = 0; } } else { fm->race_mod = 0; } if(deity_mod > 0) { char str[32]; sprintf(str, "d%u", deity_mod); auto iter = faction_array[faction_id]->mods.find(str); if(iter != faction_array[faction_id]->mods.end()) { fm->deity_mod = iter->second; } else { fm->deity_mod = 0; } } else { fm->deity_mod = 0; } return true; } //o-------------------------------------------------------------- //| Name: GetFactionName; Dec. 16 //o-------------------------------------------------------------- //| Notes: Retrieves the name of the specified faction .Returns false on failure. //o-------------------------------------------------------------- bool ZoneDatabase::GetFactionName(int32 faction_id, char* name, uint32 buflen) { if ((faction_id <= 0) || faction_id > int32(max_faction) ||(faction_array[faction_id] == 0)) return false; if (faction_array[faction_id]->name[0] != 0) { strn0cpy(name, faction_array[faction_id]->name, buflen); return true; } return false; } std::string ZoneDatabase::GetFactionName(int32 faction_id) { std::string faction_name; if ( faction_id <= 0 || faction_id > static_cast(max_faction) || !faction_array[faction_id] ) { return faction_name; } faction_name = faction_array[faction_id]->name; return faction_name; } //o-------------------------------------------------------------- //| Name: SetCharacterFactionLevel; Dec. 20, 2001 //o-------------------------------------------------------------- //| Purpose: Update characters faction level with specified faction_id to specified value. Returns false on failure. //o-------------------------------------------------------------- bool ZoneDatabase::SetCharacterFactionLevel(uint32 char_id, int32 faction_id, int32 value, uint8 temp, faction_map &val_list) { std::string query; if(temp == 2) temp = 0; if(temp == 3) temp = 1; query = StringFormat("INSERT INTO `faction_values` " "(`char_id`, `faction_id`, `current_value`, `temp`) " "VALUES (%i, %i, %i, %i) " "ON DUPLICATE KEY UPDATE `current_value`=%i,`temp`=%i", char_id, faction_id, value, temp, value, temp); auto results = QueryDatabase(query); if (!results.Success()) return false; else val_list[faction_id] = value; return true; } bool ZoneDatabase::LoadFactionData() { std::string query("SELECT MAX(`id`) FROM `faction_list`"); auto faction_max_results = QueryDatabase(query); if (!faction_max_results.Success() || faction_max_results.RowCount() == 0) { return false; } auto& fmr_row = faction_max_results.begin(); max_faction = Strings::ToUnsignedInt(fmr_row[0]); faction_array = new Faction *[max_faction + 1]; memset(faction_array, 0, (sizeof(Faction*) * (max_faction + 1))); std::vector faction_ids; // load factions query = "SELECT `id`, `name`, `base` FROM `faction_list`"; auto faction_results = QueryDatabase(query); if (!faction_results.Success()) { return false; } for (auto fr_row : faction_results) { uint32 index = Strings::ToUnsignedInt(fr_row[0]); if (index > max_faction) { Log(Logs::General, Logs::Error, "Faction '%u' is out-of-bounds for faction array size!", index); continue; } // this should never hit since `id` is keyed..but, it alleviates any risk of lost pointers if (faction_array[index] != nullptr) { Log(Logs::General, Logs::Error, "Faction '%u' has already been assigned! (Duplicate Entry)", index); continue; } faction_array[index] = new Faction; strn0cpy(faction_array[index]->name, fr_row[1], 50); faction_array[index]->base = Strings::ToInt(fr_row[2]); faction_array[index]->min = MIN_PERSONAL_FACTION; faction_array[index]->max = MAX_PERSONAL_FACTION; faction_ids.push_back(fr_row[0]); } LogInfo("Loaded [{}] faction(s)", Strings::Commify(std::to_string(faction_ids.size()))); const std::string faction_id_criteria(Strings::Implode(",", faction_ids)); // load faction mins/maxes query = fmt::format("SELECT `client_faction_id`, `min`, `max` FROM `faction_base_data` WHERE `client_faction_id` IN ({})", faction_id_criteria); auto base_results = QueryDatabase(query); if (base_results.Success()) { for (auto br_row : base_results) { uint32 index = Strings::ToUnsignedInt(br_row[0]); if (index > max_faction) { LogError("Faction [{}] is out-of-bounds for faction array size in Base adjustment!", index); continue; } if (faction_array[index] == nullptr) { LogError("Faction [{}] does not exist for Base adjustment!", index); continue; } faction_array[index]->min = Strings::ToInt(br_row[1]); faction_array[index]->max = Strings::ToInt(br_row[2]); } LogInfo("Loaded [{}] faction base(s)", Strings::Commify(std::to_string(base_results.RowCount()))); } else { LogInfo("Unable to load Faction Base data..."); } // load race, class and deity modifiers query = fmt::format("SELECT `faction_id`, `mod`, `mod_name` FROM `faction_list_mod` WHERE `faction_id` IN ({})", faction_id_criteria); auto modifier_results = QueryDatabase(query); if (modifier_results.Success()) { for (auto mr_row : modifier_results) { uint32 index = Strings::ToUnsignedInt(mr_row[0]); if (index > max_faction) { Log(Logs::General, Logs::Error, "Faction '%u' is out-of-bounds for faction array size in Modifier adjustment!", index); continue; } if (faction_array[index] == nullptr) { Log(Logs::General, Logs::Error, "Faction '%u' does not exist for Modifier adjustment!", index); continue; } faction_array[index]->mods[mr_row[2]] = Strings::ToInt(mr_row[1]); } LogInfo("Loaded [{}] faction modifier(s)", Strings::Commify(std::to_string(modifier_results.RowCount()))); } else { LogError("Unable to load Faction Modifier data"); } return true; } bool ZoneDatabase::GetFactionIDsForNPC( uint32 npc_faction_id, std::list *faction_list, int32* primary_faction ) { if (npc_faction_id <= 0) { faction_list->clear(); if (primary_faction) { *primary_faction = npc_faction_id; } return true; } const auto& npcf = zone->GetNPCFaction(npc_faction_id); if (!npcf) { LogError("No NPC faction entry for [{}]", npc_faction_id); return false; } const auto& l = zone->GetNPCFactionEntries(npc_faction_id); if (primary_faction) { *primary_faction = npcf->primaryfaction; } faction_list->clear(); for (const auto& e: l) { faction_list->emplace_back(e); } return true; } uint32 ZoneDatabase::SendCharacterCorpseToGraveyard( uint32 corpse_id, uint32 zone_id, uint16 instance_id, glm::vec4& position ) { position.x += zone->random.Real(-20, 20); position.y += zone->random.Real(-20, 20); return CharacterCorpsesRepository::SendToGraveyard(*this, corpse_id, zone_id, instance_id, position); } void ZoneDatabase::SendCharacterCorpseToNonInstance(uint32 corpse_id) { CharacterCorpsesRepository::SendToNonInstance(*this, corpse_id); } uint32 ZoneDatabase::GetCharacterCorpseDecayTimer(uint32 corpse_id) { return CharacterCorpsesRepository::GetDecayTimer(*this, corpse_id); } uint32 ZoneDatabase::UpdateCharacterCorpse( uint32 corpse_id, uint32 character_id, const std::string& name, uint32 zone_id, uint16 instance_id, const CharacterCorpseEntry& c, const glm::vec4& position, uint32 guild_consent_id, bool is_resurrected ) { auto e = CharacterCorpsesRepository::FindOne(*this, corpse_id); e.charname = name; e.zone_id = zone_id; e.instance_id = instance_id; e.charid = character_id; e.x = position.x; e.y = position.y; e.z = position.z; e.heading = position.w; e.guild_consent_id = guild_consent_id; e.is_locked = c.locked; e.exp = c.exp; e.size = c.size; e.level = c.level; e.race = c.race; e.gender = c.gender; e.class_ = c.class_; e.deity = c.deity; e.texture = c.texture; e.helm_texture = c.helmtexture; e.copper = c.copper; e.silver = c.silver; e.gold = c.gold; e.platinum = c.plat; e.hair_color = c.haircolor; e.beard_color = c.beardcolor; e.eye_color_1 = c.eyecolor1; e.eye_color_2 = c.eyecolor2; e.hair_style = c.hairstyle; e.face = c.face; e.beard = c.beard; e.drakkin_heritage = c.drakkin_heritage; e.drakkin_details = c.drakkin_details; e.drakkin_tattoo = c.drakkin_tattoo; e.wc_1 = c.item_tint.Head.Color; e.wc_2 = c.item_tint.Chest.Color; e.wc_3 = c.item_tint.Arms.Color; e.wc_4 = c.item_tint.Wrist.Color; e.wc_5 = c.item_tint.Hands.Color; e.wc_6 = c.item_tint.Legs.Color; e.wc_7 = c.item_tint.Feet.Color; e.wc_8 = c.item_tint.Primary.Color; e.wc_9 = c.item_tint.Secondary.Color; CharacterCorpsesRepository::UpdateOne(*this, e); return corpse_id; } uint32 ZoneDatabase::UpdateCharacterCorpseConsent(uint32 character_id, uint32 guild_consent_id) { return CharacterCorpsesRepository::SetGuildConsentID(*this, character_id, guild_consent_id); } void ZoneDatabase::MarkCorpseAsResurrected(uint32 corpse_id) { CharacterCorpsesRepository::ResurrectCorpse(*this, corpse_id); } uint32 ZoneDatabase::SaveCharacterCorpse( uint32 character_id, const std::string& name, uint32 zone_id, uint16 instance_id, const CharacterCorpseEntry& c, const glm::vec4& position, uint32 guild_consent_id ) { auto e = CharacterCorpsesRepository::NewEntity(); e.charname = name; e.zone_id = zone_id; e.instance_id = instance_id; e.charid = character_id; e.x = position.x; e.y = position.y; e.z = position.z; e.heading = position.w; e.guild_consent_id = guild_consent_id; e.time_of_death = std::time(nullptr); e.is_locked = c.locked; e.exp = c.exp; e.size = c.size; e.level = c.level; e.race = c.race; e.gender = c.gender; e.class_ = c.class_; e.deity = c.deity; e.texture = c.texture; e.helm_texture = c.helmtexture; e.copper = c.copper; e.silver = c.silver; e.gold = c.gold; e.platinum = c.plat; e.hair_color = c.haircolor; e.beard_color = c.beardcolor; e.eye_color_1 = c.eyecolor1; e.eye_color_2 = c.eyecolor2; e.hair_style = c.hairstyle; e.face = c.face; e.beard = c.beard; e.drakkin_heritage = c.drakkin_heritage; e.drakkin_tattoo = c.drakkin_tattoo; e.drakkin_details = c.drakkin_details; e.wc_1 = c.item_tint.Head.Color; e.wc_2 = c.item_tint.Chest.Color; e.wc_3 = c.item_tint.Arms.Color; e.wc_4 = c.item_tint.Wrist.Color; e.wc_5 = c.item_tint.Hands.Color; e.wc_6 = c.item_tint.Legs.Color; e.wc_7 = c.item_tint.Feet.Color; e.wc_8 = c.item_tint.Primary.Color; e.wc_9 = c.item_tint.Secondary.Color; e.killed_by = c.killed_by; e.rezzable = c.rezzable; e.rez_time = c.rez_time; e = CharacterCorpsesRepository::InsertOne(*this, e); std::vector v; v.reserve(c.items.size()); auto ci = CharacterCorpseItemsRepository::NewEntity(); for (const auto& i : c.items) { ci.corpse_id = e.id; ci.equip_slot = i.equip_slot; ci.item_id = i.item_id; ci.charges = i.charges; ci.aug_1 = i.aug_1; ci.aug_2 = i.aug_2; ci.aug_3 = i.aug_3; ci.aug_4 = i.aug_4; ci.aug_5 = i.aug_5; ci.aug_6 = i.aug_6; ci.attuned = i.attuned; ci.custom_data = i.custom_data; ci.ornamenticon = i.ornamenticon; ci.ornamentidfile = i.ornamentidfile; ci.ornament_hero_model = i.ornament_hero_model; v.emplace_back(ci); } if (!v.empty()) { CharacterCorpseItemsRepository::ReplaceMany(*this, v); } return e.id; } uint32 ZoneDatabase::GetCharacterBuriedCorpseCount(uint32 character_id) { return CharacterCorpsesRepository::Count( *this, fmt::format( "`charid` = {} AND `is_buried` = 1", character_id ) ); } int64 ZoneDatabase::GetCharacterCorpseCount(uint32 character_id) { return CharacterCorpsesRepository::Count( *this, fmt::format( "`charid` = {}", character_id ) ); } uint32 ZoneDatabase::GetCharacterCorpseID(uint32 character_id, uint8 corpse_limit) { const auto& l = CharacterCorpsesRepository::GetWhere( *this, fmt::format( "`charid` = {} LIMIT {}, 1", character_id, corpse_limit ) ); return l.empty() ? 0 : l[0].id; } uint32 ZoneDatabase::GetCharacterCorpseItemAt(uint32 corpse_id, uint16 slot_id) { LogCorpsesDetail("corpse_id [{}] slot_id [{}]", corpse_id, slot_id); Corpse* c = LoadCharacterCorpse(corpse_id); uint32 item_id = 0; if (c) { item_id = c->GetWornItem(slot_id); c->DepopPlayerCorpse(); } return item_id; } Corpse* ZoneDatabase::SummonBuriedCharacterCorpses( uint32 character_id, uint32 zone_id, uint16 instance_id, const glm::vec4& position ) { Corpse* c = nullptr; const auto& l = CharacterCorpsesRepository::GetWhere( *this, fmt::format( "`charid` = {} AND `is_buried` = 1 ORDER BY `time_of_death` LIMIT 1", character_id ) ); for (const auto& e : l) { c = Corpse::LoadCharacterCorpse(e, position); if (!c) { continue; } entity_list.AddCorpse(c); c->SetDecayTimer(RuleI(Character, CorpseDecayTime)); c->Spawn(); if (!UnburyCharacterCorpse(c->GetCorpseDBID(), zone_id, instance_id, position)) { LogError("Unable to unbury a summoned player corpse_id [{}] for character_id [{}]", e.id, character_id); } } return c; } bool ZoneDatabase::SummonAllCharacterCorpses( uint32 character_id, uint32 zone_id, uint16 instance_id, const glm::vec4& position ) { Corpse* c = nullptr; int64 corpse_count = 0; auto l = CharacterCorpsesRepository::GetWhere( *this, fmt::format( "`charid` = {}", character_id ) ); for (auto& e : l) { e.zone_id = zone_id; e.instance_id = instance_id; e.x = position.x; e.y = position.y; e.z = position.z; e.heading = position.w; e.is_buried = 0; e.was_at_graveyard = 0; c = Corpse::LoadCharacterCorpse(e, position); if (c) { entity_list.AddCorpse(c); c->SetDecayTimer(RuleI(Character, CorpseDecayTime)); c->Spawn(); ++corpse_count; } else { LogError("Unable to construct a player corpse_id [{}] for character_id [{}]", e.id, character_id); } } if (!l.empty()) { CharacterCorpsesRepository::ReplaceMany(*this, l); } return corpse_count > 0; } int64 ZoneDatabase::CountCharacterCorpses(uint32 character_id) { return CharacterCorpsesRepository::Count( *this, fmt::format( "`charid` = {}", character_id ) ); } int64 ZoneDatabase::CountCharacterCorpsesByZoneID(uint32 character_id, uint32 zone_id) { return CharacterCorpsesRepository::Count( *this, fmt::format( "`charid` = {} AND `zone_id` = {}", character_id, zone_id ) ); } bool ZoneDatabase::UnburyCharacterCorpse(uint32 corpse_id, uint32 zone_id, uint16 instance_id, const glm::vec4& position) { return CharacterCorpsesRepository::UnburyCorpse(*this, corpse_id, zone_id, instance_id, position); } Corpse* ZoneDatabase::LoadCharacterCorpse(uint32 corpse_id) { if (!corpse_id) { return nullptr; } const auto &e = CharacterCorpsesRepository::FindOne(*this, corpse_id); if (!e.id) { return nullptr; } auto c = Corpse::LoadCharacterCorpse(e, glm::vec4(e.x, e.y, e.z, e.heading)); entity_list.AddCorpse(c); return c; } bool ZoneDatabase::LoadCharacterCorpses(uint32 zone_id, uint16 instance_id) { const auto& l = CharacterCorpsesRepository::GetWhere( *this, fmt::format( "`zone_id` = {} AND `instance_id` = {}{}", zone_id, instance_id, RuleB(Zone, EnableShadowrest) ? " AND `is_buried` = 0" : "" ) ); for (const auto &e: l) { glm::vec4 position = glm::vec4(e.x, e.y, e.z, e.heading); entity_list.AddCorpse(Corpse::LoadCharacterCorpse(e, position)); } return true; } uint32 ZoneDatabase::GetFirstCorpseID(uint32 character_id) { const auto& l = CharacterCorpsesRepository::GetWhere( *this, fmt::format( "`charid` = {} AND `is_buried` = 0 ORDER BY `time_of_death` LIMIT 1", character_id ) ); if (l.empty()) { return 0; } return l[0].id; } bool ZoneDatabase::DeleteItemOffCharacterCorpse(uint32 corpse_id, uint32 slot_id, uint32 item_id) { return CharacterCorpseItemsRepository::DeleteWhere( *this, fmt::format( "`corpse_id` = {} AND `equip_slot` = {} AND `item_id` = {}", corpse_id, slot_id, item_id ) ); } bool ZoneDatabase::BuryCharacterCorpse(uint32 corpse_id) { return CharacterCorpsesRepository::BuryCorpse(*this, corpse_id); } bool ZoneDatabase::BuryAllCharacterCorpses(uint32 character_id) { const auto& l = CharacterCorpsesRepository::GetWhere( *this, fmt::format( "`charid` = {}", character_id ) ); if (l.empty()) { return false; } for (const auto& e : l) { BuryCharacterCorpse(e.id); } return true; } bool ZoneDatabase::DeleteCharacterCorpse(uint32 corpse_id) { return CharacterCorpsesRepository::DeleteOne(*this, corpse_id); } void ZoneDatabase::UpdateGMStatus(uint32 account_id, int new_status) { auto e = AccountRepository::FindOne(*this, account_id); if (!e.id) { return; } e.status = new_status; AccountRepository::UpdateOne(*this, e); } void ZoneDatabase::SaveCharacterBinds(Client *c) { std::vector v; auto e = CharacterBindRepository::NewEntity(); uint32 bind_count = 0; for (const auto &b : c->GetPP().binds) { if (b.zone_id) { bind_count++; } } v.reserve(bind_count); int slot_id = 0; for (const auto &b : c->GetPP().binds) { if (b.zone_id) { e.id = c->CharacterID(); e.zone_id = b.zone_id; e.instance_id = b.instance_id; e.x = b.x; e.y = b.y; e.z = b.z; e.heading = b.heading; e.slot = slot_id; v.emplace_back(e); slot_id++; } } if (bind_count > 0) { CharacterBindRepository::ReplaceMany(database, v); } } void ZoneDatabase::ZeroPlayerProfileCurrency(PlayerProfile_Struct* pp) { if (pp->copper < 0) { pp->copper = 0; } if (pp->silver < 0) { pp->silver = 0; } if (pp->gold < 0) { pp->gold = 0; } if (pp->platinum < 0) { pp->platinum = 0; } if (pp->copper_bank < 0) { pp->copper_bank = 0; } if (pp->silver_bank < 0) { pp->silver_bank = 0; } if (pp->gold_bank < 0) { pp->gold_bank = 0; } if (pp->platinum_bank < 0) { pp->platinum_bank = 0; } if (pp->platinum_cursor < 0) { pp->platinum_cursor = 0; } if (pp->gold_cursor < 0) { pp->gold_cursor = 0; } if (pp->silver_cursor < 0) { pp->silver_cursor = 0; } if (pp->copper_cursor < 0) { pp->copper_cursor = 0; } } float ZoneDatabase::GetAAEXPModifierByCharID( uint32 character_id, uint32 zone_id, int16 instance_version ) { EXPModifier m = CharacterExpModifiersRepository::GetEXPModifier( database, character_id, zone_id, instance_version ); return m.aa_modifier; } float ZoneDatabase::GetEXPModifierByCharID( uint32 character_id, uint32 zone_id, int16 instance_version ) { EXPModifier m = CharacterExpModifiersRepository::GetEXPModifier( database, character_id, zone_id, instance_version ); return m.exp_modifier; } void ZoneDatabase::SetAAEXPModifierByCharID( uint32 character_id, uint32 zone_id, float aa_modifier, int16 instance_version ) { CharacterExpModifiersRepository::SetEXPModifier( database, character_id, zone_id, instance_version, EXPModifier{ .aa_modifier = aa_modifier, .exp_modifier = -1.0f } ); } void ZoneDatabase::SetEXPModifierByCharID( uint32 character_id, uint32 zone_id, float exp_modifier, int16 instance_version ) { CharacterExpModifiersRepository::SetEXPModifier( database, character_id, zone_id, instance_version, EXPModifier{ .aa_modifier = -1.0f, .exp_modifier = exp_modifier } ); } void ZoneDatabase::LoadCharacterEXPModifier(Client* c) { if (!zone) { return; } EXPModifier m = CharacterExpModifiersRepository::GetEXPModifier( *this, c->CharacterID(), zone->GetZoneID(), zone->GetInstanceVersion() ); zone->exp_modifiers[c->CharacterID()] = m; } void ZoneDatabase::SaveCharacterEXPModifier(Client* c) { if (!zone) { return; } EXPModifier m = zone->exp_modifiers[c->CharacterID()]; CharacterExpModifiersRepository::ReplaceOne( *this, CharacterExpModifiersRepository::CharacterExpModifiers{ .character_id = static_cast(c->CharacterID()), .zone_id = static_cast(zone->GetZoneID()), .instance_version = zone->GetInstanceVersion(), .aa_modifier = m.aa_modifier, .exp_modifier = m.exp_modifier } ); }