Skip to content

Commit 34e16d5

Browse files
authored
Merge pull request #450 from CnCNet/develop
- Support for preferred colors - Prevent version mismatch crashes
2 parents 62b6170 + 53d2c0b commit 34e16d5

File tree

5 files changed

+219
-15
lines changed

5 files changed

+219
-15
lines changed

cncnet-api/app/Http/Controllers/Api/V2/Qm/MatchUpController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,8 @@ private function onMatchMeUp(Request $request, Ladder $ladder, Player $player, ?
357357
Log::info("onMatchMeUp exit: queued opponent | duration: {$duration} seconds", [
358358
'player_id' => $player->id,
359359
'username' => $player->username,
360-
'ladder' => $ladder->abbreviation
360+
'ladder' => $ladder->abbreviation,
361+
'client_version' => $qmPlayer->client_version
361362
]);
362363
return $this->quickMatchService->onCheckback($alert);
363364
}

cncnet-api/app/Http/Services/QuickMatchService.php

Lines changed: 162 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,26 @@ public function createQMPlayer($request, $player, $history)
6161

6262
$qmPlayer->chosen_side = $request->side;
6363

64+
// Store preferred colors. If not set, players get yellow/red like usual.
65+
if (isset($request->colors) && is_array($request->colors))
66+
{
67+
$qmPlayer->colors_pref = json_encode(array_values($request->colors));
68+
}
69+
if (isset($request->colors_opponent) && is_array($request->colors_opponent))
70+
{
71+
$qmPlayer->colors_opponent_pref = json_encode(array_values($request->colors_opponent));
72+
}
73+
6474
if ($request->map_sides)
6575
{
6676
$qmPlayer->map_sides_id = \App\Models\MapSideString::findValue(join(',', $request->map_sides))->id;
6777
}
6878

