wip
This commit is contained in:
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#
|
||||
# Game Modes module configuration
|
||||
#
|
||||
|
||||
########################################
|
||||
# GameModes settings
|
||||
########################################
|
||||
|
||||
#
|
||||
# GameModes.Enable
|
||||
# Description: Enable game modes system
|
||||
# Default: 1 - (Enabled)
|
||||
# 0 - (Disabled)
|
||||
#
|
||||
|
||||
GameModes.Enable = 1
|
||||
|
||||
#
|
||||
# GameModes.Traitor.Enable
|
||||
# Description: Enable Traitor game mode
|
||||
# Default: 1 - (Enabled)
|
||||
# 0 - (Disabled)
|
||||
#
|
||||
|
||||
GameModes.Traitor.Enable = 1
|
||||
@@ -0,0 +1,6 @@
|
||||
DROP TABLE IF EXISTS `mod_gamemodes_characters`;
|
||||
CREATE TABLE `mod_gamemodes_characters` (
|
||||
`guid` INT UNSIGNED NOT NULL,
|
||||
`game_mode` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0 = NORMAL, 1 = TRAITOR',
|
||||
PRIMARY KEY (`guid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "GameModes.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Log.h"
|
||||
|
||||
GameModeMgr* GameModeMgr::Instance()
|
||||
{
|
||||
static GameModeMgr instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
void GameModeMgr::LoadFromDB()
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
_gameModes.clear();
|
||||
|
||||
QueryResult result = CharacterDatabase.Query("SELECT `guid`, `game_mode` FROM `mod_gamemodes_characters`");
|
||||
if (!result)
|
||||
{
|
||||
LOG_INFO("module", ">> Loaded 0 character game modes. Table `mod_gamemodes_characters` is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 count = 0;
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
ObjectGuid::LowType guid = fields[0].Get<uint32>();
|
||||
uint8 mode = fields[1].Get<uint8>();
|
||||
|
||||
if (mode >= GAMEMODE_MAX)
|
||||
{
|
||||
LOG_ERROR("module", "mod-gamemodes: Invalid game mode {} for character {}, skipping.", mode, guid);
|
||||
continue;
|
||||
}
|
||||
|
||||
_gameModes[guid] = static_cast<GameMode>(mode);
|
||||
++count;
|
||||
} while (result->NextRow());
|
||||
|
||||
LOG_INFO("module", ">> Loaded {} character game modes in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
void GameModeMgr::SaveGameMode(ObjectGuid::LowType guid, GameMode mode)
|
||||
{
|
||||
_gameModes[guid] = mode;
|
||||
|
||||
CharacterDatabase.Execute("REPLACE INTO `mod_gamemodes_characters` (`guid`, `game_mode`) VALUES ({}, {})",
|
||||
guid, static_cast<uint8>(mode));
|
||||
}
|
||||
|
||||
void GameModeMgr::DeleteGameMode(ObjectGuid::LowType guid)
|
||||
{
|
||||
_gameModes.erase(guid);
|
||||
|
||||
CharacterDatabase.Execute("DELETE FROM `mod_gamemodes_characters` WHERE `guid` = {}", guid);
|
||||
}
|
||||
|
||||
GameMode GameModeMgr::GetGameMode(ObjectGuid::LowType guid) const
|
||||
{
|
||||
auto it = _gameModes.find(guid);
|
||||
if (it != _gameModes.end())
|
||||
return it->second;
|
||||
return GAMEMODE_NORMAL;
|
||||
}
|
||||
|
||||
bool GameModeMgr::HasGameMode(ObjectGuid::LowType guid, GameMode mode) const
|
||||
{
|
||||
return GetGameMode(guid) == mode;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef MOD_GAMEMODES_H
|
||||
#define MOD_GAMEMODES_H
|
||||
|
||||
#include "Player.h"
|
||||
#include <unordered_map>
|
||||
|
||||
enum GameMode : uint8
|
||||
{
|
||||
GAMEMODE_NORMAL = 0,
|
||||
GAMEMODE_TRAITOR = 1,
|
||||
GAMEMODE_MAX
|
||||
};
|
||||
|
||||
// Custom bit in SMSG_CHAR_ENUM character flags.
|
||||
// Chosen as a high unused bit so the packet layout stays unchanged.
|
||||
constexpr uint32 GAMEMODE_CHARACTER_FLAG_TRAITOR = 0x40000000;
|
||||
|
||||
class GameModeMgr
|
||||
{
|
||||
public:
|
||||
static GameModeMgr* Instance();
|
||||
|
||||
void LoadFromDB();
|
||||
void SaveGameMode(ObjectGuid::LowType guid, GameMode mode);
|
||||
void DeleteGameMode(ObjectGuid::LowType guid);
|
||||
|
||||
GameMode GetGameMode(ObjectGuid::LowType guid) const;
|
||||
bool HasGameMode(ObjectGuid::LowType guid, GameMode mode) const;
|
||||
|
||||
private:
|
||||
GameModeMgr() = default;
|
||||
std::unordered_map<ObjectGuid::LowType, GameMode> _gameModes;
|
||||
};
|
||||
|
||||
#define sGameModeMgr GameModeMgr::Instance()
|
||||
|
||||
#endif // MOD_GAMEMODES_H
|
||||
@@ -0,0 +1,6 @@
|
||||
void AddSC_TraitorMode();
|
||||
|
||||
void Addmod_gamemodesScripts()
|
||||
{
|
||||
AddSC_TraitorMode();
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
#include "TraitorMode.h"
|
||||
#include "GameModes.h"
|
||||
#include "Config.h"
|
||||
#include "DBCStores.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Log.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "ReputationMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
namespace TraitorMode
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void NormalizeFactionState(Player* player, FactionEntry const* factionEntry, bool visible, bool atWar)
|
||||
{
|
||||
if (!factionEntry || factionEntry->reputationListID < 0)
|
||||
return;
|
||||
|
||||
FactionState* factionState = const_cast<FactionState*>(player->GetReputationMgr().GetState(factionEntry));
|
||||
if (!factionState)
|
||||
return;
|
||||
|
||||
factionState->Flags &= ~(FACTION_FLAG_VISIBLE | FACTION_FLAG_HIDDEN | FACTION_FLAG_INVISIBLE_FORCED |
|
||||
FACTION_FLAG_PEACE_FORCED | FACTION_FLAG_INACTIVE);
|
||||
|
||||
if (visible)
|
||||
factionState->Flags |= FACTION_FLAG_VISIBLE;
|
||||
else
|
||||
factionState->Flags |= FACTION_FLAG_HIDDEN | FACTION_FLAG_INVISIBLE_FORCED;
|
||||
|
||||
if (atWar)
|
||||
factionState->Flags |= FACTION_FLAG_AT_WAR;
|
||||
else
|
||||
factionState->Flags &= ~FACTION_FLAG_AT_WAR;
|
||||
|
||||
factionState->needSend = true;
|
||||
factionState->needSave = true;
|
||||
}
|
||||
|
||||
void SetFactionStanding(Player* player, uint32 factionId, int32 targetDisplayed, bool visible, bool atWar)
|
||||
{
|
||||
FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionId);
|
||||
if (!factionEntry)
|
||||
return;
|
||||
|
||||
// ReputationMgr::GetBaseReputation now always uses the player's actual race
|
||||
// mask, so SetOneFactionReputation is safe to call directly.
|
||||
player->GetReputationMgr().SetOneFactionReputation(factionEntry, static_cast<float>(targetDisplayed), false);
|
||||
NormalizeFactionState(player, factionEntry, visible, atWar);
|
||||
}
|
||||
|
||||
void NormalizeTraitorReputationStates(Player* player)
|
||||
{
|
||||
TeamId originalTeam = player->GetTeamId(true);
|
||||
uint32 const* newFactions = (originalTeam == TEAM_ALLIANCE)
|
||||
? HORDE_REPUTATION_FACTIONS
|
||||
: ALLIANCE_REPUTATION_FACTIONS;
|
||||
uint32 const* oldFactions = (originalTeam == TEAM_ALLIANCE)
|
||||
? ALLIANCE_REPUTATION_FACTIONS
|
||||
: HORDE_REPUTATION_FACTIONS;
|
||||
|
||||
auto normalizeFactionAndParent = [player](uint32 factionId, bool visible, bool atWar)
|
||||
{
|
||||
if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionId))
|
||||
{
|
||||
NormalizeFactionState(player, factionEntry, visible, atWar);
|
||||
|
||||
if (factionEntry->team)
|
||||
NormalizeFactionState(player, sFactionStore.LookupEntry(factionEntry->team), visible, false);
|
||||
}
|
||||
};
|
||||
|
||||
for (uint32 i = 0; i < FACTION_COUNT; ++i)
|
||||
{
|
||||
normalizeFactionAndParent(newFactions[i], true, false);
|
||||
normalizeFactionAndParent(oldFactions[i], false, true);
|
||||
}
|
||||
}
|
||||
|
||||
void SendFactionVisiblePacket(Player* player, FactionEntry const* factionEntry)
|
||||
{
|
||||
if (!factionEntry || factionEntry->reputationListID < 0)
|
||||
return;
|
||||
|
||||
WorldPacket data(SMSG_SET_FACTION_VISIBLE, 4);
|
||||
data << uint32(factionEntry->reputationListID);
|
||||
player->SendDirectMessage(&data);
|
||||
}
|
||||
|
||||
void SendTraitorFactionVisibility(Player* player)
|
||||
{
|
||||
TeamId originalTeam = player->GetTeamId(true);
|
||||
uint32 const* newFactions = (originalTeam == TEAM_ALLIANCE)
|
||||
? HORDE_REPUTATION_FACTIONS
|
||||
: ALLIANCE_REPUTATION_FACTIONS;
|
||||
auto sendFactionAndParent = [player](uint32 factionId)
|
||||
{
|
||||
if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionId))
|
||||
{
|
||||
SendFactionVisiblePacket(player, factionEntry);
|
||||
|
||||
if (factionEntry->team)
|
||||
SendFactionVisiblePacket(player, sFactionStore.LookupEntry(factionEntry->team));
|
||||
}
|
||||
};
|
||||
|
||||
for (uint32 i = 0; i < FACTION_COUNT; ++i)
|
||||
sendFactionAndParent(newFactions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyFactionSwap(Player* player)
|
||||
{
|
||||
TeamId originalTeam = player->GetTeamId(true);
|
||||
TeamId newTeam = (originalTeam == TEAM_ALLIANCE) ? TEAM_HORDE : TEAM_ALLIANCE;
|
||||
|
||||
player->setTeamId(newTeam);
|
||||
|
||||
// Derive faction template from the canonical race for the new team (same
|
||||
// logic as SetFactionForRace), so we never rely on hardcoded DBC IDs.
|
||||
uint8 representativeRace = (newTeam == TEAM_HORDE) ? HORDE_DEFAULT_RACE : ALLIANCE_DEFAULT_RACE;
|
||||
if (ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(representativeRace))
|
||||
player->SetFaction(rEntry->FactionID);
|
||||
|
||||
LOG_INFO("module", "mod-gamemodes: ApplyFactionSwap for {} (GUID: {}), original team = {}, new team = {}, factionTemplate = {}",
|
||||
player->GetName(), player->GetGUID().GetCounter(),
|
||||
(originalTeam == TEAM_ALLIANCE ? "Alliance" : "Horde"),
|
||||
(newTeam == TEAM_ALLIANCE ? "Alliance" : "Horde"),
|
||||
player->GetFaction());
|
||||
}
|
||||
|
||||
void InitTraitorReputations(Player* player)
|
||||
{
|
||||
TeamId originalTeam = player->GetTeamId(true);
|
||||
|
||||
// New faction reputations -> Neutral (standing = 0)
|
||||
const uint32* newFactions = (originalTeam == TEAM_ALLIANCE)
|
||||
? HORDE_REPUTATION_FACTIONS
|
||||
: ALLIANCE_REPUTATION_FACTIONS;
|
||||
|
||||
for (uint32 i = 0; i < FACTION_COUNT; ++i)
|
||||
SetFactionStanding(player, newFactions[i], 0, true, false);
|
||||
|
||||
// Old faction reputations -> Hostile (standing = -6000)
|
||||
const uint32* oldFactions = (originalTeam == TEAM_ALLIANCE)
|
||||
? ALLIANCE_REPUTATION_FACTIONS
|
||||
: HORDE_REPUTATION_FACTIONS;
|
||||
|
||||
for (uint32 i = 0; i < FACTION_COUNT; ++i)
|
||||
SetFactionStanding(player, oldFactions[i], -6000, false, true);
|
||||
|
||||
LOG_INFO("module", "mod-gamemodes: InitTraitorReputations for {} (GUID: {})",
|
||||
player->GetName(), player->GetGUID().GetCounter());
|
||||
}
|
||||
|
||||
void TeleportToNewFactionStart(Player* player)
|
||||
{
|
||||
TeamId originalTeam = player->GetTeamId(true);
|
||||
uint8 spawnRace = (originalTeam == TEAM_ALLIANCE)
|
||||
? HORDE_DEFAULT_RACE
|
||||
: ALLIANCE_DEFAULT_RACE;
|
||||
|
||||
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(spawnRace, player->getClass());
|
||||
if (!info)
|
||||
{
|
||||
// Fallback: try warrior (class 1) for that race
|
||||
info = sObjectMgr->GetPlayerInfo(spawnRace, 1);
|
||||
}
|
||||
|
||||
if (!info)
|
||||
{
|
||||
LOG_ERROR("module", "mod-gamemodes: No spawn info for race {} class {}", spawnRace, player->getClass());
|
||||
return;
|
||||
}
|
||||
|
||||
player->TeleportTo(info->mapId, info->positionX, info->positionY, info->positionZ, info->orientation);
|
||||
|
||||
LOG_INFO("module", "mod-gamemodes: Teleported {} to faction start (map {}, {:.1f}, {:.1f}, {:.1f})",
|
||||
player->GetName(), info->mapId, info->positionX, info->positionY, info->positionZ);
|
||||
}
|
||||
}
|
||||
|
||||
class TraitorModePlayer : public PlayerScript
|
||||
{
|
||||
public:
|
||||
TraitorModePlayer() : PlayerScript("TraitorModePlayer",
|
||||
{PLAYERHOOK_ON_CREATE, PLAYERHOOK_ON_LOGIN, PLAYERHOOK_ON_REPUTATION_LOAD_FROM_DB, PLAYERHOOK_ON_UPDATE_FACTION}) {}
|
||||
|
||||
// OnPlayerCreate fires on a temporary Player object AFTER SaveToDB.
|
||||
// Changes to faction/reputation here are lost. We only save the game mode to DB.
|
||||
void OnPlayerCreate(Player* player) override
|
||||
{
|
||||
LOG_INFO("module", "mod-gamemodes: OnPlayerCreate for {} (GUID: {}), OutfitId = {}",
|
||||
player->GetName(), player->GetGUID().GetCounter(), player->GetOutfitId());
|
||||
|
||||
if (!sConfigMgr->GetOption<bool>("GameModes.Enable", true))
|
||||
return;
|
||||
if (!sConfigMgr->GetOption<bool>("GameModes.Traitor.Enable", true))
|
||||
return;
|
||||
|
||||
if (player->GetOutfitId() != GAMEMODE_TRAITOR)
|
||||
return;
|
||||
|
||||
sGameModeMgr->SaveGameMode(player->GetGUID().GetCounter(), GAMEMODE_TRAITOR);
|
||||
CharacterDatabase.Execute("UPDATE `characters` SET `cinematic` = 1 WHERE `guid` = {}", player->GetGUID().GetCounter());
|
||||
LOG_INFO("module", "mod-gamemodes: Saved TRAITOR mode for {} (GUID: {})",
|
||||
player->GetName(), player->GetGUID().GetCounter());
|
||||
}
|
||||
|
||||
// Reputation load completes before initial packets are sent to the client.
|
||||
// Traitor mode must rewrite faction/reputation state here, otherwise the client
|
||||
// builds the reputation window from the original race defaults.
|
||||
void OnPlayerReputationLoadFromDB(Player* player) override
|
||||
{
|
||||
if (!sConfigMgr->GetOption<bool>("GameModes.Enable", true))
|
||||
return;
|
||||
if (!sConfigMgr->GetOption<bool>("GameModes.Traitor.Enable", true))
|
||||
return;
|
||||
|
||||
if (!sGameModeMgr->HasGameMode(player->GetGUID().GetCounter(), GAMEMODE_TRAITOR))
|
||||
return;
|
||||
|
||||
TraitorMode::ApplyFactionSwap(player);
|
||||
|
||||
if (player->HasAtLoginFlag(AT_LOGIN_FIRST))
|
||||
TraitorMode::InitTraitorReputations(player);
|
||||
TraitorMode::NormalizeTraitorReputationStates(player);
|
||||
}
|
||||
|
||||
void OnPlayerLogin(Player* player) override
|
||||
{
|
||||
if (!sConfigMgr->GetOption<bool>("GameModes.Enable", true))
|
||||
return;
|
||||
if (!sConfigMgr->GetOption<bool>("GameModes.Traitor.Enable", true))
|
||||
return;
|
||||
|
||||
if (!sGameModeMgr->HasGameMode(player->GetGUID().GetCounter(), GAMEMODE_TRAITOR))
|
||||
return;
|
||||
|
||||
TraitorMode::ApplyFactionSwap(player);
|
||||
TraitorMode::NormalizeTraitorReputationStates(player);
|
||||
player->GetReputationMgr().SendInitialReputations();
|
||||
TraitorMode::SendTraitorFactionVisibility(player);
|
||||
player->SetPlayerFlag(PLAYER_FLAGS_TRAITOR);
|
||||
|
||||
if (player->HasAtLoginFlag(AT_LOGIN_FIRST))
|
||||
TraitorMode::TeleportToNewFactionStart(player);
|
||||
|
||||
LOG_INFO("module", "mod-gamemodes: OnPlayerLogin END for {} — TeamId = {}, FactionTemplate = {}, OriginalTeam = {}",
|
||||
player->GetName(),
|
||||
static_cast<uint32>(player->GetTeamId()),
|
||||
player->GetFaction(),
|
||||
static_cast<uint32>(player->GetTeamId(true)));
|
||||
}
|
||||
|
||||
// OnPlayerUpdateFaction fires every time SetFactionForRace is called
|
||||
// (login, race change, etc). Re-apply the swap so it's never lost.
|
||||
void OnPlayerUpdateFaction(Player* player) override
|
||||
{
|
||||
if (!sConfigMgr->GetOption<bool>("GameModes.Enable", true))
|
||||
return;
|
||||
if (!sConfigMgr->GetOption<bool>("GameModes.Traitor.Enable", true))
|
||||
return;
|
||||
|
||||
if (!sGameModeMgr->HasGameMode(player->GetGUID().GetCounter(), GAMEMODE_TRAITOR))
|
||||
return;
|
||||
|
||||
TraitorMode::ApplyFactionSwap(player);
|
||||
}
|
||||
};
|
||||
|
||||
class TraitorModeWorld : public WorldScript
|
||||
{
|
||||
public:
|
||||
TraitorModeWorld() : WorldScript("TraitorModeWorld",
|
||||
{WORLDHOOK_ON_LOAD_CUSTOM_DATABASE_TABLE}) {}
|
||||
|
||||
void OnLoadCustomDatabaseTable() override
|
||||
{
|
||||
sGameModeMgr->LoadFromDB();
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_TraitorMode()
|
||||
{
|
||||
new TraitorModePlayer();
|
||||
new TraitorModeWorld();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef MOD_GAMEMODES_TRAITOR_H
|
||||
#define MOD_GAMEMODES_TRAITOR_H
|
||||
|
||||
#include "Player.h"
|
||||
|
||||
namespace TraitorMode
|
||||
{
|
||||
// Reputation faction IDs: capital cities + PvP battleground factions
|
||||
constexpr uint32 ALLIANCE_REPUTATION_FACTIONS[] = {
|
||||
72, // Stormwind
|
||||
47, // Ironforge
|
||||
69, // Darnassus
|
||||
54, // Gnomeregan Exiles
|
||||
930, // Exodar
|
||||
890, // Silverwing Sentinels (WSG)
|
||||
730, // Stormpike Guard (AV)
|
||||
509, // League of Arathor (AB)
|
||||
};
|
||||
|
||||
constexpr uint32 HORDE_REPUTATION_FACTIONS[] = {
|
||||
76, // Orgrimmar
|
||||
81, // Thunder Bluff
|
||||
68, // Undercity
|
||||
530, // Darkspear Trolls
|
||||
911, // Silvermoon City
|
||||
889, // Warsong Outriders (WSG)
|
||||
729, // Frostwolf Clan (AV)
|
||||
510, // The Defilers (AB)
|
||||
};
|
||||
|
||||
constexpr uint32 FACTION_COUNT = 8;
|
||||
|
||||
// Default spawn points for each faction (race 2 = Orc for Horde, race 1 = Human for Alliance)
|
||||
constexpr uint8 HORDE_DEFAULT_RACE = 2; // Orc
|
||||
constexpr uint8 ALLIANCE_DEFAULT_RACE = 1; // Human
|
||||
|
||||
// Apply faction swap on login/faction update
|
||||
void ApplyFactionSwap(Player* player);
|
||||
|
||||
// Set new faction reputations to Neutral (standing 0)
|
||||
void InitTraitorReputations(Player* player);
|
||||
|
||||
// Teleport traitor to the opposite faction's starting zone
|
||||
void TeleportToNewFactionStart(Player* player);
|
||||
}
|
||||
|
||||
void AddSC_TraitorMode();
|
||||
|
||||
#endif // MOD_GAMEMODES_TRAITOR_H
|
||||
Reference in New Issue
Block a user