mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-13 00:58:33 +00:00
Fix: Arena - PersonalRating and MMR issue for bot teams (#1789)
# Fix: Arena PersonalRating and MMR issue for bot teams
## Problem
Bot arena teams are created with artificial random ratings (1000-2000
range), but when bots join these teams, their personal ratings and
matchmaker ratings (MMR) use default config values instead of being
adjusted to match the team's artificial rating. This causes matchmaking
issues since the system uses personal ratings for queue calculations.
## Root Cause
The issue occurred because `SetRatingForAll()` was called during team
creation but only affected the captain. When additional bots were added
later via `AddMember()`, they received default values from
`CONFIG_ARENA_START_PERSONAL_RATING` and
`CONFIG_ARENA_START_MATCHMAKER_RATING` instead of values appropriate for
the team's artificial rating.
## Solution
After bots are added to arena teams, the fix:
1. Uses `SetRatingForAll()` to align all personal ratings with team
rating
2. Adjusts matchmaker ratings based on team context vs default
configuration
3. Saves changes to both database tables with proper data types
## Impact
- Personal ratings now match team ratings for artificial bot teams
- MMR values are adjusted for artificial bot team ratings instead of
using default config values
- Arena matchmaking functions correctly for bot teams with random
ratings
- Only affects new arena team assignments after deployment
- Existing player teams and normal config behavior are unaffected
## Manual Database Update
For existing installations, the provided SQL script could be used to fix
bot teams created before this patch.
### Update personal rating
```sql
UPDATE arena_team_member atm
JOIN arena_team at ON atm.arenaTeamId = at.arenaTeamId
JOIN characters c ON atm.guid = c.guid
JOIN auth.account a ON c.account = a.id
SET atm.personalRating = at.rating
WHERE a.username LIKE 'rndbot%'
AND atm.personalRating != at.rating;
```
### Update MMR for existing entries
```sql
UPDATE character_arena_stats cas
JOIN characters c ON cas.guid = c.guid
JOIN auth.account a ON c.account = a.id
JOIN arena_team_member atm ON cas.guid = atm.guid
JOIN arena_team at ON atm.arenaTeamId = at.arenaTeamId
SET
cas.matchMakerRating = GREATEST(at.rating, 1500), -- Use team rating or 1500 minimum
cas.maxMMR = GREATEST(cas.maxMMR, cas.matchMakerRating) -- Update maxMMR if needed
WHERE
a.username LIKE '%rndbot%'
AND (
-- Update if MMR doesn't match team context
(at.rating > 1500 AND cas.matchMakerRating < at.rating) OR
(at.rating <= 1500 AND cas.matchMakerRating != 1500) OR
cas.matchMakerRating IS NULL
)
AND (
-- Map arena team type to character_arena_stats slot
(at.type = 2 AND cas.slot = 0) OR -- 2v2 teams use slot 0
(at.type = 3 AND cas.slot = 1) OR -- 3v3 teams use slot 1
(at.type = 5 AND cas.slot = 2) -- 5v5 teams use slot 2
);
```
### Insert missing MMR records for bots without character_arena_stats
entries
```sql
INSERT INTO character_arena_stats (guid, slot, matchMakerRating, maxMMR)
SELECT
atm.guid,
CASE
WHEN at.type = 2 THEN 0 -- 2v2 -> slot 0
WHEN at.type = 3 THEN 1 -- 3v3 -> slot 1
WHEN at.type = 5 THEN 2 -- 5v5 -> slot 2
ELSE 0
END as slot,
GREATEST(at.rating, 1500) as matchMakerRating,
GREATEST(at.rating, 1500) as maxMMR
FROM arena_team_member atm
JOIN arena_team at ON atm.arenaTeamId = at.arenaTeamId
JOIN characters c ON atm.guid = c.guid
JOIN auth.account a ON c.account = a.id
WHERE
a.username LIKE '%rndbot%'
AND NOT EXISTS (
SELECT 1 FROM character_arena_stats cas2
WHERE cas2.guid = atm.guid
AND cas2.slot = CASE
WHEN at.type = 2 THEN 0
WHEN at.type = 3 THEN 1
WHEN at.type = 5 THEN 2
ELSE 0
END
)
AND at.rating > 0;
```
## Related issues
Fixes: #1787
Fixes: #1800
## Verification Queries
### Query 1: Check personal rating alignment
```sql
SELECT
'Personal Rating Check' as check_type,
COUNT(*) as total_bot_members,
SUM(CASE WHEN atm.personalRating = at.rating THEN 1 ELSE 0 END) as correct_ratings,
SUM(CASE WHEN atm.personalRating != at.rating THEN 1 ELSE 0 END) as incorrect_ratings,
ROUND(AVG(at.rating), 2) as avg_team_rating,
ROUND(AVG(atm.personalRating), 2) as avg_personal_rating
FROM arena_team_member atm
JOIN arena_team at ON atm.arenaTeamId = at.arenaTeamId
JOIN characters c ON atm.guid = c.guid
JOIN auth.account a ON c.account = a.id
WHERE
a.username LIKE '%rndbot%';
```
### Query 2: Check MMR alignment
```sql
SELECT
'MMR Alignment Check' as check_type,
COUNT(*) as total_mmr_records,
SUM(CASE
WHEN at.rating > 1500 AND cas.matchMakerRating >= at.rating THEN 1
WHEN at.rating <= 1500 AND cas.matchMakerRating = 1500 THEN 1
ELSE 0
END) as correct_mmr,
SUM(CASE
WHEN at.rating > 1500 AND cas.matchMakerRating < at.rating THEN 1
WHEN at.rating <= 1500 AND cas.matchMakerRating != 1500 THEN 1
ELSE 0
END) as incorrect_mmr,
ROUND(AVG(at.rating), 2) as avg_team_rating,
ROUND(AVG(cas.matchMakerRating), 2) as avg_mmr,
ROUND(AVG(cas.maxMMR), 2) as avg_max_mmr
FROM arena_team_member atm
JOIN arena_team at ON atm.arenaTeamId = at.arenaTeamId
JOIN characters c ON atm.guid = c.guid
JOIN auth.account a ON c.account = a.id
JOIN character_arena_stats cas ON atm.guid = cas.guid
WHERE
a.username LIKE '%rndbot%'
AND (
(at.type = 2 AND cas.slot = 0) OR
(at.type = 3 AND cas.slot = 1) OR
(at.type = 5 AND cas.slot = 2)
);
```
### Query 3: Detailed team-by-team analysis
```sql
SELECT
at.arenaTeamId,
at.name as team_name,
at.type as team_type,
at.rating as team_rating,
COUNT(atm.guid) as member_count,
GROUP_CONCAT(DISTINCT atm.personalRating) as personal_ratings,
GROUP_CONCAT(DISTINCT cas.matchMakerRating) as mmr_values,
CASE
WHEN COUNT(DISTINCT atm.personalRating) = 1 AND MIN(atm.personalRating) = at.rating THEN 'OK'
ELSE 'MISMATCH'
END as personal_rating_status,
CASE
WHEN COUNT(DISTINCT cas.matchMakerRating) = 1 AND (
(at.rating > 1500 AND MIN(cas.matchMakerRating) >= at.rating) OR
(at.rating <= 1500 AND MIN(cas.matchMakerRating) = 1500)
) THEN 'OK'
ELSE 'MISMATCH'
END as mmr_status
FROM arena_team at
JOIN arena_team_member atm ON at.arenaTeamId = atm.arenaTeamId
JOIN characters c ON atm.guid = c.guid
JOIN auth.account a ON c.account = a.id
LEFT JOIN character_arena_stats cas ON atm.guid = cas.guid
AND cas.slot = CASE
WHEN at.type = 2 THEN 0
WHEN at.type = 3 THEN 1
WHEN at.type = 5 THEN 2
ELSE 0
END
WHERE
a.username LIKE '%rndbot%'
GROUP BY at.arenaTeamId, at.name, at.type, at.rating
ORDER BY at.rating DESC;
```
This commit is contained in:
@@ -4099,6 +4099,7 @@ void PlayerbotFactory::InitImmersive()
|
|||||||
|
|
||||||
void PlayerbotFactory::InitArenaTeam()
|
void PlayerbotFactory::InitArenaTeam()
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!sPlayerbotAIConfig->IsInRandomAccountList(bot->GetSession()->GetAccountId()))
|
if (!sPlayerbotAIConfig->IsInRandomAccountList(bot->GetSession()->GetAccountId()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -4185,10 +4186,34 @@ void PlayerbotFactory::InitArenaTeam()
|
|||||||
|
|
||||||
if (botcaptain && botcaptain->GetTeamId() == bot->GetTeamId()) // need?
|
if (botcaptain && botcaptain->GetTeamId() == bot->GetTeamId()) // need?
|
||||||
{
|
{
|
||||||
|
// Add bot to arena team
|
||||||
arenateam->AddMember(bot->GetGUID());
|
arenateam->AddMember(bot->GetGUID());
|
||||||
arenateam->SaveToDB();
|
|
||||||
|
// Only synchronize ratings once the team is full (avoid redundant work)
|
||||||
|
// The captain was added with incorrect ratings when the team was created,
|
||||||
|
// so we fix everyone's ratings once the roster is complete
|
||||||
|
if (arenateam->GetMembersSize() >= (uint32)arenateam->GetType())
|
||||||
|
{
|
||||||
|
uint32 teamRating = arenateam->GetRating();
|
||||||
|
|
||||||
|
// Use SetRatingForAll to align all members with team rating
|
||||||
|
arenateam->SetRatingForAll(teamRating);
|
||||||
|
|
||||||
|
// For bot-only teams, keep MMR synchronized with team rating
|
||||||
|
// This ensures matchmaking reflects the artificial team strength (1000-2000 range)
|
||||||
|
// instead of being influenced by the global CONFIG_ARENA_START_MATCHMAKER_RATING
|
||||||
|
for (auto& member : arenateam->GetMembers())
|
||||||
|
{
|
||||||
|
// Set MMR to match personal rating (which already matches team rating)
|
||||||
|
member.MatchMakerRating = member.PersonalRating;
|
||||||
|
member.MaxMMR = std::max(member.MaxMMR, member.PersonalRating);
|
||||||
|
}
|
||||||
|
// Force save all member data to database
|
||||||
|
arenateam->SaveToDB(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
arenateams.erase(arenateams.begin() + index);
|
arenateams.erase(arenateams.begin() + index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user