diff --git a/AUTHORS b/AUTHORS
index aee8fcc5f1..a9b79e4a99 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -180,6 +180,12 @@ Krueger and distributed under the GNU GPL terms.
Using rix0rrr.BeaconLib developed by Rico Huijbers
distributed under MIT License.
+Using DiscordRichPresence developed by Lachee
+distributed under MIT License.
+
+Using Json.NET developed by James Newton-King
+distributed under MIT License.
+
This site or product includes IP2Location LITE data
available from http://www.ip2location.com.
diff --git a/Makefile b/Makefile
index 4f8d442251..e91280634d 100644
--- a/Makefile
+++ b/Makefile
@@ -39,7 +39,7 @@
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.Game.exe OpenRA.Utility.exe OpenRA.Platforms.Default.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll OpenRA.Game.dll
# These are explicitly shipped alongside our core files by the packaging script
-WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.Core.dll
+WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.Core.dll DiscordRPC.dll Newtonsoft.Json.dll
# These are shipped in our custom minimal mono runtime and also available in the full system-installed .NET/mono stack
# This list *must* be kept in sync with the files packaged by the AppImageSupport and OpenRALauncherOSX repositories
@@ -210,6 +210,8 @@ endif
@$(INSTALL_PROGRAM) FuzzyLogicLibrary.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) Open.Nat.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) BeaconLib.dll "$(DATA_INSTALL_DIR)"
+ @$(INSTALL_PROGRAM) DiscordRPC.dll "$(DATA_INSTALL_DIR)"
+ @$(INSTALL_PROGRAM) Newtonsoft.Json.dll "$(DATA_INSTALL_DIR)"
install-common-mod-files:
@-echo "Installing OpenRA common mod files to $(DATA_INSTALL_DIR)"
diff --git a/OpenRA.Game/Manifest.cs b/OpenRA.Game/Manifest.cs
index 611c467269..fc1ed2b4ca 100644
--- a/OpenRA.Game/Manifest.cs
+++ b/OpenRA.Game/Manifest.cs
@@ -52,7 +52,7 @@ namespace OpenRA
}
/// Describes what is to be loaded in order to run a mod.
- public class Manifest
+ public class Manifest : IDisposable
{
public readonly string Id;
public readonly IReadOnlyPackage Package;
@@ -241,5 +241,15 @@ namespace OpenRA
return (T)module;
}
+
+ public void Dispose()
+ {
+ foreach (var module in modules)
+ {
+ var disposableModule = module as IDisposable;
+ if (disposableModule != null)
+ disposableModule.Dispose();
+ }
+ }
}
}
diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs
index 92bf09d475..35090ebbd4 100644
--- a/OpenRA.Game/ModData.cs
+++ b/OpenRA.Game/ModData.cs
@@ -208,6 +208,8 @@ namespace OpenRA
if (ObjectCreator != null)
ObjectCreator.Dispose();
+
+ Manifest.Dispose();
}
}
diff --git a/OpenRA.Mods.Common/DiscordService.cs b/OpenRA.Mods.Common/DiscordService.cs
new file mode 100644
index 0000000000..5e78c8be66
--- /dev/null
+++ b/OpenRA.Mods.Common/DiscordService.cs
@@ -0,0 +1,201 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
+ * This file is part of OpenRA, which is free software. It is made
+ * available to you under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version. For more
+ * information, see COPYING.
+ */
+#endregion
+
+using System;
+using DiscordRPC;
+using DiscordRPC.Message;
+using OpenRA.Network;
+
+namespace OpenRA.Mods.Common
+{
+ public enum DiscordState
+ {
+ Unknown,
+ InMenu,
+ InMapEditor,
+ InSkirmishLobby,
+ InMultiplayerLobby,
+ PlayingMultiplayer,
+ WatchingReplay,
+ PlayingCampaign,
+ PlayingSkirmish
+ }
+
+ public sealed class DiscordService : IGlobalModData, IDisposable
+ {
+ public readonly string ApplicationId = null;
+ readonly DiscordRpcClient client;
+ DiscordState currentState;
+
+ static readonly Lazy Service = Exts.Lazy(() =>
+ {
+ if (!Game.ModData.Manifest.Contains())
+ return null;
+
+ return Game.ModData.Manifest.Get();
+ });
+
+ public DiscordService(MiniYaml yaml)
+ {
+ FieldLoader.Load(this, yaml);
+
+ // HACK: Prevent service from starting when launching the utility or server.
+ if (Game.Renderer == null)
+ return;
+
+ client = new DiscordRpcClient(ApplicationId, autoEvents: true)
+ {
+ SkipIdenticalPresence = false
+ };
+
+ client.OnJoin += OnJoin;
+ client.OnJoinRequested += OnJoinRequested;
+
+ // HACK: We need to set HasRegisteredUriScheme to bypass the check that is done when calling SetPresence with a joinSecret.
+ // DiscordRpc lib expect us to register uri handlers with RegisterUriScheme(), we are doing it ourselves in our installers/launchers.
+ client.GetType().GetProperty("HasRegisteredUriScheme").SetValue(client, true);
+
+ client.SetSubscription(EventType.Join | EventType.JoinRequest);
+ client.Initialize();
+ }
+
+ void OnJoinRequested(object sender, JoinRequestMessage args)
+ {
+ var client = (DiscordRpcClient)sender;
+ client.Respond(args, true);
+ }
+
+ void OnJoin(object sender, JoinMessage args)
+ {
+ if (currentState != DiscordState.InMenu)
+ return;
+
+ var server = args.Secret.Split('|');
+ Game.RunAfterTick(() =>
+ {
+ Game.RemoteDirectConnect(new ConnectionTarget(server[0], int.Parse(server[1])));
+ });
+ }
+
+ void SetStatus(DiscordState state, string details = null, string secret = null, int? players = null, int? slots = null)
+ {
+ if (currentState == state)
+ return;
+
+ if (Service.Value == null)
+ return;
+
+ string stateText;
+ DateTime? timestamp = null;
+ Party party = null;
+ Secrets secrets = null;
+
+ switch (state)
+ {
+ case DiscordState.InMenu:
+ stateText = "In menu";
+ break;
+ case DiscordState.InMapEditor:
+ stateText = "In Map Editor";
+ break;
+ case DiscordState.InSkirmishLobby:
+ stateText = "In Skirmish Lobby";
+ break;
+ case DiscordState.InMultiplayerLobby:
+ stateText = "In Multiplayer Lobby";
+ timestamp = DateTime.UtcNow;
+ party = new Party
+ {
+ ID = Secrets.CreateFriendlySecret(new Random()),
+ Size = players.Value,
+ Max = slots.Value
+ };
+ secrets = new Secrets
+ {
+ JoinSecret = secret
+ };
+ break;
+ case DiscordState.PlayingMultiplayer:
+ stateText = "Playing Multiplayer";
+ timestamp = DateTime.UtcNow;
+ break;
+ case DiscordState.PlayingCampaign:
+ stateText = "Playing Campaign";
+ timestamp = DateTime.UtcNow;
+ break;
+ case DiscordState.WatchingReplay:
+ stateText = "Watching Replay";
+ timestamp = DateTime.UtcNow;
+ break;
+ case DiscordState.PlayingSkirmish:
+ stateText = "Playing Skirmish";
+ timestamp = DateTime.UtcNow;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException("state", state, null);
+ }
+
+ var richPresence = new RichPresence
+ {
+ Details = details,
+ State = stateText,
+ Assets = new Assets
+ {
+ LargeImageKey = "large",
+ LargeImageText = Game.ModData.Manifest.Metadata.Title,
+ },
+ Timestamps = timestamp.HasValue ? new Timestamps(timestamp.Value) : null,
+ Party = party,
+ Secrets = secrets
+ };
+
+ client.SetPresence(richPresence);
+ currentState = state;
+ }
+
+ public void Dispose()
+ {
+ if (client != null)
+ client.Dispose();
+ }
+
+ public static void UpdateStatus(DiscordState state, string details = null, string secret = null, int? players = null, int? slots = null)
+ {
+ if (Service.Value != null)
+ Service.Value.SetStatus(state, details, secret, players, slots);
+ }
+
+ public static void SetPlayers(int players, int slots)
+ {
+ if (Service.Value != null)
+ {
+ Service.Value.client.UpdateParty(new Party
+ {
+ ID = Secrets.CreateFriendlySecret(new Random()),
+ Size = players,
+ Max = slots
+ });
+ }
+ }
+
+ public static void UpdatePlayers(int players, int slots)
+ {
+ if (Service.Value != null)
+ Service.Value.client.UpdatePartySize(players, slots);
+ }
+
+ public static void UpdateDetails(string details)
+ {
+ if (Service.Value != null)
+ Service.Value.client.UpdateDetails(details);
+ }
+ }
+}
diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index ced131ea4e..ca73d91a97 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -25,6 +25,7 @@
False
+
diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs
index 0957bfa0f5..0896a4af7c 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs
@@ -61,6 +61,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
bool addBotOnMapLoad;
bool disableTeamChat;
bool teamChat;
+ bool updateDiscordStatus = true;
readonly string chatLineSound = ChromeMetrics.Get("ChatLineSound");
@@ -116,6 +117,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
orderManager.AddChatLine += AddChatLine;
Game.LobbyInfoChanged += UpdateCurrentMap;
Game.LobbyInfoChanged += UpdatePlayerList;
+ Game.LobbyInfoChanged += UpdateDiscordStatus;
Game.BeforeGameStart += OnGameStart;
Game.ConnectionStateChanged += ConnectionStateChanged;
@@ -462,6 +464,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
orderManager.AddChatLine -= AddChatLine;
Game.LobbyInfoChanged -= UpdateCurrentMap;
Game.LobbyInfoChanged -= UpdatePlayerList;
+ Game.LobbyInfoChanged -= UpdateDiscordStatus;
Game.BeforeGameStart -= OnGameStart;
Game.ConnectionStateChanged -= ConnectionStateChanged;
}
@@ -743,9 +746,56 @@ namespace OpenRA.Mods.Common.Widgets.Logic
tabCompletion.Names = orderManager.LobbyInfo.Clients.Select(c => c.Name).Distinct().ToList();
}
+ void UpdateDiscordStatus()
+ {
+ var mapTitle = map.Title;
+ var numberOfPlayers = 0;
+ var slots = 0;
+
+ if (!skirmishMode)
+ {
+ foreach (var kv in orderManager.LobbyInfo.Slots)
+ {
+ if (kv.Value.Closed)
+ continue;
+
+ slots++;
+ var client = orderManager.LobbyInfo.ClientInSlot(kv.Key);
+
+ if (client != null)
+ numberOfPlayers++;
+ }
+ }
+
+ if (updateDiscordStatus)
+ {
+ string secret = null;
+ if (orderManager.LobbyInfo.GlobalSettings.Dedicated)
+ {
+ var endpoint = orderManager.Endpoint.GetConnectEndPoints().First();
+ secret = string.Concat(endpoint.Address, "|", endpoint.Port);
+ }
+
+ var state = skirmishMode ? DiscordState.InSkirmishLobby : DiscordState.InMultiplayerLobby;
+ DiscordService.UpdateStatus(state, mapTitle, secret, numberOfPlayers, slots);
+
+ updateDiscordStatus = false;
+ }
+ else
+ {
+ if (!skirmishMode)
+ DiscordService.UpdatePlayers(numberOfPlayers, slots);
+ DiscordService.UpdateDetails(mapTitle);
+ }
+ }
+
void OnGameStart()
{
Ui.CloseWindow();
+
+ var state = skirmishMode ? DiscordState.PlayingSkirmish : DiscordState.PlayingMultiplayer;
+ DiscordService.UpdateStatus(state);
+
onStart();
}
}
diff --git a/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs
index 39ff958223..946d658970 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs
@@ -46,6 +46,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
menuType = type;
+ DiscordService.UpdateStatus(DiscordState.InMenu);
+
// Update button mouseover
Game.RunAfterTick(Ui.ResetTooltips);
}
@@ -258,6 +260,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
onIntroductionComplete();
Game.OnShellmapLoaded += OpenMenuBasedOnLastGame;
+
+ DiscordService.UpdateStatus(DiscordState.InMenu);
}
void LoadAndDisplayNews(string newsURL, Widget newsBG)
@@ -317,6 +321,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
() => { Game.LoadEditor(uid); },
() => { Game.CloseServer(); SwitchMenu(MenuType.MapEditor); });
+ DiscordService.UpdateStatus(DiscordState.InMapEditor);
+
lastGameState = MenuPanel.MapEditor;
}
diff --git a/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs
index 0a255fb324..a7140fa95b 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs
@@ -164,6 +164,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
void OnGameStart()
{
Ui.CloseWindow();
+
+ DiscordService.UpdateStatus(DiscordState.PlayingCampaign);
+
onStart();
}
diff --git a/OpenRA.Mods.Common/Widgets/Logic/MultiplayerLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MultiplayerLogic.cs
index 1999281f2e..da403446a5 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/MultiplayerLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/MultiplayerLogic.cs
@@ -95,6 +95,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
});
Game.Disconnect();
+
+ DiscordService.UpdateStatus(DiscordState.InMenu);
};
Game.OpenWindow("SERVER_LOBBY", new WidgetArgs
diff --git a/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs
index da5ee14d49..dceb1ff605 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs
@@ -688,6 +688,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (selectedReplay != null && ReplayUtils.PromptConfirmReplayCompatibility(selectedReplay))
{
cancelLoadingReplays = true;
+
+ DiscordService.UpdateStatus(DiscordState.WatchingReplay);
+
Game.JoinReplay(selectedReplay.FilePath);
}
}
diff --git a/mods/all/mod.yaml b/mods/all/mod.yaml
index 47b3e269a0..c2f9d0ef58 100644
--- a/mods/all/mod.yaml
+++ b/mods/all/mod.yaml
@@ -32,3 +32,6 @@ SpriteFormats:
SpriteSequenceFormat: DefaultSpriteSequence
ModelSequenceFormat: PlaceholderModelSequence
+
+DiscordService:
+ ApplicationId: 699222659766026240
diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml
index 59ed129dd8..a8c5062db0 100644
--- a/mods/cnc/mod.yaml
+++ b/mods/cnc/mod.yaml
@@ -288,3 +288,6 @@ ModContent:
cnc|installer/gdi95.yaml
cnc|installer/nod95.yaml
cnc|installer/origin.yaml
+
+DiscordService:
+ ApplicationId: 699223250181292033
diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml
index cd1128d0e4..bb3feb3e69 100644
--- a/mods/d2k/mod.yaml
+++ b/mods/d2k/mod.yaml
@@ -264,3 +264,6 @@ ModContent:
d2k|installer/d2k-e.yaml
d2k|installer/downloads.yaml
d2k|installer/gruntmods.yaml
+
+DiscordService:
+ ApplicationId: 712711732770111550
diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml
index 0531773526..a772bda763 100644
--- a/mods/ra/mod.yaml
+++ b/mods/ra/mod.yaml
@@ -305,3 +305,6 @@ ModContent:
ra|installer/firstdecade.yaml
ra|installer/origin.yaml
ra|installer/soviet95.yaml
+
+DiscordService:
+ ApplicationId: 699222659766026240
diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml
index dfb45f200d..259c4a8cd4 100644
--- a/mods/ts/mod.yaml
+++ b/mods/ts/mod.yaml
@@ -323,3 +323,6 @@ ModContent:
ts|installer/firstdecade.yaml
ts|installer/origin.yaml
ts|installer/tibsun.yaml
+
+DiscordService:
+ ApplicationId: 712713986558394399
diff --git a/packaging/linux/buildpackage.sh b/packaging/linux/buildpackage.sh
index 6ef8c32729..3a8d00fcaf 100755
--- a/packaging/linux/buildpackage.sh
+++ b/packaging/linux/buildpackage.sh
@@ -93,6 +93,7 @@ rm -rf libs mono.tar.bz2
build_appimage() {
MOD_ID=${1}
DISPLAY_NAME=${2}
+ DISCORD_ID=${3}
APPDIR="$(pwd)/${MOD_ID}.appdir"
APPIMAGE="OpenRA-$(echo "${DISPLAY_NAME}" | sed 's/ /-/g')${SUFFIX}-x86_64.AppImage"
@@ -107,7 +108,7 @@ build_appimage() {
sed "s/{MODID}/${MOD_ID}/g" AppRun.in | sed "s/{MODNAME}/${DISPLAY_NAME}/g" > AppRun.temp
install -m 0755 AppRun.temp "${APPDIR}/AppRun"
- sed "s/{MODID}/${MOD_ID}/g" openra.desktop.in | sed "s/{MODNAME}/${DISPLAY_NAME}/g" | sed "s/{TAG}/${TAG}/g" > temp.desktop
+ sed "s/{MODID}/${MOD_ID}/g" openra.desktop.in | sed "s/{MODNAME}/${DISPLAY_NAME}/g" | sed "s/{TAG}/${TAG}/g" | sed "s/{DISCORDAPPID}/${DISCORD_ID}/g" > temp.desktop
echo "StartupWMClass=openra-${MOD_ID}-${TAG}" >> temp.desktop
install -Dm 0755 temp.desktop "${APPDIR}/usr/share/applications/openra-${MOD_ID}.desktop"
@@ -154,9 +155,9 @@ build_appimage() {
rm -rf "${APPDIR}"
}
-build_appimage "ra" "Red Alert"
-build_appimage "cnc" "Tiberian Dawn"
-build_appimage "d2k" "Dune 2000"
+build_appimage "ra" "Red Alert" "699222659766026240"
+build_appimage "cnc" "Tiberian Dawn" "699223250181292033"
+build_appimage "d2k" "Dune 2000" "712711732770111550"
# Clean up
rm -rf openra-mod.temp openra-mod-server.temp openra-mod-utility.temp temp.desktop temp.xml AppRun.temp appimagetool-x86_64.AppImage squashfs-root "${BUILTDIR}"
diff --git a/packaging/linux/openra.desktop.in b/packaging/linux/openra.desktop.in
index f4f5013f9b..42e21aafa0 100644
--- a/packaging/linux/openra.desktop.in
+++ b/packaging/linux/openra.desktop.in
@@ -9,4 +9,4 @@ Icon=openra-{MODID}
Exec=openra-{MODID} %U
Terminal=false
Categories=Game;StrategyGame;
-MimeType=x-scheme-handler/openra-{MODID}-{TAG};
+MimeType=x-scheme-handler/openra-{MODID}-{TAG};x-scheme-handler/discord-{DISCORDAPPID};
diff --git a/packaging/macos/buildpackage.sh b/packaging/macos/buildpackage.sh
index 91568fe81e..cad5fe56ed 100755
--- a/packaging/macos/buildpackage.sh
+++ b/packaging/macos/buildpackage.sh
@@ -13,7 +13,7 @@
# MACOS_DEVELOPER_PASSWORD: App-specific password for the developer account
#
-LAUNCHER_TAG="osx-launcher-20200328"
+LAUNCHER_TAG="osx-launcher-20200525"
if [ $# -ne "2" ]; then
echo "Usage: $(basename "$0") tag outputdir"
@@ -54,6 +54,7 @@ populate_bundle() {
TEMPLATE_DIR="${BUILTDIR}/${1}"
MOD_ID=${2}
MOD_NAME=${3}
+ DISCORD_APPID=${4}
cp -r "${BUILTDIR}/OpenRA.app" "${TEMPLATE_DIR}"
# Assemble multi-resolution icon
@@ -63,6 +64,7 @@ populate_bundle() {
modify_plist "{MOD_ID}" "${MOD_ID}" "${TEMPLATE_DIR}/Contents/Info.plist"
modify_plist "{MOD_NAME}" "${MOD_NAME}" "${TEMPLATE_DIR}/Contents/Info.plist"
modify_plist "{JOIN_SERVER_URL_SCHEME}" "openra-${MOD_ID}-${TAG}" "${TEMPLATE_DIR}/Contents/Info.plist"
+ modify_plist "{ADDITIONAL_URL_SCHEMES}" "discord-${DISCORD_APPID}" "${TEMPLATE_DIR}/Contents/Info.plist"
}
# Deletes from the first argument's mod dirs all the later arguments
@@ -98,15 +100,15 @@ make install-core gameinstalldir="/Contents/Resources/" DESTDIR="${BUILTDIR}/Ope
make install-dependencies TARGETPLATFORM=osx-x64 gameinstalldir="/Contents/Resources/" DESTDIR="${BUILTDIR}/OpenRA.app"
popd > /dev/null || exit 1
-populate_bundle "OpenRA - Red Alert.app" "ra" "Red Alert"
+populate_bundle "OpenRA - Red Alert.app" "ra" "Red Alert" "699222659766026240"
delete_mods "OpenRA - Red Alert.app" "cnc" "d2k"
sign_bundle "OpenRA - Red Alert.app"
-populate_bundle "OpenRA - Tiberian Dawn.app" "cnc" "Tiberian Dawn"
+populate_bundle "OpenRA - Tiberian Dawn.app" "cnc" "Tiberian Dawn" "699223250181292033"
delete_mods "OpenRA - Tiberian Dawn.app" "ra" "d2k"
sign_bundle "OpenRA - Tiberian Dawn.app"
-populate_bundle "OpenRA - Dune 2000.app" "d2k" "Dune 2000"
+populate_bundle "OpenRA - Dune 2000.app" "d2k" "Dune 2000" "712711732770111550"
delete_mods "OpenRA - Dune 2000.app" "ra" "cnc"
sign_bundle "OpenRA - Dune 2000.app"
diff --git a/packaging/windows/OpenRA.nsi b/packaging/windows/OpenRA.nsi
index 44a1159765..1511422eb9 100644
--- a/packaging/windows/OpenRA.nsi
+++ b/packaging/windows/OpenRA.nsi
@@ -62,6 +62,10 @@ Var StartMenuFolder
!insertmacro MUI_LANGUAGE "English"
+!define RA_DISCORDID 699222659766026240
+!define CNC_DISCORDID 699223250181292033
+!define D2K_DISCORDID 712711732770111550
+
;***************************
;Section Definitions
;***************************
@@ -76,16 +80,31 @@ Section "-Reg" Reg
WriteRegStr HKLM "Software\Classes\openra-ra-${TAG}\DefaultIcon" "" "$INSTDIR\RedAlert.ico,0"
WriteRegStr HKLM "Software\Classes\openra-ra-${TAG}\Shell\Open\Command" "" "$INSTDIR\RedAlert.exe Launch.URI=%1"
+ WriteRegStr HKLM "Software\Classes\discord-${RA_DISCORDID}" "" "URL:Run game ${RA_DISCORDID} protocol"
+ WriteRegStr HKLM "Software\Classes\discord-${RA_DISCORDID}" "URL Protocol" ""
+ WriteRegStr HKLM "Software\Classes\discord-${RA_DISCORDID}\DefaultIcon" "" "$INSTDIR\RedAlert.ico,0"
+ WriteRegStr HKLM "Software\Classes\discord-${RA_DISCORDID}\Shell\Open\Command" "" "$INSTDIR\RedAlert.exe"
+
WriteRegStr HKLM "Software\Classes\openra-cnc-${TAG}" "" "URL:Join OpenRA server"
WriteRegStr HKLM "Software\Classes\openra-cnc-${TAG}" "URL Protocol" ""
WriteRegStr HKLM "Software\Classes\openra-cnc-${TAG}\DefaultIcon" "" "$INSTDIR\TiberianDawn.ico,0"
WriteRegStr HKLM "Software\Classes\openra-cnc-${TAG}\Shell\Open\Command" "" "$INSTDIR\TiberianDawn.exe Launch.URI=%1"
+ WriteRegStr HKLM "Software\Classes\discord-${CNC_DISCORDID}" "" "URL:Run game ${CNC_DISCORDID} protocol"
+ WriteRegStr HKLM "Software\Classes\discord-${CNC_DISCORDID}" "URL Protocol" ""
+ WriteRegStr HKLM "Software\Classes\discord-${CNC_DISCORDID}\DefaultIcon" "" "$INSTDIR\TiberianDawn.ico,0"
+ WriteRegStr HKLM "Software\Classes\discord-${CNC_DISCORDID}\Shell\Open\Command" "" "$INSTDIR\TiberianDawn.exe"
+
WriteRegStr HKLM "Software\Classes\openra-d2k-${TAG}" "" "URL:Join OpenRA server"
WriteRegStr HKLM "Software\Classes\openra-d2k-${TAG}" "URL Protocol" ""
WriteRegStr HKLM "Software\Classes\openra-d2k-${TAG}\DefaultIcon" "" "$INSTDIR\Dune2000.ico,0"
WriteRegStr HKLM "Software\Classes\openra-d2k-${TAG}\Shell\Open\Command" "" "$INSTDIR\Dune2000.exe Launch.URI=%1"
+ WriteRegStr HKLM "Software\Classes\discord-${D2K_DISCORDID}" "" "URL:Run game ${D2K_DISCORDID} protocol"
+ WriteRegStr HKLM "Software\Classes\discord-${D2K_DISCORDID}" "URL Protocol" ""
+ WriteRegStr HKLM "Software\Classes\discord-${D2K_DISCORDID}\DefaultIcon" "" "$INSTDIR\Dune2000.ico,0"
+ WriteRegStr HKLM "Software\Classes\discord-${D2K_DISCORDID}\Shell\Open\Command" "" "$INSTDIR\Dune2000.exe"
+
; Remove obsolete file associations
DeleteRegKey HKLM "Software\Classes\.orarep"
DeleteRegKey HKLM "Software\Classes\OpenRA_replay"
@@ -135,6 +154,8 @@ Section "Game" GAME
File "${SRCDIR}\eluant.dll"
File "${SRCDIR}\BeaconLib.dll"
File "${SRCDIR}\soft_oal.dll"
+ File "${SRCDIR}\DiscordRPC.dll"
+ File "${SRCDIR}\Newtonsoft.Json.dll"
File "${SRCDIR}\SDL2.dll"
File "${SRCDIR}\freetype6.dll"
File "${SRCDIR}\lua51.dll"
@@ -254,6 +275,8 @@ Function ${UN}Clean
Delete $INSTDIR\lua51.dll
Delete $INSTDIR\eluant.dll
Delete $INSTDIR\freetype6.dll
+ Delete $INSTDIR\DiscordRPC.dll
+ Delete $INSTDIR\Newtonsoft.Json.dll
Delete $INSTDIR\SDL2-CS.dll
Delete $INSTDIR\OpenAL-CS.Core.dll
Delete $INSTDIR\BeaconLib.dll
@@ -264,6 +287,10 @@ Function ${UN}Clean
DeleteRegKey HKLM "Software\Classes\openra-cnc-${TAG}"
DeleteRegKey HKLM "Software\Classes\openra-d2k-${TAG}"
+ DeleteRegKey HKLM "Software\Classes\discord-${RA_DISCORDID}"
+ DeleteRegKey HKLM "Software\Classes\discord-${CNC_DISCORDID}"
+ DeleteRegKey HKLM "Software\Classes\discord-${D2K_DISCORDID}"
+
Delete $INSTDIR\uninstaller.exe
RMDir $INSTDIR