LAN games discovery

This commit is contained in:
rob-v
2017-04-19 10:20:51 +02:00
committed by reaperrr
parent b2e6a0484b
commit ffc3f6e0d0
11 changed files with 132 additions and 27 deletions

View File

@@ -170,6 +170,8 @@ Krueger and distributed under the GNU GPL terms.
Using SmartIrc4Net developed by Mirco Bauer
distributed under the LGPL version 2.1 or later.
Using rix0rrr.BeaconLib developed by Rico Huijbers
distributed under MIT License.
Finally, special thanks goes to the original teams
at Westwood Studios and EA for creating the classic

View File

@@ -40,7 +40,7 @@ SDK ?=
CSC = mcs $(SDK)
CSFLAGS = -nologo -warn:4 -codepage:utf8 -unsafe -warnaserror
DEFINE = TRACE
COMMON_LIBS = System.dll System.Core.dll System.Data.dll System.Data.DataSetExtensions.dll System.Drawing.dll System.Xml.dll thirdparty/download/ICSharpCode.SharpZipLib.dll thirdparty/download/FuzzyLogicLibrary.dll thirdparty/download/MaxMind.Db.dll thirdparty/download/Eluant.dll thirdparty/download/SmarIrc4net.dll
COMMON_LIBS = System.dll System.Core.dll System.Data.dll System.Data.DataSetExtensions.dll System.Drawing.dll System.Xml.dll thirdparty/download/ICSharpCode.SharpZipLib.dll thirdparty/download/FuzzyLogicLibrary.dll thirdparty/download/MaxMind.Db.dll thirdparty/download/Eluant.dll thirdparty/download/SmarIrc4net.dll thirdparty/download/rix0rrr.BeaconLib.dll
NUNIT_LIBS_PATH :=
NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll
@@ -358,6 +358,7 @@ install-core:
@$(INSTALL_PROGRAM) Open.Nat.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) MaxMind.Db.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) SmarIrc4net.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) rix0rrr.BeaconLib.dll "$(DATA_INSTALL_DIR)"
@$(CP) *.sh "$(DATA_INSTALL_DIR)"
install-linux-icons:

View File

