This commit is contained in:
2026-04-02 21:51:05 +04:00
parent b0d62064ba
commit 6516b32fc6
21 changed files with 636 additions and 19 deletions
@@ -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 */;
+1
View File
@@ -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;
+1
View File
@@ -0,0 +1 @@
+69
View File
@@ -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;
}
+37
View File
@@ -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
View File
@@ -82,6 +82,9 @@ FORCE_PLAYERBOTS="${FORCE_PLAYERBOTS:-${ACORE_PLAYERBOTS_FORCE:-0}}"
: "${ACORE_QUEST_PARTY_ENABLE:=1}"
: "${ACORE_QUEST_PARTY_MESSAGE:=0}"
: "${ACORE_GAMEMODES_ENABLE:=1}"
: "${ACORE_GAMEMODES_TRAITOR_ENABLE:=1}"
: "${ACORE_ELUNA_ENABLED:=true}"
: "${ACORE_ELUNA_TRACEBACK:=false}"
: "${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'
}
import_world_sql_file() {
import_sql_file() {
local file_path="$1"
local database="$2"
if [[ ! -f "$file_path" ]]; then
log "skip missing ${file_path#$ROOT_DIR/}"
@@ -275,13 +279,21 @@ import_world_sql_file() {
fi
if (( DRY_RUN )); then
log "would import ${file_path#$ROOT_DIR/} into acore_world"
log "would import ${file_path#$ROOT_DIR/} into ${database}"
return
fi
docker compose exec -T ac-database bash -lc \
"mysql --default-character-set=utf8mb4 -uroot -p\"\$MYSQL_ROOT_PASSWORD\" acore_world" < "$file_path"
log "imported ${file_path#$ROOT_DIR/} into acore_world"
"mysql --default-character-set=utf8mb4 -uroot -p\"\$MYSQL_ROOT_PASSWORD\" ${database}" < "$file_path"
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() {
@@ -300,6 +312,15 @@ import_local_lua_module_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() {
if [[ -f "$WORLD_CONF" ]]; then
return
@@ -339,6 +360,8 @@ sync_module_dists() {
"$MODULES_ETC_DIR/mod_learnspells.conf.dist"
sync_file "$ROOT_DIR/modules/mod-playerbots/conf/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" \
"$MODULES_ETC_DIR/mod-quest-loot-party.conf.dist"
}
@@ -482,6 +505,16 @@ playerbots_supported() {
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() {
if ! [[ -f "$ROOT_DIR/modules/mod-playerbots/conf/playerbots.conf.dist" ]]; then
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_learnspells.conf"
"$MODULES_ETC_DIR/mod-quest-loot-party.conf"
"$MODULES_ETC_DIR/mod_gamemodes.conf"
"$MODULES_ETC_DIR/playerbots.conf"
)
@@ -626,8 +660,10 @@ main() {
write_quest_loot_party_conf
write_eluna_conf
write_learnspells_conf
write_gamemodes_conf
write_playerbots_conf
import_local_lua_module_sql
import_gamemodes_sql
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_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, "
"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 "
"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, "
"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_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_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);
+8 -5
View File
@@ -486,7 +486,7 @@ void Player::CleanupsBeforeDelete(bool finalCleanup)
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
// should check that skin, face, hair* are valid via DBC per race/class
// 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,
// 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,
// 24 25
// characters.extra_flags, character_declinedname.genitive
// 24 25 26
// characters.extra_flags, mod_gamemodes_characters.game_mode, character_declinedname.genitive
Field* fields = result->Fetch();
@@ -1184,12 +1184,15 @@ bool Player::BuildEnumData(PreparedQueryResult result, WorldPacket* data)
charFlags |= CHARACTER_FLAG_LOCKED_BY_BILLING;
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;
}
else
charFlags |= CHARACTER_FLAG_DECLINED;
if (fields[25].Get<uint8>() == 1 /* GAMEMODE_TRAITOR */)
charFlags |= CHARACTER_FLAG_UNK31;
*data << uint32(charFlags); // character flags
// character customize flags
@@ -5972,7 +5975,7 @@ void Player::RewardReputation(Unit* victim)
ChampioningFaction = GetChampioningFaction();
}
TeamId teamId = GetTeamId(true); // Always check player original reputation when rewarding
TeamId teamId = GetTeamId();
if (Rep->RepFaction1 && (!Rep->TeamDependent || teamId == TEAM_ALLIANCE))
{
+4 -1
View File
@@ -486,7 +486,7 @@ enum PlayerFlags : uint32
PLAYER_FLAGS_UNK27 = 0x08000000,
PLAYER_FLAGS_UNK28 = 0x10000000,
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,
};
@@ -2130,6 +2130,8 @@ public:
void SetFactionForRace(uint8 race);
void setTeamId(TeamId teamid) { m_team = teamid; };
[[nodiscard]] uint8 GetOutfitId() const { return m_outfitId; }
void InitDisplayIds();
bool IsAtGroupRewardDistance(WorldObject const* pRewardSource) const;
@@ -2823,6 +2825,7 @@ protected:
ObjectGuid m_lootGuid;
TeamId m_team;
uint8 m_outfitId{0};
uint32 m_nextSave; // pussywizard
uint16 m_additionalSaveTimer; // pussywizard
uint8 m_additionalSaveMask; // pussywizard
@@ -1106,8 +1106,18 @@ bool Player::SatisfyQuestRace(Quest const* qInfo, bool msg) const
uint32 reqraces = qInfo->GetAllowableRaces();
if (reqraces == 0)
return true;
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)
SendCanTakeQuestResponse(INVALIDREASON_QUEST_FAILED_WRONG_RACE);
return false;
@@ -5543,6 +5543,7 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons
// must be before inventory (some items required reputation check)
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
_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
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_HORDE;
if (area && area->flags & areaRestFlag)
@@ -1305,12 +1305,12 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea, bool force)
{
case AREATEAM_ALLY:
pvpInfo.IsInHostileArea =
GetTeamId(true) != TEAM_ALLIANCE &&
GetTeamId() != TEAM_ALLIANCE &&
(sWorld->IsPvPRealm() || zone->flags & AREA_FLAG_CAPITAL);
break;
case AREATEAM_HORDE:
pvpInfo.IsInHostileArea =
GetTeamId(true) != TEAM_HORDE &&
GetTeamId() != TEAM_HORDE &&
(sWorld->IsPvPRealm() || zone->flags & AREA_FLAG_CAPITAL);
break;
case AREATEAM_NONE:
+24 -2
View File
@@ -29,6 +29,20 @@ const int32 ReputationMgr::PointsInRank[MAX_REPUTATION_RANK] = {36000, 3000, 300
const int32 ReputationMgr::Reputation_Cap = 42999;
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)
{
int32 limit = Reputation_Cap + 1;
@@ -93,7 +107,7 @@ int32 ReputationMgr::GetBaseReputation(FactionEntry const* factionEntry) const
if (!factionEntry)
return 0;
uint32 raceMask = _player->getRaceMask();
uint32 raceMask = GetEffectiveReputationRaceMask(_player);
uint32 classMask = _player->getClassMask();
for (int i = 0; i < 4; i++)
{
@@ -147,7 +161,7 @@ uint32 ReputationMgr::GetDefaultStateFlags(FactionEntry const* factionEntry) con
if (!factionEntry)
return 0;
uint32 raceMask = _player->getRaceMask();
uint32 raceMask = GetEffectiveReputationRaceMask(_player);
uint32 classMask = _player->getClassMask();
for (int i = 0; i < 4; i++)
{
@@ -464,10 +478,18 @@ void ReputationMgr::SetVisible(FactionTemplateEntry const* factionTemplateEntry)
return;
if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionTemplateEntry->faction))
{
if (_player->GetTeamId() != _player->GetTeamId(true))
{
SetVisible(factionEntry);
return;
}
// Never show factions of the opposing team
if (!(factionEntry->BaseRepRaceMask[1] & _player->getRaceMask() && factionEntry->BaseRepValue[1] == Reputation_Bottom))
SetVisible(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));
}
void ScriptMgr::OnPlayerReputationLoadFromDB(Player* player)
{
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_REPUTATION_LOAD_FROM_DB, script->OnPlayerReputationLoadFromDB(player));
}
void ScriptMgr::OnPlayerBeforeLogout(Player* player)
{
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_BEFORE_LOGOUT, script->OnPlayerBeforeLogout(player));
@@ -67,6 +67,7 @@ enum PlayerHook
PLAYERHOOK_ON_TEXT_EMOTE,
PLAYERHOOK_ON_SPELL_CAST,
PLAYERHOOK_ON_LOAD_FROM_DB,
PLAYERHOOK_ON_REPUTATION_LOAD_FROM_DB,
PLAYERHOOK_ON_LOGIN,
PLAYERHOOK_ON_BEFORE_LOGOUT,
PLAYERHOOK_ON_LOGOUT,
@@ -320,6 +321,7 @@ public:
// Called during data loading
virtual void OnPlayerLoadFromDB(Player* /*player*/) { };
virtual void OnPlayerReputationLoadFromDB(Player* /*player*/) { };
// Called when a player logs in.
virtual void OnPlayerLogin(Player* /*player*/) { }
+1
View File
@@ -346,6 +346,7 @@ public: /* PlayerScript */
void OnPlayerSpellCast(Player* player, Spell* spell, bool skipCheck);
void OnPlayerLogin(Player* player);
void OnPlayerLoadFromDB(Player* player);
void OnPlayerReputationLoadFromDB(Player* player);
void OnPlayerBeforeLogout(Player* player);
void OnPlayerLogout(Player* player);
void OnPlayerCreate(Player* player);