Discord integration for Gamecraft
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

328 lines
11KB

  1. using System;
  2. using System.IO;
  3. using System.Reflection;
  4. using System.Text;
  5. //using Microsoft.Win32;
  6. using IllusionPlugin;
  7. using GamecraftModdingAPI.App;
  8. using GamecraftModdingAPI.Commands;
  9. using Discord;
  10. namespace GamecraftRPC
  11. {
  12. public class Plugin : IEnhancedPlugin // the Illusion Plugin Architecture (IPA) will ignore classes that don't implement IPlugin'
  13. {
  14. public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
  15. public override string Version { get; } =
  16. #if DEBUG
  17. Assembly.GetExecutingAssembly().GetName().Version.ToString() + "alpha";
  18. #else
  19. Assembly.GetExecutingAssembly().GetName().Version.ToString();
  20. #endif
  21. private const long CLIENT_ID =
  22. #if DEBUG
  23. 692733325902872619;
  24. #else
  25. 696732441012076605;
  26. #endif
  27. private const LogLevel LOG_LEVEL =
  28. #if DEBUG
  29. LogLevel.Debug;
  30. #else
  31. LogLevel.Warn;
  32. #endif
  33. internal static Discord.Discord DiscordRPC;
  34. // called when Gamecraft shuts down
  35. public override void OnApplicationQuit()
  36. {
  37. // Shutdown this mod
  38. if (DiscordRPC != null)
  39. {
  40. DiscordRPC.GetActivityManager().ClearActivity((result) => { GamecraftModdingAPI.Utility.Logging.LogDebug($"Cleared status: {result}"); DiscordRPC.Dispose(); });
  41. }
  42. GamecraftModdingAPI.Utility.Logging.LogDebug($"{Name} has shutdown");
  43. // Shutdown the Gamecraft modding API last
  44. GamecraftModdingAPI.Main.Shutdown();
  45. }
  46. // called when Gamecraft starts up
  47. public override void OnApplicationStart()
  48. {
  49. // Initialize the Gamecraft modding API first
  50. GamecraftModdingAPI.Main.Init();
  51. // detect Wine (maybe?)
  52. bool isWineDetected = false;
  53. foreach (var key in Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software").GetSubKeyNames())
  54. {
  55. if (key == "Wine")
  56. {
  57. isWineDetected = true;
  58. break;
  59. }
  60. }
  61. if (isWineDetected)
  62. {
  63. // info for getting this to work through Wine/Proton
  64. GamecraftModdingAPI.Utility.Logging.MetaLog("\n--------------------------------\n\nIt looks like you may be using Wine/Proton, cool!\nPlease install https://github.com/0e4ef622/wine-discord-ipc-bridge to get this to work.\nAlso, please note that multiplayer is broken in Wine/Proton.\n\n--------------------------------");
  65. }
  66. // Initialize this mod
  67. DiscordRPC = new Discord.Discord(CLIENT_ID, (UInt64)Discord.CreateFlags.NoRequireDiscord);
  68. DiscordRPC.SetLogHook(LOG_LEVEL, (_, msg) => { GamecraftModdingAPI.Utility.Logging.MetaLog(msg); });
  69. //DiscordRPC.GetActivityManager().RegisterSteam(1078000);
  70. ActivityManager am = DiscordRPC.GetActivityManager();
  71. am.OnActivityJoinRequest += CallbackUtility.ActivityJoinRequest;
  72. am.OnActivityJoin += CallbackUtility.ActivityJoin;
  73. am.OnActivityInvite += CallbackUtility.ActivityInvite;
  74. LobbyManager lm = DiscordRPC.GetLobbyManager();
  75. lm.OnMemberConnect += CallbackUtility.DiscordUserJoin;
  76. // init Discord game activity status
  77. SetDiscordActivity(state: $"{UnityEngine.Application.version}", details: $"Initializing...");
  78. Game.Edit += CallbackUtility.BuildEnter;
  79. Game.Enter += CallbackUtility.GameEnter;
  80. Game.Simulate += CallbackUtility.SimulationEnter;
  81. Client.EnterMenu += CallbackUtility.MenuEnter;
  82. GamecraftModdingAPI.Utility.GameEngineManager.AddGameEngine(new Engines.PlayerCountEngine());
  83. Client client = new Client();
  84. CommandBuilder.Builder()
  85. .Name("JoinDiscord")
  86. .Description("Join the Exmods server for help or more information")
  87. .Action(() =>
  88. {
  89. if (DiscordRPC != null)
  90. {
  91. DiscordRPC.GetOverlayManager().OpenGuildInvite("2CtWzZT", CallbackUtility.NobodyCares);
  92. }
  93. else
  94. {
  95. GamecraftModdingAPI.Utility.Logging.CommandLogError("Discord GameSDK functionality is unavailable. Please make sure Discord is open when launching Gamecraft.");
  96. }
  97. })
  98. .Build();
  99. CommandBuilder.Builder()
  100. .Name("InviteDiscordUser")
  101. .Description("Invite a Discord user (by id) to your game")
  102. .Action<long>((userId) =>
  103. {
  104. if (DiscordRPC != null)
  105. {
  106. Game game = Game.CurrentGame();
  107. DiscordRPC.GetActivityManager().SendInvite(userId, Discord.ActivityActionType.Join, $"Let's play Gamecraft together! (requires the GamecraftRPC mod)", CallbackUtility.NobodyCares);
  108. }
  109. else
  110. {
  111. GamecraftModdingAPI.Utility.Logging.CommandLogError("Discord GameSDK functionality is unavailable. Please make sure Discord is open when launching Gamecraft.");
  112. }
  113. })
  114. .Build();
  115. CommandBuilder.Builder()
  116. .Name(Name + "Info")
  117. .Description("Build information for the GamecraftRPC mod.")
  118. .Action(() =>
  119. {
  120. if (DiscordRPC != null)
  121. {
  122. Game game = Game.CurrentGame();
  123. GamecraftModdingAPI.Utility.Logging.CommandLog($"Gamecraft {client.Version}\nUnity {client.UnityVersion}\n{PluginInfo()}\nSDK {DiscordRPC.ToString()}\nGame {game.Name}");
  124. }
  125. else
  126. {
  127. GamecraftModdingAPI.Utility.Logging.CommandLogError("Discord GameSDK functionality is unavailable. Please make sure Discord is open when launching Gamecraft.");
  128. }
  129. })
  130. .Build();
  131. CommandBuilder.Builder()
  132. .Name("DiscordVoice")
  133. .Description("Connect to Discord Voice Lobby")
  134. .Action(() =>
  135. {
  136. if (DiscordRPC != null)
  137. {
  138. GamecraftModdingAPI.Utility.Logging.CommandLogError("Discord GameSDK is unavailable. Please make sure Discord is open when launching Gamecraft.");
  139. return;
  140. }
  141. if (PresenceUtility.Lobby.HasValue)
  142. {
  143. //LobbyManager lm = DiscordRPC.GetLobbyManager();
  144. if (PresenceUtility.IsVoiceConnected)
  145. {
  146. lm.DisconnectLobby(PresenceUtility.Lobby.Value.Id, (result) => { GamecraftModdingAPI.Utility.Logging.CommandLog($"Disconnected voice (Result: {result})"); });
  147. PresenceUtility.IsVoiceConnected = false;
  148. }
  149. else
  150. {
  151. lm.ConnectVoice(PresenceUtility.Lobby.Value.Id, (result) => { GamecraftModdingAPI.Utility.Logging.CommandLog($"Connected voice (Result: {result})"); });
  152. PresenceUtility.IsVoiceConnected = true;
  153. }
  154. }
  155. })
  156. .Build();
  157. CommandBuilder.Builder()
  158. .Name("ListDiscordLobbies")
  159. .Description($"View a list of public multiplayer lobbies available through {Name}")
  160. .Action(() =>
  161. {
  162. LobbySearchQuery query = lm.GetSearchQuery();
  163. query.Sort("slots", LobbySearchCast.Number, "999");
  164. query.Limit(100);
  165. lm.Search(query, result =>
  166. {
  167. if (result == Result.Ok)
  168. {
  169. StringBuilder sb = new StringBuilder("~ Lobby List [ID | Username (players/max)] ~\n");
  170. int lobbyCount = lm.LobbyCount();
  171. for (int i = 0; i < lobbyCount; i++)
  172. {
  173. long id = lm.GetLobbyId(i);
  174. Lobby lobby = lm.GetLobby(id);
  175. sb.AppendFormat("{3} | {0} ({1}/{2})\n", lm.GetLobbyMetadataValue(id, "name"),
  176. lm.MemberCount(id), lobby.Capacity.ToString(), lm.GetLobbyMetadataValue(id, "steamid"));
  177. }
  178. GamecraftModdingAPI.Utility.Logging.CommandLog(sb.ToString().TrimEnd());
  179. }
  180. });
  181. })
  182. .Build();
  183. string popupFileName = "." + Name + "_startuppopup";
  184. bool alreadyWarned = File.Exists(popupFileName);
  185. Client.EnterMenu += (_, args) =>
  186. {
  187. if (alreadyWarned) return;
  188. alreadyWarned = true;
  189. client.PromptUser(new DualChoicePrompt(
  190. #if !RELEASE
  191. $"Version {Version} of {Name} is a pre-release. If you encounter a bug or other issue, please report it on the Exmods Discord.",
  192. #else
  193. $"Congrats, you've installed the {Name} mod! If you encounter a bug or other issue, please report it on the Exmods Discord.",
  194. #endif
  195. Name,
  196. "Discord",
  197. () =>
  198. {
  199. if (DiscordRPC == null)
  200. {
  201. UnityEngine.Application.OpenURL("https://discord.exmods.org");
  202. }
  203. else
  204. {
  205. DiscordRPC.GetOverlayManager().OpenGuildInvite("2CtWzZT", result => { GamecraftModdingAPI.Utility.Logging.MetaLog($"Open Discord server invite through GameSDK {result}"); });
  206. }
  207. File.WriteAllText(popupFileName, Version);
  208. },
  209. "Ok",
  210. () => { File.WriteAllText(popupFileName, Version); }));
  211. };
  212. GamecraftModdingAPI.Utility.Logging.MetaLog($"{Name} has started up");
  213. }
  214. public override void OnUpdate() // called once per rendered frame (frame update)
  215. {
  216. if (DiscordRPC != null)
  217. {
  218. try
  219. {
  220. DiscordRPC.RunCallbacks();
  221. }
  222. catch (ResultException e)
  223. {
  224. GamecraftModdingAPI.Utility.Logging.LogWarning($"Discord integration has been disabled due to the exception " + e);
  225. DiscordRPC = null;
  226. }
  227. }
  228. }
  229. public static void SetDiscordActivity(string state = null, string details = null, long start = 0, long end = 0, string largeImg = "gamecraft-logo-g", string largeTxt = "Gamecraft", string smallImg = "exmods-logo-xm2", string smallTxt = null, string partyId = null, int partyCurrentSize = 0, int partyMaxSize = 0, string matchSecret = null, string joinSecret = null, string spectateSecret = null, bool instance = true, string debug = "")
  230. {
  231. if (DiscordRPC == null) return;
  232. ref Activity activity = ref PresenceUtility.Activity;
  233. activity.Instance = instance;
  234. if (state != null) activity.State = state;
  235. if (details != null) activity.Details = details;
  236. if (start != 0) activity.Timestamps.Start = start;
  237. if (end != 0) activity.Timestamps.End = end;
  238. if (!string.IsNullOrEmpty(largeImg))
  239. {
  240. activity.Assets.LargeImage = largeImg;
  241. activity.Assets.LargeText = largeTxt;
  242. }
  243. if (!string.IsNullOrEmpty(smallImg))
  244. {
  245. if (smallTxt == null) smallTxt = PluginInfo();
  246. activity.Assets.SmallImage = smallImg;
  247. activity.Assets.SmallText = smallTxt;
  248. }
  249. if (!string.IsNullOrEmpty(partyId))
  250. {
  251. activity.Party.Id = partyId;
  252. activity.Party.Size.CurrentSize = partyCurrentSize;
  253. activity.Party.Size.MaxSize = partyMaxSize;
  254. }
  255. if (!string.IsNullOrEmpty(matchSecret) || !string.IsNullOrEmpty(joinSecret) || !string.IsNullOrEmpty(spectateSecret))
  256. {
  257. activity.Secrets.Match = matchSecret;
  258. activity.Secrets.Join = joinSecret;
  259. activity.Secrets.Spectate = spectateSecret;
  260. }
  261. DiscordRPC.GetActivityManager().UpdateActivity(activity, result =>
  262. {
  263. GamecraftModdingAPI.Utility.Logging.MetaLog($"Update Activity Result: {result} {debug}");
  264. });
  265. }
  266. public static void SetDiscordActivity(Discord.Activity activity, string debug = "")
  267. {
  268. if (DiscordRPC == null) return;
  269. PresenceUtility.Activity = activity;
  270. DiscordRPC.GetActivityManager().UpdateActivity(PresenceUtility.Activity, result =>
  271. {
  272. GamecraftModdingAPI.Utility.Logging.MetaLog($"Update Activity Result: {result} {debug}");
  273. });
  274. }
  275. public static void SetDiscordActivity(string debug = "")
  276. {
  277. if (DiscordRPC == null) return;
  278. DiscordRPC.GetActivityManager().UpdateActivity(PresenceUtility.Activity, result =>
  279. {
  280. GamecraftModdingAPI.Utility.Logging.MetaLog($"Update Activity Result: {result} {debug}");
  281. });
  282. }
  283. internal static string PluginInfo()
  284. {
  285. Version version = Assembly.GetExecutingAssembly().GetName().Version;
  286. #if DEBUG
  287. string v = version.Major + "." + version.Minor + "." + version.Build + "alpha";
  288. #else
  289. string v = version.Major + "." + version.Minor + "." + version.Build;
  290. #endif
  291. return Assembly.GetExecutingAssembly().GetName().Name + " " + v;
  292. }
  293. }
  294. }