Skip to content

Commit

Permalink
Issue/#1051 Matchmaker violation config options (#1052)
Browse files Browse the repository at this point in the history
* Add config options for matchmaking violations

* Add deprecation comments for notice messages

* Add missing marks for performance tests
  • Loading branch information
Askaholic authored Mar 1, 2025
1 parent 92e97da commit b873f64
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 6 deletions.
11 changes: 11 additions & 0 deletions server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ def __init__(self):
# How many previous queue sizes to consider
self.QUEUE_POP_TIME_MOVING_AVG_SIZE = 5

self.LADDER_VIOLATIONS_ENABLED = True
# How many violations are needed to trigger a temporary ban from queuing
self.LADDER_VIOLATIONS_BAN_THRESHOLD = 2
# Number of seconds that each temporary ban lasts
self.LADDER_VIOLATIONS_BAN_DURATION = 1800
# Number of seconds that the first temporary ban lasts. By default the
# ban for the first violation is shorter than the following bans.
self.LADDER_VIOLATIONS_FIRST_BAN_DURATION = 600
# Number of seconds needed since last violation to reset the counter
self.LADDER_VIOLATIONS_RESET_TIME = 3600

self._defaults = {
key: value
for key, value in vars(self).items()
Expand Down
1 change: 1 addition & 0 deletions server/ladder_service/ladder_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ def start_search(
key=lambda v: v.get_ban_expiration()
).get_remaining()
)
# DEPRECATED: Use `search_timeout` instead
player.write_message({
"command": "notice",
"style": "info",
Expand Down
24 changes: 18 additions & 6 deletions server/ladder_service/violation_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import humanize

from server.config import config
from server.core import Service
from server.decorators import with_logger
from server.players import Player
Expand All @@ -24,13 +25,17 @@ def register(self):
self.time = datetime_now()

def get_ban_expiration(self) -> datetime:
if self.count < 2:
if self.count < config.LADDER_VIOLATIONS_BAN_THRESHOLD:
# No ban, expires as soon as it's registered
return self.time
elif self.count == 2:
return self.time + timedelta(minutes=10)
elif self.count == config.LADDER_VIOLATIONS_BAN_THRESHOLD:
return self.time + timedelta(
seconds=config.LADDER_VIOLATIONS_FIRST_BAN_DURATION,
)
else:
return self.time + timedelta(minutes=30)
return self.time + timedelta(
seconds=config.LADDER_VIOLATIONS_BAN_DURATION,
)

def get_remaining(self, now: Optional[datetime] = None) -> timedelta:
return self.get_ban_expiration() - (now or datetime_now())
Expand All @@ -42,8 +47,8 @@ def is_expired(self, now: Optional[datetime] = None) -> bool:
`get_ban_expiration`.
"""
now = now or datetime_now()
# TODO: Config?
return self.time + timedelta(hours=1) <= now
exp = self.time + timedelta(seconds=config.LADDER_VIOLATIONS_RESET_TIME)
return exp <= now

def to_dict(self) -> dict:
return {
Expand Down Expand Up @@ -76,6 +81,9 @@ def clear_expired(self):
self._clear_violation(player)

def register_violations(self, players: list[Player]):
if not config.LADDER_VIOLATIONS_ENABLED:
return

now = datetime_now()
for player in players:
violation = self.get_violation(player)
Expand All @@ -95,6 +103,7 @@ def register_violations(self, players: list[Player]):
violation.get_ban_expiration() - now
)
extra_text = f" You can queue again in {delta_text}"
# DEPRECATED: Use `search_violation` instead
player.write_message({
"command": "notice",
"style": "info",
Expand All @@ -107,6 +116,9 @@ def register_violations(self, players: list[Player]):
})

def get_violations(self, players: list[Player]) -> dict[Player, Violation]:
if not config.LADDER_VIOLATIONS_ENABLED:
return {}

now = datetime_now()
result = {}
for player in players:
Expand Down
28 changes: 28 additions & 0 deletions tests/integration_tests/test_matchmaker_violations.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
from datetime import datetime, timezone

from server.config import config
from tests.utils import fast_forward

from .conftest import connect_and_sign_in, read_until_command
Expand Down Expand Up @@ -254,3 +255,30 @@ async def test_violation_persisted_across_parties(mocker, lobby_server):
"expires_at": "2022-02-05T00:10:00+00:00"
}]
}


@fast_forward(360)
async def test_violation_config_disabled(mocker, monkeypatch, lobby_server):
monkeypatch.setattr(config, "LADDER_VIOLATIONS_ENABLED", False)
mocker.patch(
"server.ladder_service.violation_service.datetime_now",
return_value=datetime(2022, 2, 5, tzinfo=timezone.utc)
)
_, host, _, guest = await queue_players_for_matchmaking(lobby_server)

# Players never receive a timeout for failing matches
for _ in range(5):
await read_until_command(host, "match_cancelled", timeout=120)
await read_until_command(guest, "match_cancelled", timeout=10)
await read_until_command(host, "game_info", timeout=10, state="closed")
# DEPRECATED: Because the game sends a game_launch to the guest after the
# host times out, we need to simulate opening and closing the game before
# we can queue again. If we wait for the timeout, our violations expire.
await open_fa(guest)
await guest.send_message({
"target": "game",
"command": "GameState",
"args": ["Ended"]
})
await start_search(host)
await start_search(guest)
1 change: 1 addition & 0 deletions tests/unit_tests/test_matchmaker_algorithm_bucket_teams.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def test_make_teams_single_2v2_small_pool(player_factory):
assert p2.ratings[RatingType.LADDER_1V1][0] > 900


@pytest.mark.performance
def test_make_buckets_performance(bench, player_factory):
NUM_SEARCHES = 1000
searches = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ def test_matchmaker(player_factory):
assert top_player not in match_pair


@pytest.mark.performance
def test_matchmaker_performance(player_factory, bench, caplog):
# Disable debug logging for performance
caplog.set_level(logging.INFO)
Expand Down

0 comments on commit b873f64

Please sign in to comment.