eqemu-server/world/worlddb.cpp
Knightly 7ab909ee47 Standardize Licensing
- License was intended to be GPLv3 per earlier commit of GPLv3 LICENSE FILE
- This is confirmed by the inclusion of libraries that are incompatible with GPLv2
- This is also confirmed by KLS and the agreement of KLS's predecessors
- Added GPLv3 license headers to the compilable source files
- Removed Folly licensing in strings.h since the string functions do not match the Folly functions and are standard functions - this must have been left over from previous implementations
- Removed individual contributor license headers since the project has been under the "developer" mantle for many years
- Removed comments on files that were previously automatically generated since they've been manually modified multiple times and there are no automatic scripts referencing them (removed in 2023)
2026-04-01 17:09:57 -07:00

919 lines
26 KiB
C++

/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "worlddb.h"
#include "common/eq_packet_structs.h"
#include "common/inventory_profile.h"
#include "common/repositories/character_bind_repository.h"
#include "common/repositories/character_data_repository.h"
#include "common/repositories/character_instance_safereturns_repository.h"
#include "common/repositories/character_material_repository.h"
#include "common/repositories/criteria/content_filter_criteria.h"
#include "common/repositories/inventory_repository.h"
#include "common/repositories/start_zones_repository.h"
#include "common/rulesys.h"
#include "common/strings.h"
#include "common/zone_store.h"
#include "world/sof_char_create_data.h"
#include <cstdlib>
#include <vector>
WorldDatabase database;
WorldDatabase content_db;
extern std::vector<RaceClassAllocation> character_create_allocations;
extern std::vector<RaceClassCombos> character_create_race_class_combos;
void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **out_app, uint32 client_version_bit)
{
EQ::versions::ClientVersion
client_version = EQ::versions::ConvertClientVersionBitToClientVersion(client_version_bit);
size_t character_limit = EQ::constants::StaticLookup(client_version)->CharacterCreationLimit;
if (character_limit > EQ::constants::CHARACTER_CREATION_LIMIT) {
character_limit = EQ::constants::CHARACTER_CREATION_LIMIT;
}
// Force Titanium clients to use '8'
if (client_version == EQ::versions::ClientVersion::Titanium) {
character_limit = 8;
}
auto characters = CharacterDataRepository::GetWhere(
database,
fmt::format(
"`account_id` = {} AND `deleted_at` IS NULL ORDER BY `name` LIMIT {}",
account_id,
character_limit
)
);
size_t character_count = characters.size();
if (characters.empty()) {
*out_app = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct));
auto *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer;
cs->CharCount = 0;
cs->TotalChars = character_limit;
return;
}
std::vector<uint32_t> character_ids;
for (auto &e: characters) {
character_ids.push_back(e.id);
}
const auto& inventories = InventoryRepository::GetWhere(
*this,
fmt::format(
"`character_id` IN ({}) AND `slot_id` BETWEEN {} AND {}",
Strings::Join(character_ids, ","),
EQ::invslot::slotHead,
EQ::invslot::slotFeet
)
);
const auto& character_binds = CharacterBindRepository::GetWhere(
*this,
fmt::format(
"`id` IN ({}) ORDER BY id, slot",
Strings::Join(character_ids, ",")
)
);
const auto& character_materials = CharacterMaterialRepository::GetWhere(
*this,
fmt::format(
"`id` IN ({}) ORDER BY id, slot",
Strings::Join(character_ids, ",")
)
);
size_t packet_size = sizeof(CharacterSelect_Struct) + (sizeof(CharacterSelectEntry_Struct) * character_count);
*out_app = new EQApplicationPacket(OP_SendCharInfo, packet_size);
unsigned char *buff_ptr = (*out_app)->pBuffer;
auto *cs = (CharacterSelect_Struct *) buff_ptr;
cs->CharCount = character_count;
cs->TotalChars = character_limit;
buff_ptr += sizeof(CharacterSelect_Struct);
for (auto &e: characters) {
auto *cse = (CharacterSelectEntry_Struct *) buff_ptr;
PlayerProfile_Struct pp;
EQ::InventoryProfile inv;
pp.SetPlayerProfileVersion(EQ::versions::ConvertClientVersionToMobVersion(client_version));
inv.SetInventoryVersion(client_version);
inv.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support
uint32 character_id = e.id;
uint8 has_home = 0;
uint8 has_bind = 0;
memset(&pp, 0, sizeof(PlayerProfile_Struct));
memset(cse->Name, 0, sizeof(cse->Name));
strcpy(cse->Name, e.name.c_str());
cse->Class = e.class_;
cse->Race = e.race;
cse->Level = e.level;
cse->ShroudClass = cse->Class;
cse->ShroudRace = cse->Race;
cse->Zone = e.zone_id;
cse->Instance = 0;
cse->Gender = e.gender;
cse->Face = e.face;
for (auto &s: cse->Equip) {
s.Material = 0;
s.Unknown1 = 0;
s.EliteModel = 0;
s.HerosForgeModel = 0;
s.Unknown2 = 0;
s.Color = 0;
}
cse->Unknown15 = 0xFF;
cse->Unknown19 = 0xFF;
cse->DrakkinTattoo = e.drakkin_tattoo;
cse->DrakkinDetails = e.drakkin_details;
cse->Deity = e.deity;
cse->PrimaryIDFile = 0; // Processed Below
cse->SecondaryIDFile = 0; // Processed Below
cse->HairColor = e.hair_color;
cse->BeardColor = e.beard_color;
cse->EyeColor1 = e.eye_color_1;
cse->EyeColor2 = e.eye_color_2;
cse->HairStyle = e.hair_style;
cse->Beard = e.beard;
cse->GoHome = 0; // Processed Below
cse->Tutorial = 0; // Processed Below
cse->DrakkinHeritage = e.drakkin_heritage;
cse->Unknown1 = 0;
cse->Enabled = 1;
cse->LastLogin = e.last_login; // RoF2 value: 1212696584
cse->Unknown2 = 0;
if (RuleB(World, EnableReturnHomeButton)) {
int now = time(nullptr);
if (now - e.last_login >= RuleI(World, MinOfflineTimeToReturnHome)) {
cse->GoHome = 1;
}
}
if (RuleB(World, EnableTutorialButton) && (cse->Level <= RuleI(World, MaxLevelForTutorial))) {
cse->Tutorial = 1;
}
// binds
int bind_count = 0;
for (auto &bind : character_binds) {
if (bind.id != e.id) {
continue;
}
if (bind.slot == 4) {
has_home = 1;
pp.binds[4].zone_id = bind.zone_id;
pp.binds[4].instance_id = bind.instance_id;
pp.binds[4].x = bind.x;
pp.binds[4].y = bind.y;
pp.binds[4].z = bind.z;
pp.binds[4].heading = bind.heading;
}
if (bind.slot == 0) {
has_bind = 1;
}
bind_count++;
}
if (has_home == 0 || has_bind == 0) {
const auto &start_zones = StartZonesRepository::GetWhere(
content_db,
fmt::format(
"`player_class` = {} AND `player_deity` = {} AND `player_race` = {} {}",
cse->Class,
cse->Deity,
cse->Race,
ContentFilterCriteria::apply().c_str()
)
);
if (!start_zones.empty()) {
pp.binds[4].zone_id = start_zones[0].zone_id;
pp.binds[4].x = start_zones[0].x;
pp.binds[4].y = start_zones[0].y;
pp.binds[4].z = start_zones[0].z;
pp.binds[4].heading = start_zones[0].heading;
if (start_zones[0].bind_id != 0) {
pp.binds[4].zone_id = start_zones[0].bind_id;
auto z = GetZone(pp.binds[4].zone_id);
if (z) {
pp.binds[4].x = z->safe_x;
pp.binds[4].y = z->safe_y;
pp.binds[4].z = z->safe_z;
pp.binds[4].heading = z->safe_heading;
}
} else {
pp.binds[4].zone_id = start_zones[0].zone_id;
pp.binds[4].x = start_zones[0].x;
pp.binds[4].y = start_zones[0].y;
pp.binds[4].z = start_zones[0].z;
pp.binds[4].heading = start_zones[0].heading;
if (pp.binds[4].x == 0 && pp.binds[4].y == 0 && pp.binds[4].z == 0 && pp.binds[4].heading == 0) {
auto zone = GetZone(pp.binds[4].zone_id);
if (zone) {
pp.binds[4].x = zone->safe_x;
pp.binds[4].y = zone->safe_y;
pp.binds[4].z = zone->safe_z;
pp.binds[4].heading = zone->safe_heading;
}
}
}
}
else {
LogError("No start zone found for class [{}] deity [{}] race [{}]", cse->Class, cse->Deity, cse->Race);
}
pp.binds[0] = pp.binds[4];
// If we don't have home set, set it
if (has_home == 0) {
auto bind = CharacterBindRepository::NewEntity();
bind.id = character_id;
bind.zone_id = pp.binds[4].zone_id;
bind.instance_id = 0;
bind.x = pp.binds[4].x;
bind.y = pp.binds[4].y;
bind.z = pp.binds[4].z;
bind.heading = pp.binds[4].heading;
bind.slot = 4;
CharacterBindRepository::ReplaceOne(*this, bind);
}
// If we don't have regular bind set, set it
if (has_bind == 0) {
auto bind = CharacterBindRepository::NewEntity();
bind.id = character_id;
bind.zone_id = pp.binds[0].zone_id;
bind.instance_id = 0;
bind.x = pp.binds[0].x;
bind.y = pp.binds[0].y;
bind.z = pp.binds[0].z;
bind.heading = pp.binds[0].heading;
bind.slot = 0;
CharacterBindRepository::ReplaceOne(*this, bind);
}
}
// If our bind count is less than 5, then we have null data that needs to be filled in
if (bind_count < 5) {
// we know that home and main bind must be valid here, so we don't check those
// we also use home to fill in the null data like live does.
std::vector<CharacterBindRepository::CharacterBind> binds;
for (int i = 1; i < 4; i++) {
if (pp.binds[i].zone_id != 0) { // we assume 0 is the only invalid one ...
continue;
}
auto bind = CharacterBindRepository::NewEntity();
bind.slot = i;
bind.id = character_id;
bind.zone_id = pp.binds[4].zone_id;
bind.instance_id = 0;
bind.x = pp.binds[4].x;
bind.y = pp.binds[4].y;
bind.z = pp.binds[4].z;
bind.heading = pp.binds[4].heading;
binds.emplace_back(bind);
}
CharacterBindRepository::ReplaceMany(*this, binds);
}
for (auto &cm : character_materials) {
pp.item_tint.Slot[cm.slot].Red = cm.red;
pp.item_tint.Slot[cm.slot].Green = cm.green;
pp.item_tint.Slot[cm.slot].Blue = cm.blue;
pp.item_tint.Slot[cm.slot].UseTint = cm.use_tint;
pp.item_tint.Slot[cm.slot].Color = cm.color;
}
if (GetCharSelInventory(inventories, e, &inv)) {
const EQ::ItemData *item = nullptr;
const EQ::ItemInstance *inst = nullptr;
int16 inventory_slot = 0;
for (uint32 matslot = EQ::textures::textureBegin; matslot < EQ::textures::materialCount; matslot++) {
inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(matslot);
if (inventory_slot == INVALID_INDEX) { continue; }
inst = inv.GetItem(inventory_slot);
if (inst == nullptr) {
continue;
}
item = inst->GetItem();
if (item == nullptr) {
continue;
}
if (matslot > 6) {
uint32 item_id_file = 0;
// Weapon Models
if (inst->GetOrnamentationIDFile() != 0) {
item_id_file = inst->GetOrnamentationIDFile();
cse->Equip[matslot].Material = item_id_file;
}
else {
if (strlen(item->IDFile) > 2) {
item_id_file = Strings::ToInt(&item->IDFile[2]);
cse->Equip[matslot].Material = item_id_file;
}
}
if (matslot == EQ::textures::weaponPrimary) {
cse->PrimaryIDFile = item_id_file;
}
else {
cse->SecondaryIDFile = item_id_file;
}
}
else {
// Armor Materials/Models
uint32 color = (
pp.item_tint.Slot[matslot].UseTint ?
pp.item_tint.Slot[matslot].Color :
inst->GetColor()
);
cse->Equip[matslot].Material = item->Material;
cse->Equip[matslot].EliteModel = item->EliteMaterial;
cse->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot);
cse->Equip[matslot].Color = color;
}
}
}
else {
printf("Error loading inventory for %s\n", cse->Name);
}
buff_ptr += sizeof(CharacterSelectEntry_Struct);
}
}
int WorldDatabase::MoveCharacterToBind(int character_id, uint8 bind_number)
{
/* if an invalid bind point is specified, use the primary bind */
if (bind_number > 4)
bind_number = 0;
std::string query = fmt::format(
SQL(
SELECT
zone_id, instance_id, x, y, z, heading
FROM
character_bind
WHERE
id = {}
AND
slot = {}
LIMIT 1
),
character_id,
bind_number
);
auto results = database.QueryDatabase(query);
if(!results.Success() || results.RowCount() == 0) {
return 0;
}
int zone_id, instance_id;
double x, y, z, heading;
for (auto row = results.begin(); row != results.end(); ++row) {
zone_id = Strings::ToInt(row[0]);
instance_id = Strings::ToInt(row[1]);
x = Strings::ToFloat(row[2]);
y = Strings::ToFloat(row[3]);
z = Strings::ToFloat(row[4]);
heading = Strings::ToFloat(row[5]);
}
query = fmt::format(
SQL(
UPDATE
`character_data`
SET
`zone_id` = {},
`zone_instance` = {},
`x` = {},
`y` = {},
`z` = {},
`heading` = {}
WHERE `id` = {}
),
zone_id,
instance_id,
x,
y,
z,
heading,
character_id
);
results = database.QueryDatabase(query);
if(!results.Success()) {
return 0;
}
return zone_id;
}
int WorldDatabase::MoveCharacterToInstanceSafeReturn(
int character_id,
int instance_zone_id,
int instance_id
)
{
int zone_id = 0;
// only moves if safe return is for specified zone instance
auto entries = CharacterInstanceSafereturnsRepository::GetWhere(
database,
fmt::format(
"character_id = {} AND instance_zone_id = {} AND instance_id = {} AND safe_zone_id > 0",
character_id, instance_zone_id, instance_id
)
);
if (!entries.empty()) {
auto entry = entries.front();
auto results = QueryDatabase(
fmt::format(
SQL(
UPDATE character_data
SET zone_id = {}, zone_instance = 0, x = {}, y = {}, z = {}, heading = {}
WHERE id = {};
),
entry.safe_zone_id,
entry.safe_x,
entry.safe_y,
entry.safe_z,
entry.safe_heading,
character_id
)
);
if (results.Success() && results.RowsAffected() > 0) {
zone_id = entry.safe_zone_id;
}
}
if (zone_id == 0) {
zone_id = MoveCharacterToBind(character_id);
}
return zone_id;
}
bool WorldDatabase::GetStartZone(
PlayerProfile_Struct *pp,
CharCreate_Struct *p_char_create_struct,
bool is_titanium
)
{
// SoF doesn't send the player_choice field in character creation, it now sends the real zoneID instead.
//
// For SoF, search for an entry in start_zones with a matching zone_id, class, race and deity.
//
// For now, if no row matching row is found, send them to Crescent Reach, as that is probably the most likely
// reason for no match being found.
//
if (!pp || !p_char_create_struct) {
return false;
}
pp->x = 0;
pp->y = 0;
pp->z = 0;
pp->heading = 0;
pp->zone_id = 0;
pp->binds[0].x = 0;
pp->binds[0].y = 0;
pp->binds[0].z = 0;
pp->binds[0].zone_id = 0;
pp->binds[0].instance_id = 0;
// see if we have an entry for start_zone. We can support both titanium & SOF+ by having two entries per class/race/deity combo with different zone_ids
std::string query;
if (is_titanium) {
// Titanium sends player choice (starting city) instead of a zone id
query = StringFormat(
"SELECT x, y, z, heading, start_zone, bind_id, bind_x, bind_y, bind_z FROM start_zones WHERE player_choice = %i "
"AND player_class = %i AND player_deity = %i AND player_race = %i %s",
p_char_create_struct->start_zone,
p_char_create_struct->class_,
p_char_create_struct->deity,
p_char_create_struct->race,
ContentFilterCriteria::apply().c_str()
);
LogInfo("Titanium Start zone query: [{}]\n", query.c_str());
}
else {
query = StringFormat(
"SELECT x, y, z, heading, start_zone, bind_id, bind_x, bind_y, bind_z FROM start_zones WHERE zone_id = %i "
"AND player_class = %i AND player_deity = %i AND player_race = %i %s",
p_char_create_struct->start_zone,
p_char_create_struct->class_,
p_char_create_struct->deity,
p_char_create_struct->race,
ContentFilterCriteria::apply().c_str()
);
LogInfo("SoF Start zone query: [{}]\n", query.c_str());
}
auto results = QueryDatabase(query);
if(!results.Success()) {
return false;
}
LogInfo("SoF Start zone query: [{}]\n", query.c_str());
if (results.RowCount() == 0) {
printf("No start_zones entry in database, using defaults\n");
is_titanium ? SetTitaniumDefaultStartZone(pp, p_char_create_struct) : SetSoFDefaultStartZone(pp, p_char_create_struct);
}
else {
LogInfo("Found starting location in start_zones");
auto row = results.begin();
pp->x = Strings::ToFloat(row[0]);
pp->y = Strings::ToFloat(row[1]);
pp->z = Strings::ToFloat(row[2]);
pp->heading = Strings::ToFloat(row[3]);
pp->zone_id = Strings::ToInt(row[4]);
pp->binds[0].zone_id = Strings::ToInt(row[5]);
pp->binds[0].x = Strings::ToFloat(row[6]);
pp->binds[0].y = Strings::ToFloat(row[7]);
pp->binds[0].z = Strings::ToFloat(row[8]);
pp->binds[0].heading = Strings::ToFloat(row[3]);
}
if (
pp->x == 0 &&
pp->y == 0 &&
pp->z == 0 &&
pp->heading == 0
) {
auto zone = GetZone(pp->zone_id);
if (zone) {
pp->x = zone->safe_x;
pp->y = zone->safe_y;
pp->z = zone->safe_z;
pp->heading = zone->safe_heading;
}
}
if (
pp->binds[0].x == 0 &&
pp->binds[0].y == 0 &&
pp->binds[0].z == 0 &&
pp->binds[0].heading == 0
) {
auto zone = GetZone(pp->binds[0].zone_id);
if (zone) {
pp->binds[0].x = zone->safe_x;
pp->binds[0].y = zone->safe_y;
pp->binds[0].z = zone->safe_z;
pp->binds[0].heading = zone->safe_heading;
}
}
return true;
}
void WorldDatabase::SetSoFDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc){
int sof_start_zone_id = RuleI(World, SoFStartZoneID);
if (sof_start_zone_id > 0) {
in_pp->zone_id = sof_start_zone_id;
in_cc->start_zone = in_pp->zone_id;
}
else if (in_cc->start_zone == RuleI(World, TutorialZoneID)) {
in_pp->zone_id = in_cc->start_zone;
}
else {
in_pp->x = in_pp->binds[0].x = -51.0f;
in_pp->y = in_pp->binds[0].y = -20.0f;
in_pp->z = in_pp->binds[0].z = 0.79f;
in_pp->heading = in_pp->binds[0].heading = 0.0f;
in_pp->zone_id = in_pp->binds[0].zone_id = Zones::CRESCENT; // Crescent Reach.
}
}
void WorldDatabase::SetTitaniumDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc)
{
int titanium_start_zone_id = RuleI(World, TitaniumStartZoneID);
if (titanium_start_zone_id > 0) {
in_pp->zone_id = titanium_start_zone_id;
in_pp->binds[0].zone_id = titanium_start_zone_id;
} else {
switch (in_cc->start_zone)
{
case StartZoneIndex::Odus:
{
if (in_cc->deity == Deity::CazicThule) // Cazic-Thule Erudites go to Paineel
{
in_pp->zone_id = Zones::PAINEEL; // paineel
in_pp->binds[0].zone_id = Zones::PAINEEL;
}
else
{
in_pp->zone_id = Zones::ERUDNEXT; // erudnext
in_pp->binds[0].zone_id = Zones::TOX; // tox
}
break;
}
case StartZoneIndex::Qeynos:
{
in_pp->zone_id = Zones::QEYNOS2; // qeynos2
in_pp->binds[0].zone_id = Zones::QEYNOS2; // qeynos2
break;
}
case StartZoneIndex::Halas:
{
in_pp->zone_id = Zones::HALAS; // halas
in_pp->binds[0].zone_id = Zones::EVERFROST; // everfrost
break;
}
case StartZoneIndex::Rivervale:
{
in_pp->zone_id = Zones::RIVERVALE; // rivervale
in_pp->binds[0].zone_id = Zones::KITHICOR; // kithicor
break;
}
case StartZoneIndex::Freeport:
{
in_pp->zone_id = Zones::FREPORTW; // freportw
in_pp->binds[0].zone_id = Zones::FREPORTW; // freportw
break;
}
case StartZoneIndex::Neriak:
{
in_pp->zone_id = Zones::NERIAKA; // neriaka
in_pp->binds[0].zone_id = Zones::NEKTULOS; // nektulos
break;
}
case StartZoneIndex::Grobb:
{
in_pp->zone_id = Zones::GROBB; // grobb
in_pp->binds[0].zone_id = Zones::INNOTHULE; // innothule
break;
}
case StartZoneIndex::Oggok:
{
in_pp->zone_id = Zones::OGGOK; // oggok
in_pp->binds[0].zone_id = Zones::FEERROTT; // feerrott
break;
}
case StartZoneIndex::Kaladim:
{
in_pp->zone_id = Zones::KALADIMA; // kaladima
in_pp->binds[0].zone_id = Zones::BUTCHER; // butcher
break;
}
case StartZoneIndex::GreaterFaydark:
{
in_pp->zone_id = Zones::GFAYDARK; // gfaydark
in_pp->binds[0].zone_id = Zones::GFAYDARK; // gfaydark
break;
}
case StartZoneIndex::Felwithe:
{
in_pp->zone_id = Zones::FELWITHEA; // felwithea
in_pp->binds[0].zone_id = Zones::GFAYDARK; // gfaydark
break;
}
case StartZoneIndex::Akanon:
{
in_pp->zone_id = Zones::AKANON; // akanon
in_pp->binds[0].zone_id = Zones::STEAMFONT; // steamfont
break;
}
case StartZoneIndex::Cabilis:
{
in_pp->zone_id = Zones::CABWEST; // cabwest
in_pp->binds[0].zone_id = Zones::FIELDOFBONE; // fieldofbone
break;
}
case StartZoneIndex::SharVahl:
{
in_pp->zone_id = Zones::SHARVAHL; // sharvahl
in_pp->binds[0].zone_id = Zones::SHARVAHL; // sharvahl
break;
}
case StartZoneIndex::RatheMtn:
{
in_pp->zone_id = Zones::RATHEMTN; // rathemtn
in_pp->binds[0].zone_id = Zones::RATHEMTN; // rathemtn
break;
}
}
}
}
void WorldDatabase::GetLauncherList(std::vector<std::string> &rl) {
rl.clear();
const std::string query = "SELECT name FROM launcher";
auto results = QueryDatabase(query);
if (!results.Success()) {
LogError("WorldDatabase::GetLauncherList: {}", results.ErrorMessage().c_str());
return;
}
for (auto row = results.begin(); row != results.end(); ++row)
rl.push_back(row[0]);
}
bool WorldDatabase::GetCharacterLevel(const char *name, int &level)
{
std::string query = StringFormat("SELECT level FROM character_data WHERE name = '%s'", name);
auto results = QueryDatabase(query);
if (!results.Success()) {
LogError("WorldDatabase::GetCharacterLevel: {}", results.ErrorMessage().c_str());
return false;
}
if (results.RowCount() == 0)
return false;
auto row = results.begin();
level = Strings::ToInt(row[0]);
return true;
}
bool WorldDatabase::LoadCharacterCreateAllocations()
{
character_create_allocations.clear();
std::string query = "SELECT * FROM char_create_point_allocations ORDER BY id";
auto results = QueryDatabase(query);
if (!results.Success())
return false;
for (auto row = results.begin(); row != results.end(); ++row) {
RaceClassAllocation allocate;
allocate.Index = Strings::ToInt(row[0]);
allocate.BaseStats[0] = Strings::ToInt(row[1]);
allocate.BaseStats[3] = Strings::ToInt(row[2]);
allocate.BaseStats[1] = Strings::ToInt(row[3]);
allocate.BaseStats[2] = Strings::ToInt(row[4]);
allocate.BaseStats[4] = Strings::ToInt(row[5]);
allocate.BaseStats[5] = Strings::ToInt(row[6]);
allocate.BaseStats[6] = Strings::ToInt(row[7]);
allocate.DefaultPointAllocation[0] = Strings::ToInt(row[8]);
allocate.DefaultPointAllocation[3] = Strings::ToInt(row[9]);
allocate.DefaultPointAllocation[1] = Strings::ToInt(row[10]);
allocate.DefaultPointAllocation[2] = Strings::ToInt(row[11]);
allocate.DefaultPointAllocation[4] = Strings::ToInt(row[12]);
allocate.DefaultPointAllocation[5] = Strings::ToInt(row[13]);
allocate.DefaultPointAllocation[6] = Strings::ToInt(row[14]);
character_create_allocations.push_back(allocate);
}
return true;
}
bool WorldDatabase::LoadCharacterCreateCombos()
{
character_create_race_class_combos.clear();
std::string query = "SELECT * FROM char_create_combinations ORDER BY race, class, deity, start_zone";
auto results = QueryDatabase(query);
if (!results.Success())
return false;
for (auto row = results.begin(); row != results.end(); ++row) {
RaceClassCombos combo;
combo.AllocationIndex = Strings::ToInt(row[0]);
combo.Race = Strings::ToInt(row[1]);
combo.Class = Strings::ToInt(row[2]);
combo.Deity = Strings::ToInt(row[3]);
combo.Zone = Strings::ToInt(row[4]);
combo.ExpansionRequired = Strings::ToInt(row[5]);
character_create_race_class_combos.push_back(combo);
}
return true;
}
bool WorldDatabase::GetCharSelInventory(
const std::vector<InventoryRepository::Inventory> &inventories,
const CharacterDataRepository::CharacterData &character,
EQ::InventoryProfile *inv
)
{
if (inventories.empty() || !character.id || !inv) {
return false;
}
for (const auto& e : inventories) {
if (e.character_id != character.id) {
continue;
}
switch (e.slot_id) {
case EQ::invslot::slotFace:
case EQ::invslot::slotEar2:
case EQ::invslot::slotNeck:
case EQ::invslot::slotShoulders:
case EQ::invslot::slotBack:
case EQ::invslot::slotFinger1:
case EQ::invslot::slotFinger2:
continue;
default:
break;
}
uint32 augment_ids[EQ::invaug::SOCKET_COUNT] = {
e.augment_one,
e.augment_two,
e.augment_three,
e.augment_four,
e.augment_five,
e.augment_six
};
const EQ::ItemData* item = content_db.GetItem(e.item_id);
if (!item) {
continue;
}
EQ::ItemInstance *inst = content_db.CreateBaseItem(item, e.charges);
if (!inst) {
continue;
}
inst->SetCharges(e.charges);
if (e.color > 0) {
inst->SetColor(e.color);
}
if (item->IsClassCommon()) {
for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) {
if (augment_ids[i]) {
inst->PutAugment(this, i, augment_ids[i]);
}
}
}
inst->SetAttuned(e.instnodrop);
if (!e.custom_data.empty()) {
inst->SetCustomDataString(e.custom_data);
}
if (e.ornament_icon != 0 || e.ornament_idfile != 0 || e.ornament_hero_model != 0) {
inst->SetOrnamentIcon(e.ornament_icon);
inst->SetOrnamentationIDFile(e.ornament_idfile);
inst->SetOrnamentHeroModel(e.ornament_hero_model);
} else if (item->HerosForgeModel > 0) {
inst->SetOrnamentHeroModel(item->HerosForgeModel);
}
inv->PutItem(e.slot_id, *inst);
safe_delete(inst);
}
return true;
}