Skip to content

Commit

Permalink
User map pool, games and engines in matchmaking (#601)
Browse files Browse the repository at this point in the history
- Grabs the relevant maps when a queue process starts
- Return this list of map in the response to matchmaking/list
- If the map list is empty, joining a queue should result in an internal_error
-  Pass the map info along to the pairing room process
- When a match is confirmed, the pairing room process picks a map at random and create the start script data
- Added games and engines to the queue process and their selection to pairing room, passing them to start_script and showing them in matchmaking/list
  • Loading branch information
L-e-x-o-n authored Feb 24, 2025
1 parent 73c068b commit 4307777
Show file tree
Hide file tree
Showing 10 changed files with 443 additions and 92 deletions.
3 changes: 3 additions & 0 deletions lib/teiserver/asset.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ defmodule Teiserver.Asset do
@spec get_map(String.t()) :: Asset.Map.t() | nil
defdelegate get_map(spring_name), to: MapQueries

@spec get_maps_for_queue(Teiserver.Matchmaking.queue_id()) :: [Asset.Map.t()] | nil
defdelegate get_maps_for_queue(spring_name), to: MapQueries

@spec get_all_maps() :: [Asset.Map.t()]
defdelegate get_all_maps(), to: MapQueries

Expand Down
10 changes: 10 additions & 0 deletions lib/teiserver/asset/queries/map_queries.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ defmodule Teiserver.Asset.MapQueries do
base_query() |> where_spring_name(spring_name) |> Repo.one()
end

@spec get_map(Teiserver.Matchmaking.queue_id()) :: [Asset.Map.t()] | nil
def get_maps_for_queue(queue) do
base_query() |> where_has_queue(queue) |> Repo.all()
end

@spec get_all_maps() :: [Asset.Map.t()]
def get_all_maps() do
base_query() |> Repo.all()
Expand All @@ -27,4 +32,9 @@ defmodule Teiserver.Asset.MapQueries do
from [map: map] in query,
where: map.spring_name == ^name
end

defp where_has_queue(query, queue) do
from [map: map] in query,
where: ^queue in map.matchmaking_queues
end
end
83 changes: 54 additions & 29 deletions lib/teiserver/matchmaking/pairing_room.ex
Original file line number Diff line number Diff line change
Expand Up @@ -117,40 +117,48 @@ defmodule Teiserver.Matchmaking.PairingRoom do
{:stop, :normal, state}

id ->
start_script = hardcoded_start_script(state)
engine = select_engine(state.queue.engines)
%{spring_game: game} = select_game(state.queue.games)
%{spring_name: map} = select_map(state.queue.maps)

case Teiserver.TachyonBattle.start_battle(id, start_script) do
{:error, reason} ->
QueueServer.disband_pairing(state.queue_id, self())
start_battle(state, id, engine, game, map)
end
end

for team <- state.teams, member <- team, p_id <- member.player_ids do
Teiserver.Player.matchmaking_notify_lost(p_id, {:server_error, reason})
end
defp start_battle(state, host_id, engine, game, map) do
start_script = start_script(state, engine, game, map)

{:stop, :normal, state}
case Teiserver.TachyonBattle.start_battle(host_id, start_script) do
{:error, reason} ->
QueueServer.disband_pairing(state.queue_id, self())

{:ok, host_data} ->
QueueServer.disband_pairing(state.queue_id, self())
for team <- state.teams, member <- team, p_id <- member.player_ids do
Teiserver.Player.matchmaking_notify_lost(p_id, {:server_error, reason})
end

ids =
for team <- state.teams, member <- team, p_id <- member.player_ids do
p_id
end
{:stop, :normal, state}

Logger.debug("Pairing completed for players " <> Enum.join(ids, ","))
{:ok, host_data} ->
QueueServer.disband_pairing(state.queue_id, self())

battle_start_data =
host_data
|> Map.put(:engine, %{version: start_script.engineVersion})
|> Map.put(:game, %{springName: start_script.gameName})
|> Map.put(:map, %{springName: start_script.mapName})
ids =
for team <- state.teams, member <- team, p_id <- member.player_ids do
p_id
end

for team <- state.teams, member <- team, p_id <- member.player_ids do
Teiserver.Player.battle_start(p_id, battle_start_data)
end
Logger.debug("Pairing completed for players " <> Enum.join(ids, ","))

{:stop, :normal, state}
battle_start_data =
host_data
|> Map.put(:engine, engine)
|> Map.put(:game, %{springName: game})
|> Map.put(:map, %{springName: map})

for team <- state.teams, member <- team, p_id <- member.player_ids do
Teiserver.Player.battle_start(p_id, battle_start_data)
end

{:stop, :normal, state}
end
end

Expand Down Expand Up @@ -217,12 +225,13 @@ defmodule Teiserver.Matchmaking.PairingRoom do
{:stop, :normal, state}
end

@spec hardcoded_start_script(state()) :: Teiserver.TachyonBattle.start_script()
defp hardcoded_start_script(state) do
@spec start_script(state(), %{version: String.t()}, String.t(), String.t()) ::
Teiserver.TachyonBattle.start_script()
defp start_script(state, engine, game, map) do
%{
engineVersion: "105.1.1-2590-gb9462a0 bar",
gameName: "Beyond All Reason test-26929-d709d32",
mapName: "Red Comet Remake 1.8",
engineVersion: engine.version,
gameName: game,
mapName: map,
startPosType: :ingame,
allyTeams: get_ally_teams(state)
}
Expand All @@ -240,4 +249,20 @@ defmodule Teiserver.Matchmaking.PairingRoom do
%{teams: teams}
end
end

# TODO implement some smarter engine/game selection logic here in the future, get first for now
@spec select_engine([%{version: String.t()}]) :: %{version: String.t()}
def select_engine(engines) do
Enum.at(engines, 0)
end

@spec select_game([%{spring_game: String.t()}]) :: %{spring_game: String.t()}
def select_game(games) do
Enum.at(games, 0)
end

@spec select_map([Teiserver.Asset.Map.t()]) :: Teiserver.Asset.Map.t()
def select_map(maps) do
Enum.random(maps)
end
end
47 changes: 43 additions & 4 deletions lib/teiserver/matchmaking/queue_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule Teiserver.Matchmaking.QueueServer do
require Logger
alias Teiserver.Matchmaking.{QueueRegistry, PairingRoom}
alias Teiserver.Data.Types, as: T
alias Teiserver.Asset

@typedoc """
member of a queue. Holds of the information required to match members together.
Expand Down Expand Up @@ -51,7 +52,10 @@ defmodule Teiserver.Matchmaking.QueueServer do
name: String.t(),
team_size: pos_integer(),
team_count: pos_integer(),
ranked: boolean()
ranked: boolean(),
engines: [%{version: String.t()}],
games: [%{spring_game: String.t()}],
maps: [Teiserver.Asset.Map.t()]
}

@type state :: %{
Expand Down Expand Up @@ -81,6 +85,9 @@ defmodule Teiserver.Matchmaking.QueueServer do
required(:name) => String.t(),
required(:team_size) => pos_integer(),
required(:team_count) => pos_integer(),
optional(:engines) => [%{version: String.t()}],
optional(:games) => [%{spring_game: String.t()}],
optional(:maps) => [Teiserver.Asset.Map.t()],
optional(:settings) => settings(),
optional(:members) => [member()]
}) :: state()
Expand All @@ -91,7 +98,10 @@ defmodule Teiserver.Matchmaking.QueueServer do
name: attrs.name,
team_size: attrs.team_size,
team_count: attrs.team_count,
ranked: true
ranked: true,
engines: Map.get(attrs, :engines, []),
games: Map.get(attrs, :games, []),
maps: Map.get(attrs, :maps, [])
},
settings: Map.merge(default_settings(), Map.get(attrs, :settings, %{})),
members: Map.get(attrs, :members, []),
Expand All @@ -108,7 +118,15 @@ defmodule Teiserver.Matchmaking.QueueServer do
QueueRegistry.via_tuple(queue_id, queue)
end

@type join_result :: :ok | {:error, :invalid_queue | :already_queued | :too_many_players}
@type join_result ::
:ok
| {:error,
:invalid_queue
| :already_queued
| :too_many_players
| :missing_engines
| :missing_games
| :missing_maps}

@doc """
Join the specified queue
Expand Down Expand Up @@ -162,7 +180,19 @@ defmodule Teiserver.Matchmaking.QueueServer do
:timer.send_interval(state.settings.tick_interval_ms, :tick)
end

{:ok, state}
{:ok, state, {:continue, :init_engines_games_maps}}
end

@impl true
def handle_continue(:init_engines_games_maps, state) do
# TODO Get engines and games from somewhere else
engines = state.queue.engines
games = state.queue.games
maps = Asset.get_maps_for_queue(state.id)

queue = %{state.queue | engines: engines, games: games, maps: maps}

{:noreply, %{state | queue: queue}}
end

@impl true
Expand All @@ -185,6 +215,15 @@ defmodule Teiserver.Matchmaking.QueueServer do
Enum.count(new_member.player_ids) > state.queue.team_size ->
{:reply, {:error, :too_many_players}, state}

Enum.empty?(state.queue.engines) ->
{:reply, {:error, :missing_engines}, state}

Enum.empty?(state.queue.games) ->
{:reply, {:error, :missing_games}, state}

Enum.empty?(state.queue.maps) ->
{:reply, {:error, :missing_maps}, state}

!is_queuing && !is_pairing ->
monitors =
Enum.map(new_member.player_ids, fn user_id ->
Expand Down
17 changes: 16 additions & 1 deletion lib/teiserver/player/tachyon_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,18 @@ defmodule Teiserver.Player.TachyonHandler do
queues =
Matchmaking.list_queues()
|> Enum.map(fn {qid, queue} ->
game_names = Enum.map(queue.games, fn game -> %{springName: game.spring_game} end)
map_names = Enum.map(queue.maps, fn map -> %{springName: map.spring_name} end)

%{
id: qid,
name: queue.name,
numOfTeams: queue.team_count,
teamSize: queue.team_size,
ranked: queue.ranked
ranked: queue.ranked,
engines: queue.engines,
games: game_names,
maps: map_names
}
end)

Expand All @@ -186,6 +192,15 @@ defmodule Teiserver.Player.TachyonHandler do
:too_many_players ->
%{reason: :invalid_request, details: "too many player for a playlist"}

:missing_engines ->
%{reason: :internal_error, details: "missing engine list"}

:missing_games ->
%{reason: :internal_error, details: "missing game list"}

:missing_maps ->
%{reason: :internal_error, details: "missing map list"}

x ->
%{reason: x}
end
Expand Down
2 changes: 0 additions & 2 deletions priv/tachyon/schema/matchmaking/list/request.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"$id": "https://schema.beyondallreason.dev/tachyon/matchmaking/list/request.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "MatchmakingListRequest",
"tachyon": {
"source": "user",
Expand Down
69 changes: 63 additions & 6 deletions priv/tachyon/schema/matchmaking/list/response.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"$id": "https://schema.beyondallreason.dev/tachyon/matchmaking/list/response.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "MatchmakingListResponse",
"tachyon": {
"source": "server",
Expand Down Expand Up @@ -29,14 +27,51 @@
"name": { "type": "string" },
"numOfTeams": { "type": "integer" },
"teamSize": { "type": "integer" },
"ranked": { "type": "boolean" }
"ranked": { "type": "boolean" },
"engines": {
"type": "array",
"items": {
"type": "object",
"properties": {
"version": { "type": "string" }
},
"required": ["version"]
}
},
"games": {
"type": "array",
"items": {
"type": "object",
"properties": {
"springName": {
"type": "string"
}
},
"required": ["springName"]
}
},
"maps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"springName": {
"type": "string"
}
},
"required": ["springName"]
}
}
},
"required": [
"id",
"name",
"numOfTeams",
"teamSize",
"ranked"
"ranked",
"engines",
"games",
"maps"
]
}
}
Expand All @@ -50,14 +85,36 @@
"name": "Duel",
"numOfTeams": 2,
"teamSize": 1,
"ranked": true
"ranked": true,
"engines": [{ "version": "2025.01.6" }],
"games": [
{
"springName": "Beyond All Reason test-27414-a84d7e6"
}
],
"maps": [
{ "springName": "Theta Crystals 1.3" },
{
"springName": "Comet Catcher Remake 1.8"
},
{ "springName": "Aurelia v4.1" }
]
},
{
"id": "1v1v1",
"name": "3 Way FFA",
"numOfTeams": 3,
"teamSize": 1,
"ranked": true
"ranked": true,
"engines": [{ "version": "2025.01.6" }],
"games": [
{
"springName": "Beyond All Reason test-27414-a84d7e6"
}
],
"maps": [
{ "springName": "Ghenna Rising 4.0.1" }
]
}
]
}
Expand Down
1 change: 1 addition & 0 deletions test/support/fixtures/asset.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
defmodule Teiserver.AssetFixtures do
require Logger
alias Teiserver.Asset
alias Teiserver.Repo

Expand Down
Loading

0 comments on commit 4307777

Please sign in to comment.