Skip to content

Commit 045a572

Browse files
committed
Add Torch client mod. Currently supports dialogs and notifications.
Add dialog output to !longhelp Add !notify command Silently inserts mod into session when client connects, server admins don't need to do anything to enable it.
1 parent d8915d1 commit 045a572

12 files changed

Lines changed: 501 additions & 7 deletions
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using ProtoBuf;
7+
using Sandbox.ModAPI;
8+
9+
namespace Torch.Mod.Messages
10+
{
11+
/// Dialogs are structured as follows
12+
///
13+
/// _____________________________________
14+
/// | Title |
15+
/// --------------------------------------
16+
/// | Prefix Subtitle |
17+
/// --------------------------------------
18+
/// | ________________________________ |
19+
/// | | Content | |
20+
/// | --------------------------------- |
21+
/// | ____________ |
22+
/// | | ButtonText | |
23+
/// | -------------- |
24+
/// --------------------------------------
25+
///
26+
/// Button has a callback on click option,
27+
/// but can't serialize that, so ¯\_(ツ)_/¯
28+
[ProtoContract]
29+
public class DialogMessage : MessageBase
30+
{
31+
[ProtoMember(201)]
32+
public string Title;
33+
[ProtoMember(202)]
34+
public string Subtitle;
35+
[ProtoMember(203)]
36+
public string Prefix;
37+
[ProtoMember(204)]
38+
public string Content;
39+
[ProtoMember(205)]
40+
public string ButtonText;
41+
42+
public DialogMessage()
43+
{ }
44+
45+
public DialogMessage(string title, string subtitle, string content)
46+
{
47+
Title = title;
48+
Subtitle = subtitle;
49+
Content = content;
50+
Prefix = String.Empty;
51+
}
52+
53+
public DialogMessage(string title = null, string prefix = null, string subtitle = null, string content = null, string buttonText = null)
54+
{
55+
Title = title;
56+
Subtitle = subtitle;
57+
Prefix = prefix ?? String.Empty;
58+
Content = content;
59+
ButtonText = buttonText;
60+
}
61+
62+
public override void ProcessClient()
63+
{
64+
MyAPIGateway.Utilities.ShowMissionScreen(Title, Prefix, Subtitle, Content, null, ButtonText);
65+
}
66+
67+
public override void ProcessServer()
68+
{
69+
throw new Exception();
70+
}
71+
}
72+
}

