Core/Player: Added character templates
* Characters with predefined levels can be created * Avaiable factions and classes can be configured * Valid values for `factionGroup` in table `character_template_class` are 3 (Alliance) or 5 (Horde) * Added new permission RBAC_PERM_USE_CHARACTER_TEMPLATES - has to be set, to allow the usage of the templates Closes #13952
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
DELETE FROM `rbac_permissions` WHERE `id` IN (10, 662);
|
||||
INSERT INTO `rbac_permissions` (`id`, `name`) VALUES
|
||||
(10, 'Use character templates'),
|
||||
(662, 'Command: reload character_template');
|
||||
|
||||
DELETE FROM `rbac_linked_permissions` WHERE `linkedId` IN (10, 662);
|
||||
INSERT INTO `rbac_linked_permissions` (`id`, `linkedId`) VALUES
|
||||
(196, 10),
|
||||
(196, 662);
|
||||
@@ -0,0 +1,16 @@
|
||||
DROP TABLE IF EXISTS `character_template`;
|
||||
CREATE TABLE IF NOT EXISTS `character_template` (
|
||||
`id` int(10) unsigned NOT NULL,
|
||||
`name` varchar(70) NOT NULL,
|
||||
`description` varchar(100) NOT NULL,
|
||||
`level` tinyint(3) unsigned NOT NULL DEFAULT '1',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `character_template_class`;
|
||||
CREATE TABLE IF NOT EXISTS `character_template_class` (
|
||||
`templateId` int(10) unsigned NOT NULL,
|
||||
`factionGroup` tinyint(3) unsigned NOT NULL COMMENT '3 - Alliance, 5 - Horde',
|
||||
`class` tinyint(3) unsigned NOT NULL,
|
||||
PRIMARY KEY (`templateId`,`factionGroup`,`class`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
@@ -59,7 +59,7 @@ enum RBACPermissions
|
||||
// 7 - reuse
|
||||
// 8 - reuse
|
||||
// 9 - reuse
|
||||
// 10 - reuse
|
||||
RBAC_PERM_USE_CHARACTER_TEMPLATES = 10,
|
||||
RBAC_PERM_LOG_GM_TRADE = 11,
|
||||
// 12 - reuse
|
||||
RBAC_PERM_SKIP_CHECK_INSTANCE_REQUIRED_BOSSES = 13,
|
||||
@@ -566,7 +566,7 @@ enum RBACPermissions
|
||||
RBAC_PERM_COMMAND_RELOAD_LOCALES_CRETURE_TEXT = 659,
|
||||
RBAC_PERM_COMMAND_RELOAD_LOCALES_GAMEOBJECT = 660,
|
||||
RBAC_PERM_COMMAND_RELOAD_LOCALES_GOSSIP_MENU_OPTION = 661,
|
||||
// UNUSED
|
||||
RBAC_PERM_COMMAND_RELOAD_CHARACTER_TEMPLATE = 662,
|
||||
RBAC_PERM_COMMAND_RELOAD_LOCALES_ITEM_SET_NAME = 663,
|
||||
RBAC_PERM_COMMAND_RELOAD_QUEST_GREETING = 664,
|
||||
RBAC_PERM_COMMAND_RELOAD_LOCALES_PAGE_TEXT = 665,
|
||||
|
||||
@@ -9133,6 +9133,81 @@ void ObjectMgr::LoadRaceAndClassExpansionRequirements()
|
||||
TC_LOG_INFO("server.loading", ">> Loaded 0 class expansion requirements. DB table `class_expansion_requirement` is empty.");
|
||||
}
|
||||
|
||||
void ObjectMgr::LoadCharacterTemplates()
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
_characterTemplateStore.clear();
|
||||
|
||||
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_TEMPLATES);
|
||||
PreparedQueryResult templates = CharacterDatabase.Query(stmt);
|
||||
|
||||
if (!templates)
|
||||
{
|
||||
TC_LOG_INFO("server.loading", ">> Loaded 0 character templates. DB table `character_template` is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
PreparedQueryResult classes;
|
||||
uint32 count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
Field* fields = templates->Fetch();
|
||||
|
||||
uint32 templateSetId = fields[0].GetUInt32();
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_TEMPLATE_CLASSES);
|
||||
stmt->setUInt32(0, templateSetId);
|
||||
classes = CharacterDatabase.Query(stmt);
|
||||
|
||||
if (classes)
|
||||
{
|
||||
CharacterTemplate templ;
|
||||
templ.TemplateSetId = templateSetId;
|
||||
templ.Name = fields[1].GetString();
|
||||
templ.Description = fields[2].GetString();
|
||||
templ.Level = fields[3].GetUInt8();
|
||||
|
||||
do
|
||||
{
|
||||
fields = classes->Fetch();
|
||||
|
||||
uint8 factionGroup = fields[0].GetUInt8();
|
||||
uint8 classID = fields[1].GetUInt8();
|
||||
|
||||
if (!((factionGroup & (FACTION_MASK_PLAYER | FACTION_MASK_ALLIANCE)) == (FACTION_MASK_PLAYER | FACTION_MASK_ALLIANCE)) &&
|
||||
!((factionGroup & (FACTION_MASK_PLAYER | FACTION_MASK_HORDE)) == (FACTION_MASK_PLAYER | FACTION_MASK_HORDE)))
|
||||
{
|
||||
TC_LOG_ERROR("sql.sql", "Faction group %u defined for character template %u in `character_template_class` is invalid. Skipped.", factionGroup, templateSetId);
|
||||
continue;
|
||||
}
|
||||
|
||||
ChrClassesEntry const* classEntry = sChrClassesStore.LookupEntry(classID);
|
||||
if (!classEntry)
|
||||
{
|
||||
TC_LOG_ERROR("sql.sql", "Class %u defined for character template %u in `character_template_class` does not exists, skipped.", classID, templateSetId);
|
||||
continue;
|
||||
}
|
||||
|
||||
templ.Classes.emplace_back(factionGroup, classID);
|
||||
|
||||
} while (classes->NextRow());
|
||||
|
||||
if (!templ.Classes.empty())
|
||||
{
|
||||
_characterTemplateStore[templateSetId] = templ;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TC_LOG_ERROR("sql.sql", "Character template %u does not have any classes defined in `character_template_class`. Skipped.", templateSetId);
|
||||
continue;
|
||||
}
|
||||
} while (templates->NextRow());
|
||||
TC_LOG_INFO("server.loading", ">> Loaded %u character templates in %u ms.", count, GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
void ObjectMgr::LoadRealmNames()
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
@@ -596,6 +596,26 @@ typedef std::unordered_map<uint32, TrainerSpellData> CacheTrainerSpellContainer;
|
||||
typedef std::unordered_map<uint8, uint8> ExpansionRequirementContainer;
|
||||
typedef std::unordered_map<uint32, std::string> RealmNameContainer;
|
||||
|
||||
struct CharcterTemplateClass
|
||||
{
|
||||
CharcterTemplateClass(uint8 factionGroup, uint8 classID) :
|
||||
FactionGroup(factionGroup), ClassID(classID) { }
|
||||
|
||||
uint8 FactionGroup;
|
||||
uint8 ClassID;
|
||||
};
|
||||
|
||||
struct CharacterTemplate
|
||||
{
|
||||
uint32 TemplateSetId;
|
||||
std::list<CharcterTemplateClass> Classes;
|
||||
std::string Name;
|
||||
std::string Description;
|
||||
uint8 Level;
|
||||
};
|
||||
|
||||
typedef std::unordered_map<uint32, CharacterTemplate> CharacterTemplateContainer;
|
||||
|
||||
enum SkillRangeType
|
||||
{
|
||||
SKILL_RANGE_LANGUAGE, // 300..300
|
||||
@@ -1287,6 +1307,7 @@ class ObjectMgr
|
||||
bool IsTransportMap(uint32 mapId) const { return _transportMaps.count(mapId) != 0; }
|
||||
|
||||
void LoadRaceAndClassExpansionRequirements();
|
||||
void LoadCharacterTemplates();
|
||||
void LoadRealmNames();
|
||||
|
||||
std::string GetRealmName(uint32 realm) const;
|
||||
@@ -1309,6 +1330,16 @@ class ObjectMgr
|
||||
return EXPANSION_CLASSIC;
|
||||
}
|
||||
|
||||
CharacterTemplateContainer const& GetCharacterTemplates() const { return _characterTemplateStore; }
|
||||
CharacterTemplate const* GetCharacterTemplate(uint32 id) const
|
||||
{
|
||||
auto itr = _characterTemplateStore.find(id);
|
||||
if (itr != _characterTemplateStore.end())
|
||||
return &itr->second;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
// first free id for selected id type
|
||||
uint32 _auctionId;
|
||||
@@ -1457,6 +1488,8 @@ class ObjectMgr
|
||||
ExpansionRequirementContainer _classExpansionRequirementStore;
|
||||
RealmNameContainer _realmNameStore;
|
||||
|
||||
CharacterTemplateContainer _characterTemplateStore;
|
||||
|
||||
enum CreatureLinkedRespawnType
|
||||
{
|
||||
CREATURE_TO_CREATURE,
|
||||
|
||||
@@ -39,6 +39,10 @@ void WorldSession::SendAuthResponse(uint8 code, bool queued, uint32 queuePos)
|
||||
// Send current home realm. Also there is no need to send it later in realm queries.
|
||||
response.SuccessInfo.Value.VirtualRealms.emplace_back(GetVirtualRealmAddress(), true, false, realmName, realmName);
|
||||
|
||||
if (HasPermission(rbac::RBAC_PERM_USE_CHARACTER_TEMPLATES))
|
||||
for (auto& templ : sObjectMgr->GetCharacterTemplates())
|
||||
response.SuccessInfo.Value.Templates.emplace_back(templ.second);
|
||||
|
||||
response.SuccessInfo.Value.AvailableClasses = &sObjectMgr->GetClassExpansionRequirements();
|
||||
response.SuccessInfo.Value.AvailableRaces = &sObjectMgr->GetRaceExpansionRequirements();
|
||||
}
|
||||
|
||||
@@ -705,6 +705,25 @@ void WorldSession::HandleCharCreateCallback(PreparedQueryResult result, WorldPac
|
||||
|
||||
LoginDatabase.CommitTransaction(trans);
|
||||
|
||||
if (createInfo->TemplateSet.HasValue)
|
||||
{
|
||||
if (HasPermission(rbac::RBAC_PERM_USE_CHARACTER_TEMPLATES))
|
||||
{
|
||||
if (CharacterTemplate const* charTemplate = sObjectMgr->GetCharacterTemplate(createInfo->TemplateSet.Value))
|
||||
{
|
||||
if (charTemplate->Level != 1)
|
||||
{
|
||||
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_LEVEL);
|
||||
stmt->setUInt8(0, uint8(charTemplate->Level));
|
||||
stmt->setUInt64(1, newChar.GetGUID().GetCounter());
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
TC_LOG_WARN("cheat", "Account: %u (IP: %s) tried to use a character template without given permission. Possible cheating attempt.", GetAccountId(), GetRemoteAddress().c_str());
|
||||
}
|
||||
|
||||
SendCharCreate(CHAR_CREATE_SUCCESS);
|
||||
|
||||
TC_LOG_INFO("entities.player.character", "Account: %u (IP: %s) Create Character: %s %s", GetAccountId(), GetRemoteAddress().c_str(), createInfo->Name.c_str(), newChar.GetGUID().ToString().c_str());
|
||||
|
||||
@@ -104,11 +104,11 @@ WorldPacket const* WorldPackets::Auth::AuthResponse::Write()
|
||||
for (auto& templat : SuccessInfo.Value.Templates)
|
||||
{
|
||||
_worldPacket << uint32(templat.TemplateSetId);
|
||||
_worldPacket << uint32(templat.TemplateClasses.size());
|
||||
for (auto& templatClass : templat.TemplateClasses)
|
||||
_worldPacket << uint32(templat.Classes.size());
|
||||
for (auto& templateClass : templat.Classes)
|
||||
{
|
||||
_worldPacket << uint8(templatClass.Class);
|
||||
_worldPacket << uint8(templatClass.FactionGroup);
|
||||
_worldPacket << uint8(templateClass.ClassID);
|
||||
_worldPacket << uint8(templateClass.FactionGroup);
|
||||
}
|
||||
|
||||
_worldPacket.WriteBits(templat.Name.length(), 7);
|
||||
|
||||
@@ -83,20 +83,6 @@ namespace WorldPackets
|
||||
std::string RealmNameNormalized; ///< the name of the realm without spaces
|
||||
};
|
||||
|
||||
struct CharacterTemplate
|
||||
{
|
||||
struct TemplateClass
|
||||
{
|
||||
uint8 Class;
|
||||
uint8 FactionGroup; ///< @todo research
|
||||
};
|
||||
|
||||
uint32 TemplateSetId; ///< @todo research
|
||||
std::list<TemplateClass> TemplateClasses;
|
||||
std::string Name;
|
||||
std::string Description;
|
||||
};
|
||||
|
||||
struct AuthSuccessInfo
|
||||
{
|
||||
uint32 TimeRemain = 0; ///< the remaining game time that the account has in seconds. It is not currently implemented and probably won't ever be.
|
||||
@@ -111,7 +97,7 @@ namespace WorldPackets
|
||||
uint32 CurrencyID = 0; ///< this is probably used for the ingame shop. @todo implement
|
||||
|
||||
std::list<RealmInfo> VirtualRealms; ///< list of realms connected to this one (inclusive) @todo implement
|
||||
std::list<CharacterTemplate> Templates; ///< list of pre-made character templates. @todo implement
|
||||
std::list<CharacterTemplate> Templates; ///< list of pre-made character templates.
|
||||
|
||||
ExpansionRequirementContainer const* AvailableClasses = nullptr; ///< the minimum AccountExpansion required to select the classes
|
||||
ExpansionRequirementContainer const* AvailableRaces = nullptr; ///< the minimum AccountExpansion required to select the races
|
||||
|
||||
@@ -2028,6 +2028,9 @@ void World::SetInitialWorldSettings()
|
||||
TC_LOG_INFO("server.loading", "Loading race and class expansion requirements...");
|
||||
sObjectMgr->LoadRaceAndClassExpansionRequirements();
|
||||
|
||||
TC_LOG_INFO("server.loading", "Loading character templates...");
|
||||
sObjectMgr->LoadCharacterTemplates();
|
||||
|
||||
TC_LOG_INFO("server.loading", "Loading realm names...");
|
||||
sObjectMgr->LoadRealmNames();
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ public:
|
||||
{ "areatrigger_teleport", rbac::RBAC_PERM_COMMAND_RELOAD_AREATRIGGER_TELEPORT, true, &HandleReloadAreaTriggerTeleportCommand, "", NULL },
|
||||
{ "autobroadcast", rbac::RBAC_PERM_COMMAND_RELOAD_AUTOBROADCAST, true, &HandleReloadAutobroadcastCommand, "", NULL },
|
||||
{ "battleground_template", rbac::RBAC_PERM_COMMAND_RELOAD_BATTLEGROUND_TEMPLATE, true, &HandleReloadBattlegroundTemplate, "", NULL },
|
||||
{ "character_template", rbac::RBAC_PERM_COMMAND_RELOAD_CHARACTER_TEMPLATE, true, &HandleReloadCharacterTemplate, "", NULL },
|
||||
{ "command", rbac::RBAC_PERM_COMMAND_RELOAD_COMMAND, true, &HandleReloadCommandCommand, "", NULL },
|
||||
{ "conditions", rbac::RBAC_PERM_COMMAND_RELOAD_CONDITIONS, true, &HandleReloadConditions, "", NULL },
|
||||
{ "config", rbac::RBAC_PERM_COMMAND_RELOAD_CONFIG, true, &HandleReloadConfigCommand, "", NULL },
|
||||
@@ -202,6 +203,7 @@ public:
|
||||
|
||||
HandleReloadAutobroadcastCommand(handler, "");
|
||||
HandleReloadBattlegroundTemplate(handler, "");
|
||||
HandleReloadCharacterTemplate(handler, "");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -383,6 +385,14 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleReloadCharacterTemplate(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
TC_LOG_INFO("misc", "Re-Loading Character Templates...");
|
||||
sObjectMgr->LoadCharacterTemplates();
|
||||
handler->SendGlobalGMSysMessage("DB table `character_template` and `character_template_class` reloaded.");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleReloadCommandCommand(ChatHandler* handler, const char* /*args*/)
|
||||
{
|
||||
handler->SetLoadCommandTable(true);
|
||||
|
||||
@@ -68,6 +68,8 @@ void CharacterDatabaseConnection::DoPrepareStatements()
|
||||
PrepareStatement(CHAR_SEL_CHAR_ZONE, "SELECT zone FROM characters WHERE guid = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_CHAR_POSITION_XYZ, "SELECT map, position_x, position_y, position_z FROM characters WHERE guid = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_CHAR_POSITION, "SELECT position_x, position_y, position_z, orientation, map, taxi_path FROM characters WHERE guid = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_TEMPLATES, "SELECT id, name, description, level FROM character_template", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_TEMPLATE_CLASSES, "SELECT factionGroup, class FROM character_template_class WHERE templateId = ?", CONNECTION_SYNCH);
|
||||
|
||||
PrepareStatement(CHAR_DEL_BATTLEGROUND_RANDOM_ALL, "DELETE FROM character_battleground_random", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_BATTLEGROUND_RANDOM, "DELETE FROM character_battleground_random WHERE guid = ?", CONNECTION_ASYNC);
|
||||
|
||||
@@ -71,6 +71,8 @@ enum CharacterDatabaseStatements
|
||||
CHAR_SEL_CHAR_ZONE,
|
||||
CHAR_SEL_CHAR_POSITION_XYZ,
|
||||
CHAR_SEL_CHAR_POSITION,
|
||||
CHAR_SEL_CHARACTER_TEMPLATES,
|
||||
CHAR_SEL_CHARACTER_TEMPLATE_CLASSES,
|
||||
|
||||
CHAR_DEL_BATTLEGROUND_RANDOM_ALL,
|
||||
CHAR_DEL_BATTLEGROUND_RANDOM,
|
||||
|
||||
Reference in New Issue
Block a user