← Back to changelog
What changed
CargoSinks · v1.1.0 → v1.1.1 (current)
Diff
3 added · 2 removed · 418 → 419 total lines
1
1
/*▄▄▄ ███▄ ▄███▓ ▄████ ▄▄▄██▀▀▀▓█████▄▄▄█████▓
2
2
▓█████▄ ▓██▒▀█▀ ██▒ ██▒ ▀█▒ ▒██ ▓█ ▀▓ ██▒ ▓▒
3
3
▒██▒ ▄██▓██ ▓██░▒██░▄▄▄░ ░██ ▒███ ▒ ▓██░ ▒░
4
4
▒██░█▀ ▒██ ▒██ ░▓█ ██▓▓██▄██▓ ▒▓█ ▄░ ▓██▓ ░
5
5
░▓█ ▀█▓▒██▒ ░██▒░▒▓███▀▒ ▓███▒ ░▒████▒ ▒██▒ ░
6
6
░▒▓███▀▒░ ▒░ ░ ░ ░▒ ▒ ▒▓▒▒░ ░░ ▒░ ░ ▒ ░░
7
7
▒░▒ ░ ░ ░ ░ ░ ░ ▒ ░▒░ ░ ░ ░ ░
8
8
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
9
9
░ ░ ░ ░ ░ ░ ░*/
10
10
using HarmonyLib;
11
11
using Newtonsoft.Json;
12
12
using Oxide.Core.Plugins;
13
13
using System;
14
14
using System.Collections.Generic;
15
15
using System.Linq;
16
16
using UnityEngine;
17
17
18
18
namespace Oxide.Plugins
19
19
{
20
-
[Info("CargoSinks", "bmgjet", "1.1.0")]
20
+
[Info("CargoSinks", "bmgjet", "1.1.1")]
21
21
class CargoSinks : RustPlugin
22
22
{
23
23
public static CargoSinks cargoSinks;
24
24
private const string c4 = "assets/prefabs/tools/c4/effects/c4_explosion.prefab";
25
25
private const string debris = "assets/content/vehicles/attackhelicopter/debris_effect.prefab";
26
26
private List<CargoShip> CargoKillShipList = new List<CargoShip>();
27
27
private Vector3[] SubEntities = new Vector3[] { new Vector3(0.55f, 9.58f, -61.38f), new Vector3(0.01f, 24.5f, -38.64f), new Vector3(9.62f, 9.14f, -16.50f), new Vector3(-9.62f, 9.14f, -16.50f), new Vector3(9.62f, 9.14f, 10.50f), new Vector3(-9.62f, 9.14f, 10.50f), new Vector3(9.62f, 9.14f, 37.50f), new Vector3(-9.62f, 9.14f, 37.50f) };
28
28
29
29
#region Configuration
30
30
private Configuration config;
31
31
private class Configuration
32
32
{
33
33
[JsonProperty("Show Console Debug Messages")]
34
34
public bool Debug = false;
35
35
36
36
[JsonProperty("Chat Command To Force-Sink Cargo Ship You're Looking At")]
37
37
public string SinkCommand = "sinkme";
38
38
39
39
[JsonProperty("Require Permission Instead Of Admin Only For Sink Command")]
40
40
public bool RequirePermission = false;
41
41
42
42
[JsonProperty("Permission Needed For Sink Command (Only If Above Is True)")]
43
43
public string SinkPermission = "cargosinks.admin";
44
44
45
45
[JsonProperty("Reverse Cargo Ship Patrol Path")]
46
46
public bool ReverseCargoPath = false;
47
47
48
48
[JsonProperty("Topology That Blocks Egress (Bitmask, Default = Monument)")]
49
49
public int TopologyBaseBlock = TerrainTopology.MONUMENT;
50
50
51
51
[JsonProperty("Retry Delay When Egress Blocked By Topology (Seconds)")]
52
52
public float TopologyBlockRetryDelay = 30f;
53
53
54
54
[JsonProperty("Radius To Check For Player Bases Blocking Egress")]
55
55
public float BaseBlockRadius = 100f;
56
56
57
57
[JsonProperty("Retry Delay When Egress Blocked By A Player Base (Seconds)")]
58
58
public float BaseBlockRetryDelay = 60f;
59
59
60
60
[JsonProperty("Delay Before Explosions Start After Egress Is Triggered (Seconds)")]
61
61
public int ExplodeDelay = 80;
62
62
63
63
[JsonProperty("Delay Between Each Explosion (Seconds)")]
64
64
public float DelayBetweenExplosion = 1.0f;
65
65
66
66
[JsonProperty("Sink Speed (Negative = Downward, Units Per Second)")]
67
67
public float SinkSpeed = -0.85f;
68
68
69
69
[JsonProperty("Height (Y) At Which The Ship Is Considered Fully Sunk")]
70
70
public int SinkAmount = -45;
71
71
72
72
[JsonProperty("Give Radiation To Nearby Players While Sinking")]
73
73
public bool GiveRadsOnSink = true;
74
74
75
75
[JsonProperty("Delay After Fully Sunk Before The Ship Entity Is Removed (Seconds)")]
76
76
public int SunkDeathDelay = 80;
77
77
78
78
[JsonProperty("Radius To Check For Nearby Players Before Removing A Sunk Ship")]
79
79
public float KillCheckRadius = 100f;
80
80
81
81
[JsonProperty("Retry Delay When A Nearby Player Blocks Ship Removal (Seconds)")]
82
82
public float KillRetryDelay = 60f;
83
83
84
84
[JsonProperty("Harbor Pathing: Terrain Height That Blocks A Path (Meters)")]
85
85
public float HarborPathHeightBlock = 3f;
86
86
87
87
[JsonProperty("Harbor Pathing: Step Size Used To Scan For Blocked Paths (Meters)")]
88
88
public float HarborPathStepSize = 2f;
89
89
90
90
[JsonProperty("Harbor Pathing: Distance Penalty Multiplier When No Line Of Sight")]
91
91
public float HarborPathNoLosPenalty = 20f;
92
92
93
93
[JsonProperty("Harbor Pathing: Line Of Sight Layer Mask")]
94
94
public int HarborPathLayerMask = 1084293377;
95
95
96
96
public string ToJson() => JsonConvert.SerializeObject(this);
97
97
public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
98
98
}
99
99
100
100
protected override void LoadDefaultConfig()
101
101
{
102
102
config = new Configuration();
103
103
}
104
104
protected override void LoadConfig()
105
105
{
106
106
base.LoadConfig();
107
107
try
108
108
{
109
109
config = Config.ReadObject<Configuration>();
110
110
if (config == null)
111
111
{
112
112
throw new JsonException();
113
113
}
114
114
115
115
if (!config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys))
116
116
{
117
117
PrintWarning("Configuration appears to be outdated; updating and saving");
118
118
SaveConfig();
119
119
}
120
120
}
121
121
catch
122
122
{
123
123
PrintWarning($"Configuration file {Name}.json is invalid; using defaults");
124
124
LoadDefaultConfig();
125
125
}
126
126
}
127
127
protected override void SaveConfig()
128
128
{
129
129
PrintWarning($"Configuration changes saved to {Name}.json");
130
130
Config.WriteObject(config, true);
131
131
}
132
132
#endregion Configuration
133
133
134
134
private void SinkMeCommand(BasePlayer player, string command, string[] args)
135
135
{
136
136
bool allowed = config.RequirePermission
137
137
? permission.UserHasPermission(player.UserIDString, config.SinkPermission)
138
138
: player.IsAdmin;
139
139
if (!allowed) { player.ChatMessage("You do not have permission to use this command."); return; }
140
140
141
141
RaycastHit hit;
142
142
var raycast = Physics.Raycast(player.eyes.HeadRay(), out hit, 50, -1);
143
143
BaseEntity entity = raycast ? hit.GetEntity() : null;
144
144
if (entity != null && entity is CargoShip) { (entity as CargoShip)?.StartEgress(); }
145
145
}
146
146
147
147
private void OnServerInitialized()
148
148
{
149
149
CargoShip.hasCalculatedApproaches = false;
150
150
if (config.ReverseCargoPath)
151
151
{
152
152
TerrainMeta.Path.OceanPatrolFar.Reverse(); //Reverse cargopath
153
153
}
154
154
}
155
155
156
156
private void Init()
157
157
{
158
158
cargoSinks = this;
159
159
permission.RegisterPermission(config.SinkPermission, this);
160
160
cmd.AddChatCommand(config.SinkCommand, this, nameof(SinkMeCommand));
161
161
}
162
162
163
163
private void Unload()
164
164
{
165
165
cargoSinks = null;
166
166
if (config.ReverseCargoPath)
167
167
{
168
168
TerrainMeta.Path.OceanPatrolFar.Reverse(); //Restore cargopath
169
169
}
170
170
foreach (var c in CargoKillShipList) { if (c != null) { c.Kill(); } }
171
171
}
172
172
173
173
private void OnEntitySpawned(CargoShip cs)
174
174
{
175
175
if (cs != null)
176
176
{
177
177
Vector3 pos = TerrainMeta.Path.OceanPatrolFar.GetRandom();
178
178
pos.y = 0;
179
179
cs.transform.position = pos;
180
180
int index = GetClosestNodeToUs(pos) - 1;
181
181
if (index < 0) { index = TerrainMeta.Path.OceanPatrolFar.Count - 1; }
182
182
pos = TerrainMeta.Path.OceanPatrolFar[index];
183
183
pos.y = 0;
184
184
cs.transform.LookAt(pos);
185
185
cs.SendNetworkUpdateImmediate();
186
186
if (config.Debug) Puts("Corrected Cargo Position");
187
187
timer.Once(5f, () =>
188
188
{
189
189
if (cs != null)
190
190
{
191
191
int Entities = 2;
192
192
foreach (var pe in cs.children)
193
193
{
194
194
if (pe != null)
195
195
{
196
196
try
197
197
{
198
198
if (pe is RHIB) { pe.transform.localPosition = SubEntities[0]; }
199
199
else if (pe is MicrophoneStand) { pe.transform.localPosition = SubEntities[1]; }
200
200
else if (pe is ConnectedSpeaker) { pe.transform.localPosition = SubEntities[Entities++]; }
201
201
}
202
202
catch { }
203
203
}
204
204
}
205
205
if (config.Debug) Puts("Corrected SubEntities Positions");
206
206
}
207
207
});
208
208
}
209
209
}
210
210
211
211
public int GetClosestNodeToUs(Vector3 position)
212
212
{
213
213
int result = 0;
214
214
try
215
215
{
216
216
float num = float.PositiveInfinity;
217
217
for (int i = 0; i < TerrainMeta.Path.OceanPatrolFar.Count; i++)
218
218
{
219
219
Vector3 b = TerrainMeta.Path.OceanPatrolFar[i];
220
220
float num2 = Vector3.Distance(position, b);
221
221
if (num2 < num)
222
222
{
223
223
result = i;
224
224
num = num2;
225
225
}
226
226
}
227
227
}
228
228
catch { }
229
229
return result;
230
230
}
231
231
232
232
private object OnCargoShipEgress(CargoShip _cargoship)
233
233
{
234
234
if (_cargoship != null)
235
235
{
236
236
//Prevent leave when in topology
237
237
if (TerrainMeta.TopologyMap.GetTopology(_cargoship.transform.position, config.TopologyBaseBlock))
238
238
{
239
239
timer.Once(config.TopologyBlockRetryDelay, () => { if (_cargoship != null) { _cargoship.StartEgress(); } });
240
240
if (config.Debug) Puts("Topo Blocked");
241
241
return true;
242
242
}
243
243
//Prevent leave when near player base
244
244
List<BuildingBlock> list = Facepunch.Pool.Get<List<BuildingBlock>>();
245
245
Vis.Entities<BuildingBlock>(_cargoship.transform.position, config.BaseBlockRadius, list);
246
246
if (list.Count != 0)
247
247
{
248
248
timer.Once(config.BaseBlockRetryDelay, () => { if (_cargoship != null) { _cargoship.StartEgress(); } });
249
249
Facepunch.Pool.FreeUnmanaged<BuildingBlock>(ref list);
250
250
if (config.Debug) Puts("Base Blocked");
251
251
return true;
252
252
}
253
253
Facepunch.Pool.FreeUnmanaged<BuildingBlock>(ref list);
254
254
if (config.Debug) Puts("Start Egress");
255
255
Vector3 bow = _cargoship.transform.position + (_cargoship.transform.forward * 81) + (_cargoship.transform.up * 1);
256
-
_cargoship.SetFlag(BaseEntity.Flags.Reserved8, true, false, true);
256
+
_cargoship.SetFlagLocal(BaseEntity.Flags.Reserved8, true, false);
257
257
_cargoship.targetNodeIndex = -1;
258
258
_cargoship.currentThrottle = 0;
259
+
_cargoship.SendNetworkUpdateImmediate();
259
260
List<Vector3> ExpoPos = new List<Vector3>();
260
261
for (int i = 0; i < 9; i++)
261
262
{
262
263
ExpoPos.Add(bow + (_cargoship.transform.forward * -32) + (_cargoship.transform.forward * (-10 * i)) + (_cargoship.transform.right * 6));
263
264
ExpoPos.Add(bow + (_cargoship.transform.forward * -32) + (_cargoship.transform.forward * (-10 * i)) + (_cargoship.transform.right * -6));
264
265
}
265
266
RunEffect(c4, null, bow + (_cargoship.transform.forward * -7) + (_cargoship.transform.right * 2));
266
267
RunEffect(debris, null, bow + (_cargoship.transform.forward * -7) + (_cargoship.transform.right * 2));
267
268
RunEffect(c4, null, bow + (_cargoship.transform.forward * -7) + (_cargoship.transform.right * -2));
268
269
RunEffect(debris, null, bow + (_cargoship.transform.forward * -7) + (_cargoship.transform.right * -2));
269
270
timer.Once(config.ExplodeDelay, () => { if (_cargoship != null) { playexp(ExpoPos, _cargoship); } });
270
271
CargoKillShipList.Add(_cargoship);
271
272
}
272
273
return null;
273
274
}
274
275
275
276
private void RunEffect(string name, BaseEntity entity = null, Vector3 position = new Vector3(), Vector3 offset = new Vector3())
276
277
{
277
278
if (entity != null) { Effect.server.Run(name, entity, 0, offset, position, null, true); return; }
278
279
Effect.server.Run(name, position, Vector3.up, null, true);
279
280
}
280
281
281
282
private void playexp(List<Vector3> ExPoint, CargoShip cargoShip)
282
283
{
283
284
if (ExPoint != null && ExPoint.Count > 1)
284
285
{
285
286
RunEffect(c4, null, ExPoint[0]);
286
287
RunEffect(debris, null, ExPoint[1]);
287
288
ExPoint.RemoveAt(0);
288
289
ExPoint.RemoveAt(0);
289
290
timer.Once(config.DelayBetweenExplosion, () => { playexp(ExPoint, cargoShip); });
290
291
return;
291
292
}
292
293
if (config.Debug) Puts("Expo Ran");
293
294
for (int i = cargoShip.children.Count - 1; i >= 0; i--)
294
295
{
295
296
BaseEntity baseEntity = cargoShip.children[i];
296
297
if (baseEntity != null && !baseEntity.IsDestroyed && baseEntity is ScientistNPC)
297
298
{
298
299
if ((baseEntity as ScientistNPC).IsAlive()) { baseEntity.Kill(); }
299
300
}
300
301
}
301
302
if (config.Debug) Puts("NPCs Killed");
302
303
Sink(cargoShip);
303
304
}
304
305
305
306
private void Sink(CargoShip _cargoship)
306
307
{
307
308
if (_cargoship != null)
308
309
{
309
310
_cargoship.transform.position += _cargoship.transform.up * config.SinkSpeed * Time.deltaTime;
310
311
if (_cargoship.transform.position.y <= config.SinkAmount)
311
312
{
312
313
if (config.GiveRadsOnSink) { _cargoship.InvokeRepeating(new Action(_cargoship.UpdateRadiation), 10f, 5f); }
313
314
timer.Once(config.SunkDeathDelay, () => { killme(_cargoship); });
314
315
}
315
316
else { _cargoship.Invoke(() => { Sink(_cargoship); }, 0.02f); }
316
317
}
317
318
}
318
319
319
320
public void killme(CargoShip _cargoship)
320
321
{
321
322
if (_cargoship == null) { return; }
322
323
if (BaseNetworkable.HasCloseConnections(_cargoship.transform.position, config.KillCheckRadius))
323
324
{
324
325
timer.Once(config.KillRetryDelay, () => { killme(_cargoship); });
325
326
if (config.Debug) Puts("Blocked Kill Player Nearby");
326
327
return;
327
328
}
328
329
foreach (BaseEntity b in _cargoship.children.ToArray())
329
330
{
330
331
if (b != null && b.GetParentEntity() is CargoShip)
331
332
{
332
333
if (b is BasePlayer || b is LootableCorpse || b is BaseCorpse || b is PlayerCorpse)
333
334
{
334
335
Vector3 oldpos = b.transform.position;
335
336
oldpos.y = 1;
336
337
b.SetParent(null, true, true);
337
338
if (config.Debug) Puts("Move Loot");
338
339
continue;
339
340
}
340
341
b.Kill();
341
342
}
342
343
}
343
344
NextFrame(() =>
344
345
{
345
346
CargoKillShipList.Remove(_cargoship);
346
347
_cargoship.Kill();
347
348
if (config.Debug) Puts("End");
348
349
});
349
350
}
350
351
351
352
[AutoPatch]
352
353
[HarmonyPatch(typeof(CargoShip), "CalculateHarborApproachNodes")]
353
354
public static class CargoShip_CalculateHarborApproachNodes_Patch
354
355
{
355
356
[HarmonyPrefix]
356
357
public static bool Prefix()
357
358
{
358
359
if (CargoShip.hasCalculatedApproaches) return false;
359
360
360
361
CargoShip.hasCalculatedApproaches = true;
361
362
Configuration cfg = cargoSinks.config;
362
363
363
364
for (int i = 0; i < CargoShip.harbors.Count; i++)
364
365
{
365
366
CargoShip.HarborInfo harborInfo = CargoShip.harbors[i];
366
367
cargoSinks.Puts($"Harbor {i} is generating path!");
367
368
float bestDistance = float.MaxValue;
368
369
int bestNodeIndex = -1;
369
370
370
371
Vector3 harborPos = harborInfo.harborPath.nodes[0].Position;
371
372
372
373
for (int j = 0; j < TerrainMeta.Path.OceanPatrolFar.Count; j++)
373
374
{
374
375
Vector3 nodePos = TerrainMeta.Path.OceanPatrolFar[j];
375
376
float distance = Vector3.Distance(nodePos, harborPos);
376
377
bool pathBlockedByLand = false;
377
378
for (float step = 0; step < distance; step += cfg.HarborPathStepSize)
378
379
{
379
380
Vector3 checkPoint = Vector3.MoveTowards(nodePos, harborPos, step);
380
381
float terrainHeight = TerrainMeta.HeightMap.GetHeight(checkPoint);
381
382
if (terrainHeight > cfg.HarborPathHeightBlock)
382
383
{
383
384
pathBlockedByLand = true;
384
385
break;
385
386
}
386
387
}
387
388
388
389
if (pathBlockedByLand) continue;
389
390
390
391
Vector3 heightOffset = Vector3.up * 3f;
391
392
if (!GamePhysics.LineOfSightRadius(nodePos + heightOffset, harborPos + heightOffset, cfg.HarborPathLayerMask, 3f, null))
392
393
{
393
394
distance *= cfg.HarborPathNoLosPenalty;
394
395
}
395
396
396
397
if (distance < bestDistance)
397
398
{
398
399
bestDistance = distance;
399
400
bestNodeIndex = j;
400
401
}
401
402
}
402
403
403
404
if (bestNodeIndex == -1)
404
405
{
405
406
cargoSinks.Puts($"[CargoPatch] Harbor {i} is isolated! No ocean node has a clear water path to it.");
406
407
continue;
407
408
}
408
409
409
410
harborInfo.approachNode = bestNodeIndex;
410
411
CargoShip.harbors[i] = harborInfo;
411
412
}
412
413
413
414
return false;
414
415
}
415
416
}
416
417
}
417
418
}
418
419