wip
This commit is contained in:
@@ -0,0 +1,45 @@
|
|||||||
|
-- MySQL dump 10.13 Distrib 8.4.3, for Win64 (x86_64)
|
||||||
|
--
|
||||||
|
-- Host: localhost Database: acore_characters
|
||||||
|
-- ------------------------------------------------------
|
||||||
|
-- Server version 8.4.3
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
/*!50503 SET NAMES utf8mb4 */;
|
||||||
|
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||||
|
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||||
|
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||||
|
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `mod_gamemodes_characters`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `mod_gamemodes_characters`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
|
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 COMMENT='Character Game Modes';
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `mod_gamemodes_characters`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `mod_gamemodes_characters` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `mod_gamemodes_characters` DISABLE KEYS */;
|
||||||
|
/*!40000 ALTER TABLE `mod_gamemodes_characters` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||||
|
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||||
|
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
|
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
@@ -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
|
||||||
+40
-4
@@ -82,6 +82,9 @@ FORCE_PLAYERBOTS="${FORCE_PLAYERBOTS:-${ACORE_PLAYERBOTS_FORCE:-0}}"
|
|||||||
: "${ACORE_QUEST_PARTY_ENABLE:=1}"
|
: "${ACORE_QUEST_PARTY_ENABLE:=1}"
|
||||||
: "${ACORE_QUEST_PARTY_MESSAGE:=0}"
|
: "${ACORE_QUEST_PARTY_MESSAGE:=0}"
|
||||||
|
|
||||||
|
: "${ACORE_GAMEMODES_ENABLE:=1}"
|
||||||
|
: "${ACORE_GAMEMODES_TRAITOR_ENABLE:=1}"
|
||||||
|
|
||||||
: "${ACORE_ELUNA_ENABLED:=true}"
|
: "${ACORE_ELUNA_ENABLED:=true}"
|
||||||
: "${ACORE_ELUNA_TRACEBACK:=false}"
|
: "${ACORE_ELUNA_TRACEBACK:=false}"
|
||||||
: "${ACORE_ELUNA_SCRIPT_PATH:=lua_scripts}"
|
: "${ACORE_ELUNA_SCRIPT_PATH:=lua_scripts}"
|
||||||
@@ -266,8 +269,9 @@ database_service_running() {
|
|||||||
docker compose ps --status running --services 2>/dev/null | grep -qx 'ac-database'
|
docker compose ps --status running --services 2>/dev/null | grep -qx 'ac-database'
|
||||||
}
|
}
|
||||||
|
|
||||||
import_world_sql_file() {
|
import_sql_file() {
|
||||||
local file_path="$1"
|
local file_path="$1"
|
||||||
|
local database="$2"
|
||||||
|
|
||||||
if [[ ! -f "$file_path" ]]; then
|
if [[ ! -f "$file_path" ]]; then
|
||||||
log "skip missing ${file_path#$ROOT_DIR/}"
|
log "skip missing ${file_path#$ROOT_DIR/}"
|
||||||
@@ -275,13 +279,21 @@ import_world_sql_file() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if (( DRY_RUN )); then
|
if (( DRY_RUN )); then
|
||||||
log "would import ${file_path#$ROOT_DIR/} into acore_world"
|
log "would import ${file_path#$ROOT_DIR/} into ${database}"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docker compose exec -T ac-database bash -lc \
|
docker compose exec -T ac-database bash -lc \
|
||||||
"mysql --default-character-set=utf8mb4 -uroot -p\"\$MYSQL_ROOT_PASSWORD\" acore_world" < "$file_path"
|
"mysql --default-character-set=utf8mb4 -uroot -p\"\$MYSQL_ROOT_PASSWORD\" ${database}" < "$file_path"
|
||||||
log "imported ${file_path#$ROOT_DIR/} into acore_world"
|
log "imported ${file_path#$ROOT_DIR/} into ${database}"
|
||||||
|
}
|
||||||
|
|
||||||
|
import_world_sql_file() {
|
||||||
|
import_sql_file "$1" "acore_world"
|
||||||
|
}
|
||||||
|
|
||||||
|
import_characters_sql_file() {
|
||||||
|
import_sql_file "$1" "acore_characters"
|
||||||
}
|
}
|
||||||
|
|
||||||
import_local_lua_module_sql() {
|
import_local_lua_module_sql() {
|
||||||
@@ -300,6 +312,15 @@ import_local_lua_module_sql() {
|
|||||||
import_world_sql_file "$start_zone_race_teleporters_sql"
|
import_world_sql_file "$start_zone_race_teleporters_sql"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import_gamemodes_sql() {
|
||||||
|
if ! database_service_running; then
|
||||||
|
log "skip gamemodes SQL: ac-database is not running"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
import_characters_sql_file "$ROOT_DIR/modules/mod-gamemodes/data/sql/db-characters/base/mod_gamemodes_characters.sql"
|
||||||
|
}
|
||||||
|
|
||||||
ensure_world_conf() {
|
ensure_world_conf() {
|
||||||
if [[ -f "$WORLD_CONF" ]]; then
|
if [[ -f "$WORLD_CONF" ]]; then
|
||||||
return
|
return
|
||||||
@@ -339,6 +360,8 @@ sync_module_dists() {
|
|||||||
"$MODULES_ETC_DIR/mod_learnspells.conf.dist"
|
"$MODULES_ETC_DIR/mod_learnspells.conf.dist"
|
||||||
sync_file "$ROOT_DIR/modules/mod-playerbots/conf/playerbots.conf.dist" \
|
sync_file "$ROOT_DIR/modules/mod-playerbots/conf/playerbots.conf.dist" \
|
||||||
"$MODULES_ETC_DIR/playerbots.conf.dist"
|
"$MODULES_ETC_DIR/playerbots.conf.dist"
|
||||||
|
sync_file "$ROOT_DIR/modules/mod-gamemodes/conf/mod_gamemodes.conf.dist" \
|
||||||
|
"$MODULES_ETC_DIR/mod_gamemodes.conf.dist"
|
||||||
sync_file "$ROOT_DIR/modules/mod-quest-loot-party/conf/mod-quest-loot-party.conf.dist" \
|
sync_file "$ROOT_DIR/modules/mod-quest-loot-party/conf/mod-quest-loot-party.conf.dist" \
|
||||||
"$MODULES_ETC_DIR/mod-quest-loot-party.conf.dist"
|
"$MODULES_ETC_DIR/mod-quest-loot-party.conf.dist"
|
||||||
}
|
}
|
||||||
@@ -482,6 +505,16 @@ playerbots_supported() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
write_gamemodes_conf() {
|
||||||
|
write_file "$MODULES_ETC_DIR/mod_gamemodes.conf.dist" <<EOF
|
||||||
|
[worldserver]
|
||||||
|
|
||||||
|
# Managed by ./setup-modules.sh
|
||||||
|
GameModes.Enable = ${ACORE_GAMEMODES_ENABLE}
|
||||||
|
GameModes.Traitor.Enable = ${ACORE_GAMEMODES_TRAITOR_ENABLE}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
write_playerbots_conf() {
|
write_playerbots_conf() {
|
||||||
if ! [[ -f "$ROOT_DIR/modules/mod-playerbots/conf/playerbots.conf.dist" ]]; then
|
if ! [[ -f "$ROOT_DIR/modules/mod-playerbots/conf/playerbots.conf.dist" ]]; then
|
||||||
log "skip playerbots: module config source not found"
|
log "skip playerbots: module config source not found"
|
||||||
@@ -570,6 +603,7 @@ cleanup_legacy_module_overrides() {
|
|||||||
"$MODULES_ETC_DIR/mod_eluna.conf"
|
"$MODULES_ETC_DIR/mod_eluna.conf"
|
||||||
"$MODULES_ETC_DIR/mod_learnspells.conf"
|
"$MODULES_ETC_DIR/mod_learnspells.conf"
|
||||||
"$MODULES_ETC_DIR/mod-quest-loot-party.conf"
|
"$MODULES_ETC_DIR/mod-quest-loot-party.conf"
|
||||||
|
"$MODULES_ETC_DIR/mod_gamemodes.conf"
|
||||||
"$MODULES_ETC_DIR/playerbots.conf"
|
"$MODULES_ETC_DIR/playerbots.conf"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -626,8 +660,10 @@ main() {
|
|||||||
write_quest_loot_party_conf
|
write_quest_loot_party_conf
|
||||||
write_eluna_conf
|
write_eluna_conf
|
||||||
write_learnspells_conf
|
write_learnspells_conf
|
||||||
|
write_gamemodes_conf
|
||||||
write_playerbots_conf
|
write_playerbots_conf
|
||||||
import_local_lua_module_sql
|
import_local_lua_module_sql
|
||||||
|
import_gamemodes_sql
|
||||||
|
|
||||||
log "module bootstrap completed"
|
log "module bootstrap completed"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,14 +41,18 @@ void CharacterDatabaseConnection::DoPrepareStatements()
|
|||||||
PrepareStatement(CHAR_SEL_BANINFO_LIST, "SELECT bandate, unbandate, bannedby, banreason FROM character_banned WHERE guid = ? ORDER BY unbandate", CONNECTION_SYNCH);
|
PrepareStatement(CHAR_SEL_BANINFO_LIST, "SELECT bandate, unbandate, bannedby, banreason FROM character_banned WHERE guid = ? ORDER BY unbandate", CONNECTION_SYNCH);
|
||||||
PrepareStatement(CHAR_SEL_BANNED_NAME, "SELECT characters.name FROM characters, character_banned WHERE character_banned.guid = ? AND character_banned.guid = characters.guid", CONNECTION_SYNCH);
|
PrepareStatement(CHAR_SEL_BANNED_NAME, "SELECT characters.name FROM characters, character_banned WHERE character_banned.guid = ? AND character_banned.guid = characters.guid", CONNECTION_SYNCH);
|
||||||
PrepareStatement(CHAR_SEL_ENUM, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, c.position_x, c.position_y, c.position_z, "
|
PrepareStatement(CHAR_SEL_ENUM, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, c.position_x, c.position_y, c.position_z, "
|
||||||
"gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, cb.guid, c.extra_flags "
|
"gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, cb.guid, c.extra_flags, COALESCE(mgc.game_mode, 0) "
|
||||||
"FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? LEFT JOIN guild_member AS gm ON c.guid = gm.guid "
|
"FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? LEFT JOIN guild_member AS gm ON c.guid = gm.guid "
|
||||||
"LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.account = ? AND c.deleteInfos_Name IS NULL ORDER BY COALESCE(c.order, c.guid)", CONNECTION_ASYNC);
|
"LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 "
|
||||||
|
"LEFT JOIN mod_gamemodes_characters AS mgc ON c.guid = mgc.guid "
|
||||||
|
"WHERE c.account = ? AND c.deleteInfos_Name IS NULL ORDER BY COALESCE(c.order, c.guid)", CONNECTION_ASYNC);
|
||||||
PrepareStatement(CHAR_SEL_ENUM_DECLINED_NAME, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, "
|
PrepareStatement(CHAR_SEL_ENUM_DECLINED_NAME, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, "
|
||||||
"c.position_x, c.position_y, c.position_z, gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, "
|
"c.position_x, c.position_y, c.position_z, gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, "
|
||||||
"cb.guid, c.extra_flags, cd.genitive FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? "
|
"cb.guid, c.extra_flags, COALESCE(mgc.game_mode, 0), cd.genitive FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? "
|
||||||
"LEFT JOIN character_declinedname AS cd ON c.guid = cd.guid LEFT JOIN guild_member AS gm ON c.guid = gm.guid "
|
"LEFT JOIN character_declinedname AS cd ON c.guid = cd.guid LEFT JOIN guild_member AS gm ON c.guid = gm.guid "
|
||||||
"LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.account = ? AND c.deleteInfos_Name IS NULL ORDER BY COALESCE(c.order, c.guid)", CONNECTION_ASYNC);
|
"LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 "
|
||||||
|
"LEFT JOIN mod_gamemodes_characters AS mgc ON c.guid = mgc.guid "
|
||||||
|
"WHERE c.account = ? AND c.deleteInfos_Name IS NULL ORDER BY COALESCE(c.order, c.guid)", CONNECTION_ASYNC);
|
||||||
PrepareStatement(CHAR_SEL_FREE_NAME, "SELECT guid, name, at_login FROM characters WHERE guid = ? AND account = ? AND NOT EXISTS (SELECT NULL FROM characters WHERE name = ?)", CONNECTION_ASYNC);
|
PrepareStatement(CHAR_SEL_FREE_NAME, "SELECT guid, name, at_login FROM characters WHERE guid = ? AND account = ? AND NOT EXISTS (SELECT NULL FROM characters WHERE name = ?)", CONNECTION_ASYNC);
|
||||||
PrepareStatement(CHAR_SEL_CHAR_ZONE, "SELECT zone FROM characters WHERE guid = ?", CONNECTION_SYNCH);
|
PrepareStatement(CHAR_SEL_CHAR_ZONE, "SELECT zone FROM characters WHERE guid = ?", CONNECTION_SYNCH);
|
||||||
PrepareStatement(CHAR_SEL_CHARACTER_NAME_DATA, "SELECT race, class, gender, level FROM characters WHERE guid = ?", CONNECTION_SYNCH);
|
PrepareStatement(CHAR_SEL_CHARACTER_NAME_DATA, "SELECT race, class, gender, level FROM characters WHERE guid = ?", CONNECTION_SYNCH);
|
||||||
|
|||||||
@@ -486,7 +486,7 @@ void Player::CleanupsBeforeDelete(bool finalCleanup)
|
|||||||
|
|
||||||
bool Player::Create(ObjectGuid::LowType guidlow, CharacterCreateInfo* createInfo)
|
bool Player::Create(ObjectGuid::LowType guidlow, CharacterCreateInfo* createInfo)
|
||||||
{
|
{
|
||||||
// FIXME: outfitId not used in player creating
|
m_outfitId = createInfo->OutfitId;
|
||||||
/// @todo: need more checks against packet modifications
|
/// @todo: need more checks against packet modifications
|
||||||
// should check that skin, face, hair* are valid via DBC per race/class
|
// should check that skin, face, hair* are valid via DBC per race/class
|
||||||
// also do it in Player::BuildEnumData, Player::LoadFromDB
|
// also do it in Player::BuildEnumData, Player::LoadFromDB
|
||||||
@@ -1111,8 +1111,8 @@ bool Player::BuildEnumData(PreparedQueryResult result, WorldPacket* data)
|
|||||||
// characters.hairColor, characters.facialStyle, character.level, characters.zone, characters.map, characters.position_x, characters.position_y, characters.position_z,
|
// characters.hairColor, characters.facialStyle, character.level, characters.zone, characters.map, characters.position_x, characters.position_y, characters.position_z,
|
||||||
// 16 17 18 19 20 21 22 23
|
// 16 17 18 19 20 21 22 23
|
||||||
// guild_member.guildid, characters.playerFlags, characters.at_login, character_pet.entry, character_pet.modelid, character_pet.level, characters.equipmentCache, character_banned.guid,
|
// guild_member.guildid, characters.playerFlags, characters.at_login, character_pet.entry, character_pet.modelid, character_pet.level, characters.equipmentCache, character_banned.guid,
|
||||||
// 24 25
|
// 24 25 26
|
||||||
// characters.extra_flags, character_declinedname.genitive
|
// characters.extra_flags, mod_gamemodes_characters.game_mode, character_declinedname.genitive
|
||||||
|
|
||||||
Field* fields = result->Fetch();
|
Field* fields = result->Fetch();
|
||||||
|
|
||||||
@@ -1184,12 +1184,15 @@ bool Player::BuildEnumData(PreparedQueryResult result, WorldPacket* data)
|
|||||||
charFlags |= CHARACTER_FLAG_LOCKED_BY_BILLING;
|
charFlags |= CHARACTER_FLAG_LOCKED_BY_BILLING;
|
||||||
if (sWorld->getBoolConfig(CONFIG_DECLINED_NAMES_USED))
|
if (sWorld->getBoolConfig(CONFIG_DECLINED_NAMES_USED))
|
||||||
{
|
{
|
||||||
if (!fields[25].Get<std::string>().empty())
|
if (!fields[26].Get<std::string>().empty())
|
||||||
charFlags |= CHARACTER_FLAG_DECLINED;
|
charFlags |= CHARACTER_FLAG_DECLINED;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
charFlags |= CHARACTER_FLAG_DECLINED;
|
charFlags |= CHARACTER_FLAG_DECLINED;
|
||||||
|
|
||||||
|
if (fields[25].Get<uint8>() == 1 /* GAMEMODE_TRAITOR */)
|
||||||
|
charFlags |= CHARACTER_FLAG_UNK31;
|
||||||
|
|
||||||
*data << uint32(charFlags); // character flags
|
*data << uint32(charFlags); // character flags
|
||||||
|
|
||||||
// character customize flags
|
// character customize flags
|
||||||
@@ -5972,7 +5975,7 @@ void Player::RewardReputation(Unit* victim)
|
|||||||
ChampioningFaction = GetChampioningFaction();
|
ChampioningFaction = GetChampioningFaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamId teamId = GetTeamId(true); // Always check player original reputation when rewarding
|
TeamId teamId = GetTeamId();
|
||||||
|
|
||||||
if (Rep->RepFaction1 && (!Rep->TeamDependent || teamId == TEAM_ALLIANCE))
|
if (Rep->RepFaction1 && (!Rep->TeamDependent || teamId == TEAM_ALLIANCE))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -486,7 +486,7 @@ enum PlayerFlags : uint32
|
|||||||
PLAYER_FLAGS_UNK27 = 0x08000000,
|
PLAYER_FLAGS_UNK27 = 0x08000000,
|
||||||
PLAYER_FLAGS_UNK28 = 0x10000000,
|
PLAYER_FLAGS_UNK28 = 0x10000000,
|
||||||
PLAYER_FLAGS_UNK29 = 0x20000000,
|
PLAYER_FLAGS_UNK29 = 0x20000000,
|
||||||
PLAYER_FLAGS_UNK30 = 0x40000000,
|
PLAYER_FLAGS_TRAITOR = 0x40000000, // mod-gamemodes: player is playing for the opposite faction
|
||||||
PLAYER_FLAGS_UNK31 = 0x80000000,
|
PLAYER_FLAGS_UNK31 = 0x80000000,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2130,6 +2130,8 @@ public:
|
|||||||
void SetFactionForRace(uint8 race);
|
void SetFactionForRace(uint8 race);
|
||||||
void setTeamId(TeamId teamid) { m_team = teamid; };
|
void setTeamId(TeamId teamid) { m_team = teamid; };
|
||||||
|
|
||||||
|
[[nodiscard]] uint8 GetOutfitId() const { return m_outfitId; }
|
||||||
|
|
||||||
void InitDisplayIds();
|
void InitDisplayIds();
|
||||||
|
|
||||||
bool IsAtGroupRewardDistance(WorldObject const* pRewardSource) const;
|
bool IsAtGroupRewardDistance(WorldObject const* pRewardSource) const;
|
||||||
@@ -2823,6 +2825,7 @@ protected:
|
|||||||
ObjectGuid m_lootGuid;
|
ObjectGuid m_lootGuid;
|
||||||
|
|
||||||
TeamId m_team;
|
TeamId m_team;
|
||||||
|
uint8 m_outfitId{0};
|
||||||
uint32 m_nextSave; // pussywizard
|
uint32 m_nextSave; // pussywizard
|
||||||
uint16 m_additionalSaveTimer; // pussywizard
|
uint16 m_additionalSaveTimer; // pussywizard
|
||||||
uint8 m_additionalSaveMask; // pussywizard
|
uint8 m_additionalSaveMask; // pussywizard
|
||||||
|
|||||||
@@ -1106,8 +1106,18 @@ bool Player::SatisfyQuestRace(Quest const* qInfo, bool msg) const
|
|||||||
uint32 reqraces = qInfo->GetAllowableRaces();
|
uint32 reqraces = qInfo->GetAllowableRaces();
|
||||||
if (reqraces == 0)
|
if (reqraces == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if ((reqraces & getRaceMask()) == 0)
|
if ((reqraces & getRaceMask()) == 0)
|
||||||
{
|
{
|
||||||
|
// Allow faction-swapped characters to take quests restricted to their active faction,
|
||||||
|
// while still respecting truly race-specific quests on the opposite side.
|
||||||
|
if (GetTeamId() != GetTeamId(true))
|
||||||
|
{
|
||||||
|
uint32 currentFactionRaceMask = (GetTeamId() == TEAM_ALLIANCE) ? RACEMASK_ALLIANCE : RACEMASK_HORDE;
|
||||||
|
if ((reqraces & currentFactionRaceMask) == reqraces)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (msg)
|
if (msg)
|
||||||
SendCanTakeQuestResponse(INVALIDREASON_QUEST_FAILED_WRONG_RACE);
|
SendCanTakeQuestResponse(INVALIDREASON_QUEST_FAILED_WRONG_RACE);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -5543,6 +5543,7 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons
|
|||||||
|
|
||||||
// must be before inventory (some items required reputation check)
|
// must be before inventory (some items required reputation check)
|
||||||
m_reputationMgr->LoadFromDB(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_REPUTATION));
|
m_reputationMgr->LoadFromDB(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_REPUTATION));
|
||||||
|
sScriptMgr->OnPlayerReputationLoadFromDB(this);
|
||||||
|
|
||||||
// xinef: load mails before inventory, so problematic items can be added to already loaded mails
|
// xinef: load mails before inventory, so problematic items can be added to already loaded mails
|
||||||
_LoadMail(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAILS), holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS));
|
_LoadMail(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAILS), holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS));
|
||||||
|
|||||||
@@ -1244,7 +1244,7 @@ void Player::UpdateArea(uint32 newArea)
|
|||||||
else
|
else
|
||||||
RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY);
|
RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY);
|
||||||
|
|
||||||
uint32 const areaRestFlag = (GetTeamId(true) == TEAM_ALLIANCE)
|
uint32 const areaRestFlag = (GetTeamId() == TEAM_ALLIANCE)
|
||||||
? AREA_FLAG_REST_ZONE_ALLIANCE
|
? AREA_FLAG_REST_ZONE_ALLIANCE
|
||||||
: AREA_FLAG_REST_ZONE_HORDE;
|
: AREA_FLAG_REST_ZONE_HORDE;
|
||||||
if (area && area->flags & areaRestFlag)
|
if (area && area->flags & areaRestFlag)
|
||||||
@@ -1305,12 +1305,12 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea, bool force)
|
|||||||
{
|
{
|
||||||
case AREATEAM_ALLY:
|
case AREATEAM_ALLY:
|
||||||
pvpInfo.IsInHostileArea =
|
pvpInfo.IsInHostileArea =
|
||||||
GetTeamId(true) != TEAM_ALLIANCE &&
|
GetTeamId() != TEAM_ALLIANCE &&
|
||||||
(sWorld->IsPvPRealm() || zone->flags & AREA_FLAG_CAPITAL);
|
(sWorld->IsPvPRealm() || zone->flags & AREA_FLAG_CAPITAL);
|
||||||
break;
|
break;
|
||||||
case AREATEAM_HORDE:
|
case AREATEAM_HORDE:
|
||||||
pvpInfo.IsInHostileArea =
|
pvpInfo.IsInHostileArea =
|
||||||
GetTeamId(true) != TEAM_HORDE &&
|
GetTeamId() != TEAM_HORDE &&
|
||||||
(sWorld->IsPvPRealm() || zone->flags & AREA_FLAG_CAPITAL);
|
(sWorld->IsPvPRealm() || zone->flags & AREA_FLAG_CAPITAL);
|
||||||
break;
|
break;
|
||||||
case AREATEAM_NONE:
|
case AREATEAM_NONE:
|
||||||
|
|||||||
@@ -29,6 +29,20 @@ const int32 ReputationMgr::PointsInRank[MAX_REPUTATION_RANK] = {36000, 3000, 300
|
|||||||
const int32 ReputationMgr::Reputation_Cap = 42999;
|
const int32 ReputationMgr::Reputation_Cap = 42999;
|
||||||
const int32 ReputationMgr::Reputation_Bottom = -42000;
|
const int32 ReputationMgr::Reputation_Bottom = -42000;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
uint32 GetEffectiveReputationRaceMask(Player const* player)
|
||||||
|
{
|
||||||
|
// Always use the player's actual race mask so the server BaseRep calculation
|
||||||
|
// matches what the client computes (client always uses the original race, not
|
||||||
|
// the current team). This is correct for both the race-change service (where
|
||||||
|
// getRace() already reflects the new race) and for game modes like Traitor
|
||||||
|
// that swap team without changing race (where the original race mask is the
|
||||||
|
// right baseline for reputation display and incremental gain alike).
|
||||||
|
return player->getRaceMask();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ReputationRank ReputationMgr::ReputationToRank(int32 standing)
|
ReputationRank ReputationMgr::ReputationToRank(int32 standing)
|
||||||
{
|
{
|
||||||
int32 limit = Reputation_Cap + 1;
|
int32 limit = Reputation_Cap + 1;
|
||||||
@@ -93,7 +107,7 @@ int32 ReputationMgr::GetBaseReputation(FactionEntry const* factionEntry) const
|
|||||||
if (!factionEntry)
|
if (!factionEntry)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
uint32 raceMask = _player->getRaceMask();
|
uint32 raceMask = GetEffectiveReputationRaceMask(_player);
|
||||||
uint32 classMask = _player->getClassMask();
|
uint32 classMask = _player->getClassMask();
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
@@ -147,7 +161,7 @@ uint32 ReputationMgr::GetDefaultStateFlags(FactionEntry const* factionEntry) con
|
|||||||
if (!factionEntry)
|
if (!factionEntry)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
uint32 raceMask = _player->getRaceMask();
|
uint32 raceMask = GetEffectiveReputationRaceMask(_player);
|
||||||
uint32 classMask = _player->getClassMask();
|
uint32 classMask = _player->getClassMask();
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
@@ -464,9 +478,17 @@ void ReputationMgr::SetVisible(FactionTemplateEntry const* factionTemplateEntry)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionTemplateEntry->faction))
|
if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionTemplateEntry->faction))
|
||||||
|
{
|
||||||
|
if (_player->GetTeamId() != _player->GetTeamId(true))
|
||||||
|
{
|
||||||
|
SetVisible(factionEntry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Never show factions of the opposing team
|
// Never show factions of the opposing team
|
||||||
if (!(factionEntry->BaseRepRaceMask[1] & _player->getRaceMask() && factionEntry->BaseRepValue[1] == Reputation_Bottom))
|
if (!(factionEntry->BaseRepRaceMask[1] & _player->getRaceMask() && factionEntry->BaseRepValue[1] == Reputation_Bottom))
|
||||||
SetVisible(factionEntry);
|
SetVisible(factionEntry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReputationMgr::SetVisible(FactionEntry const* factionEntry)
|
void ReputationMgr::SetVisible(FactionEntry const* factionEntry)
|
||||||
|
|||||||
@@ -215,6 +215,11 @@ void ScriptMgr::OnPlayerLoadFromDB(Player* player)
|
|||||||
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_LOAD_FROM_DB, script->OnPlayerLoadFromDB(player));
|
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_LOAD_FROM_DB, script->OnPlayerLoadFromDB(player));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptMgr::OnPlayerReputationLoadFromDB(Player* player)
|
||||||
|
{
|
||||||
|
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_REPUTATION_LOAD_FROM_DB, script->OnPlayerReputationLoadFromDB(player));
|
||||||
|
}
|
||||||
|
|
||||||
void ScriptMgr::OnPlayerBeforeLogout(Player* player)
|
void ScriptMgr::OnPlayerBeforeLogout(Player* player)
|
||||||
{
|
{
|
||||||
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_BEFORE_LOGOUT, script->OnPlayerBeforeLogout(player));
|
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_BEFORE_LOGOUT, script->OnPlayerBeforeLogout(player));
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ enum PlayerHook
|
|||||||
PLAYERHOOK_ON_TEXT_EMOTE,
|
PLAYERHOOK_ON_TEXT_EMOTE,
|
||||||
PLAYERHOOK_ON_SPELL_CAST,
|
PLAYERHOOK_ON_SPELL_CAST,
|
||||||
PLAYERHOOK_ON_LOAD_FROM_DB,
|
PLAYERHOOK_ON_LOAD_FROM_DB,
|
||||||
|
PLAYERHOOK_ON_REPUTATION_LOAD_FROM_DB,
|
||||||
PLAYERHOOK_ON_LOGIN,
|
PLAYERHOOK_ON_LOGIN,
|
||||||
PLAYERHOOK_ON_BEFORE_LOGOUT,
|
PLAYERHOOK_ON_BEFORE_LOGOUT,
|
||||||
PLAYERHOOK_ON_LOGOUT,
|
PLAYERHOOK_ON_LOGOUT,
|
||||||
@@ -320,6 +321,7 @@ public:
|
|||||||
|
|
||||||
// Called during data loading
|
// Called during data loading
|
||||||
virtual void OnPlayerLoadFromDB(Player* /*player*/) { };
|
virtual void OnPlayerLoadFromDB(Player* /*player*/) { };
|
||||||
|
virtual void OnPlayerReputationLoadFromDB(Player* /*player*/) { };
|
||||||
|
|
||||||
// Called when a player logs in.
|
// Called when a player logs in.
|
||||||
virtual void OnPlayerLogin(Player* /*player*/) { }
|
virtual void OnPlayerLogin(Player* /*player*/) { }
|
||||||
|
|||||||
@@ -346,6 +346,7 @@ public: /* PlayerScript */
|
|||||||
void OnPlayerSpellCast(Player* player, Spell* spell, bool skipCheck);
|
void OnPlayerSpellCast(Player* player, Spell* spell, bool skipCheck);
|
||||||
void OnPlayerLogin(Player* player);
|
void OnPlayerLogin(Player* player);
|
||||||
void OnPlayerLoadFromDB(Player* player);
|
void OnPlayerLoadFromDB(Player* player);
|
||||||
|
void OnPlayerReputationLoadFromDB(Player* player);
|
||||||
void OnPlayerBeforeLogout(Player* player);
|
void OnPlayerBeforeLogout(Player* player);
|
||||||
void OnPlayerLogout(Player* player);
|
void OnPlayerLogout(Player* player);
|
||||||
void OnPlayerCreate(Player* player);
|
void OnPlayerCreate(Player* player);
|
||||||
|
|||||||
Reference in New Issue
Block a user