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:
Carbenium
2015-04-21 00:52:21 +02:00
parent 4208c0d839
commit bba6eb8d3d
13 changed files with 180 additions and 21 deletions
+9
View File
@@ -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;
+2 -2
View File
@@ -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,
+75
View File
@@ -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();
+33
View File
@@ -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,
+4
View File
@@ -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
+3
View File
@@ -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();
+10
View File
@@ -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,