79+
if ($request->client_version)
80+
{
81+
$qmPlayer->client_version = $request->client_version;
82+
}
83+
6984
if ($request->version && $request->platform)
7085
{
7186
$qmPlayer->version_id = \App\Models\PlayerDataString::findValue($request->version)->id;
@@ -287,10 +302,21 @@ public function createOrUpdateQueueEntry($player, $qmPlayer, $history, $gameType
287302

288303
public function fetchQmQueueEntry(LadderHistory $history, ?QmQueueEntry $qmQueueEntry = null): \Illuminate\Database\Eloquent\Collection|array
289304
{
290-
return QmQueueEntry::query()
291-
->when(isset($qmQueueEntry), fn($q) => $q->where('qm_match_player_id', '!=', $qmQueueEntry->qmPlayer->id))
305+
$query = QmQueueEntry::query()
292306
->where('ladder_history_id', '=', $history->id)
293-
->get();
307+
->with('qmPlayer');
308+
309+
if ($qmQueueEntry)
310+
{
311+
// Client versions need to match. Otherwise players simply don't see each other.
312+
$currentVersion = $qmQueueEntry->qmPlayer->client_version;
313+
$query->where('qm_match_player_id', '!=', $qmQueueEntry->qmPlayer->id)
314+
->whereHas('qmPlayer', function ($sub) use ($currentVersion) {
315+
$sub->where('client_version', $currentVersion);
316+
});
317+
}
318+
319+
return $query->get();
294320
}
295321

296322
/**
@@ -935,6 +961,8 @@ private function set1v1QmSpawns($otherQmQueueEntries, $qmMatch, $qmPlayer, $expe
935961
$numSpawns = $qmMap->map->spawn_count;
936962
$spawnArr = [];
937963

964+
// Assign colour & spawn locations for current QM player
965+
// Then again for other players below
938966
for ($i = 1; $i <= $numSpawns; $i++)
939967
{
940968
$spawnArr[] = $i;
@@ -947,20 +975,77 @@ private function set1v1QmSpawns($otherQmQueueEntries, $qmMatch, $qmPlayer, $expe
947975
Log::debug("QuickMatchService ** Random spawns selected for qmMap: '" . $qmMap->description . "', " . $spawnOrder[0] . "," . $spawnOrder[1]);
948976
}
949977

950-
951-
# Assign colour & spawn locations for current QM player
952-
# Then again for other players below
953-
$colorsArr = $this->getColorsArr(8, false);
978+
// Find the opponent player and check if both players submitted their preferred colors and make sure their is no observers.
979+
// Streamers might not be prepared for other colors than yellow/red. With someone observer and color info missing, we stick
980+
// to the old logic.
954981
$i = 0;
982+
$colorsArr = null;
955983

956-
if ($qmPlayer->isObserver() == false)
984+
foreach ($otherQmQueueEntries as $otherQmQueueEntry)
957985
{
958-
$qmPlayer->color = $colorsArr[$i];
959-
$qmPlayer->location = $spawnOrder[$i] - 1;
960-
$qmPlayer->save();
961-
$i++;
986+
$candidate = \App\Models\QmMatchPlayer::where("id", $otherQmQueueEntry->qmPlayer->id)->first();
987+
if ($candidate && !$candidate->isObserver())
988+
{
989+
$opponentPlayer = $candidate;
990+
$opponentsCount++;
991+
}
992+
}
993+
994+
$bothHaveColorPrefs = false;
995+
$p1Colors = null;
996+
$p2Colors = null;
997+
if ($opponentPlayer && $opponentsCount === 1)
998+
{
999+
$p1Colors = isset($qmPlayer->colors_pref) ? json_decode($qmPlayer->colors_pref, true) : null;
1000+
$p2Colors = isset($opponentPlayer->colors_pref) ? json_decode($opponentPlayer->colors_pref, true) : null;
1001+
$p1OppColors = isset($qmPlayer->colors_opponent_pref) ? json_decode($qmPlayer->colors_opponent_pref, true) : null;
1002+
$p2OppColors = isset($opponentPlayer->colors_opponent_pref) ? json_decode($opponentPlayer->colors_opponent_pref, true) : null;
1003+
1004+
$qmPlayerIsAnonymous = $qmPlayer->player->user->userSettings->getIsAnonymous();
1005+
$opponentPlayerIsAnonymous = $opponentPlayer->player->user->userSettings->getIsAnonymous();
1006+
1007+
if ($qmPlayerIsAnonymous && !$opponentPlayerIsAnonymous)
1008+
{
1009+
// Do what opponent wants.
1010+
$p1Colors = $p2OppColors;
1011+
$p1OppColors = $p2Colors;
1012+
}
1013+
else if (!$qmPlayerIsAnonymous && $opponentPlayerIsAnonymous)
1014+
{
1015+
// Do what player 1 wants.
1016+
$p2Colors = $p1OppColors;
1017+
$p2OppColors = $p1Colors;
1018+
}
9621019

963-
Log::debug("QuickMatchService ** Assigning Spot for " . $qmPlayer->player->username . "Color: " . $qmPlayer->color . " Location: " . $qmPlayer->location);
1020+
$bothHaveColorPrefs = is_array($p1Colors) && is_array($p2Colors) && is_array($p1OppColors) && is_array($p2OppColors);
1021+
}
1022+
1023+
if (!$matchHasObserver && $bothHaveColorPrefs)
1024+
{
1025+
// Determine best matching colors.
1026+
[$p1Color, $p2Color] = $this->setColors($p1Colors, $p1OppColors, $p2Colors, $p2OppColors);
1027+
$colorsArr = [$p1Color, $p2Color];
1028+
if ($qmPlayer->isObserver() == false)
1029+
{
1030+
$qmPlayer->color = $colorsArr[$i];
1031+
$qmPlayer->location = $spawnOrder[$i] - 1;
1032+
$qmPlayer->save();
1033+
$i++;
1034+
Log::debug("QuickMatchService ** Assigning Spot (prefs) for " . $qmPlayer->player->username . " Color: " . $qmPlayer->color . " Location: " . $qmPlayer->location);
1035+
}
1036+
}
1037+
else
1038+
{
1039+
// No preferred colors. Used old logic.
1040+
$colorsArr = $this->getColorsArr(8, false);
1041+
if ($qmPlayer->isObserver() == false)
1042+
{
1043+
$qmPlayer->color = $colorsArr[$i];
1044+
$qmPlayer->location = $spawnOrder[$i] - 1;
1045+
$qmPlayer->save();
1046+
$i++;
1047+
Log::debug("QuickMatchService ** Assigning Spot for " . $qmPlayer->player->username . " Color: " . $qmPlayer->color . " Location: " . $qmPlayer->location);
1048+
}
9641049
}
9651050

9661051
foreach ($otherQmQueueEntries as $otherQmQueueEntry)
@@ -1006,7 +1091,7 @@ private function set1v1QmSpawns($otherQmQueueEntries, $qmMatch, $qmPlayer, $expe
10061091
$otherQmPlayer->tunnel_id = $qmMatch->seed + $otherQmPlayer->color;
10071092
$otherQmPlayer->save();
10081093

1009-
if (!$otherQmPlayer->isObserver())
1094+
if (!$otherQmPlayer->isObserver() && $opponentPlayer === null)
10101095
{
10111096
$opponentPlayer = $otherQmPlayer;
10121097
$opponentsCount++;
@@ -1526,6 +1611,69 @@ private function getColorsArr($numPlayers, $randomize)
15261611
return array_slice($possibleColors, 0, $numPlayers);
15271612
}
15281613

1614+
/**
1615+
* Selects final colors for player 1 and 2 based on their preferences.
1616+
* @param int[] $prefColorsP1 The preferred colors of player 1
1617+
* @param int[] $prefOpponentColorsP1 What player 1 prefers for their opponent.
1618+
* @param int[] $prefColorsP2 The preferred colors of player 2.
1619+
* @param int[] $prefOpponentColorsP2 What player 2 prefers for their opponent.
1620+
* @return array{int,int} [colorPlayer1, colorPlayer2]
1621+
*/
1622+
private function setColors(array $prefColorsP1, array $prefOpponentColorsP1, array $prefColorsP2, array $prefOpponentColorsP2): array
1623+
{
1624+
// Penalty mapping: position in preference array -> penalty points.
1625+
$penalties = [0, 2, 5, 10];
1626+
1627+
// Generate all possible color combinations (0-3).
1628+
$bestCombinations = [];
1629+
$lowestPenalty = PHP_INT_MAX;
1630+
1631+
for ($colorP1 = 0; $colorP1 < 4; $colorP1++)
1632+
{
1633+
for ($colorP2 = 0; $colorP2 < 4; $colorP2++)
1634+
{
1635+
// Cannot assign same color to both players.
1636+
if ($colorP1 === $colorP2)
1637+
{
1638+
continue;
1639+
}
1640+
1641+
// Calculate penalty for player 1's color choice.
1642+
$posP1 = array_search($colorP1, $prefColorsP1);
1643+
$penaltyP1Color = $penalties[$posP1];
1644+
1645+
// Calculate penalty for player 1's opponent color preference.
1646+
$posP1Opponent = array_search($colorP2, $prefOpponentColorsP1);
1647+
$penaltyP1Opponent = $penalties[$posP1Opponent];
1648+
1649+
// Calculate penalty for player 2's color choice.
1650+
$posP2 = array_search($colorP2, $prefColorsP2);
1651+
$penaltyP2Color = $penalties[$posP2];
1652+
1653+
// Calculate penalty for player 2's opponent color preference.
1654+
$posP2Opponent = array_search($colorP1, $prefOpponentColorsP2);
1655+
$penaltyP2Opponent = $penalties[$posP2Opponent];
1656+
1657+
// Total penalty for this combination.
1658+
$totalPenalty = $penaltyP1Color + $penaltyP1Opponent + $penaltyP2Color + $penaltyP2Opponent;
1659+
1660+
// Track best combinations.
1661+
if ($totalPenalty < $lowestPenalty)
1662+
{
1663+
// Replace.
1664+
$lowestPenalty = $totalPenalty;
1665+
$bestCombinations = [[$colorP1, $colorP2]];
1666+
}
1667+
elseif ($totalPenalty === $lowestPenalty)
1668+
{
1669+
// Add.
1670+
$bestCombinations[] = [$colorP1, $colorP2];
1671+
}
1672+
}
1673+
}
1674+
1675+
return $bestCombinations[array_rand($bestCombinations)];
1676+
}
15291677

15301678
/**
15311679
*

cncnet-api/app/Models/QmMatchPlayer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class QmMatchPlayer extends Model
1717
'color',
1818
'actual_side',
1919
'location',
20+
'client_version'
2021
];
2122

2223
protected $_map_side_array = null;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::table('qm_match_players', function (Blueprint $table)
12+
{
13+
$table->json('colors_pref')->nullable()->after('chosen_side');
14+
$table->json('colors_opponent_pref')->nullable()->after('colors_pref');
15+
});
16+
}
17+
18+
public function down(): void
19+
{
20+
Schema::table('qm_match_players', function (Blueprint $table)
21+
{
22+
$table->dropColumn('colors_opponent_pref');
23+
$table->dropColumn('colors_pref');
24+
});
25+
}
26+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::table('qm_match_players', function (Blueprint $table) {
15+
$table->string('client_version', 32)->nullable()->after('ddraw_id');
16+
});
17+
}
18+
19+
/**
20+
* Reverse the migrations.
21+
*/
22+
public function down(): void
23+
{
24+
Schema::table('qm_match_players', function (Blueprint $table) {
25+
$table->dropColumn('client_version');
26+
});
27+
}
28+
};

0 commit comments

Comments
 (0)