1
0
mirror of synced 2025-01-18 22:24:04 +01:00

Partially (maybe fully?) working implementation of MGA matching.

This commit is contained in:
Jennifer Taylor 2023-08-07 00:56:43 +00:00
parent 52819fcce7
commit 53144b3123
2 changed files with 141 additions and 47 deletions

View File

@ -1,10 +1,11 @@
# vim: set fileencoding=utf-8
import base64
import random
from typing import List
from bemani.backend.mga.base import MetalGearArcadeBase
from bemani.backend.ess import EventLogHandler
from bemani.common import ID, Profile, VersionConstants, Time
from bemani.common import Profile, VersionConstants, Time
from bemani.data import UserID
from bemani.protocol import Node
@ -151,26 +152,106 @@ class MetalGearArcade(
if userid is None:
root = Node.void("matching")
root.add_child(
Node.s32("result", 1)
) # Set to guest mode so matching doesn't happen.
Node.s32("result", -1)
) # Set to error so matching doesn't happen.
return root
# Create a lobby with this player as the "host".
shop_id = ID.parse_machine_id(request.child_value("data/locationid"))
# Game sends how long it intends to wait, so we should use that.
wait_time = request.child_value("data/waittime")
# Look up active lobbies, see if there was a previous one for us.
# Matchmaking takes at most 60 seconds, so assume any lobbies older
# than this are dead.
lobbies = self.data.local.lobby.get_all_lobbies(
self.game, self.version, max_age=wait_time
)
previous_hosted_lobbies = [True for uid, _ in lobbies if uid == userid]
previous_joined_lobbies = [
(uid, lobby) for uid, lobby in lobbies if userid in lobby["participants"]
]
# See if there's a random lobby we can be slotted into. Don't choose potentially
# our old one, since it will be overwritten by a new entry, if we were ever a host.
nonfull_lobbies = [
(uid, lobby) for uid, lobby in lobbies if len(lobby["participants"]) < 8
]
# Make sure to put our session information somewhere that we can find again.
self.data.local.lobby.put_play_session_info(
self.game,
self.version,
userid,
{
"joinip": request.child_value("data/joinip"),
"joinport": request.child_value("data/joinport"),
"localip": request.child_value("data/localip"),
"localport": request.child_value("data/localport"),
"pcbid": self.config.machine.pcbid,
},
)
play_session_info = self.data.local.lobby.get_play_session_info(
self.game,
self.version,
userid,
)
if (nonfull_lobbies or previous_joined_lobbies) and not previous_hosted_lobbies:
if previous_joined_lobbies:
# If we're already "in" a lobby, we should go back to that one.
uid, lobby = previous_joined_lobbies[0]
else:
# Pick a random one, assign ourselves to it.
uid, lobby = random.choice(nonfull_lobbies)
# Look up the host's information.
host_play_session_info = self.data.local.lobby.get_play_session_info(
self.game,
self.version,
uid,
)
# Join this lobby.
participants = set(lobby["participants"])
participants.add(userid)
lobby["participants"] = list(participants)
self.data.local.lobby.put_lobby(self.game, self.version, uid, lobby)
# Now that we've joined the lobby, tell the game about our host ID.
root = Node.void("matching")
root.add_child(
Node.s32("result", 1)
) # Setting this to 1 makes the client consider itself a guest and join a host.
root.add_child(Node.s64("hostid", lobby.get_int("id")))
root.add_child(
Node.string("hostip_g", host_play_session_info.get_str("joinip"))
)
root.add_child(
Node.s32("hostport_g", host_play_session_info.get_int("joinport"))
)
root.add_child(
Node.string("hostip_l", host_play_session_info.get_str("localip"))
)
root.add_child(
Node.s32("hostport_l", host_play_session_info.get_int("localport"))
)
return root
# The game does weird things if you let it wait as long as its own countdown,
# so subtract a bit of wiggle-room from the wait time as reported by the game.
wait_time -= 1
# Create a lobby with this player as the "host", since there are no non-full lobbies
# or we were previously a host and want to be one again.
self.data.local.lobby.put_lobby(
self.game,
self.version,
userid,
{
"matchgrp": request.child_value("data/matchgrp"),
"joinip": request.child_value("data/joinip"),
"joinport": request.child_value("data/joinport"),
"localip": request.child_value("data/localip"),
"localport": request.child_value("data/localport"),
"waituser": request.child_value("data/waituser"),
"waittime": request.child_value("data/waittime"),
"pcbid": self.config.machine.pcbid,
"lid": shop_id,
"waittime": wait_time,
"createtime": Time.now(),
"participants": [userid],
},
)
lobby = self.data.local.lobby.get_lobby(
@ -183,61 +264,74 @@ class MetalGearArcade(
root = Node.void("matching")
root.add_child(
Node.s32("result", 0)
) # Setting this to 1 makes the game think we're a guest instead of host.
) # Setting this to 0 makes the client consider itself a host and listen for guests.
root.add_child(Node.s64("hostid", lobby.get_int("id")))
root.add_child(Node.string("hostip_g", lobby.get_str("joinip")))
root.add_child(Node.s32("hostport_g", lobby.get_int("joinport")))
root.add_child(Node.string("hostip_l", lobby.get_str("localip")))
root.add_child(Node.s32("hostport_l", lobby.get_int("localport")))
root.add_child(Node.string("hostip_g", play_session_info.get_str("joinip")))
root.add_child(Node.s32("hostport_g", play_session_info.get_int("joinport")))
root.add_child(Node.string("hostip_l", play_session_info.get_str("localip")))
root.add_child(Node.s32("hostport_l", play_session_info.get_int("localport")))
return root
def handle_matching_wait_request(self, request: Node) -> Node:
# Tell the client about up to 8 additional hosts that could be paired to us.
# If we want to only match against hosts in this shop, this is what we'd use to do it.
host_id = request.child_value("data/hostid")
shop_id = ID.parse_machine_id(request.child_value("data/locationid"))
# List all lobbies out, up to 8 of them. It's unclear whether the game wants to know
# about it's own lobby or not.
# List all lobbies out, find the one that we're either a host or a guest of.
lobbies = self.data.local.lobby.get_all_lobbies(self.game, self.version)
lobbycount = 0
info_by_uid = {
uid: data
for uid, data in self.data.local.lobby.get_all_play_session_infos(
self.game, self.version
)
}
# Find our own lobby.
our_lobby = [l[1] for l in lobbies if l[1].get_int("id") == host_id][0]
# We should be able to filter by host_id that the game gave us.
joined_lobby = [
(uid, lobby) for uid, lobby in lobbies if lobby.get_int("id") == host_id
]
if len(joined_lobby) != 1:
# This shouldn't happen.
root = Node.void("matching")
root.add_child(Node.s32("result", -1))
return root
# Calculate creation time, figure out when to join the match after that.
host_uid, lobby = joined_lobby[0]
time_left = max(
lobby.get_int("waittime") - (Time.now() - lobby.get_int("createtime")), 0
)
root = Node.void("matching")
root.add_child(Node.s32("result", 0))
root.add_child(Node.s32("prwtime", 60))
root.add_child(
Node.s32("result", 0 if time_left > 0 else 1)
) # We send 1 to start the match.
root.add_child(Node.s32("prwtime", time_left))
matchlist = Node.void("matchlist")
root.add_child(matchlist)
for _, lobby in lobbies:
if lobby.get_int("id") == host_id:
playercount = 0
for uid in lobby["participants"]:
# Grab player-specific IPs and stuff.
if uid not in info_by_uid:
continue
# TODO: Possibly filter by only locationid matching, if this is enabled
# in server operator settings.
# TODO: Possibly need to link match groups?
uinfo = info_by_uid[uid]
record = Node.void("record")
record.add_child(Node.string("pcbid", lobby.get_str("pcbid")))
record.add_child(Node.string("pcbid", uinfo.get_str("pcbid")))
record.add_child(Node.string("statusflg", ""))
record.add_child(Node.s32("matchgrp", lobby.get_int("matchgrp")))
record.add_child(Node.s64("hostid", lobby.get_int("id")))
record.add_child(Node.u64("jointime", lobby.get_int("time") * 1000))
record.add_child(Node.string("connip_g", lobby.get_str("joinip")))
record.add_child(Node.s32("connport_g", lobby.get_int("joinport")))
record.add_child(Node.string("connip_l", lobby.get_str("localip")))
record.add_child(Node.s32("connport_l", lobby.get_int("localport")))
record.add_child(Node.u64("jointime", uinfo.get_int("time") * 1000))
record.add_child(Node.string("connip_g", uinfo.get_str("joinip")))
record.add_child(Node.s32("connport_g", uinfo.get_int("joinport")))
record.add_child(Node.string("connip_l", uinfo.get_str("localip")))
record.add_child(Node.s32("connport_l", uinfo.get_int("localport")))
matchlist.add_child(record)
lobbycount += 1
if lobbycount >= 8:
playercount += 1
if playercount >= 8:
break
matchlist.add_child(Node.u32("record_num", lobbycount))
matchlist.add_child(Node.u32("record_num", playercount))
return root

View File

@ -231,7 +231,7 @@ class LobbyData(BaseData):
return data
def get_all_lobbies(
self, game: GameConstants, version: int
self, game: GameConstants, version: int, max_age: int = Time.SECONDS_IN_HOUR
) -> List[Tuple[UserID, ValidatedDict]]:
"""
Given a game and version, look up all active lobbies.
@ -252,7 +252,7 @@ class LobbyData(BaseData):
{
"game": game.value,
"version": version,
"time": Time.now() - Time.SECONDS_IN_HOUR,
"time": Time.now() - max_age,
},
)