forked from ScriptedEvents/ScriptedEvents
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEventScriptModule.cs
More file actions
379 lines (311 loc) · 13.9 KB
/
EventScriptModule.cs
File metadata and controls
379 lines (311 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
using Exiled.API.Enums;
using PlayerRoles;
namespace ScriptedEvents.API.Modules
{
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Exiled.API.Features;
using Exiled.API.Features.Doors;
using Exiled.API.Features.Items;
using Exiled.API.Features.Roles;
using Exiled.Events.EventArgs.Interfaces;
using Exiled.Events.Features;
using Exiled.Loader;
using MapGeneration.Distributors;
using ScriptedEvents.API.Enums;
using ScriptedEvents.API.Extensions;
using ScriptedEvents.API.Features;
using ScriptedEvents.API.Features.Exceptions;
using ScriptedEvents.Structures;
public class EventScriptModule : SEModule
{
/// <summary>
/// Gets an array of Event "Handler" types defined by Exiled.
/// </summary>
public static Type[] HandlerTypes { get; private set; } = [];
public static EventScriptModule? Singleton { get; private set; }
public override string Name => "EventScriptModule";
public List<Tuple<PropertyInfo, Delegate>> StoredDelegates { get; } = new();
public Dictionary<string, List<string>> CurrentEventData { get; set; } = [];
public Dictionary<string, List<string>> CurrentCustomEventData { get; set; } = [];
public List<string> DynamicallyConnectedEvents { get; set; } = new();
// Connection methods
public static void OnArgumentedEvent<T>(T ev)
where T : IExiledEvent
{
Type evType = typeof(T);
string evName = evType.Name.Replace("EventArgs", string.Empty);
Singleton!.OnAnyEvent(evName, ev);
}
public static void OnNonArgumentedEvent()
{
Singleton!.OnAnyEvent(new StackFrame(2).GetMethod().Name.Replace("EventArgs", string.Empty));
}
public override void Init()
{
base.Init();
Singleton = this;
try
{
HandlerTypes = Loader.Plugins.First(plug => plug.Name == "Exiled.Events")
.Assembly.GetTypes()
.Where(t => t.FullName?.Equals($"Exiled.Events.Handlers.{t.Name}") is true).ToArray();
}
catch
{
Logger.Error($"Fetching HandlerTypes failed! Exiled.Events does not exist in loaded plugins:\n{string.Join(", ", Loader.Plugins.Select(x => x.Name))}");
}
// Events
Exiled.Events.Handlers.Server.RestartingRound += TerminateConnections;
Exiled.Events.Handlers.Server.WaitingForPlayers += BeginConnections;
}
public override void Kill()
{
base.Kill();
TerminateConnections();
// Disconnect events
Exiled.Events.Handlers.Server.RestartingRound -= TerminateConnections;
Exiled.Events.Handlers.Server.WaitingForPlayers -= BeginConnections;
Singleton = null;
}
// Methods to make and destroy connections
public void BeginConnections()
{
if (CurrentEventData is not null)
return;
CurrentEventData = new();
CurrentCustomEventData = new();
foreach (Script scr in MainPlugin.ScriptModule.ListScripts())
{
if (scr.HasFlag("EVENT", out Flag f))
{
string evName = f.Arguments[0];
if (CurrentEventData.ContainsKey(evName))
{
CurrentEventData[evName].Add(scr.Name);
}
else
{
CurrentEventData.Add(evName, new List<string>() { scr.Name });
}
}
if (scr.HasFlag("CUSTOMEVENT", out Flag cf))
{
string cEvName = cf.Arguments[0];
if (CurrentCustomEventData.ContainsKey(cEvName))
{
CurrentCustomEventData[cEvName].Add(scr.Name);
}
else
{
CurrentCustomEventData.Add(cEvName, new List<string>() { scr.Name });
}
}
scr.Dispose();
}
foreach (KeyValuePair<string, List<string>> ev in CurrentEventData)
{
Logger.Debug("Setting up new 'on' event");
Logger.Debug($"Event: {ev.Key}");
Logger.Debug($"Scripts: {string.Join(", ", ev.Value)}");
ConnectDynamicExiledEvent(ev.Key);
}
}
public void ConnectDynamicExiledEvent(string key)
{
if (DynamicallyConnectedEvents.Contains(key)) return;
DynamicallyConnectedEvents.Add(key);
bool made = false;
foreach (Type handler in HandlerTypes)
{
// Credit to DevTools & Yamato for below code.
Delegate @delegate;
PropertyInfo propertyInfo = handler.GetProperty(key);
if (propertyInfo is null)
continue;
EventInfo eventInfo = propertyInfo.PropertyType.GetEvent("InnerEvent", (BindingFlags)(-1));
MethodInfo subscribe = propertyInfo.PropertyType.GetMethods().First(x => x.Name is "Subscribe");
if (propertyInfo.PropertyType == typeof(Event))
{
@delegate = new CustomEventHandler(OnNonArgumentedEvent);
}
else if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Event<>))
{
@delegate = typeof(EventScriptModule)
.GetMethod(nameof(OnArgumentedEvent))
.MakeGenericMethod(eventInfo.EventHandlerType.GenericTypeArguments)
.CreateDelegate(typeof(CustomEventHandler<>)
.MakeGenericType(eventInfo.EventHandlerType.GenericTypeArguments));
}
else
{
Logger.Warn(propertyInfo.Name);
continue;
}
subscribe.Invoke(propertyInfo.GetValue(MainPlugin.Handlers), [@delegate]);
StoredDelegates.Add(new Tuple<PropertyInfo, Delegate>(propertyInfo, @delegate));
made = true;
}
if (made)
Logger.Debug($"Dynamic event {key} connected successfully");
else
Logger.Debug($"Dynamic event {key} failed to be connected");
}
public void TerminateConnections()
{
foreach (Tuple<PropertyInfo, Delegate> tuple in StoredDelegates)
{
PropertyInfo propertyInfo = tuple.Item1;
Delegate handler = tuple.Item2;
Logger.Debug($"Removing dynamic connection for event '{propertyInfo.Name}'");
EventInfo eventInfo = propertyInfo.PropertyType.GetEvent("InnerEvent", (BindingFlags)(-1));
MethodInfo unSubscribe = propertyInfo.PropertyType.GetMethods().First(x => x.Name is "Unsubscribe");
unSubscribe.Invoke(propertyInfo.GetValue(MainPlugin.Handlers), [handler]);
Logger.Debug($"Removed dynamic connection for event '{propertyInfo.Name}'");
}
StoredDelegates.Clear();
CurrentEventData = [];
CurrentCustomEventData = [];
DynamicallyConnectedEvents = [];
}
// Code to run when connected event is executed
public void OnAnyEvent(string eventName, IExiledEvent? ev = null)
{
if (ev == null) return;
Stopwatch stopwatch = new();
stopwatch.Start();
if (ev is IDeniableEvent deniable and IPlayerEvent playerEvent)
{
bool playerIsNotNone = playerEvent.Player is not null;
bool isRegisteredRule = MainPlugin.Handlers.GetPlayerDisableEvent(eventName, playerEvent.Player).HasValue;
if (playerIsNotNone && isRegisteredRule)
{
Log.Debug("Event is disabled.");
deniable.IsAllowed = false;
return;
}
}
if (CurrentEventData is null || !CurrentEventData.TryGetValue(eventName, out var scriptNames))
{
return;
}
Log.Debug("Scripts connected to this event:");
List<Script> scripts = new();
foreach (string scrName in scriptNames)
{
try
{
scripts.Add(MainPlugin.ScriptModule.ReadScript(scrName, null));
Log.Debug($"- {scrName}.txt");
}
catch (DisabledScriptException)
{
Logger.Warn(ErrorGen.Get(ErrorCode.On_DisabledScript, eventName, scrName));
}
catch (FileNotFoundException)
{
Logger.Warn(ErrorGen.Get(ErrorCode.On_NotFoundScript, eventName, scrName));
}
catch (Exception ex)
{
Logger.Warn(ErrorGen.Get(ErrorCode.On_UnknownError, eventName) + $": {ex}");
}
}
var properties = (
from prop in ev.GetType().GetProperties()
let value = prop.GetValue(ev)
where value is not null
select new Tuple<object, string>(value, prop.Name)).ToList();
IPlayerEvent? reference = ev as IPlayerEvent;
switch (eventName)
{
case "Left":
case "ChangingRole":
LastPlayerState state = new(reference!.Player);
properties.Add(new(state, "ShouldNotHappen"));
break;
}
foreach (var (propValue, propName) in properties)
{
switch (propValue)
{
case Player player:
if (player is Npc) continue;
foreach (var script in scripts)
script.AddPlayerVariable($"{{EV{propName.ToUpper()}}}", string.Empty, new[] { player });
Log.Debug($"Adding variable {{EV{propName.ToUpper()}}} to all scripts above.");
break;
case Item item:
AddVariable(item.Base.ItemSerial.ToString());
break;
case Door door:
AddVariable(door.Type.ToString());
break;
case Scp079Generator gen:
AddVariable(gen.GetInstanceID().ToString());
break;
case Role role:
AddVariable(role.Type.ToString());
break;
case Enum anyEnum:
AddVariable(anyEnum.ToString());
break;
case bool @bool:
AddVariable(@bool.ToUpper());
break;
case LastPlayerState lastPlayerState:
foreach (FieldInfo field in typeof(LastPlayerState).GetFields(BindingFlags.Public | BindingFlags.Instance))
{
string fieldName = field.Name;
object value = field.GetValue(lastPlayerState);
foreach (Script script in scripts)
{
script.AddVariable($"{{EV{fieldName.ToUpper()}}}", string.Empty, value.ToString());
Log.Debug($"Adding variable {{EV{fieldName.ToUpper()}}} to all scripts above.");
}
}
break;
default:
AddVariable(propValue.ToString());
break;
}
continue;
void AddVariable(string varValue)
{
foreach (Script script in scripts)
{
script.AddVariable($"{{EV{propName.ToUpper()}}}", string.Empty, varValue);
Log.Debug($"Adding variable {{EV{propName.ToUpper()}}} to all scripts above.");
}
}
}
foreach (Script script in scripts)
script.Execute();
stopwatch.Stop();
Log.Debug($"Handling event '{eventName}' cost {stopwatch.ElapsedMilliseconds} ms");
}
private class LastPlayerState
{
public string LastName;
public string LastUserId;
public RoleTypeId LastRole;
public Team LastTeam;
public ZoneType LastZone;
public RoomType LastRoom;
public LastPlayerState(Player player)
{
LastName = player.Nickname;
LastUserId = player.UserId;
LastRole = player.Role.Type;
LastTeam = player.Role.Team;
LastZone = player.Zone;
LastRoom = player.CurrentRoom?.Type ?? RoomType.Unknown;
}
}
}
}