‎Torch.Mod/Messages/MessageBase.cs‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using ProtoBuf;
7+
8+
namespace Torch.Mod.Messages
9+
{
10+
#region Includes
11+
[ProtoInclude(1, typeof(DialogMessage))]
12+
[ProtoInclude(2, typeof(NotificationMessage))]
13+
#endregion
14+
15+
[ProtoContract]
16+
public abstract class MessageBase
17+
{
18+
[ProtoMember(101)]
19+
public ulong SenderId;
20+
21+
public abstract void ProcessClient();
22+
public abstract void ProcessServer();
23+
24+
//members below not serialized, they're just metadata about the intended target(s) of this message
25+
internal MessageTarget TargetType;
26+
internal ulong Target;
27+
internal ulong[] Ignore;
28+
internal byte[] CompressedData;
29+
}
30+
31+
public enum MessageTarget
32+
{
33+
/// <summary>
34+
/// Send to Target
35+
/// </summary>
36+
Single,
37+
/// <summary>
38+
/// Send to Server
39+
/// </summary>
40+
Server,
41+
/// <summary>
42+
/// Send to all Clients (only valid from server)
43+
/// </summary>
44+
AllClients,
45+
/// <summary>
46+
/// Send to all except those steam ID listed in Ignore
47+
/// </summary>
48+
AllExcept,
49+
}
50+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using ProtoBuf;
5+
using Sandbox.ModAPI;
6+
7+
namespace Torch.Mod.Messages
8+
{
9+
[ProtoContract]
10+
public class NotificationMessage : MessageBase
11+
{
12+
[ProtoMember(201)]
13+
public string Message;
14+
[ProtoMember(202)]
15+
public string Font;
16+
[ProtoMember(203)]
17+
public int DisappearTimeMs;
18+
19+
public NotificationMessage()
20+
{ }
21+
22+
public NotificationMessage(string message, int disappearTimeMs, string font)
23+
{
24+
Message = message;
25+
DisappearTimeMs = disappearTimeMs;
26+
Font = font;
27+
}
28+
29+
public override void ProcessClient()
30+
{
31+
MyAPIGateway.Utilities.ShowNotification(Message, DisappearTimeMs, Font);
32+
}
33+
34+
public override void ProcessServer()
35+
{
36+
throw new Exception();
37+
}
38+
}
39+
}

‎Torch.Mod/ModCommunication.cs‎

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Sandbox.ModAPI;
9+
using Torch.Mod.Messages;
10+
using VRage;
11+
using VRage.Game.ModAPI;
12+
using VRage.Utils;
13+
using Task = ParallelTasks.Task;
14+
15+
namespace Torch.Mod
16+
{
17+
public static class ModCommunication
18+
{
19+
public const ushort NET_ID = 4352;
20+
private static bool _closing;
21+
private static ConcurrentQueue<MessageBase> _outgoing;
22+
private static ConcurrentQueue<byte[]> _incoming;
23+
private static List<IMyPlayer> _playerCache;
24+
private static FastResourceLock _lock;
25+
private static Task _task;
26+
27+
public static void Register()
28+
{
29+
MyLog.Default.WriteLineAndConsole("TORCH MOD: Registering mod communication.");
30+
_outgoing = new ConcurrentQueue<MessageBase>();
31+
_incoming = new ConcurrentQueue<byte[]>();
32+
_playerCache = new List<IMyPlayer>();
33+
_lock = new FastResourceLock();
34+
35+
36+
MyAPIGateway.Multiplayer.RegisterMessageHandler(NET_ID, MessageHandler);
37+
//background thread to handle de/compression and processing
38+
_task = MyAPIGateway.Parallel.StartBackground(DoProcessing);
39+
MyLog.Default.WriteLineAndConsole("TORCH MOD: Mod communication registered successfully.");
40+
}
41+
42+
public static void Unregister()
43+
{
44+
MyLog.Default.WriteLineAndConsole("TORCH MOD: Unregistering mod communication.");
45+
MyAPIGateway.Multiplayer.UnregisterMessageHandler(NET_ID, MessageHandler);
46+
_closing = true;
47+
ReleaseLock();
48+
_task.Wait();
49+
}
50+
51+
private static void MessageHandler(byte[] bytes)
52+
{
53+
_incoming.Enqueue(bytes);
54+
ReleaseLock();
55+
}
56+
57+
public static void DoProcessing()
58+
{
59+
while (!_closing)
60+
{
61+
try
62+
{
63+
byte[] incoming;
64+
while (_incoming.TryDequeue(out incoming))
65+
{
66+
MessageBase m;
67+
try
68+
{
69+
var o = MyCompression.Decompress(incoming);
70+
m = MyAPIGateway.Utilities.SerializeFromBinary<MessageBase>(o);
71+
}
72+
catch (Exception ex)
73+
{
74+
MyLog.Default.WriteLineAndConsole($"TORCH MOD: Failed to deserialize message! {ex}");
75+
continue;
76+
}
77+
if (MyAPIGateway.Multiplayer.IsServer)
78+
m.ProcessServer();
79+
else
80+
m.ProcessClient();
81+
}
82+
83+
if (!_outgoing.IsEmpty)
84+
{
85+
List<MessageBase> tosend = new List<MessageBase>(_outgoing.Count);
86+
MessageBase outMessage;
87+
while (_outgoing.TryDequeue(out outMessage))
88+
{
89+
var b = MyAPIGateway.Utilities.SerializeToBinary(outMessage);
90+
outMessage.CompressedData = MyCompression.Compress(b);
91+
tosend.Add(outMessage);
92+
}
93+
94+
MyAPIGateway.Utilities.InvokeOnGameThread(() =>
95+
{
96+
MyAPIGateway.Players.GetPlayers(_playerCache);
97+
foreach (var outgoing in tosend)
98+
{
99+
switch (outgoing.TargetType)
100+
{
101+
case MessageTarget.Single:
102+
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, outgoing.CompressedData, outgoing.Target);
103+
break;
104+
case MessageTarget.Server:
105+
MyAPIGateway.Multiplayer.SendMessageToServer(NET_ID, outgoing.CompressedData);
106+
break;
107+
case MessageTarget.AllClients:
108+
foreach (var p in _playerCache)
109+
{
110+
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId)
111+
continue;
112+
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, outgoing.CompressedData, p.SteamUserId);
113+
}
114+
break;
115+
case MessageTarget.AllExcept:
116+
foreach (var p in _playerCache)
117+
{
118+
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId || outgoing.Ignore.Contains(p.SteamUserId))
119+
continue;
120+
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, outgoing.CompressedData, p.SteamUserId);
121+
}
122+
break;
123+
default:
124+
throw new Exception();
125+
}
126+
}
127+
_playerCache.Clear();
128+
});
129+
}
130+
131+
AcquireLock();
132+
}
133+
catch (Exception ex)
134+
{
135+
MyLog.Default.WriteLineAndConsole($"TORCH MOD: Exception occurred in communication thread! {ex}");
136+
}
137+
}
138+
139+
MyLog.Default.WriteLineAndConsole("TORCH MOD: COMMUNICATION THREAD: EXIT SIGNAL RECIEVED!");
140+
//exit signal received. Clean everything and GTFO
141+
_outgoing = null;
142+
_incoming = null;
143+
_playerCache = null;
144+
_lock = null;
145+
}
146+
147+
public static void SendMessageTo(MessageBase message, ulong target)
148+
{
149+
if (!MyAPIGateway.Multiplayer.IsServer)
150+
throw new Exception("Only server can send targeted messages");
151+
message.Target = target;
152+
message.TargetType = MessageTarget.Single;
153+
MyLog.Default.WriteLineAndConsole($"Sending message of type {message.GetType().FullName}");
154+
_outgoing.Enqueue(message);
155+
ReleaseLock();
156+
}
157+
158+
public static void SendMessageToClients(MessageBase message)
159+
{
160+
if (!MyAPIGateway.Multiplayer.IsServer)
161+
throw new Exception("Only server can send targeted messages");
162+
message.TargetType = MessageTarget.AllClients;
163+
_outgoing.Enqueue(message);
164+
ReleaseLock();
165+
}
166+
167+
public static void SendMessageExcept(MessageBase message, params ulong[] ignoredUsers)
168+
{
169+
if (MyAPIGateway.Multiplayer.IsServer)
170+
throw new Exception("Only server can send targeted messages");
171+
message.TargetType = MessageTarget.AllExcept;
172+
message.Ignore = ignoredUsers;
173+
_outgoing.Enqueue(message);
174+
ReleaseLock();
175+
}
176+
177+
public static void SendMessageToServer(MessageBase message)
178+
{
179+
message.TargetType = MessageTarget.Server;
180+
_outgoing.Enqueue(message);
181+
ReleaseLock();
182+
}
183+
184+
private static void ReleaseLock()
185+
{
186+
while(!_lock.TryAcquireExclusive())
187+
_lock.ReleaseExclusive();
188+
_lock.ReleaseExclusive();
189+
}
190+
191+
private static void AcquireLock()
192+
{
193+
ReleaseLock();
194+
_lock.AcquireExclusive();
195+
_lock.AcquireExclusive();
196+
}
197+
}
198+
}

