PvP-Matchmaking-Algorithmus

Aus Guild Wars 2 Wiki
Zur Navigation springen Zur Suche springen

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)