@@ -22,6 +22,7 @@ namespace OpenRA
public static class Platform
{
public static PlatformType CurrentPlatform { get { return currentPlatform.Value; } }
public static readonly Guid SessionGUID = Guid.NewGuid();
static Lazy<PlatformType> currentPlatform = Exts.Lazy(GetCurrentPlatform);

View File

@@ -41,6 +41,10 @@
<HintPath>..\thirdparty\download\FuzzyLogicLibrary.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="rix0rrr.BeaconLib, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\thirdparty\download\rix0rrr.BeaconLib.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />

View File

@@ -14,44 +14,60 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using BeaconLib;
using OpenRA.Server;
using S = OpenRA.Server.Server;
namespace OpenRA.Mods.Common.Server
{
public class MasterServerPinger : ServerTrait, ITick, INotifySyncLobbyInfo, IStartGame, IEndGame
public class MasterServerPinger : ServerTrait, ITick, INotifyServerStart, INotifySyncLobbyInfo, IStartGame, IEndGame
{
// 3 minutes. Server has a 5 minute TTL for games, so give ourselves a bit of leeway.
const int MasterPingInterval = 60 * 3;
static readonly Beacon LanGameBeacon = new Beacon("OpenRALANGame", (ushort)new Random(DateTime.Now.Millisecond).Next(2048, 60000));
public int TickTimeout { get { return MasterPingInterval * 10000; } }
public void Tick(S server)
{
if ((Game.RunTime - lastPing > MasterPingInterval * 1000) || isInitialPing)
PingMasterServer(server);
else
lock (masterServerMessages)
while (masterServerMessages.Count > 0)
server.SendMessage(masterServerMessages.Dequeue());
}
public void LobbyInfoSynced(S server) { PingMasterServer(server); }
public void GameStarted(S server) { PingMasterServer(server); }
public void GameEnded(S server) { PingMasterServer(server); }
long lastPing = 0;
bool isInitialPing = true;
volatile bool isBusy;
Queue<string> masterServerMessages = new Queue<string>();
public void PingMasterServer(S server)
public void Tick(S server)
{
if (isBusy || !server.Settings.AdvertiseOnline) return;
if ((Game.RunTime - lastPing > MasterPingInterval * 1000) || isInitialPing)
PublishGame(server);
else
lock (masterServerMessages)
while (masterServerMessages.Count > 0)
server.SendMessage(masterServerMessages.Dequeue());
}
lastPing = Game.RunTime;
isBusy = true;
public void ServerStarted(S server)
{
if (!server.Ip.Equals(IPAddress.Loopback))
LanGameBeacon.Start();
}
public void LobbyInfoSynced(S server)
{
PublishGame(server);
}
public void GameStarted(S server)
{
PublishGame(server);
}
public void GameEnded(S server)
{
LanGameBeacon.Stop();
PublishGame(server);
}
void PublishGame(S server)
{
var mod = server.ModData.Manifest;
// important to grab these on the main server thread, not in the worker we're about to spawn -- they may be modified
@@ -60,9 +76,21 @@ namespace OpenRA.Mods.Common.Server
var numBots = server.LobbyInfo.Clients.Where(c1 => c1.Bot != null).Count();
var numSpectators = server.LobbyInfo.Clients.Where(c1 => c1.Bot == null && c1.Slot == null).Count();
var numSlots = server.LobbyInfo.Slots.Where(s => !s.Value.Closed).Count() - numBots;
var passwordProtected = string.IsNullOrEmpty(server.Settings.Password) ? 0 : 1;
var passwordProtected = !string.IsNullOrEmpty(server.Settings.Password);
var clients = server.LobbyInfo.Clients.Where(c1 => c1.Bot == null).Select(c => Convert.ToBase64String(Encoding.UTF8.GetBytes(c.Name))).ToArray();
UpdateMasterServer(server, numPlayers, numSlots, numBots, numSpectators, mod, passwordProtected, clients);
UpdateLANGameBeacon(server, numPlayers, numSlots, numBots, numSpectators, mod, passwordProtected);
}
void UpdateMasterServer(S server, int numPlayers, int numSlots, int numBots, int numSpectators, Manifest mod, bool passwordProtected, string[] clients)
{
if (isBusy || !server.Settings.AdvertiseOnline)
return;
lastPing = Game.RunTime;
isBusy = true;
Action a = () =>
{
try
@@ -84,7 +112,7 @@ namespace OpenRA.Mods.Common.Server
server.LobbyInfo.GlobalSettings.Map,
numSlots,
numSpectators,
passwordProtected,
passwordProtected ? 1 : 0,
string.Join(",", clients)));
if (isInitialPing)
@@ -116,5 +144,28 @@ namespace OpenRA.Mods.Common.Server
a.BeginInvoke(null, null);
}
void UpdateLANGameBeacon(S server, int numPlayers, int numSlots, int numBots, int numSpectators, Manifest mod, bool passwordProtected)
{
var settings = server.Settings;
// TODO: Serialize and send client names
var lanGameYaml =
@"Game:
Id: {0}
Name: {1}
Address: {2}:{3}
State: {4}
Players: {5}
MaxPlayers: {6}
Bots: {7}
Spectators: {8}
Map: {9}
Mods: {10}@{11}
Protected: {12}".F(Platform.SessionGUID, settings.Name, server.Ip, settings.ListenPort, (int)server.State, numPlayers, numSlots, numBots, numSpectators,
server.Map.Uid, mod.Id, mod.Metadata.Version, passwordProtected);
LanGameBeacon.BeaconData = lanGameYaml;
}
}
}

View File

