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 Using SmartIrc4Net developed by Mirco Bauer
distributed under the LGPL version 2.1 or later. 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 Finally, special thanks goes to the original teams
at Westwood Studios and EA for creating the classic at Westwood Studios and EA for creating the classic

View File

@@ -40,7 +40,7 @@ SDK ?=
CSC = mcs $(SDK) CSC = mcs $(SDK)
CSFLAGS = -nologo -warn:4 -codepage:utf8 -unsafe -warnaserror CSFLAGS = -nologo -warn:4 -codepage:utf8 -unsafe -warnaserror
DEFINE = TRACE 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_PATH :=
NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll
@@ -358,6 +358,7 @@ install-core:
@$(INSTALL_PROGRAM) Open.Nat.dll "$(DATA_INSTALL_DIR)" @$(INSTALL_PROGRAM) Open.Nat.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) MaxMind.Db.dll "$(DATA_INSTALL_DIR)" @$(INSTALL_PROGRAM) MaxMind.Db.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) SmarIrc4net.dll "$(DATA_INSTALL_DIR)" @$(INSTALL_PROGRAM) SmarIrc4net.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) rix0rrr.BeaconLib.dll "$(DATA_INSTALL_DIR)"
@$(CP) *.sh "$(DATA_INSTALL_DIR)" @$(CP) *.sh "$(DATA_INSTALL_DIR)"
install-linux-icons: install-linux-icons:

View File

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

View File

@@ -41,6 +41,10 @@
<HintPath>..\thirdparty\download\FuzzyLogicLibrary.dll</HintPath> <HintPath>..\thirdparty\download\FuzzyLogicLibrary.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </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" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />

View File

@@ -14,44 +14,60 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using BeaconLib;
using OpenRA.Server; using OpenRA.Server;
using S = OpenRA.Server.Server; using S = OpenRA.Server.Server;
namespace OpenRA.Mods.Common.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. // 3 minutes. Server has a 5 minute TTL for games, so give ourselves a bit of leeway.
const int MasterPingInterval = 60 * 3; 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 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; long lastPing = 0;
bool isInitialPing = true; bool isInitialPing = true;
volatile bool isBusy; volatile bool isBusy;
Queue<string> masterServerMessages = new Queue<string>(); 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; public void ServerStarted(S server)
isBusy = true; {
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; 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 // 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 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 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 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(); 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 = () => Action a = () =>
{ {
try try
@@ -84,7 +112,7 @@ namespace OpenRA.Mods.Common.Server
server.LobbyInfo.GlobalSettings.Map, server.LobbyInfo.GlobalSettings.Map,
numSlots, numSlots,
numSpectators, numSpectators,
passwordProtected, passwordProtected ? 1 : 0,
string.Join(",", clients))); string.Join(",", clients)));
if (isInitialPing) if (isInitialPing)
@@ -116,5 +144,28 @@ namespace OpenRA.Mods.Common.Server
a.BeginInvoke(null, null); 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 #endregion
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using BeaconLib;
using OpenRA.Network; using OpenRA.Network;
using OpenRA.Server; using OpenRA.Server;
using OpenRA.Widgets; using OpenRA.Widgets;
@@ -39,6 +39,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly Color incompatibleGameColor; readonly Color incompatibleGameColor;
readonly ModData modData; readonly ModData modData;
readonly WebServices services; readonly WebServices services;
readonly Probe lanGameProbe;
GameServer currentServer; GameServer currentServer;
MapPreview currentMap; MapPreview currentMap;
@@ -53,6 +54,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
SearchStatus searchStatus = SearchStatus.Fetching; SearchStatus searchStatus = SearchStatus.Fetching;
Download currentQuery; Download currentQuery;
Widget serverList; Widget serverList;
IEnumerable<BeaconLocation> lanGameLocations;
public string ProgressLabelText() public string ProgressLabelText()
{ {
@@ -109,6 +111,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
widget.Get<ButtonWidget>("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); }; widget.Get<ButtonWidget>("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
Game.LoadWidget(null, "GLOBALCHAT_PANEL", widget.Get("GLOBALCHAT_ROOT"), new WidgetArgs()); 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(); RefreshServerList();
if (directConnectHost != null) 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( 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) if (location != null)
{ {
var font = Game.Renderer.Fonts[location.Font]; 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); var label = WidgetUtils.TruncateText(cachedServerLocation, location.Bounds.Width, font);
location.GetText = () => label; location.GetText = () => label;
location.GetColor = () => canJoin ? location.TextColor : incompatibleGameColor; location.GetColor = () => canJoin ? location.TextColor : incompatibleGameColor;

View File

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

View File

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

View File

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

View File

@@ -158,4 +158,12 @@ if (!(Test-Path "SmarIrc4net.dll"))
rmdir SmartIrc4net -Recurse 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 .. cd ..

View File

@@ -115,3 +115,10 @@ if [ ! -f SmarIrc4net.dll ]; then
cp ./SmartIrc4net/lib/net40/SmarIrc4net* . cp ./SmartIrc4net/lib/net40/SmarIrc4net* .
rm -rf SmartIrc4net rm -rf SmartIrc4net
fi 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