‎Torch.Mod/Torch.Mod.projitems‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
5+
<HasSharedItems>true</HasSharedItems>
6+
<SharedGUID>3ce4d2e9-b461-4f19-8233-f87e0dfddd74</SharedGUID>
7+
</PropertyGroup>
8+
<PropertyGroup Label="Configuration">
9+
<Import_RootNamespace>Torch.Mod</Import_RootNamespace>
10+
</PropertyGroup>
11+
<ItemGroup>
12+
<Compile Include="$(MSBuildThisFileDirectory)Messages\NotificationMessage.cs" />
13+
<Compile Include="$(MSBuildThisFileDirectory)Messages\DialogMessage.cs" />
14+
<Compile Include="$(MSBuildThisFileDirectory)Messages\MessageBase.cs" />
15+
<Compile Include="$(MSBuildThisFileDirectory)ModCommunication.cs" />
16+
<Compile Include="$(MSBuildThisFileDirectory)TorchModCore.cs" />
17+
</ItemGroup>
18+
</Project>

‎Torch.Mod/Torch.Mod.shproj‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup Label="Globals">
4+
<ProjectGuid>3ce4d2e9-b461-4f19-8233-f87e0dfddd74</ProjectGuid>
5+
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
6+
</PropertyGroup>
7+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
8+
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
9+
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
10+
<PropertyGroup />
11+
<Import Project="Torch.Mod.projitems" Label="Shared" />
12+
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
13+
</Project>

0 commit comments

Comments
 (0)