@@ -10,12 +10,12 @@
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Text;
using BeaconLib;
using OpenRA.Network;
using OpenRA.Server;
using OpenRA.Widgets;
@@ -39,6 +39,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly Color incompatibleGameColor;
readonly ModData modData;
readonly WebServices services;
readonly Probe lanGameProbe;
GameServer currentServer;
MapPreview currentMap;
@@ -53,6 +54,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
SearchStatus searchStatus = SearchStatus.Fetching;
Download currentQuery;
Widget serverList;
IEnumerable<BeaconLocation> lanGameLocations;
public string ProgressLabelText()
{
@@ -109,6 +111,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
widget.Get<ButtonWidget>("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
Game.LoadWidget(null, "GLOBALCHAT_PANEL", widget.Get("GLOBALCHAT_ROOT"), new WidgetArgs());
lanGameLocations = new List<BeaconLocation>();
lanGameProbe = new Probe("OpenRALANGame");
lanGameProbe.BeaconsUpdated += locations => lanGameLocations = locations;
lanGameProbe.Start();
RefreshServerList();
if (directConnectHost != null)
@@ -322,7 +329,29 @@ namespace OpenRA.Mods.Common.Widgets.Logic
}
}
Game.RunAfterTick(() => RefreshServerListInner(games));
var lanGames = new List<GameServer>();
foreach (var bl in lanGameLocations)
{
var game = MiniYaml.FromString(bl.Data)[0].Value;
var idNode = game.Nodes.FirstOrDefault(n => n.Key == "Id");
// Skip beacons created by this instance and replace Id by expected int value
if (idNode != null && idNode.Value.Value != Platform.SessionGUID.ToString())
{
idNode.Value.Value = "-1";
// Rewrite the server address with the correct IP
var addressNode = game.Nodes.FirstOrDefault(n => n.Key == "Address");
if (addressNode != null)
addressNode.Value.Value = bl.Address.ToString().Split(':')[0] + ":" + addressNode.Value.Value.Split(':')[1];
lanGames.Add(new GameServer(game));
}
}
lanGames = lanGames.GroupBy(gs => gs.Address).Select(g => g.Last()).ToList();
Game.RunAfterTick(() => RefreshServerListInner(games.Concat(lanGames).ToList()));
};
var queryURL = services.ServerList + "games?version={0}&mod={1}&modversion={2}".F(
@@ -444,7 +473,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (location != null)
{
var font = Game.Renderer.Fonts[location.Font];
var cachedServerLocation = GeoIP.LookupCountry(game.Address.Split(':')[0]);
var cachedServerLocation = game.Id != -1 ? GeoIP.LookupCountry(game.Address.Split(':')[0]) : "Local Network";
var label = WidgetUtils.TruncateText(cachedServerLocation, location.Bounds.Width, font);
location.GetText = () => label;
location.GetColor = () => canJoin ? location.TextColor : incompatibleGameColor;

View File

@@ -24,7 +24,7 @@ Container@MULTIPLAYER_BROWSER_PANEL:
X: 380
Width: 110
Height: 25
Text: Country
Text: Location
Font: Bold
Label@STATUS:
X: 495

View File

@@ -24,7 +24,7 @@ Container@MULTIPLAYER_BROWSER_PANEL:
X: 380
Width: 110
Height: 25
Text: Country
Text: Location
Font: Bold
Label@STATUS:
X: 495

View File

@@ -122,6 +122,7 @@ Section "Game" GAME
File "${SRCDIR}\GeoLite2-Country.mmdb.gz"
File "${SRCDIR}\eluant.dll"
File "${SRCDIR}\SmarIrc4net.dll"
File "${SRCDIR}\rix0rrr.BeaconLib.dll"
File "${DEPSDIR}\soft_oal.dll"
File "${DEPSDIR}\SDL2.dll"
File "${DEPSDIR}\freetype6.dll"
@@ -235,6 +236,7 @@ Function ${UN}Clean
Delete $INSTDIR\SDL2-CS.dll
Delete $INSTDIR\OpenAL-CS.dll
Delete $INSTDIR\SmarIrc4net.dll
Delete $INSTDIR\rix0rrr.BeaconLib.dll
RMDir /r $INSTDIR\Support
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenRA${SUFFIX}"

View File

@@ -158,4 +158,12 @@ if (!(Test-Path "SmarIrc4net.dll"))
rmdir SmartIrc4net -Recurse
}
if (!(Test-Path "rix0rrr.BeaconLib.dll"))
{
echo "Fetching rix0rrr.BeaconLib from NuGet."
./nuget.exe install rix0rrr.BeaconLib -Version 1.0.0 -ExcludeVersion -Verbosity quiet
cp rix0rrr.BeaconLib/lib/net40/rix0rrr.BeaconLib.dll .
rmdir rix0rrr.BeaconLib -Recurse
}
cd ..

View File

@@ -115,3 +115,10 @@ if [ ! -f SmarIrc4net.dll ]; then
cp ./SmartIrc4net/lib/net40/SmarIrc4net* .
rm -rf SmartIrc4net
fi
if [ ! -f rix0rrr.BeaconLib.dll ]; then
echo "Fetching rix0rrr.BeaconLib from NuGet."
get rix0rrr.BeaconLib 1.0.0
cp ./rix0rrr.BeaconLib/lib/net40/rix0rrr.BeaconLib.dll .
rm -rf rix0rrr.BeaconLib
fi