diff --git a/data/sql/db-characters/replayarena.sql b/data/sql/db-characters/replayarena.sql index 43a2931..978bf6f 100644 --- a/data/sql/db-characters/replayarena.sql +++ b/data/sql/db-characters/replayarena.sql @@ -8,11 +8,9 @@ CREATE TABLE IF NOT EXISTS `character_arena_replays` ( `winnerTeamName` VARCHAR(255) NULL DEFAULT NULL, `winnerTeamRating` INT NULL DEFAULT NULL, `winnerTeamMMR` INT NULL DEFAULT NULL, - `winnerClassIds` VARCHAR(255) NULL DEFAULT NULL, `loserTeamName` VARCHAR(255) NULL DEFAULT NULL, `loserTeamRating` INT NULL DEFAULT NULL, `loserTeamMMR` INT NULL DEFAULT NULL, - `loserClassIds` VARCHAR(255) NULL DEFAULT NULL, `winnerPlayerGuids` VARCHAR(255) NULL DEFAULT NULL, `loserPlayerGuids` VARCHAR(255) NULL DEFAULT NULL, `timesWatched` INT NOT NULL DEFAULT 0, diff --git a/src/ArenaReplay.cpp b/src/ArenaReplay.cpp index ab34872..25c2a3b 100644 --- a/src/ArenaReplay.cpp +++ b/src/ArenaReplay.cpp @@ -91,9 +91,11 @@ CMSG_ATTACKSTOP*/ struct PacketRecord { uint32 timestamp; WorldPacket packet; }; struct MatchRecord { BattlegroundTypeId typeId; uint8 arenaTypeId; uint32 mapId; std::deque packets; }; +struct BgPlayersGuids { std::string alliancePlayerGuids; std::string hordePlayerGuids; }; std::unordered_map records; std::unordered_map loadedReplays; std::unordered_map bgReplayIds; +std::unordered_map bgPlayersGuids; class ArenaReplayServerScript : public ServerScript { @@ -173,8 +175,6 @@ class ArenaReplayArenaScript : public ArenaScript { std::string teamLoserName; std::string winnerGuids; std::string loserGuids; - std::string winnerClassIds; - std::string loserClassIds; for (const auto& playerPair : bg->GetPlayers()) { @@ -185,7 +185,6 @@ class ArenaReplayArenaScript : public ArenaScript { if (!player) continue; - std::string playerClassId = std::to_string(player->getClass()); std::string playerGuid = std::to_string(player->GetGUID().GetRawValue()); TeamId bgTeamId = player->GetBgTeamId(); uint32 bgInstanceId = bg->GetInstanceID(); @@ -214,13 +213,10 @@ class ArenaReplayBGScript : public BGScript return; if (!bg->isArena() && !sConfigMgr->GetOption("ArenaReplay.SaveBattlegrounds", true)) - { return; - } + if (!bg->isRated() && !sConfigMgr->GetOption("ArenaReplay.SaveUnratedArenas", true)) - { return; - } uint32 replayId = bgReplayIds.at(bg->GetInstanceID()); @@ -238,6 +234,7 @@ class ArenaReplayBGScript : public BGScript auto it = loadedReplays.find(replayId); if (it == loadedReplays.end()) return; + MatchRecord& match = it->second; // if replay ends or spectator left > free arena replay data and/or kick player @@ -247,6 +244,7 @@ class ArenaReplayBGScript : public BGScript if (!bg->GetPlayers().empty()) bg->GetPlayers().begin()->second->LeaveBattleground(bg); + return; } @@ -263,17 +261,52 @@ class ArenaReplayBGScript : public BGScript } } - void OnBattlegroundEnd(Battleground *bg, TeamId winnerTeamId ) override { + void OnBattlegroundAddPlayer(Battleground* bg, Player* player) override + { + if (!player) + return; + + if (player->IsSpectator()) + return; if (!bg->isArena() && !sConfigMgr->GetOption("ArenaReplay.SaveBattlegrounds", true)) - { return; - } if (!bg->isRated() && !sConfigMgr->GetOption("ArenaReplay.SaveUnratedArenas", true)) - { return; + + if (bgPlayersGuids.find(bg->GetInstanceID()) == bgPlayersGuids.end()) + { + BgPlayersGuids playerguids; + bgPlayersGuids[bg->GetInstanceID()] = playerguids; + } + + std::string playerGuid = std::to_string(player->GetGUID().GetRawValue()); + TeamId bgTeamId = player->GetBgTeamId(); + + if (bgTeamId == TEAM_ALLIANCE) + { + if (!bgPlayersGuids[bg->GetInstanceID()].alliancePlayerGuids.empty()) + bgPlayersGuids[bg->GetInstanceID()].alliancePlayerGuids += ", "; + + bgPlayersGuids[bg->GetInstanceID()].alliancePlayerGuids += playerGuid; + } + else + { + if (!bgPlayersGuids[bg->GetInstanceID()].hordePlayerGuids.empty()) + bgPlayersGuids[bg->GetInstanceID()].hordePlayerGuids += ", "; + + bgPlayersGuids[bg->GetInstanceID()].hordePlayerGuids += playerGuid; } + } + + void OnBattlegroundEnd(Battleground *bg, TeamId winnerTeamId ) override { + + if (!bg->isArena() && !sConfigMgr->GetOption("ArenaReplay.SaveBattlegrounds", true)) + return; + + if (!bg->isRated() && !sConfigMgr->GetOption("ArenaReplay.SaveUnratedArenas", true)) + return; const bool isReplay = bgReplayIds.find(bg->GetInstanceID()) != bgReplayIds.end(); @@ -289,13 +322,16 @@ class ArenaReplayBGScript : public BGScript } bgReplayIds.erase(bg->GetInstanceID()); + bgPlayersGuids.erase(bg->GetInstanceID()); } void saveReplay(Battleground* bg, TeamId winnerTeamId) { - //retrieve replay data + // retrieve replay data auto it = records.find(bg->GetInstanceID()); - if (it == records.end()) return; + if (it == records.end()) + return; + MatchRecord& match = it->second; /** serialize arena replay data **/ @@ -307,8 +343,8 @@ class ArenaReplayBGScript : public BGScript headerSize = it.packet.size(); //header 4Bytes packet size timestamp = it.timestamp; - buffer << headerSize; //4 bytes - buffer << timestamp; //4 bytes + buffer << headerSize; // 4 bytes + buffer << timestamp; // 4 bytes buffer << it.packet.GetOpcode(); // 2 bytes if (headerSize > 0) buffer.append(it.packet.contents(), it.packet.size()); // headerSize bytes @@ -322,16 +358,23 @@ class ArenaReplayBGScript : public BGScript std::string teamLoserName; std::string winnerGuids; std::string loserGuids; - std::string winnerClassIds; - std::string loserClassIds; + + if (winnerTeamId == TEAM_ALLIANCE) { + winnerGuids = bgPlayersGuids[bg->GetInstanceID()].alliancePlayerGuids; + loserGuids = bgPlayersGuids[bg->GetInstanceID()].hordePlayerGuids; + } + else + { + loserGuids = bgPlayersGuids[bg->GetInstanceID()].alliancePlayerGuids; + winnerGuids = bgPlayersGuids[bg->GetInstanceID()].hordePlayerGuids; + } for (const auto& playerPair : bg->GetPlayers()) { Player* player = playerPair.second; - if (!player) + if (!player || player->IsSpectator()) continue; - std::string playerClassId = std::to_string(player->getClass()); std::string playerGuid = std::to_string(player->GetGUID().GetRawValue()); TeamId bgTeamId = player->GetBgTeamId(); ArenaTeam* team = sArenaTeamMgr->GetArenaTeamById(bg->GetArenaTeamIdForTeam(bgTeamId)); @@ -341,73 +384,13 @@ class ArenaReplayBGScript : public BGScript if (bgTeamId == winnerTeamId) { - if (!winnerClassIds.empty()) - winnerClassIds += ", "; - winnerClassIds += playerClassId; - - if (!winnerGuids.empty()) - winnerGuids += ", "; - winnerGuids += playerGuid; - - if (bg->isRated() && team) - { - if (team->GetId() < 0xFFF00000) - { - teamWinnerName = team->GetName(); - teamWinnerRating = team->GetRating(); - teamWinnerMMR = teamMMR; - } - // 3v3 Solo Queue match (temporary team that merge players in 1 team) - else if (team->GetId() >= 0xFFF00000) - { - teamWinnerName = "3v3 Solo Queue"; - teamWinnerRating = team->GetRating(); - teamWinnerMMR = teamMMR; - } - } - if (bg->isArena() && !bg->isRated()) - { - teamWinnerName = "Skirmish Arena"; - } - else if (!bg->isArena()) - { - teamWinnerName = "Battleground"; - } + getTeamInformation(bg, team, teamWinnerName, teamWinnerRating); + teamWinnerMMR = teamMMR; } else // Loss { - if (!loserClassIds.empty()) - loserClassIds += ", "; - loserClassIds += playerClassId; - - if (!loserGuids.empty()) - loserGuids += ", "; - loserGuids += playerGuid; - - if (bg->isRated() && team) - { - if (team->GetId() < 0xFFF00000) - { - teamLoserName = team->GetName(); - teamLoserRating = team->GetRating(); - teamLoserMMR = teamMMR; - } - // 3v3 Solo Queue match - else if (team->GetId() >= 0xFFF00000) - { - teamLoserName = "3v3 Solo Queue"; - teamLoserRating = team->GetRating(); - teamLoserMMR = teamMMR; - } - } - if (bg->isArena() && !bg->isRated()) - { - teamLoserName = "Skirmish Arena"; - } - else if (!bg->isArena()) - { - teamLoserName = "Battleground"; - } + getTeamInformation(bg, team, teamLoserName, teamLoserRating); + teamLoserMMR = teamMMR; } // Send replay ID to player after a game end @@ -423,6 +406,17 @@ class ArenaReplayBGScript : public BGScript ChatHandler(player->GetSession()).PSendSysMessage("Replay saved. Match ID: {}", replayfightid + 1); } + const uint8 ARENA_TYPE_3V3_SOLO_QUEUE = sConfigMgr->GetOption("ArenaReplay.3v3soloQ.ArenaType", 4); + if (bg->isArena() && (!bg->isRated() || bg->GetArenaType() == ARENA_TYPE_3V3_SOLO_QUEUE)) { + teamWinnerName = GetTeamName(winnerGuids); + teamLoserName = GetTeamName(loserGuids); + } + else if (!bg->isArena()) + { + teamWinnerName = "Battleground"; + teamLoserName = "Battleground"; + } + // // if loser has a negative value. the uint variable could return this (wrong) value // if (teamLoserMMR >= 4294967286) // teamLoserMMR=0; @@ -435,13 +429,13 @@ class ArenaReplayBGScript : public BGScript teamWinnerMMR=0; CharacterDatabase.Execute("INSERT INTO `character_arena_replays` " - // 1 2 3 4 5 6 7 8 9 - "(`arenaTypeId`, `typeId`, `contentSize`, `contents`, `mapId`, `winnerTeamName`, `winnerTeamRating`, `winnerTeamMMR`, `winnerClassIds`, " - // 10 11 12 13 14 15 - "`loserTeamName`, `loserTeamRating`, `loserTeamMMR`, `loserClassIds`, `winnerPlayerGuids`, `loserPlayerGuids`) " + // 1 2 3 4 5 6 7 8 + "(`arenaTypeId`, `typeId`, `contentSize`, `contents`, `mapId`, `winnerTeamName`, `winnerTeamRating`, `winnerTeamMMR`, " + // 9 10 11 12 13 + "`loserTeamName`, `loserTeamRating`, `loserTeamMMR`, `winnerPlayerGuids`, `loserPlayerGuids`) " - "VALUES ({}, {}, {}, \"{}\", {}, '{}', {}, {}, \"{}\", '{}', {}, {}, \"{}\", \"{}\", \"{}\")", - // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + "VALUES ({}, {}, {}, \"{}\", {}, '{}', {}, {}, '{}', {}, {}, \"{}\", \"{}\")", + // 1 2 3 4 5 6 7 8 9 10 11 12 13 uint32(match.arenaTypeId), // 1 uint32(match.typeId), // 2 @@ -451,17 +445,52 @@ class ArenaReplayBGScript : public BGScript teamWinnerName, // 6 teamWinnerRating, // 7 teamWinnerMMR, // 8 - winnerClassIds, // 9 - teamLoserName, // 10 - teamLoserRating, // 11 - teamLoserMMR, // 12 - loserClassIds, // 13 - winnerGuids, // 14 - loserGuids // 15 + teamLoserName, // 9 + teamLoserRating, // 10 + teamLoserMMR, // 11 + winnerGuids, // 12 + loserGuids // 13 ); records.erase(it); } + +private: + void getTeamInformation(Battleground *bg, ArenaTeam* team, std::string &teamName, uint32 &teamRating) { + if (bg->isRated() && team) + { + if (team->GetId() < 0xFFF00000) + { + teamName = team->GetName(); + teamRating = team->GetRating(); + } + } + } + + std::string GetTeamName(std::string listPlayerGuids) { + std::string teamName; + std::stringstream ssPlayerGuids(listPlayerGuids); + + std::vector playerGuids; + std::string playerGuid; + while (std::getline(ssPlayerGuids, playerGuid, ',')) + playerGuids.push_back(playerGuid); + + for (const std::string& guid : playerGuids) + { + uint64 _guid = std::stoi(guid); + CharacterCacheEntry const* playerData = sCharacterCache->GetCharacterCacheByGuid(ObjectGuid(_guid)); + if (playerData) + teamName += playerData->Name + " "; + } + + // truncate last character if space + if (!teamName.empty() && teamName.substr(teamName.size()-1, teamName.size()) == " ") { + teamName.pop_back(); + } + + return teamName; + } }; enum ReplayGossips @@ -496,32 +525,35 @@ class ReplayGossip : public CreatureScript return true; } + const bool isArena1v1Enabled = sConfigMgr->GetOption("ArenaReplay.1v1.Enable", false); + const bool isArena3v3soloQEnabled = sConfigMgr->GetOption("ArenaReplay.3v3soloQ.Enable", false); + + if (isArena1v1Enabled) + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 1v1 games of the last 30 days", GOSSIP_SENDER_MAIN, REPLAY_LATEST_1V1); + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 2v2 games of the last 30 days", GOSSIP_SENDER_MAIN, REPLAY_LATEST_2V2); AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 3v3 games of the last 30 days", GOSSIP_SENDER_MAIN, REPLAY_LATEST_3V3); - AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 5v5 games of the last 30 days", GOSSIP_SENDER_MAIN, REPLAY_LATEST_3V3); - if (sConfigMgr->GetOption("ArenaReplay.3v3soloQ.Enable", false)) { + if (isArena3v3soloQEnabled) AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 3v3 Solo games of the last 30 days", GOSSIP_SENDER_MAIN, REPLAY_LATEST_3V3SOLO); - } - if (sConfigMgr->GetOption("ArenaReplay.1v1.Enable", false)) { - AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 1v1 games of the last 30 days", GOSSIP_SENDER_MAIN, REPLAY_LATEST_1V1); - } + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 5v5 games of the last 30 days", GOSSIP_SENDER_MAIN, REPLAY_LATEST_5V5); AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Replay a Match ID", GOSSIP_SENDER_MAIN, REPLAY_MATCH_ID, "", 0, true); // maybe add command .replay 'replayID' aswell AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Replay list by player name", GOSSIP_SENDER_MAIN, REPLAY_LIST_BY_PLAYERNAME, "", 0, true); // to do: show a list, showing games with type, teamname and teamrating AddGossipItemFor(player, GOSSIP_ICON_TRAINER, "My favorite matches", GOSSIP_SENDER_MAIN, MY_FAVORITE_MATCHES); // To do: somehow show teamName/TeamRating/Classes (it's a different db table) + + if (isArena1v1Enabled) + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 1v1 games of all time", GOSSIP_SENDER_MAIN, REPLAY_TOP_1V1_ALLTIME); + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 2v2 games of all time", GOSSIP_SENDER_MAIN, REPLAY_TOP_2V2_ALLTIME); AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 3v3 games of all time", GOSSIP_SENDER_MAIN, REPLAY_TOP_3V3_ALLTIME); - AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 5v5 games of all time", GOSSIP_SENDER_MAIN, REPLAY_TOP_5V5_ALLTIME); - if (sConfigMgr->GetOption("ArenaReplay.3v3soloQ.Enable", false)) { + if (isArena3v3soloQEnabled) AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 3v3 Solo games of all time", GOSSIP_SENDER_MAIN, REPLAY_TOP_3V3SOLO_ALLTIME); - } - if (sConfigMgr->GetOption("ArenaReplay.1v1.Enable", false)) { - AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 1v1 games of all time", GOSSIP_SENDER_MAIN, REPLAY_TOP_1V1_ALLTIME); - } + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay top 5v5 games of all time", GOSSIP_SENDER_MAIN, REPLAY_TOP_5V5_ALLTIME); + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Replay most watched games of all time", GOSSIP_SENDER_MAIN, REPLAY_MOST_WATCHED_ALLTIME); // To Do: show arena type + watchedTimes, maybe hide team name SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); @@ -591,9 +623,7 @@ class ReplayGossip : public CreatureScript default: if (action >= GOSSIP_ACTION_INFO_DEF + 30) // Replay selected arenas (intid >= 30) - { return replayArenaMatch(player, action - (GOSSIP_ACTION_INFO_DEF + 30)); - } } return true; @@ -606,6 +636,7 @@ class ReplayGossip : public CreatureScript CloseGossipMenuFor(player); return false; } + // Forbidden: ', %, and , (' causes crash when using 'Replay list by player name') std::string inputCode = std::string(code); if (inputCode.find('\'') != std::string::npos || inputCode.find('%') != std::string::npos || inputCode.find(',') != std::string::npos) @@ -614,11 +645,13 @@ class ReplayGossip : public CreatureScript CloseGossipMenuFor(player); return false; } + if (inputCode.length() > 50) { CloseGossipMenuFor(player); return false; } + if (inputCode.empty()) { ChatHandler(player->GetSession()).PSendSysMessage("Invalid input."); @@ -641,21 +674,20 @@ class ReplayGossip : public CreatureScript CloseGossipMenuFor(player); return false; } + return replayArenaMatch(player, replayId); } case REPLAY_LIST_BY_PLAYERNAME: { - QueryResult resultGUID = CharacterDatabase.Query("SELECT guid FROM characters WHERE LOWER(name) = LOWER('" + std::string(code) + "')"); - if (!resultGUID) + CharacterCacheEntry const* playerData = sCharacterCache->GetCharacterCacheByName(std::string(code)); + if (!playerData) { ChatHandler(player->GetSession()).PSendSysMessage("No player found with the name: {}", std::string(code)); CloseGossipMenuFor(player); return false; } - Field* fieldsGUID = resultGUID->Fetch(); - uint64 playerGuid = fieldsGUID[0].Get(); - std::string playerGuidStr = std::to_string(playerGuid); + std::string playerGuidStr = std::to_string(playerData->Guid.GetRawValue()); QueryResult result = CharacterDatabase.Query("SELECT id FROM character_arena_replays WHERE winnerPlayerGuids LIKE '%{}%' OR loserPlayerGuids LIKE '%{}%'", playerGuidStr, playerGuidStr); if (result) @@ -703,122 +735,133 @@ class ReplayGossip : public CreatureScript std::string GetClassIconById(uint8 id) { - std::string sClass = ""; switch (id) { case CLASS_WARRIOR: - sClass = "|TInterface\\icons\\inv_sword_27"; - break; + return "|TInterface\\icons\\inv_sword_27"; case CLASS_PALADIN: - sClass = "|TInterface\\icons\\inv_hammer_01"; - break; + return "|TInterface\\icons\\inv_hammer_01"; case CLASS_HUNTER: - sClass = "|TInterface\\icons\\inv_weapon_bow_07"; - break; + return "|TInterface\\icons\\inv_weapon_bow_07"; case CLASS_ROGUE: - sClass = "|TInterface\\icons\\inv_throwingknife_04"; - break; + return "|TInterface\\icons\\inv_throwingknife_04"; case CLASS_PRIEST: - sClass = "|TInterface\\icons\\inv_staff_30"; - break; + return "|TInterface\\icons\\inv_staff_30"; case CLASS_DEATH_KNIGHT: - sClass = "|TInterface\\icons\\spell_deathknight_classicon"; - break; + return "|TInterface\\icons\\spell_deathknight_classicon"; case CLASS_SHAMAN: - sClass = "TInterface\\icons\\inv_jewelry_talisman_04"; - break; + return "TInterface\\icons\\inv_jewelry_talisman_04"; case CLASS_MAGE: - sClass = "|TInterface\\icons\\inv_staff_13"; - break; + return "|TInterface\\icons\\inv_staff_13"; case CLASS_WARLOCK: - sClass = "|TInterface\\icons\\spell_nature_drowsy"; - break; + return "|TInterface\\icons\\spell_nature_drowsy"; case CLASS_DRUID: - sClass = "|TInterface\\icons\\inv_misc_monsterclaw_04"; - break; + return "|TInterface\\icons\\inv_misc_monsterclaw_04"; + default: + return ""; + } + } + + std::string GetRaceIconById(uint8 id, uint8 gender) { + const std::string gender_icon = gender == GENDER_MALE ? "male" : "female"; + switch (id) { + case RACE_HUMAN: + return "|TInterface/ICONS/achievement_character_human_" + gender_icon; + case RACE_ORC: + return "|TInterface/ICONS/achievement_character_orc_" + gender_icon; + case RACE_DWARF: + return "|TInterface/ICONS/achievement_character_dwarf_" + gender_icon; + case RACE_NIGHTELF: + return "|TInterface/ICONS/achievement_character_nightelf_" + gender_icon; + case RACE_UNDEAD_PLAYER: + return "|TInterface/ICONS/achievement_character_undead_" + gender_icon; + case RACE_TAUREN: + return "|TInterface/ICONS/achievement_character_tauren_" + gender_icon; + case RACE_GNOME: + return "|TInterface/ICONS/achievement_character_gnome_" + gender_icon; + case RACE_TROLL: + return "|TInterface/ICONS/achievement_character_troll_" + gender_icon; + case RACE_BLOODELF: + return "|TInterface/ICONS/achievement_character_bloodelf_" + gender_icon; + case RACE_DRAENEI: + return "|TInterface/ICONS/achievement_character_draenei_" + gender_icon; + default: + return ""; } - return sClass; + } + + std::string GetPlayersIconTexts(std::string playerGuids) { + std::string iconsTextTeam; + std::vector playerGuidsTeam1; + + std::stringstream ssPlayerGuids(playerGuids); + std::string item; + + while (std::getline(ssPlayerGuids, item, ',')) + playerGuidsTeam1.push_back(item); + + for (const std::string& guid : playerGuidsTeam1) + { + uint64 _guid = std::stoi(guid); + CharacterCacheEntry const* playerData = sCharacterCache->GetCharacterCacheByGuid(ObjectGuid(_guid)); + if (playerData) + { + iconsTextTeam += GetClassIconById(playerData->Class) + ":14:14:05:00|t|r"; + iconsTextTeam += GetRaceIconById(playerData->Race, playerData->Sex) + ":14:14:05:00|t|r "; + } + } + + if (!iconsTextTeam.empty() && iconsTextTeam.back() == '\n') + iconsTextTeam.pop_back(); + + return iconsTextTeam; } struct ReplayInfo { uint32 matchId; + + std::string winnerPlayerGuids; std::string winnerTeamName; uint32 winnerTeamRating; - std::string winnerClasses; + + std::string loserPlayerGuids; std::string loserTeamName; uint32 loserTeamRating; - std::string loserClasses; + //uint32 winnerMMR; //uint32 loserMMR; }; - void ShowReplaysAllTime(Player* player, Creature* creature, uint8 arenaTypeId) - { - auto matchInfos = loadReplaysAllTimeByArenaType(arenaTypeId); - if (matchInfos.empty()) - { - AddGossipItemFor(player, GOSSIP_ICON_TAXI, "No replays found for this arena type.", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); - } - else - { - AddGossipItemFor(player, GOSSIP_ICON_TRAINER, "[Replay ID] (Team Rating) 'Team Name'\n----------------------------------------------", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); // Back to Main Menu - for (const auto& info : matchInfos) - { - std::string classIconsTextTeam1; - std::string classIconsTextTeam2; - std::vector classIdsTeam1; - std::stringstream ssWinnerClasses(info.winnerClasses); - std::string item; - while (std::getline(ssWinnerClasses, item, ',')) - { - classIdsTeam1.push_back(item); - } - std::vector classIdsTeam2; - std::stringstream ssLoserClasses(info.loserClasses); - while (std::getline(ssLoserClasses, item, ',')) - { - classIdsTeam2.push_back(item); - } + std::string GetGossipText(ReplayInfo info) { + std::string iconsTextTeam1 = GetPlayersIconTexts(info.winnerPlayerGuids); + std::string iconsTextTeam2 = GetPlayersIconTexts(info.loserPlayerGuids); - for (const std::string& classId : classIdsTeam1) - { - uint32 id = std::stoi(classId); - classIconsTextTeam1 += GetClassIconById(id) + ":14:14:05:00|t|r"; - } - for (const std::string& classId : classIdsTeam2) - { - uint32 id = std::stoi(classId); - classIconsTextTeam2 += GetClassIconById(id) + ":14:14:05:00|t|r"; - } - if (!classIconsTextTeam1.empty() && classIconsTextTeam1.back() == '\n') - classIconsTextTeam1.pop_back(); - if (!classIconsTextTeam2.empty() && classIconsTextTeam2.back() == '\n') - classIconsTextTeam2.pop_back(); - std::string coloredWinnerTeamName = "|cff00ff00" + info.winnerTeamName + "|r"; - std::string LoserTeamName = info.loserTeamName; - - std::string gossipText = ("[" + std::to_string(info.matchId) + "] (" + - std::to_string(info.winnerTeamRating) + ")" + - classIconsTextTeam1 + "" + - " '" + coloredWinnerTeamName + "'" + - "\n vs (" + std::to_string(info.loserTeamRating) + ")" + - classIconsTextTeam2 + "" + - " '" + LoserTeamName + "'"); + std::string coloredWinnerTeamName = "|cff33691E" + info.winnerTeamName + "|r"; + std::string LoserTeamName = info.loserTeamName; - const uint32 actionOffset = GOSSIP_ACTION_INFO_DEF + 30; - AddGossipItemFor(player, GOSSIP_ICON_BATTLE, gossipText, GOSSIP_SENDER_MAIN, actionOffset + info.matchId); - } - } + std::string gossipText = ("[" + std::to_string(info.matchId) + "] (" + + std::to_string(info.winnerTeamRating) + ")" + + iconsTextTeam1 + "" + + " '" + coloredWinnerTeamName + "'" + + "\n vs (" + std::to_string(info.loserTeamRating) + ")" + + iconsTextTeam2 + "" + + " '" + LoserTeamName + "'"); - AddGossipItemFor(player, GOSSIP_ICON_TAXI, "Back", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); - SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + return gossipText; + } + + void ShowReplaysAllTime(Player* player, Creature* creature, uint8 arenaTypeId) + { + auto matchInfos = loadReplaysAllTimeByArenaType(arenaTypeId); + ShowReplays(player, creature, matchInfos); } + std::vector loadReplaysAllTimeByArenaType(uint8 arenaTypeId) { std::vector records; - QueryResult result = CharacterDatabase.Query("SELECT id, winnerTeamName, winnerTeamRating, winnerClassIds, loserTeamName, loserTeamRating, loserClassIds FROM character_arena_replays WHERE arenaTypeId = {} ORDER BY winnerTeamRating DESC LIMIT 20", arenaTypeId); + QueryResult result = CharacterDatabase.Query("SELECT id, winnerTeamName, winnerTeamRating, winnerPlayerGuids, loserTeamName, loserTeamRating, loserPlayerGuids FROM character_arena_replays WHERE arenaTypeId = {} ORDER BY winnerTeamRating DESC LIMIT 20", arenaTypeId); if (!result) return records; @@ -833,10 +876,10 @@ class ReplayGossip : public CreatureScript info.matchId = fields[0].Get(); info.winnerTeamName = fields[1].Get(); info.winnerTeamRating = fields[2].Get(); - info.winnerClasses = fields[3].Get(); + info.winnerPlayerGuids = fields[3].Get(); info.loserTeamName = fields[4].Get(); info.loserTeamRating = fields[5].Get(); - info.loserClasses = fields[6].Get(); + info.loserPlayerGuids = fields[6].Get(); records.push_back(info); } while (result->NextRow()); @@ -847,65 +890,9 @@ class ReplayGossip : public CreatureScript void ShowReplaysLast30Days(Player* player, Creature* creature, uint8 arenaTypeId) { auto matchInfos = loadReplaysLast30Days(arenaTypeId); - - if (matchInfos.empty()) - { - AddGossipItemFor(player, GOSSIP_ICON_TAXI, "No replays found for this arena type.", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); - } - else - { - AddGossipItemFor(player, GOSSIP_ICON_TRAINER, "[Replay ID] (Team Rating) 'Team Name'\n----------------------------------------------", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); // Back to Main Menu - for (const auto& info : matchInfos) - { - std::string classIconsTextTeam1; - std::string classIconsTextTeam2; - std::vector classIdsTeam1; - std::stringstream ssWinnerClasses(info.winnerClasses); - std::string item; - while (std::getline(ssWinnerClasses, item, ',')) - { - classIdsTeam1.push_back(item); - } - std::vector classIdsTeam2; - std::stringstream ssLoserClasses(info.loserClasses); - while (std::getline(ssLoserClasses, item, ',')) - { - classIdsTeam2.push_back(item); - } - - for (const std::string& classId : classIdsTeam1) - { - uint32 id = std::stoi(classId); - classIconsTextTeam1 += GetClassIconById(id) + ":14:14:05:00|t|r"; - } - for (const std::string& classId : classIdsTeam2) - { - uint32 id = std::stoi(classId); - classIconsTextTeam2 += GetClassIconById(id) + ":14:14:05:00|t|r"; - } - if (!classIconsTextTeam1.empty() && classIconsTextTeam1.back() == '\n') - classIconsTextTeam1.pop_back(); - if (!classIconsTextTeam2.empty() && classIconsTextTeam2.back() == '\n') - classIconsTextTeam2.pop_back(); - std::string coloredWinnerTeamName = "|cff00ff00" + info.winnerTeamName + "|r"; - std::string LoserTeamName = info.loserTeamName; - - std::string gossipText = ("[" + std::to_string(info.matchId) + "] (" + - std::to_string(info.winnerTeamRating) + ")" + - classIconsTextTeam1 + "" + - " '" + coloredWinnerTeamName + "'" + - "\n vs (" + std::to_string(info.loserTeamRating) + ")" + - classIconsTextTeam2 + "" + - " '" + LoserTeamName + "'"); - - const uint32 actionOffset = GOSSIP_ACTION_INFO_DEF + 30; - AddGossipItemFor(player, GOSSIP_ICON_BATTLE, gossipText, GOSSIP_SENDER_MAIN, actionOffset + info.matchId); - } - } - - AddGossipItemFor(player, GOSSIP_ICON_TAXI, "Back", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); - SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + ShowReplays(player, creature, matchInfos); } + std::vector loadReplaysLast30Days(uint8 arenaTypeId) { std::vector records; @@ -921,10 +908,10 @@ class ReplayGossip : public CreatureScript // Only show games that are 30 days old QueryResult result = CharacterDatabase.Query( - "SELECT id, winnerTeamName, winnerTeamRating, winnerClassIds, loserTeamName, loserTeamRating, loserClassIds, timestamp " + "SELECT id, winnerTeamName, winnerTeamRating, winnerPlayerGuids, loserTeamName, loserTeamRating, loserPlayerGuids, timestamp " "FROM character_arena_replays " "WHERE arenaTypeId = {} AND timestamp >= '{}' " - "ORDER BY winnerTeamRating DESC LIMIT 20", arenaTypeId, thirtyDaysAgo.c_str()); + "ORDER BY id DESC LIMIT 20", arenaTypeId, thirtyDaysAgo.c_str()); if (!result) return records; @@ -939,10 +926,10 @@ class ReplayGossip : public CreatureScript info.matchId = fields[0].Get(); info.winnerTeamName = fields[1].Get(); info.winnerTeamRating = fields[2].Get(); - info.winnerClasses = fields[3].Get(); + info.winnerPlayerGuids = fields[3].Get(); info.loserTeamName = fields[4].Get(); info.loserTeamRating = fields[5].Get(); - info.loserClasses = fields[6].Get(); + info.loserPlayerGuids = fields[6].Get(); records.push_back(info); } while (result->NextRow()); @@ -953,57 +940,18 @@ class ReplayGossip : public CreatureScript void ShowMostWatchedReplays(Player* player, Creature* creature) { auto matchInfos = loadMostWatchedReplays(); + ShowReplays(player, creature, matchInfos); + } + void ShowReplays(Player* player, Creature* creature, std::vector matchInfos) { if (matchInfos.empty()) - { AddGossipItemFor(player, GOSSIP_ICON_TAXI, "No replays found.", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); - } else { AddGossipItemFor(player, GOSSIP_ICON_TRAINER, "[Replay ID] (Team Rating) 'Team Name'\n----------------------------------------------", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); // Back to Main Menu for (const auto& info : matchInfos) { - std::string classIconsTextTeam1; - std::string classIconsTextTeam2; - std::vector classIdsTeam1; - std::stringstream ssWinnerClasses(info.winnerClasses); - std::string item; - while (std::getline(ssWinnerClasses, item, ',')) - { - classIdsTeam1.push_back(item); - } - std::vector classIdsTeam2; - std::stringstream ssLoserClasses(info.loserClasses); - while (std::getline(ssLoserClasses, item, ',')) - { - classIdsTeam2.push_back(item); - } - - for (const std::string& classId : classIdsTeam1) - { - uint32 id = std::stoi(classId); - classIconsTextTeam1 += GetClassIconById(id) + ":14:14:05:00|t|r"; - } - for (const std::string& classId : classIdsTeam2) - { - uint32 id = std::stoi(classId); - classIconsTextTeam2 += GetClassIconById(id) + ":14:14:05:00|t|r"; - } - if (!classIconsTextTeam1.empty() && classIconsTextTeam1.back() == '\n') - classIconsTextTeam1.pop_back(); - if (!classIconsTextTeam2.empty() && classIconsTextTeam2.back() == '\n') - classIconsTextTeam2.pop_back(); - std::string coloredWinnerTeamName = "|cff00ff00" + info.winnerTeamName + "|r"; - std::string LoserTeamName = info.loserTeamName; - - std::string gossipText = "[" + std::to_string(info.matchId) + "] (" + - std::to_string(info.winnerTeamRating) + ")" + - classIconsTextTeam1 + "" + - " '" + coloredWinnerTeamName + "'" + - "\n vs (" + std::to_string(info.loserTeamRating) + ")" + - classIconsTextTeam2 + "" + - " '" + LoserTeamName + "'"; - + const std::string gossipText = GetGossipText(info); const uint32 actionOffset = GOSSIP_ACTION_INFO_DEF + 30; AddGossipItemFor(player, GOSSIP_ICON_BATTLE, gossipText, GOSSIP_SENDER_MAIN, actionOffset + info.matchId); } @@ -1012,11 +960,12 @@ class ReplayGossip : public CreatureScript AddGossipItemFor(player, GOSSIP_ICON_TAXI, "Back", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); } + std::vector loadMostWatchedReplays() { std::vector records; QueryResult result = CharacterDatabase.Query( - "SELECT id, winnerTeamName, winnerTeamRating, winnerClassIds, loserTeamName, loserTeamRating, loserClassIds " + "SELECT id, winnerTeamName, winnerTeamRating, winnerPlayerGuids, loserTeamName, loserTeamRating, loserPlayerGuids " "FROM character_arena_replays " "ORDER BY timesWatched DESC, winnerTeamRating DESC " "LIMIT 28"); @@ -1034,10 +983,10 @@ class ReplayGossip : public CreatureScript info.matchId = fields[0].Get(); info.winnerTeamName = fields[1].Get(); info.winnerTeamRating = fields[2].Get(); - info.winnerClasses = fields[3].Get(); + info.winnerPlayerGuids = fields[3].Get(); info.loserTeamName = fields[4].Get(); info.loserTeamRating = fields[5].Get(); - info.loserClasses = fields[6].Get(); + info.loserPlayerGuids = fields[6].Get(); records.push_back(info); } while (result->NextRow()); @@ -1052,9 +1001,7 @@ class ReplayGossip : public CreatureScript std::string sortOrder = (firstPage) ? "ASC" : "DESC"; QueryResult result = CharacterDatabase.Query("SELECT replay_id FROM character_saved_replays WHERE character_id = " + std::to_string(player->GetGUID().GetCounter()) + " ORDER BY id " + sortOrder + " LIMIT 29"); if (!result) - { AddGossipItemFor(player, GOSSIP_ICON_TAXI, "No saved replays found.", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); - } else { do @@ -1217,21 +1164,21 @@ class ConfigLoaderArenaReplay : public WorldScript private: void DeleteOldReplays() { // delete all the replays older than X days - const auto days = sConfigMgr->GetOption("ArenaReplay.DeleteReplaysAfterDays", 0); + const auto days = sConfigMgr->GetOption("ArenaReplay.DeleteReplaysAfterDays", 30); if (days > 0) { std::string addition = ""; - const bool deleteReplays = sConfigMgr->GetOption("ArenaReplay.DeleteSavedReplays", false); + const bool deleteSavedReplays = sConfigMgr->GetOption("ArenaReplay.DeleteSavedReplays", false); - if (!deleteReplays) { + if (!deleteSavedReplays) { addition = "AND `id` NOT IN (SELECT `replay_id` FROM `character_saved_replays`)"; } const auto query = "DELETE FROM `character_arena_replays` WHERE `timestamp` < (NOW() - INTERVAL " + std::to_string(days) + " DAY) " + addition; CharacterDatabase.Execute(query); - if (deleteReplays) { + if (deleteSavedReplays) { CharacterDatabase.Execute("DELETE FROM `character_saved_replays` WHERE `replay_id` NOT IN (SELECT `id` FROM `character_arena_replays`)"); } }