PvP-Matchmaking-Algorithmus
Der PvP-Matchmaking-Algorithmus bestimmt die Paarungen für Spieler im strukturierten PvP.
Bewertungen[Bearbeiten]
Im Zentrum des PvP-Matchmaking-Algorithmus steht die Glicko2 Bewertung für Matchmaking (Matchmaking Rating oder kurz MMR). Dieses Rating, das einen Annäherungswert eures Fähigkeitsniveaus darstellt, hilft euch dabei, andere Spieler von ähnlicher Stärke zu finden. Zusätzlich zu zwei Grundwerten (je einen für Arenen mit und ohne Rangwertung) gibt es auch ein Rating für jede Klasse. Indem wir mehrere Bewertungsaspekte heranziehen, wollen wir die Spieler dazu ermutigen, auch mit Klassen zu experimentieren, die sie normalerweise nicht so häufig spielen.
Anstelle der anderen gängigen Alternative Elo haben wir uns für das „Glicko“-System entschieden. So wie Elo zeichnet auch Glicko das MMR jedes Spielers auf und aktualisiert es mit fortlaufender Spielzeit. Glickos größte Verbesserung gegenüber seinem Vorgänger ist die Erweiterung um eine Ratings Deviation (RD; Rating-Abweichung), welche die Zuverlässigkeit des Ratings misst. Durch die Verwendung von RD kann der Matchmaking-Algorithmus die Werte von Spielern, über die er wenig oder unvollständige Informationen besitzt, ausgleichen.
Zusätzlich gibt es noch einen Volatilitätswert, der das Maß an Schwankung in einem Spieler-Rating anzeigt. Je höher die Volatilität, desto größer die Rating-Schwankungen. So wie auch eure Spielweise ändert die Volatilität sich im Laufe der Zeit. In Zeiten der Stabilität sollte eure Volatilität niedrig sein und umgekehrt. Dies soll bezwecken, dass sich das System so schnell wie möglich auf euer passendes Rating ausrichtet.
Falls ihr nach längerer Inaktivität ein wenig eingerostet seid, ist das System ist ebenfalls darauf eingestellt, eure RD zu erhöhen. (Siehe unten bei Ratings/@period and Ratings/@max-periods)
Konfiguration[Bearbeiten]
<Ratings period="3d" max-periods="20"> <Rating default="1500" min="100" max="5000" max-change="300" profession-ratio="0"/> <Deviation default="350" min="30" max="350" /> <Volatility default="0.06" min="0.04" max="0.08" system-constant="0.5" /> </Ratings> <Ratings type="Unranked" reset="2014-02-01"/> <Ratings type="Ranked" reset="2014-02-01"/>
Element (XPath) | Beschreibung |
---|---|
Ratings/@type | Falls angegeben, zeigt es Überbrückungswerte nach Typen an. |
Ratings/@reset | Alle Rating-Daten mit einem Zeitstempel vor diesem Datum werden auf den Standardwert zurückgesetzt. |
Ratings/@period | Dauer der Inaktivität für einen einzelnen Zeitraum. |
Ratings/@max-periods | Anzahl der Zeiträume der Inaktivität, bevor die Rating-Abweichung des Spielers vom Minimal- auf den Maximalwert gesetzt wird. |
Rating/@default | Standard-Rating, das verwendet wird, wenn keine Daten verfügbar sind. |
Rating/@min | Kleinstmögliches Rating, jeder Wert darunter wird auf diesen Wert angehoben. |
Rating/@max | Größtmögliches Rating, jeder Wert darüber wird auf diesen Wert gekürzt. |
Rating/@max-change | Der größtmögliche, absolute Wert, um der das Rating durch ein einziges Spiel verändert werden kann. |
Rating/@profession-ratio | Bestimmt die Balance zwischen Klassenwertung und der Grundwertung. 0 bedeutet, nur die Grundwertung benutzt wird, und 1, dass nur die Klassenwertung genutzt wird. |
Deviation/@default | Standard-Rating-Abweichung, die verwendet wird, wenn keine Daten verfügbar sind. |
Deviation/@min | Kleinstmögliche Abweichung, jeder Wert darunter wird auf diesen Wert angehoben. |
Deviation/@max | Größtmögliche Abweichung, jeder Wert darüber wird auf diesen Wert gekürzt. |
Volatility/@default | Standard-Rating-Volatilität, die verwendet wird, wenn keine Daten verfügbar sind. |
Volatility/@min | Kleinstmögliche Volatilität, jeder Wert darunter wird auf diesen Wert angehoben. |
Volatility/@max | Größtmögliche Volatilität, jeder Wert darüber wird auf diesen Wert gekürzt. |
Volatility/@system-constant | Glicko2-Einstellungsparameter, der die Veränderung der Volatilität über die Zeit beschränkt. |
Matchmaking[Bearbeiten]
Matchmaking ist der Vorgang, bei dem die Spieler so in Gruppen eingeteilt werden, dass sie unterhaltsame und wettkampfmäßige Begegnungen erleben. Das System nutzt eine zweiphasige, punktebasierte Suchmethode, die verschiedene Kriterien in Betracht zieht. Eine punktebasierte Suchmethode wurde gewählt, weil sie einen guten Kompromiss zwischen den oft gegensätzlichen Zielen einer guten Match-Qualität und möglichst kurzer Wartezeiten darstellt.
Zu Beginn des Matchmakings versucht das System, ein Match zu finden, das auf die ersten Iteration/@rosters Spielerlisten (Gruppen) in der Warteschlange zugeschnitten ist. Wenn kein Match erstellt werden kann, werden diese Spieler ans Ende der Schlange gesetzt, damit sichergestellt werden kann, dass andere Spieler die Möglichkeit auf ein auf sie zugeschnittenes Spiel haben. Obwohl das auf den ersten Blick unfair erscheinen mag, hat sich herausgestellt, dass sich die Wartezeiten für alle Spieler verkürzen.
Die erste Phase, auch Filtern genannt, sammelt Spieler basierend auf ihrem derzeitigen MMR. Der eigentliche Zweck dieser Phase besteht darin, die Anzahl der für ein Match in Betracht kommenden Spieler zu reduzieren. Außerdem wird dadurch bewirkt, dass das Match dem Fähigkeitsniveau der Spieler entspricht. Mit der Zeit wird euer Spieler-Rating manipuliert (Padding). Das kann vielleicht die Match-Qualität herabsetzen, hilft aber dabei, dass Ausreißer ebenfalls Matches zugeteilt bekommen.
Die zweite Phase des Algorithmus ist die Bewertungsphase. Während dieser Phase wird jeder Spieler mit jedem anderen Spieler, der fürs Matchmaking in Betracht gezogen wird, verglichen. Die Kriterien, die in dieser Phase verwendet werden beinhalten: Rating, Rang, Gruppengröße, Klasse, Ranglistenposition und Unehre. Mit jedem Kriterium sucht das System nach Spielern, die so nah wie möglich am Durchschnitt der bereits ausgewählten Spieler liegen. Außerdem versucht das System, die Anzahl doppelter Klassen so gering wie möglich zu halten.
Konfiguration[Bearbeiten]
<Filter> <Iteration rosters="50" limit="50ms"/> <Potentials min="20" max="500"/> <Rating padding="10" start="30s" end="4m"/> </Filter> <Scoring type="Team"> <Age seconds="15"/> <RosterSize distance="-500" perfect-fit="200"/> <Rank distance="-10"/> <Rating distance="-5"/> <Profession max="2" common="-500" unique="500"/> <Dishonor distance="-100" stack="-50"/> </Scoring>
Element (XPath) | Beschreibung |
---|---|
Filter/Iteration/@rosters | Die Anzahl der Spielerlisten, für die während des Filterns selbsterstellte Matches erstellt werden soll. |
Filter/Iteration/@limit | Der maximale Zeitraum, in dem der Server pro Wiederholung versuchen soll, Matches zu generieren. Das ist ein Performance-Sicherungsvorgang, um die Aktivität des Servers zu gewährleisten. |
Filter/Potentials/@min | Die kleinstmögliche Anzahl an Spielerlisten, die die Filterphase passieren müssen, bevor versucht wird, ein Match zu erstellen. |
Filter/Potentials/@max | Die größtmögliche Anzahl an Spielerlisten, die in der Filterphase zusammengeführt werden. Das ist ein Performance-Sicherungsvorgang, um die Aktivität des Servers zu gewährleisten. |
Filter/Rating/@padding | Nachdem Filter/Rating/@start vergangen sind, wird Padding für jede Sekunde hinzugefügt, die ihr in der Schlange wartet. Das ist ein Sicherungssystem für Ausreißer, um sicherzugehen, dass jeder Spieler an einem Match teilnehmen kann.
|
Filter/Rating/@start | Erst nach diesem Zeitraum wird dem Spielerlisten-Rating Padding hinzugefügt. |
Filter/Rating/@end | Nachdem so viel Zeit vergangen ist, wird kein zusätzliches Padding hinzugefügt. Das ist ein Sicherungssystem um zu verhindern, dass die Match-Qualität tiefer sinkt als beabsichtigt. |
Scoring/@type | Der verwendete Bewertungsalgorithmus. Team bewertet Spielerlisten auf einer pro-Team-Basis, d. h., dass nur innerhalb des Teams nach doppelten Klassen gesucht wird, aber nicht im gesamten Match.
|
Scoring/Age/@seconds | Hinzugefügte oder abgezogene Punkte für jede Sekunde, die eine Spielerliste warten muss. Das ist ein Sicherungssystem für Ausreißer, um sicherzugehen, dass niemand zu lange warten muss. |
Scoring/RosterSize/@distance | Hinzugefügte oder abgezogene Punkte, basierend auf der Diskrepanz zwischen der Größe der potenziellen Spielerliste und der maximalen Spielerlistengröße aller ausgewählten Spielerlisten – beide Teams eingeschlossen. |
Scoring/RosterSize/@perfect-fit | Hinzugefügte oder abgezogene Punkte, falls die Größe der potenziellen Spielerliste mit der exakten Anzahl an Spielern übereinstimmt, die benötigt wird, um die leeren Plätze im Team zu besetzen. |
Scoring/Rank/@distance | Hinzugefügte oder abgezogene Punkte, basierend auf der Diskrepanz zwischen dem Durchschnittsrang der potenziellen Spielerliste und dem Durchschnittsrang aller ausgewählten Spielerlisten – beide Teams eingeschlossen. |
Scoring/Rating/@distance | Hinzugefügte oder abgezogene Punkte, basierend auf der Diskrepanz zwischen dem effektiven Durchschnittsrating (d. h. Rating - Abweichung) der potenziellen Spielerliste und dem effektiven Durchschnittsrating aller ausgewählten Spielerlisten – beide Teams eingeschlossen. |
Scoring/Ladder/@distance | Hinzugefügte oder abgezogene Punkte, basierend auf der Diskrepanz zwischen den durchschnittlichen Rangpunkten der potenziellen Spielerliste und den durchschnittlichen Rangpunkten aller ausgewählten Spielerlisten – beide Teams eingeschlossen. Nur für bewertete Arena. |
Scoring/Profession/@max | Größte Anzahl an Klassen, die es in einem Team geben soll. |
Scoring/Profession/@unique | Hinzugefügte oder abgezogene Punkte für jede einzigartige Klasse, unter Scoring/Profession/@max , die aus der potenziellen Spielerliste dem Team hinzugefügt würde.
|
Scoring/Profession/@common | Hinzugefügte oder abgezogene Punkte für jede doppelte Klasse, bei oder über Scoring/Profession/@max , die aus der potenziellen Spielerliste dem Team hinzugefügt würde.
|
Scoring/Dishonor/@distance | Hinzugefügte oder abgezogene Punkte, basierend auf der Diskrepanz zwischen der gesamten Unehre der potenziellen Spielerliste und der gesamten Unehre aller ausgewählten Spielerlisten – beide Teams eingeschlossen. |
Scoring/Dishonor/@stack | Hinzugefügte oder abgezogene Punkte pro Stapel Unehre, den ein Spieler einer potenziellen Spielerliste aufweist. |
Pseudo-Code[Bearbeiten]
def gatherPotentials(queue, target, config): potentials = [] for roster in queue: if roster.ratingLow > target.ratingHigh: continue if roster.ratingHigh < target.ratingLow: continue potentials.append(roster) if len(potentials) >= config.filter.potentials.max: break return potentials def shouldPopulateRed(red, blue, config): if red.players >= config.teamSize: return False if blue.players >= config.teamSize: return True return red.ratingLow < blue.ratingLow def scoreRoster(roster, team, maxLadder, maxRosterSize, config): score = 0 # adjust score by time queued score += roster.age * config.age.seconds # adjust score by lower-bound rating distance distance = abs(team.ratingLow - roster.ratingLow) score += distance * config.rating.distance # adjust score by rank distance distance = abs(team.rank - roster.rank) score += distance * config.rank.distance # adjust score by ladder distance distance = abs(maxLadder - roster.ladder) score += distance * config.ladder.distance # adjust score by roster size distance = abs(maxRosterSize - roster.players) score += distance * config.rosterSize.distance if roster.players == maxRosterSize: score += config.rosterSize.perfectFit # adjust score by dishonor distance = abs(team.dishonor - roster.dishonor) score += distance * config.dishonor.distance score += roster.dishonor * config.dishonor.stack # adjust score by profession count for profession in list_of_all_professions: count = roster.countProfessions(profession) if count == 0: continue count += team.countProfessions(professions) if count >= config.professions.max: count -= config.professions.max - 1; score += count * config.professions.common else: count = config.professions.max - count score += count * config.professions.unique return score def pickRoster(team, maxLadder, maxRosterSize, potentials, config): best = None playersNeeded = config.teamSize - team.players for roster in potentials: if roster.players > playersNeeded: continue roster.score = scoreRoster(roster, team, maxLadder, maxRosterSize, config.scoring) if best is None or best.score < roster.score: best = roster return best def populateMatch(target, potentials, config): red = [] blue = [] # add target roster to red team red.append(target) maxRosterSize = target.players maxLadder = target.ladder while red.players < config.teamSize or blue.players < config.teamSize: chosen = None if shouldPopulateRed(red, blue, config): chosen = pickRoster(red, maxLadder, maxRosterSize, potentials, config) else: chosen = pickRoster(blue, maxLadder, maxRosterSize, potentials, config) if chosen is None: break maxLadder = max(maxLadder, chosen.ladder) maxRosterSize = max(maxRosterSize, chosen.players) selected = [] for roster in red: roster.team = 'red' selected.append(roster) for roster in blue: roster.team = 'blue' selected.append(roster) return selected def withdrawRosters(queue, target, config): # gather rosters that could be potentially matched potentials = gatherPotentials(queue, target, config) if len(potentials) < config.potentials.min: return None return populateMatch(target, potentials, config) def rollbackRosters(queue, rosters): for roster in rosters: roster.team = None queue.append(roster) def gatherRostersForCustomMatch(queue, config): rosters = [] for roster in queue: rosters.append(roster) if len(rosters) >= config.filter.rosters: break return rosters def createMatches(queue, config): rosters = gatherRostersForCustomMatch(queue, config) failed = [] while len(rosters) > 0: roster = rosters.pop() queue.remove(roster) selected = withdrawRosters(queue, roster, config) if selected is None or selected.players != config.teamSize * 2: failed.append(roster) selected.remove(roster) rollbackRosters(queue, selected) else: sendCreateMatch(selected) # move rosters we couldn't find match for to the end of the queue for roster in failed: queue.append(roster)
Rangliste[Bearbeiten]
In der Rangliste sind alle Spieler vertreten, die am kompetitiven PvP teilnehmen. Euer Rang auf dieser Liste richtet sich nach den Punkten, die ihr während einer Saison erhaltet.
Punkte erhaltet ihr für gutes und häufiges Spielen und manchmal auch fürs Verlieren. Auch wenn ihr ein Match nicht mehr drehen könnt, könnt ihr trotzdem Punkte erhalten, wenn ihr weiterhin euer Bestes gebt. Wenn ihr euch in einem unausgeglichenen Match wiederfindet, müsst ihr nichts befürchten, denn ihr habt nur wenige Punkte zu verlieren. Und falls ihr doch gewinnen solltet, wird der Punktgewinn umso höher ausfallen. Das gilt natürlich auch umgekehrt: Wiegt euch nicht in Sicherheit, wenn ihr einem leichten Match beitretet. Für einen Gewinn erhaltet ihr zwar Punkte, aber falls ihr verliert, bekommt ihr so einige abgezogen.
(Unter Match-Vorhersage ist aufgelistet, wie das System die Gewinnchancen bestimmt.)
Konfiguration[Bearbeiten]
<Ladder default="0" min="0" max="1000000" leaderboard-points="1"> <Matrix odds="0.0"> <Score min="0" points="-1"/> <Score min="200" points="0"/> <Score min="300" points="1"/> <Score min="400" points="2"/> <Score min="500" points="3"/> </Matrix> <Matrix odds="0.2"> <Score min="0" points="-1"/> <Score min="300" points="0"/> <Score min="400" points="1"/> <Score min="500" points="2"/> </Matrix> <Matrix odds="0.4"> <Score min="0" points="-1"/> <Score min="400" points="0"/> <Score min="500" points="1"/> </Matrix> <Matrix odds="0.6"> <Score min="0" points="-2"/> <Score min="300" points="-1"/> <Score min="400" points="0"/> <Score min="500" points="1"/> </Matrix> <Matrix odds="0.8"> <Score min="0" points="-3"/> <Score min="200" points="-2"/> <Score min="300" points="-1"/> <Score min="400" points="0"/> <Score min="500" points="1"/> </Matrix> </Ladder> <Ladder type="Competitive" start="2014-11-12" end="2014-11-30" leaderboard="PvPLadder"/>
Element (XPath) | Beschreibung |
---|---|
Ladder/@type | Falls angegeben, zeigt es an. dass dieses Element Überbrückungswerte nach Typen enthält.overrides. |
Ladder/@start | Alle Spielergebnisse vor diesem Datum werden nicht in der Rangliste berücksichtigt. |
Ladder/@end | Alle Spielergebnisse an oder nach diesem Datum werden nicht in der Rangliste berücksichtigt. |
Ladder/@default | Standardpunkte, die verrechnet werden, wenn keine Daten verfügbar sind. |
Ladder/@min | Kleinstmögliche Anzahl an Ranglistenpunkten. |
Ladder/@max | Größtmögliche Anzahl an Ranglistenpunkten. |
Ladder/@leaderboard | Die externe Rangliste, die mit dieser Rangliste in Zusammenhang steht. |
Ladder/@leaderboard-points | Kleinste Anzahl an benötigten Punkten, bevor die Spielerdaten an die Rangliste übermittelt werden. |
Matrix/@odds | Niedrigste Schwelle für odds. Zusammen mit Matrix/Score/@min ergeben sich daraus die zu gewinnenden Ranglistenpunkte.
|
Score/@min | Niedrigste Punkteschwelle, die benötigt wird, um Score/@points Ranglistenpunkte zu verdienen.
|
Score/@points | Punkte, die vergeben werden, wenn die Schwellen von sowohl Matrix/@odds als auch Score/@min erreicht wurden. Nur die höchsten Schwellenwerte zählen.
|
Pseudo-Code[Bearbeiten]
def getPoints(oddsOfVictory, finalScore, config): currentDate = Time.now() if currentDate < config.startDate or currentDate >= config.endDate: return 0 bestMatrix = null for matrix in config.ladderMatrix: if matrix.odds > oddsOfVictory: continue if bestMatrix is null or matrix.odds > bestMatrix.odds: bestMatrix = matrix bestScore = null for score in bestMatrix.scores: if score.min > finalScore: continue if bestScore is null or score.min > bestScore.min bestScore = score return bestScore.points def processGame(player, game, config): oddsOfVictory = predictionToOddsOfVictory(game.prediction, player.team) finalScore = game.score[player.team] if game.result == 'desertion': finalScore = 0 prevPoints = player.ladderPoints pointsAwarded = getPoints(oddsOfVictory, finalScore, config) if game.result == 'victory': pointsAwarded = max(1, pointsAwarded) player.ladderPoints += pointsAwarded if player.ladderPoints >= config.leaderboardPoints: sendLeaderboardUpdate(config.leaderboard, player.id, player.ladderPoints) else if prevPoints >= config.leaderboardPoints: sendLeaderboardRemove(config.leaderboard, player.id)
Match-Vorhersage[Bearbeiten]
Das System versucht den Ausgang eines Matchs anhand derselben Daten vorherzusagen, die im Matchmaking verwendet werden. Davon scheinen die zwei größten entscheidenden Faktoren der Unterschied zwischen den Bewertungen der einzelnen Teams und die Größe ihrer größten Gruppe zu sein.
Derzeit wird auch der Rang zu Testzwecken einkalkuliert, dies wird aber möglicherweise wieder entfernt.
Konfiguration[Bearbeiten]
<Prediction> <Rank method="Spread" spread="40" weight="1"/> <Rating method="Spread" spread="200" weight="5"/> <Roster method="Spread" spread="4" weight="2"/> </Prediction>
Element (XPath) | Beschreibung |
---|---|
Prediction/*/@method | Gibt an, welche Berechnungsmethode verwendet wird. |
Prediction/*/@spread | Größtmögliche Differenz, die berechnet wird. |
Prediction/*/@weight | Den Einfluss (höhere Zahlen bedeuten höheren Einfluss), den diese Berechnung auf die finale Vorhersage hat. |
Prediction/Rank | Wenn gesetzt, wird der durchschnittliche Rang eines jeden Teams in der Berechnung berücksichtigt. |
Prediction/Rating | Wenn gesetzt, wird das effektive Durchschnittsrating (d. h. Rating-Abweichung) eines jeden Teams in der Berechnung berücksichtigt. |
Prediction/Roster | Wenn gesetzt, wird die maximale Spielerlistengröße eines jeden Teams in der Berechnung berücksichtigt. |
Pseudo-Code[Bearbeiten]
// Return the spread between two values normalized to -1..1. def calculateSpread (red, blue, maxSpread): spread = (blue - red) / maxSpread return clamp(spread, -1, +1); // Returns a prediction value between -1 and 1, where -1 means red dominate, // and +1 means blue dominate. def predict(red, blue, config): rank = calculateSpread(red.averageRank, blue.averageRank, config.rankSpread) * config.rankWeight rating = calculateSpread(red.averageRatingLow, blue.averageRatingLow, config.ratingSpread) * config.ratingWeight roster = calculateSpread(red.maxRosterSize, blue.maxRosterSize, config.rosterSpread) * config.rosterWeight totalScore = rank + rating + roster totalWeight = config.rankWeight + config.ratingWeight + config.rosterWeight return clamp(totalScore / totalWeight, -1, +1) // Returns the team's odds of victory as a ratio of 0..1, where 0 means // minimal chance of victory and 1 means minimal chance of defeat. def predictionToOddsOfVictory (prediction, team): normalized = prediction / 2 + 1; if team == 'red': return 1 - normalized else: return normalized
Unehre[Bearbeiten]
Unehre ist eine der Methoden, die sportliches Verhalten fördern soll. Das Verhalten wird mittel- und langfristig durch Stapel gekennzeichnet. Jeder Stapel steht für eine Dauer, die sich mit der Zeit verkürzt. Jedes Mal, wenn ihr Unehre erhaltet, werdet ihr außerdem zu einer Auszeit gezwungen. Die Dauer der Auszeit erhöht sich exponentiell, basierend auf der Anzahl der bereits vorhandenen Stapel. Anders ausgedrückt heißt das, dass euer erstes Vergehen eine kurze Sperre bedeutet, während euer 20. Verstoß eine sehr viel längere Auszeit nach sich zieht.
Während der Auszeit könnt ihr zwar nicht in Arenen mit oder ohne Rangwertung spielen, aber in selbsterstellten Arenen seid ihr spielberechtigt.
Unehre beeinflusst das Matchmaking dahingehend, dass ihr bevorzugt mit anderen Spielern spielt, die ebenfalls Unehre haben. Das stellt keine separate Warteschlange dar, sondern ist lediglich eine „Empfehlung“ an das Matchmaking-System.
Es ist möglich, Unehre-Stapel zu haben, ohne dass ihr von einer Auszeit betroffen seid, denn Unehre vergeht viel langsamer.
Konfiguration[Bearbeiten]
<Dishonor stack-duration="15m" timeout-duration="30s" timeout-exponent="1.5" timeout-rounding="1m"> <Penalty reason="Abandon" stacks="10"/> <Penalty reason="QueueDodge" stacks="4"/> <Penalty reason="Banned" stacks="1000000"/> </Dishonor>
Element (XPath) | Beschreibung |
---|---|
Dishonor/@stack-duration | Die Zeit, die ein einzelner Stapel Unehre braucht, um zu verfallen. |
Dishonor/@timeout-duration | Zeitspanne, pro Stapel Unehre hoch @timeout-exponent , die zur Auszeit des Spielers addiert wird.
|
Dishonor/@timeout-exponent | Verwendeter Exponent, wenn Unehre-Stapel mit Dishonor/@timeout multipliziert werden.
|
Dishonor/@timeout-rounding | Zusätzliche Auszeit, wird zum nächsten @timeout-rounding Intervall gerundet, bevor sie zur Auszeit des Spielers addiert wird. Stellt auch die geringste Auszeit dar, die erlangt werden kann.
|
Penalty/@reason | Der Grund, aus dem Unehre erlangt wurde. Abandon: Ein Match vorzeitig verlassen. QueueDodge: Für das Verlassen oder die Versäumnis, die Bereitschaft zum Spielen zu bestätigen. Banned: Wenn ein GM entschieden hat, dass ihr nicht mehr in Arenen mit oder ohne Rangwertung spielen dürft. |
Penalty/@stacks | Anzahl der Unehre-Stapel, die ein Spieler erhält. |
Pseudo-Code[Bearbeiten]
def roundInterval(value, interval): return max(interval, round(value / interval) * interval) def applyDishonor(player, penalty, config): player.stacks += penalty.stacks newTimeout = pow(player.stacks, config.timeoutExponent) * config.timeoutDuration player.timeout += roundInterval(newTimeout, config.timeoutRounding)