Skip to content

Commit

Permalink
Add dual spec mod
Browse files Browse the repository at this point in the history
  • Loading branch information
Niam5 committed Aug 8, 2024
1 parent 9bd71dc commit c555eb5
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,10 @@ if(BUILD_SOLOCRAFT)
endif()
endif()

if(BUILD_DUAL_SPEC)
set_property(TARGET game APPEND PROPERTY COMPILE_DEFINITIONS BUILD_DUAL_SPEC)
endif()

# if(SQL)
# add_subdirectory(sql)
# endif()
2 changes: 2 additions & 0 deletions cmake/options.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ option(BUILD_SCRIPTDEV "Build ScriptDev. (OFF Speedup build
option(BUILD_PLAYERBOTS "Build Playerbots mod" OFF)
option(BUILD_ELUNA "Build Eluna Lua Engine" OFF)
option(BUILD_SOLOCRAFT "Build SoloCraft mod" OFF)
option(BUILD_DUAL_SPEC "Build Dual Spec mod" OFF)
option(BUILD_AHBOT "Build Auction House Bot mod" OFF)
option(BUILD_METRICS "Build Metrics, generate data for Grafana" OFF)
option(BUILD_RECASTDEMOMOD "Build map/vmap/mmap viewer" OFF)
Expand Down Expand Up @@ -39,6 +40,7 @@ message(STATUS
BUILD_PLAYERBOTS Build Playerbots mod
BUILD_ELUNA Build Eluna Lua Engine
BUILD_SOLOCRAFT Build SoloCraft Mod
BUILD_DUAL_SPEC Build Dual Spec mod
BUILD_AHBOT Build Auction House Bot mod
BUILD_METRICS Build Metrics, generate data for Grafana
BUILD_RECASTDEMOMOD Build map/vmap/mmap viewer
Expand Down
6 changes: 6 additions & 0 deletions cmake/showoptions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ else()
message(STATUS "Build SoloCraft Mod : No (default)")
endif()

if(BUILD_DUAL_SPEC)
message(STATUS "Build Dual Spec mod : Yes")
else()
message(STATUS "Build Dual Spec mod : No (default)")
endif()

if(BUILD_AHBOT)
message(STATUS "Build AHBot : Yes")
else()
Expand Down
33 changes: 33 additions & 0 deletions sql/updates/characters/z9999_02_characters_custom_dualspec.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
DROP TABLE IF EXISTS `character_altspec`;
CREATE TABLE `character_altspec` (
`guid` int(11) unsigned NOT NULL default '0' COMMENT 'Global Unique Identifier',
`altspells` int(11) unsigned NOT NULL default '0',
PRIMARY KEY (`guid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='Player Dual Spec System';

--
-- Dumping data for table `characters_altspec`
--

LOCK TABLES `character_altspec` WRITE;
/*!40000 ALTER TABLE `character_altspec` DISABLE KEYS */;
/*!40000 ALTER TABLE `character_altspec` ENABLE KEYS */;
UNLOCK TABLES;

DROP TABLE IF EXISTS `character_altspec_action`;
CREATE TABLE `character_altspec_action` (
`guid` int(11) unsigned NOT NULL default '0' COMMENT 'Global Unique Identifier',
`button` tinyint(3) unsigned NOT NULL default '0',
`action` smallint(5) unsigned NOT NULL default '0',
`type` tinyint(3) unsigned NOT NULL default '0',
PRIMARY KEY (`guid`,`button`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='Player Dual Spec System, table for actions';

--
-- Dumping data for table `character_altspec_action`
--

LOCK TABLES `character_altspec_action` WRITE;
/*!40000 ALTER TABLE `character_altspec_action` DISABLE KEYS */;
/*!40000 ALTER TABLE `character_altspec_action` ENABLE KEYS */;
UNLOCK TABLES;
3 changes: 3 additions & 0 deletions src/game/Chat/Chat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,9 @@ ChatCommand* ChatHandler::getCommandTable()
{ "aura", SEC_ADMINISTRATOR, false, &ChatHandler::HandleAuraCommand, "", nullptr },
{ "unaura", SEC_ADMINISTRATOR, false, &ChatHandler::HandleUnAuraCommand, "", nullptr },
{ "announce", SEC_MODERATOR, true, &ChatHandler::HandleAnnounceCommand, "", nullptr },
#ifdef BUILD_DUAL_SPEC
{ "swapspec", SEC_PLAYER, false, &ChatHandler::HandleSwapSpec, "", nullptr },
#endif
{ "notify", SEC_MODERATOR, true, &ChatHandler::HandleNotifyCommand, "", nullptr },
{ "goname", SEC_MODERATOR, false, &ChatHandler::HandleGonameCommand, "", nullptr },
{ "appear", SEC_MODERATOR, false, &ChatHandler::HandleGonameCommand, "", nullptr },
Expand Down
3 changes: 3 additions & 0 deletions src/game/Chat/Chat.h
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,9 @@ class ChatHandler
bool HandleGroupgoCommand(char* args);
bool HandleRecallCommand(char* args);
bool HandleAnnounceCommand(char* args);
#ifdef BUILD_DUAL_SPEC
bool HandleSwapSpec(char* args);
#endif
bool HandleNotifyCommand(char* args);
bool HandleGPSCommand(char* args);
bool HandleTaxiCheatCommand(char* args);
Expand Down
22 changes: 22 additions & 0 deletions src/game/Chat/Level0.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,25 @@ bool ChatHandler::HandleWhisperRestrictionCommand(char* args)

return true;
}

#ifdef BUILD_DUAL_SPEC
bool ChatHandler::HandleSwapSpec(char* /*args*/)
{
uint32 res = m_session->GetPlayer()->SwapSpec();
switch (res) {
case 3: {
PSendSysMessage("Oh, wait a bit, please!");
break;
}
case 2: {
PSendSysMessage("Too low level");
break;
}
case 1: {
PSendSysMessage("Swapped!");
break;
}
}
return true;
}
#endif
178 changes: 178 additions & 0 deletions src/game/Entities/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20768,3 +20768,181 @@ uint32 Player::LookupHighestLearnedRank(uint32 spellId)
} while ((higherRank = sSpellMgr.GetNextSpellInChain(ownedRank)));
return ownedRank;
}

#ifdef BUILD_DUAL_SPEC
void Player::SaveAlternativeSpec()
{
sLog.outDebug("Saving spec");
//Save current spec
for (SpellIDList::iterator it = m_altspec_talents.begin(); it != m_altspec_talents.end(); it++)
{
CharacterDatabase.PExecute("INSERT INTO `character_altspec` (`guid`, `altspells`) VALUES (%u, %u)", GetGUIDLow(), *it);
}
sLog.outDebug("Spec saved");

//Remove previous spec action buttons
CharacterDatabase.PExecute("DELETE FROM character_altspec_action WHERE guid = '%u'", GetGUIDLow());
sLog.outDebug("Old buttons removed");

//Now - seek all needed keys and insert into DB
for (uint32 button = 0; button < MAX_ACTION_BUTTONS; ++button)
{
//Find button by button id
ActionButtonList::const_iterator itr = m_altspec_actionButtons.find(button);
//STL returns end() if not found. We need to skip it.
if (itr != m_altspec_actionButtons.end())
{
//Okay, now we're ready to insert it
CharacterDatabase.PExecute("INSERT INTO character_altspec_action (guid,button,action,type) VALUES ('%u', '%u', '%u', '%u')",
GetGUIDLow(), button, itr->second.GetAction(), itr->second.GetType());
}
}
}

void Player::LoadAlternativeSpec()
{
//Load talents from database
auto queryResult = CharacterDatabase.PQuery("SELECT `altspells` FROM character_altspec WHERE guid = %u", GetGUIDLow());

if (queryResult)
{
Field* fields = queryResult->Fetch();
do
{
m_loaded_talents.push_back(fields[0].GetUInt32());
} while (queryResult->NextRow());
}

//Delete loaded spec, will be replaced with current spec
CharacterDatabase.PExecute("DELETE FROM `character_altspec` WHERE guid = %u", GetGUIDLow());

sLog.outDebug("Loaded spec");
}

uint32 Player::SwapSpec()
{
/*
Error codes:
2 - Too low level
3 - Too fast
Strategy:
1) save keys
2) save talents
3) load talents
4) load keys
*/

//Level check
if (GetLevel() <= 10)
return 2;

//Time check
if (uint32(time(NULL) - m_altspec_lastswap) < sWorld.getConfig(CONFIG_UINT32_DUAL_SPEC_TIME_DELTA))
return 3;

/*********************************************************/
/*** SAVE ACTIONBUTTONS ***/
/*********************************************************/
sLog.outDebug("Save action buttons");
//Save buttons before
ActionButtonList tmp_buttons = m_altspec_actionButtons;

//Just copy current content
m_altspec_actionButtons = m_actionButtons;

/*********************************************************/
/*** SAVE TALENTS ***/
/*********************************************************/
sLog.outDebug("Save talents");
//erase it for populating using current talents
m_altspec_talents.clear();
m_loaded_talents.clear();

//Find all talents, general idea from Player::resetTalents
for (unsigned int i = 0; i < sTalentStore.GetNumRows(); ++i) {
TalentEntry const* talentInfo = sTalentStore.LookupEntry(i);
if (!talentInfo) continue;
TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab);
if (!talentTabInfo) continue;
if ((getClassMask() & talentTabInfo->ClassMask) == 0) continue;
for (int j = 0; j < 5; ++j) {
for (PlayerSpellMap::iterator itr = GetSpellMap().begin(); itr != GetSpellMap().end();) {

//skip disabled talents like Pyroblast or some else
if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled)
{
++itr;
continue;
}

//for spells, which can be updated via trainers(like Pyroblast), we can'n just compare, cuz
// >1 ranks are not in talens store. So, first rank it is. We can just get lowerest rank of skill
// and search it in the talents storage.
uint32 itrFirstId = sSpellMgr.GetFirstSpellInChain(itr->first);

//now - just compare. Also, it make sense to add "|| spellmgr.IsSpellLearnToSpell(talentInfo->RankID[j],itrFirstId)"
//but i have no idea what it is, it uses in the Player::resetTalents function and it may be needed.
//Also, there is a some spells like Prayer of Spirit, which not in talents tree, but its depends on talents.
//So, we need just to get required spell by current spell and find is it in player spellbook.
if (itrFirstId == talentInfo->RankID[j]
|| sSpellMgr.IsSpellLearnToSpell(talentInfo->RankID[j], itrFirstId)
)//|| HasSpell(spellmgr.GetSpellRequired(itrFirstId)))
m_altspec_talents.push_back(itr->first);
++itr;
}
}
}
//Must call load first since old spec will be deleted
LoadAlternativeSpec();
auto queryResultButtons = CharacterDatabase.PQuery("SELECT `button`, `action`, `type` FROM character_altspec_action WHERE guid = %u", GetGUIDLow());
SaveAlternativeSpec();

/*********************************************************/
/*** LOAD TALENTS ***/
/*********************************************************/
sLog.outDebug("Load talents");
resetTalents(true);
for (SpellIDList::iterator it = m_loaded_talents.begin(); it != m_loaded_talents.end(); it++)
{
learnSpell(*it, false);
}
InitTalentForLevel();
//learnSkillRewardedSpells();

/*********************************************************/
/*** LOAD ACTIONBUTTONS ***/
/*********************************************************/
sLog.outDebug("Load action buttons");
//Clean up
for (int button = 0; button < MAX_ACTION_BUTTONS; ++button)
removeActionButton(button);
sLog.outDebug("Action buttons cleaned");

//Add new actions buttons for new spec
if (queryResultButtons)
{
Field* fields = queryResultButtons->Fetch();

do
{
uint8 button = fields[0].GetUInt8();
uint32 action = fields[1].GetUInt32();
uint8 type = fields[2].GetUInt8();

addActionButton(button, action, type);
} while (queryResultButtons->NextRow());
}

sLog.outDebug("Action buttons added");

//SendInitialActionButtons();//doesnt work

//Drop mana and health to minimum for preventing of profit from swappings
SetHealth(1);
SetPower(POWER_MANA, 1);
SetPower(POWER_RAGE, 1);
SetPower(POWER_ENERGY, 1);
GetSession()->LogoutPlayer(); //Action bars won't reload unless player is kicked then relogs
return 1; //Okay
}
#endif
15 changes: 15 additions & 0 deletions src/game/Entities/Player.h
Original file line number Diff line number Diff line change
Expand Up @@ -2166,6 +2166,21 @@ class Player : public Unit
AreaLockStatus GetAreaTriggerLockStatus(AreaTrigger const* at, uint32& miscRequirement, bool forceAllChecks = false);
void SendTransferAbortedByLockStatus(MapEntry const* mapEntry, AreaTrigger const* at, AreaLockStatus lockStatus, uint32 miscRequirement = 0) const;

#ifdef BUILD_DUAL_SPEC
/*********************************************************/
/*** DUEL SPEC SYSTEM ***/
/*********************************************************/

void LoadAlternativeSpec();
void SaveAlternativeSpec();
typedef std::list<uint32> SpellIDList;
SpellIDList m_altspec_talents;
SpellIDList m_loaded_talents;
ActionButtonList m_altspec_actionButtons;
time_t m_altspec_lastswap;
uint32 SwapSpec();
#endif

/*********************************************************/
/*** GROUP SYSTEM ***/
/*********************************************************/
Expand Down
4 changes: 4 additions & 0 deletions src/game/World/World.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,10 @@ void World::LoadConfigSettings(bool reload)
//End Solocraft Config
#endif

#ifdef BUILD_DUAL_SPEC
setConfig(CONFIG_UINT32_DUAL_SPEC_TIME_DELTA, "DualSpecTimeDelta", 300);
#endif

sLog.outString();
}

Expand Down
3 changes: 3 additions & 0 deletions src/game/World/World.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@ enum eConfigUInt32Values
CONFIG_UINT32_AHNQIRAJ_LEVEL,
CONFIG_UINT32_AHNQIRAJTEMPLE_LEVEL,
CONFIG_UINT32_STRATHOLMERAID_LEVEL,
#endif
#ifdef BUILD_DUAL_SPEC
CONFIG_UINT32_DUAL_SPEC_TIME_DELTA,
#endif
CONFIG_UINT32_VALUE_COUNT
};
Expand Down
4 changes: 4 additions & 0 deletions src/mangosd/mangosd.conf.dist.in
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,9 @@ Visibility.AIRelocationNotifyDelay = 1000
# Modifies the speed of player's ghosts, removed upon reviving, not permanent/saved, in non-BG and BG maps
# Default: 1.0 (normal speed)
#
# DualSpecTimeDelta
# Time duration to switch talen specs, dual spec command .swapspec
#
###################################################################################################################

Rate.Health = 1
Expand Down Expand Up @@ -1544,6 +1547,7 @@ Death.Bones.World = 1
Death.Bones.Battleground = 1
Death.Ghost.RunSpeed.World = 1.0
Death.Ghost.RunSpeed.Battleground = 1.0
DualSpecTimeDelta = 300

###################################################################################################################
# BATTLEGROUND CONFIG
Expand Down

0 comments on commit c555eb5

Please sign in to comment.