← Back to changelog
What changed
Personal Vault Door · v1.0.6 → v1.0.7 (current)
Diff
315 added · 3 removed · 1048 → 1360 total lines
1
1
/*▄▄▄ ███▄ ▄███▓ ▄████ ▄▄▄██▀▀▀▓█████▄▄▄█████▓
2
2
▓█████▄ ▓██▒▀█▀ ██▒ ██▒ ▀█▒ ▒██ ▓█ ▀▓ ██▒ ▓▒
3
3
▒██▒ ▄██▓██ ▓██░▒██░▄▄▄░ ░██ ▒███ ▒ ▓██░ ▒░
4
4
▒██░█▀ ▒██ ▒██ ░▓█ ██▓▓██▄██▓ ▒▓█ ▄░ ▓██▓ ░
5
5
░▓█ ▀█▓▒██▒ ░██▒░▒▓███▀▒ ▓███▒ ░▒████▒ ▒██▒ ░
6
6
░▒▓███▀▒░ ▒░ ░ ░ ░▒ ▒ ▒▓▒▒░ ░░ ▒░ ░ ▒ ░░
7
7
▒░▒ ░ ░ ░ ░ ░ ░ ▒ ░▒░ ░ ░ ░ ░
8
8
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
9
9
░ ░ ░ ░ ░ ░ ░*/
10
10
using Oxide.Game.Rust.Cui;
11
11
using ProtoBuf;
12
12
using Newtonsoft.Json;
13
13
using System.Collections.Generic;
14
14
using System.Linq;
15
15
using UnityEngine;
16
16
using Oxide.Core.Plugins;
17
17
18
18
namespace Oxide.Plugins
19
19
{
20
-
[Info("Personal Vault Door", "bmgjet", "1.0.6")]
20
+
[Info("Personal Vault Door", "bmgjet", "1.0.7")]
21
21
[Description("Lets you place a vault door")]
22
22
public class PersonalVaultDoor : RustPlugin
23
23
{
24
24
[PluginReference]
25
25
private Plugin ServerRewards, Economics;
26
26
#region Configuration
27
27
28
28
private Configuration config;
29
29
30
30
private class Configuration
31
31
{
32
32
[JsonProperty("Vault Max Health")]
33
33
public float VaultMaxHealth = 2000;
34
34
35
35
[JsonProperty("Show Health % Bar CUI")]
36
36
public bool HealthBarEnabled = false;
37
37
38
38
[JsonProperty(PropertyName = "Range to show health")]
39
39
public float vaultrange = 3f;
40
40
41
41
42
42
[JsonProperty("Slide Door Settings")]
43
43
public SlideSettings SlideDoor = new SlideSettings();
44
44
45
+
[JsonProperty("Pie Menu Settings")]
46
+
public PieMenuSettings PieMenu = new PieMenuSettings();
47
+
48
+
public class PieMenuSettings
49
+
{
50
+
[JsonProperty("Tool Shortname Required (e.g. hammer)")]
51
+
public string ToolShortname = "hammer";
52
+
53
+
[JsonProperty("Hold Time To Open Menu (seconds)")]
54
+
public float HoldTime = 0.4f;
55
+
56
+
[JsonProperty("Raycast Distance (meters)")]
57
+
public float RaycastDistance = 3f;
58
+
}
59
+
45
60
public class SlideSettings
46
61
{
47
62
[JsonProperty("Enable Slide Mode (false = classic swing)")]
48
63
public bool Enabled = false;
49
64
50
65
[JsonProperty("Slide Distance (meters)")]
51
66
public float Distance = 2.0f;
52
67
53
68
[JsonProperty("Slide Duration (seconds)")]
54
69
public float Duration = 3.5f;
55
70
56
71
[JsonProperty("Auto Close Delay (seconds, 0 = disabled)")]
57
72
public float AutoCloseDelay = 0f;
58
73
}
59
74
60
75
[JsonProperty("Health Bar Settings")]
61
76
public HealthBarSettings HealthBar = new HealthBarSettings();
62
77
63
78
public class HealthBarSettings
64
79
{
65
80
[JsonProperty("Healthy Color (health >= 60%)")]
66
81
public string HighColor = "#4CAF50";
67
82
68
83
[JsonProperty("Damaged Color (health 25-60%)")]
69
84
public string MidColor = "#FFC107";
70
85
71
86
[JsonProperty("Critical Color (health < 25%)")]
72
87
public string LowColor = "#F44336";
73
88
74
89
[JsonProperty("Damage Flash Color")]
75
90
public string FlashColor = "#FF5252";
76
91
77
92
[JsonProperty("Damage Flash Duration (seconds)")]
78
93
public float FlashDuration = 0.3f;
79
94
}
80
95
81
96
[JsonProperty("Repair Delay")]
82
97
public float RepairDelay = 60;
83
98
84
99
[JsonProperty("Repair Ammount")]
85
100
public int RepairAmmount = 25;
86
101
87
102
[JsonProperty("Spawn Cost type (serverrewards,economics,resources)")]
88
103
public string Costtype = "resources";
89
104
90
105
[JsonProperty("Repair Cost type (serverrewards,economics,resources)")]
91
106
public string RepairCosttype = "resources";
92
107
93
108
[JsonProperty("Spawn Cost for (serverrewards,economics)")]
94
109
public float Spawncost = 2000;
95
110
96
111
[JsonProperty("Repair Cost for (serverrewards,economics)")]
97
112
public float Repaircost = 50;
98
113
99
114
[JsonProperty("Currency Symbol for (serverrewards,economics)")]
100
115
public string CurrencySymbol = "$";
101
116
102
117
[JsonProperty("Charge to craft")]
103
118
public bool craftcosts = true;
104
119
105
120
[JsonProperty("Repair Cost")]
106
121
//Items and quanity needed to repair
107
122
public Dictionary<string, int> RepairCost = new Dictionary<string, int>
108
123
{
109
124
{"scrap", 1},
110
125
{"metal.fragments", 100},
111
126
{"metal.refined", 10},
112
127
};
113
128
114
129
[JsonProperty("Craft Cost")]
115
130
//Items and quanity needed per craft
116
131
public Dictionary<string, int> CraftCost = new Dictionary<string, int>
117
132
{
118
133
{"scrap", 10},
119
134
{"metal.fragments", 1000},
120
135
{"metal.refined", 100},
121
136
};
122
137
123
138
public string ToJson() => JsonConvert.SerializeObject(this);
124
139
125
140
public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
126
141
}
127
142
128
143
protected override void LoadDefaultConfig() => config = new Configuration();
129
144
130
145
protected override void LoadConfig()
131
146
{
132
147
base.LoadConfig();
133
148
try
134
149
{
135
150
config = Config.ReadObject<Configuration>();
136
151
if (config == null)
137
152
{
138
153
throw new JsonException();
139
154
}
140
155
141
156
if (!config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys))
142
157
{
143
158
PrintWarning("Configuration appears to be outdated; updating and saving");
144
159
SaveConfig();
145
160
}
146
161
}
147
162
catch
148
163
{
149
164
PrintWarning($"Configuration file {Name}.json is invalid; using defaults");
150
165
LoadDefaultConfig();
151
166
}
152
167
}
153
168
154
169
protected override void SaveConfig()
155
170
{
156
171
PrintWarning($"Configuration changes saved to {Name}.json");
157
172
Config.WriteObject(config, true);
158
173
}
159
174
#endregion Configuration
160
175
161
176
#region Vars
162
177
//Offset from door slot position to move lock.
163
178
Vector3 codeoffset = new Vector3(0.71f, -0.1f, -0.3f); //Puts it on flat pannel, On handles was too far for it to trigger on door still.
164
179
//Skin of Icon
165
180
private const ulong skinID = 2643584466;
166
181
//Replacement prefab
167
182
private const string prefab = "assets/bundled/prefabs/modding/asset_store/bankheist_package/bankheist_vol03/prefabs/door.vault.static.prefab";
168
183
//Permission
169
184
private const string permUse = "PersonalVaultDoor.use";
170
185
//Show Debug Info
171
186
private bool showDebug = false;
172
187
//Sound Effects to play on vault break.
173
188
static List<string> effects = new List<string>
174
189
{
175
190
"assets/bundled/prefabs/fx/entities/loot_barrel/gib.prefab",
176
191
"assets/bundled/prefabs/fx/building/metal_sheet_gib.prefab"
177
192
};
178
193
private List<Vector3> REPlaced = new List<Vector3>();
179
194
private static PersonalVaultDoor plugin;
180
195
#endregion
181
196
182
197
#region Language
183
198
protected override void LoadDefaultMessages()
184
199
{
185
200
lang.RegisterMessages(new Dictionary<string, string>
186
201
{
187
202
{"Name", "Vault Door"},
188
203
{"Pickup", "You picked up Vault Door!"},
189
204
{"Receive", "You received Vault Door!"},
190
205
{"Repair", "You need more resources:\n{0}"},
191
206
{"Wait", "You must wait: {0} before you can repair."},
192
-
{"Permission", "You need permission to do that!"}
207
+
{"Permission", "You need permission to do that!"},
208
+
{"NotAuthed", "You are not authorized on this vault door's lock."},
209
+
{"NeedBuildingAuth", "You need building privilege to pick this up."}
193
210
}, this);
194
211
}
195
212
private static string HexToRustFormat(string hex)
196
213
{
197
214
Color color;
198
215
return ColorUtility.TryParseHtmlString(hex, out color) ? $"{color.r:F2} {color.g:F2} {color.b:F2} {color.a:F2}" : "false";
199
216
}
200
217
private static string HexToRustFormat(string hex, float alpha)
201
218
{
202
219
Color color;
203
220
return ColorUtility.TryParseHtmlString(hex, out color) ? $"{color.r:F2} {color.g:F2} {color.b:F2} {alpha:F2}" : "false";
204
221
}
205
222
//Send player message
206
223
private void message(BasePlayer player, string key, params object[] args)
207
224
{
208
225
if (player == null) { return; }
209
226
var message = string.Format(lang.GetMessage(key, this, player.UserIDString), args);
210
227
player.ChatMessage(message);
211
228
}
212
229
#endregion
213
230
214
231
#region Oxide Hooks
215
232
private void OnServerInitialized(bool initial)
216
233
{
217
234
plugin = this;
218
235
Fstartup(initial ? 30 : 1);
236
+
//Attach pie menu controller to anyone already holding the tool (e.g. after a plugin reload)
237
+
foreach (BasePlayer player in BasePlayer.activePlayerList)
238
+
{
239
+
CheckHammer(player);
240
+
}
219
241
}
220
242
221
243
private void Fstartup(int delay)
222
244
{
223
245
//Wait to start up vault door componant for slow servers.
224
246
timer.Once(delay, () =>
225
247
{
226
248
CheckVaultDoor();
227
249
});
228
250
}
229
251
230
252
private void Init()
231
253
{
232
254
//Setup Permissions
233
255
permission.RegisterPermission(permUse, this);
234
256
}
235
257
236
258
private void Unload()
237
259
{
238
260
//Clear Vault Door Health CUI
239
261
foreach (BasePlayer player in BasePlayer.activePlayerList.ToArray())
240
262
{
241
263
CuiHelper.DestroyUi(player, "VaultHealthUI");
242
264
}
243
265
//Snap any mid-slide vault doors back to closed before the component is destroyed
244
266
foreach (var slider in VaultSlide.ActiveSliders.ToArray())
245
267
{
246
268
slider?.ResetToClosed();
247
269
}
270
+
//Remove pie menu controller from anyone holding the tool
271
+
foreach (BasePlayer player in BasePlayer.activePlayerList.ToArray())
272
+
{
273
+
var comp = player.GetComponent<PersonalVaultDoorController>();
274
+
if (comp != null) { UnityEngine.Object.DestroyImmediate(comp); }
275
+
}
248
276
int VDS = DestroyVaultDoorScript();
249
277
if (showDebug) Puts("Destroyed " + VDS.ToString() + " VaultDoor Scripts");
250
278
//Unload Statics
251
279
effects = null;
252
280
plugin = null;
253
281
}
254
282
283
+
//Attaches the pie menu controller to anyone already holding the tool (e.g. after a plugin reload)
284
+
private void OnPlayerConnected(BasePlayer player) { CheckHammer(player); }
285
+
286
+
//Standard Oxide hook fired whenever the player's active held item changes
287
+
private void OnActiveItemChanged(BasePlayer player, Item oldItem, Item newItem)
288
+
{
289
+
if (player == null) { return; }
290
+
NextTick(() => CheckHammer(player));
291
+
}
292
+
293
+
//Adds/removes the pie menu watcher component based on whether the player is holding the configured tool
294
+
private void CheckHammer(BasePlayer player)
295
+
{
296
+
if (player == null || player.IsDestroyed) { return; }
297
+
var activeItem = player.GetActiveItem();
298
+
bool holdingTool = activeItem != null && activeItem.info.shortname == config.PieMenu.ToolShortname;
299
+
300
+
var comp = player.GetComponent<PersonalVaultDoorController>();
301
+
if (holdingTool)
302
+
{
303
+
if (comp == null) { player.gameObject.AddComponent<PersonalVaultDoorController>(); }
304
+
}
305
+
else
306
+
{
307
+
if (comp != null) { UnityEngine.Object.DestroyImmediate(comp); }
308
+
}
309
+
}
310
+
255
311
void OnEntityKill(Door entity)
256
312
{
257
313
//Check if vault door
258
314
if (entity.ShortPrefabName == "door.vault.static")
259
315
{
260
316
VaultDoor VD = entity.GetComponent<VaultDoor>();
261
317
if (VD == null) return;
262
318
//Remove Door Frame
263
319
NextTick(() =>
264
320
{
265
321
if (VD.Frame != null)
266
322
{
267
323
VD.Frame.Kill();
268
324
}
269
325
});
270
326
if (VD.PlayersCUIed == null) return;
271
327
//Remove CUI
272
328
foreach (BasePlayer player in VD.PlayersCUIed)
273
329
{
274
330
if (player != null)
275
331
{
276
332
CuiHelper.DestroyUi(player, "VaultHealthUI");
277
333
}
278
334
}
279
335
UnityEngine.Object.DestroyImmediate(VD);
280
336
}
281
337
}
282
338
339
+
//Frame was killed directly (e.g. RemoverTool targeted the doorway instead of the door) - kill the paired vault door immediately
340
+
void OnEntityKill(BuildingBlock entity)
341
+
{
342
+
if (entity == null || entity.ShortPrefabName != "wall.doorway") return;
343
+
344
+
foreach (var VD in VaultDoor.ActiveVaultDoors.ToArray())
345
+
{
346
+
if (VD != null && VD.Frame == entity)
347
+
{
348
+
NextTick(() =>
349
+
{
350
+
if (VD.vdoor != null)
351
+
{
352
+
foreach (var effect in effects) { Effect.server.Run(effect, VD.Position); }
353
+
VD.vdoor.Kill();
354
+
}
355
+
});
356
+
break;
357
+
}
358
+
}
359
+
}
360
+
283
361
void OnEntitySpawned(CodeLock cl)
284
362
{
285
363
BaseEntity Door = cl.GetParentEntity();
286
364
if (Door != null && Door.ToString().Contains("door.vault.static"))
287
365
{
288
366
//Moves codelock and update
289
367
cl.transform.localPosition += codeoffset;
290
368
cl.SendNetworkUpdateImmediate();
291
369
}
292
370
}
293
371
294
372
void OnEntitySpawned(KeyLock cl)
295
373
{
296
374
BaseEntity Door = cl.GetParentEntity();
297
375
if (Door != null && Door.ToString().Contains("door.vault.static"))
298
376
{
299
377
//Moves codelock and update
300
378
cl.transform.localPosition += codeoffset;
301
379
cl.SendNetworkUpdateImmediate();
302
380
}
303
381
}
304
382
305
383
//Code and KeyLock position Fix
306
384
void OnEntitySpawned(Door cl)
307
385
{
308
386
if (Rust.Application.isLoading) { return; }
309
387
//Add component in spawn here so Copypaste can Work with vaultdoors.
310
388
if (cl is Door && cl.ToString().Contains("door.vault.static"))
311
389
{
312
390
if (cl.GetComponent<VaultDoor>() == null)
313
391
{
314
392
if (REPlaced.Contains(cl.transform.position))
315
393
{
316
394
if (showDebug) Puts("Skipping Rust Edit Placed Vault Door");
317
395
return;
318
396
}
319
397
if (showDebug) Puts("Adding VaultDoor Component");
320
398
//Delay since copypaste might not of spawn door frame yet.
321
399
timer.Once(2f, () =>
322
400
{
323
401
try
324
402
{
325
403
cl.gameObject.AddComponent<VaultDoor>();
326
404
if (config.SlideDoor.Enabled && cl.GetComponent<VaultSlide>() == null)
327
405
{
328
406
cl.gameObject.AddComponent<VaultSlide>();
329
407
}
330
408
}
331
409
catch { };
332
410
});
333
411
}
334
412
}
335
413
}
336
414
337
415
//Hook vault placement to switch in.
338
416
private void OnEntityBuilt(Planner plan, GameObject go) { CheckDeploy(go.ToBaseEntity()); }
339
417
340
418
//Hooks if should pickup
341
419
private void OnHammerHit(BasePlayer player, HitInfo info) { CheckHit(player, info?.HitEntity); }
342
420
343
421
//Flashes the health bar red when the vault door takes damage
344
422
private void OnEntityTakeDamage(Door entity, HitInfo info)
345
423
{
346
424
if (entity == null || !IsVaultDoor(entity.skinID)) { return; }
347
425
entity.GetComponent<VaultDoor>()?.FlashDamage();
348
426
}
349
427
350
428
//Slide Mode: replaces the vault door's native swing with a sliding animation
351
429
private void OnDoorOpened(Door door, BasePlayer player)
352
430
{
353
431
if (!config.SlideDoor.Enabled || door == null || !IsVaultDoor(door.skinID)) { return; }
354
432
VaultSlide slider = door.GetComponent<VaultSlide>();
355
433
if (slider == null) { return; }
356
434
//Cancel the native open flag/animation - the slider tracks open/closed itself
357
435
door.SetOpen(false);
358
436
slider.Toggle();
359
437
}
360
438
#endregion
361
439
362
440
#region Core
363
441
List<Door> FindVaultDoors(Vector3 pos, float radius)
364
442
{
365
443
//Casts a sphere at given position and find all doors there
366
444
var hits = Physics.SphereCastAll(pos, radius, Vector3.one);
367
445
var x = new List<Door>();
368
446
foreach (var hit in hits)
369
447
{
370
448
var entity = hit.GetEntity()?.GetComponent<Door>();
371
449
if (entity && !x.Contains(entity))
372
450
x.Add(entity);
373
451
}
374
452
return x;
375
453
}
376
454
377
455
void DestroyGroundComp(BaseEntity ent)
378
456
{
379
457
UnityEngine.Object.DestroyImmediate(ent.GetComponent<DestroyOnGroundMissing>());
380
458
UnityEngine.Object.DestroyImmediate(ent.GetComponent<GroundWatch>());
381
459
//Stops Decay
382
460
UnityEngine.Object.DestroyImmediate(ent.GetComponent<DeployableDecay>());
383
461
}
384
462
385
463
void DestroyMeshCollider(BaseEntity ent)
386
464
{
387
465
foreach (var mesh in ent.GetComponentsInChildren<MeshCollider>())
388
466
{
389
467
UnityEngine.Object.DestroyImmediate(mesh);
390
468
}
391
469
}
392
470
393
471
//Resets the component after server restart
394
472
private void CheckVaultDoor()
395
473
{
396
474
//Build list of VaultDoors In Map File
397
475
for (int i = World.Serialization.world.prefabs.Count - 1; i >= 0; i--)
398
476
{
399
477
PrefabData prefabdata = World.Serialization.world.prefabs[i];
400
478
if (prefabdata.id == 3595032872)
401
479
{
402
480
REPlaced.Add(prefabdata.position);
403
481
//Check its still there and fix if not
404
482
if (FindVaultDoors(prefabdata.position, 4f).Count == 0)
405
483
{
406
484
Door replacement = GameManager.server.CreateEntity(StringPool.Get(prefabdata.id), prefabdata.position, prefabdata.rotation) as Door;
407
485
if (replacement == null) return;
408
486
DestroyGroundComp(replacement);
409
487
DestroyMeshCollider(replacement);
410
488
replacement.Spawn();
411
489
replacement.transform.position = prefabdata.position;
412
490
replacement.transform.rotation = prefabdata.rotation;
413
491
replacement.pickup.enabled = false;
414
492
replacement.SendNetworkUpdateImmediate();
415
493
}
416
494
}
417
495
}
418
496
if (showDebug) Puts("Founded " + REPlaced.Count.ToString() + " Map Placed Vault Doors");
419
497
int VaultsUpdated = 0;
420
-
foreach (var vaultdoor in GameObject.FindObjectsOfType<BaseEntity>())
498
+
foreach (var vaultdoor in GameObject.FindObjectsByType<BaseEntity>(FindObjectsSortMode.None))
421
499
{
422
500
//Skip Servers Vault Doors
423
501
if (REPlaced.Contains(vaultdoor.transform.position))
424
502
{
425
503
if (showDebug) Puts("Skipping Map Placed Vault Door @ " + vaultdoor.transform.position.ToString());
426
504
continue;
427
505
}
428
506
if (vaultdoor.ShortPrefabName == "door.vault.static" && vaultdoor.GetComponent<VaultDoor>() == null)
429
507
{
430
508
if (showDebug) Puts("Found vaultdoor " + vaultdoor.ToString() + " " + vaultdoor.OwnerID.ToString() + " Adding Component");
431
509
vaultdoor.gameObject.AddComponent<VaultDoor>();
432
510
if (config.SlideDoor.Enabled && vaultdoor.GetComponent<VaultSlide>() == null)
433
511
{
434
512
vaultdoor.gameObject.AddComponent<VaultSlide>();
435
513
}
436
514
VaultsUpdated++;
437
515
}
438
516
}
439
517
Puts("Updated " + VaultsUpdated.ToString() + " VaultDoors");
440
518
}
441
519
442
520
//Gives player vault door
443
521
private void GiveVaultDoor(BasePlayer player, bool pickup = false)
444
522
{
445
523
var item = CreateItem();
446
524
if (item != null && player != null)
447
525
{
448
526
player.GiveItem(item);
449
527
message(player, pickup ? "Pickup" : "Receive");
450
528
}
451
529
}
452
530
bool ChargePlayer(BasePlayer player)
453
531
{
454
532
object result = null;
455
533
if (!config.craftcosts)
456
534
{
457
535
return true;
458
536
}
459
537
else
460
538
{
461
539
if (config.Costtype == "serverrewards" && ServerRewards != null)
462
540
{
463
541
result = ServerRewards.Call("TakePoints", player.UserIDString, (int)config.Spawncost);
464
542
}
465
543
else if (config.Costtype == "economics" && Economics != null)
466
544
{
467
545
result = Economics.Call("Withdraw", player.UserIDString, (double)config.Spawncost);
468
546
}
469
547
else
470
548
{
471
549
// No supported rewards plugin loaded or configured
472
550
message(player, "Currency Type Not supported on this server");
473
551
return false;
474
552
}
475
553
if (result == null || (result is bool && (bool)result == false))
476
554
{
477
555
message(player, "Looks like you can not afford to buy this");
478
556
return false;
479
557
}
480
558
message(player, "Charged {amount} {currency} for Vault Door".Replace("{amount}", config.Spawncost.ToString()).Replace("{currency}", config.CurrencySymbol));
481
559
return true;
482
560
}
483
561
}
484
562
485
563
bool CanaffordFix(BasePlayer player, Door VD)
486
564
{
487
565
object result = null;
488
566
if (VD.SecondsSinceAttacked < config.RepairDelay)
489
567
{
490
568
message(player, "Wait", (config.RepairDelay - VD.SecondsSinceAttacked).ToString("#.#"));
491
569
return false;
492
570
}
493
571
else
494
572
{
495
573
if (config.RepairCosttype == "serverrewards" && ServerRewards != null)
496
574
{
497
575
result = ServerRewards.Call("TakePoints", player.UserIDString, (int)config.Repaircost);
498
576
}
499
577
else if (config.RepairCosttype == "economics" && Economics != null)
500
578
{
501
579
result = Economics.Call("Withdraw", player.UserIDString, (double)config.Repaircost);
502
580
}
503
581
else
504
582
{
505
583
// No supported rewards plugin loaded or configured
506
584
message(player, "Currency Type Not supported on this server");
507
585
return false;
508
586
}
509
587
if (result == null || (result is bool && (bool)result == false))
510
588
{
511
589
message(player, "Looks like you can not afford to repair this");
512
590
return false;
513
591
}
514
592
message(player, "Charged {amount} {currency} for repairing Vault Door".Replace("{amount}", config.Repaircost.ToString()).Replace("{currency}", config.CurrencySymbol));
515
593
return true;
516
594
}
517
595
}
518
596
519
597
//Checks if has the correct permission
520
598
private bool CanCraft(BasePlayer player)
521
599
{
522
600
if (!permission.UserHasPermission(player.UserIDString, permUse))
523
601
{
524
602
message(player, "Permission");
525
603
return false;
526
604
}
527
605
return CanFix(player, null, true);
528
606
}
529
607
530
608
//Creates vault door
531
609
private Item CreateItem()
532
610
{
533
611
var item = ItemManager.CreateByName("wall.frame.garagedoor", 1, skinID);
534
612
if (item != null)
535
613
{
536
614
item.text = "Vault Door";
537
615
item.name = item.text;
538
616
}
539
617
return item;
540
618
}
541
619
542
620
//Checks if its a vault door when hit with hammer
543
621
private void CheckHit(BasePlayer player, BaseEntity entity)
544
622
{
545
623
if (entity == null) { return; }
546
624
if (!IsVaultDoor(entity.skinID)) { return; }
547
625
//Check if door is open and has no lock to remove Otherwise heal door
548
626
Door VD = entity as Door;
549
627
VaultSlide slider = entity.GetComponent<VaultSlide>();
550
628
bool isOpen = slider != null ? slider.IsOpen : VD.IsOpen();
551
629
if (VD.GetSlot(0) == null && isOpen)
552
630
{
553
631
entity.GetComponent<VaultDoor>()?.TryPickup(player);
554
632
}
555
633
else
556
634
{
557
635
if (VD._health < VD._maxHealth)
558
636
{
559
637
if (config.RepairCosttype != "resources")
560
638
{
561
639
if (CanaffordFix(player, VD))
562
640
{
563
641
Effect.server.Run("assets/bundled/prefabs/fx/build/repair_full_metal.prefab", VD.transform.position);
564
642
VD.health += config.RepairAmmount;
565
643
}
566
644
}
567
645
else
568
646
{
569
647
if (CanFix(player, VD))
570
648
{
571
649
Effect.server.Run("assets/bundled/prefabs/fx/build/repair_full_metal.prefab", VD.transform.position);
572
650
VD.health += config.RepairAmmount;
573
651
}
574
652
}
575
653
}
576
654
else
577
655
{
578
656
message(player, "Vault Door Is full health");
579
657
}
580
658
}
581
659
}
582
660
583
661
//Checks if can fix
584
662
private bool CanFix(BasePlayer player, Door VD, bool craft = false)
585
663
{
586
664
Dictionary<string, int> Needed = new Dictionary<string, int>();
587
665
Dictionary<string, int> Parts = new Dictionary<string, int>();
588
666
589
667
if (!craft)
590
668
{
591
669
Parts = config.RepairCost;
592
670
//Check repair delay
593
671
if (VD.SecondsSinceAttacked < config.RepairDelay)
594
672
{
595
673
message(player, "Wait", (config.RepairDelay - VD.SecondsSinceAttacked).ToString("#.#"));
596
674
return false;
597
675
}
598
676
}
599
677
else
600
678
{
601
679
if (!config.craftcosts) return true;
602
680
Parts = config.CraftCost;
603
681
}
604
682
605
683
//Check has needed meterials
606
684
foreach (var component in Parts)
607
685
{
608
686
string name = component.Key;
609
687
if (player.inventory.GetAmount(ItemManager.FindItemDefinition(component.Key).itemid) < component.Value)
610
688
{
611
689
if (!Needed.ContainsKey(name))
612
690
{
613
691
Needed.Add(name, 0);
614
692
}
615
693
Needed[name] += component.Value;
616
694
}
617
695
}
618
696
//Has everything needed so remove from player and send can fix.
619
697
if (Needed.Count == 0)
620
698
{
621
699
foreach (var item in Parts)
622
700
{
623
701
player.inventory.Take(null, ItemManager.FindItemDefinition(item.Key).itemid, item.Value);
624
702
}
625
703
return true;
626
704
}
627
705
//Doesnt have everything needed to build list and message use. Send cant fix
628
706
else
629
707
{
630
708
string text = "";
631
709
foreach (var item in Needed)
632
710
{
633
711
text += $" * {item.Key} x{item.Value}\n";
634
712
}
635
713
message(player, "Repair", text);
636
714
return false;
637
715
}
638
716
}
639
717
640
718
private bool IsVaultDoor(ulong skin) { return skin != 0 && skin == skinID; }
641
719
//Checks if vault door should be swapped in
642
720
private void CheckDeploy(BaseEntity entity)
643
721
{
644
722
if (entity == null) { return; }
645
723
//Checks if is using vault door skin
646
724
if (!IsVaultDoor(entity.skinID)) { return; }
647
725
//Creates Doorway
648
726
var doorway = GameManager.server.CreateEntity("assets/prefabs/building core/wall.doorway/wall.doorway.prefab", entity.transform.position, entity.transform.rotation);
649
727
if (doorway == null)
650
728
{
651
729
//Something Failed
652
730
return;
653
731
}
654
732
doorway.Spawn();
655
733
doorway.OwnerID = entity.OwnerID;
656
734
//sets up door frame to fill in gaps around vault door
657
735
var buildingBlock = doorway as BuildingBlock;
658
736
if (buildingBlock != null)
659
737
{
660
738
//Upgrade HQM
661
739
buildingBlock.SetGrade((BuildingGrade.Enum)4);
662
740
//Grounded so doesnt fall apart
663
741
buildingBlock.grounded = false;
664
742
//Sets health as max
665
743
buildingBlock.health = buildingBlock.MaxHealth();
666
744
//Rotate from Door Way
667
745
Vector3 rot = buildingBlock.transform.rotation.eulerAngles;
668
746
rot = new Vector3(rot.x, rot.y + 180, rot.z);
669
747
//Create Vault Door
670
748
Door vaultdoor = GameManager.server.CreateEntity(prefab, buildingBlock.transform.position, buildingBlock.transform.rotation) as Door;
671
749
if (vaultdoor == null) { return; }
672
750
//Stupid rotation based move stuff
673
751
Vector3 movepos = buildingBlock.transform.position;
674
752
movepos += buildingBlock.transform.forward * 1.0f;
675
753
movepos += buildingBlock.transform.right * -0.54f;
676
754
movepos += buildingBlock.transform.up * 0.2f;
677
755
//Sets door way as creator so can destory vault on door way being destroyed
678
756
vaultdoor.creatorEntity = doorway;
679
757
vaultdoor.transform.rotation = Quaternion.Euler(rot);
680
758
vaultdoor.transform.position = movepos;
681
759
//Set skin and owner
682
760
vaultdoor.skinID = skinID;
683
761
vaultdoor.OwnerID = entity.OwnerID;
684
762
//Delay setting Max health and health other wise it defaults to 800
685
763
timer.Once(1f, () =>
686
764
{
687
765
vaultdoor.SetMaxHealth(config.VaultMaxHealth);
688
766
vaultdoor.SetHealth(config.VaultMaxHealth);
689
767
if (config.SlideDoor.Enabled && vaultdoor.GetComponent<VaultSlide>() == null)
690
768
{
691
769
vaultdoor.gameObject.AddComponent<VaultSlide>();
692
770
}
693
771
});
694
772
//Sets up functions
695
773
vaultdoor.Spawn();
696
774
vaultdoor.SendNetworkUpdateImmediate();
697
775
}
698
776
//Cleans out placeholder
699
777
NextTick(() => { entity?.Kill(); });
700
778
}
701
779
702
780
//Removes script on unload incase some ones restarting plugin but not restarted server
703
781
int DestroyVaultDoorScript()
704
782
{
705
783
int killed = 0;
706
784
foreach (var vd in VaultDoor.ActiveVaultDoors.ToArray())
707
785
{
708
786
if (vd != null)
709
787
{
710
788
UnityEngine.Object.DestroyImmediate(vd);
711
789
killed++;
712
790
}
713
791
}
714
792
foreach (var vs in VaultSlide.ActiveSliders.ToArray())
715
793
{
716
794
if (vs != null)
717
795
{
718
796
UnityEngine.Object.DestroyImmediate(vs);
719
797
killed++;
720
798
}
721
799
}
722
800
return killed;
723
801
}
802
+
803
+
#region Pie Menu
804
+
805
+
//Checks if player has access to operate the door's lock. No lock at all = public door, anyone can use it.
806
+
//Delegates to the lock's own HasLockPermission (CodeLock checks its whitelist/guest code,
807
+
//KeyLock checks ownership and matching key items) instead of reimplementing that logic here.
808
+
private bool PlayerAuthedOnLock(BasePlayer player, BaseLock baseLock)
809
+
{
810
+
if (baseLock == null) { return true; }
811
+
return baseLock.HasLockPermission(player);
812
+
}
813
+
814
+
//Raycasts from the player's eyes and returns a vault door if they're looking at one
815
+
private Door FindVaultDoorLookedAt(BasePlayer player)
816
+
{
817
+
RaycastHit hit;
818
+
if (!Physics.Raycast(player.eyes.HeadRay(), out hit, config.PieMenu.RaycastDistance)) { return null; }
819
+
820
+
Door door = hit.GetEntity() as Door;
821
+
if (door == null || !IsVaultDoor(door.skinID)) { return null; }
822
+
823
+
return door;
824
+
}
825
+
826
+
//Finds a door by its network ID, used by the console commands the pie menu buttons call
827
+
private Door FindDoorByNetId(ulong id)
828
+
{
829
+
BaseNetworkable ent = BaseNetworkable.serverEntities.Find(new NetworkableId(id));
830
+
return ent as Door;
831
+
}
832
+
833
+
public void TryOpenVaultMenu(BasePlayer player)
834
+
{
835
+
if (player == null) { return; }
836
+
837
+
Door door = FindVaultDoorLookedAt(player);
838
+
if (door == null) { return; }
839
+
840
+
BaseLock baseLock = door.GetSlot(BaseEntity.Slot.Lock) as BaseLock;
841
+
if (!PlayerAuthedOnLock(player, baseLock))
842
+
{
843
+
message(player, "NotAuthed");
844
+
return;
845
+
}
846
+
847
+
SendVaultPie(player, door, baseLock);
848
+
}
849
+
850
+
//CustomPie/CustomPieMenu fields are public, so we build entries ourselves without needing
851
+
//CommunityEntity's private AddPieMenu helper.
852
+
private void AddPie(CustomPie pie, string name, string description, string command, string sprite, bool disabled, bool selected)
853
+
{
854
+
CustomPieMenu menu = Facepunch.Pool.Get<CustomPieMenu>();
855
+
menu.name = name;
856
+
menu.description = description;
857
+
menu.command = command;
858
+
menu.sprite = sprite;
859
+
menu.disabled = disabled;
860
+
menu.selected = selected;
861
+
pie.menus.Add(menu);
862
+
}
863
+
864
+
private void SendVaultPie(BasePlayer player, Door door, BaseLock baseLock)
865
+
{
866
+
ulong netId = door.net.ID.Value;
867
+
VaultSlide slider = door.GetComponent<VaultSlide>();
868
+
bool isMoving = slider != null && slider.IsMoving;
869
+
bool isOpen = slider != null ? slider.IsOpen : door.IsOpen();
870
+
bool canPickup = !isMoving && isOpen && baseLock == null && player.IsBuildingAuthed();
871
+
872
+
using (CustomPie pie = Facepunch.Pool.Get<CustomPie>())
873
+
{
874
+
pie.menus = Facepunch.Pool.Get<List<CustomPieMenu>>();
875
+
876
+
AddPie(pie, "Exit", "Close this menu", "", "assets/icons/close.png", false, false);
877
+
878
+
//While the door is mid-slide, IsOpen is unreliable (it's between open/closed), so hide
879
+
//Open/Close/Pickup until the slide finishes rather than risk showing the wrong button.
880
+
if (!isMoving)
881
+
{
882
+
AddPie(pie,
883
+
isOpen ? "Close" : "Open",
884
+
isOpen ? "Close the vault door" : "Open the vault door",
885
+
$"personalvaultdoor.toggle {netId}",
886
+
isOpen ? "assets/icons/lock.png" : "assets/icons/unlock.png",
887
+
false, false);
888
+
889
+
if (canPickup)
890
+
{
891
+
AddPie(pie, "Pickup", "Pick up the vault door", $"personalvaultdoor.pickup {netId}", "assets/icons/pickup.png", false, false);
892
+
}
893
+
}
894
+
895
+
CommunityEntity.ServerInstance.SendPie(player, pie);
896
+
}
897
+
}
898
+
899
+
#endregion Pie Menu
724
900
#endregion
725
901
726
902
#region Commands
727
903
//Chat command
728
904
[ChatCommand("vaultdoor")]
729
905
private void Craft(BasePlayer player)
730
906
{
731
907
if (config.Costtype != "resources")
732
908
{
733
909
if (ChargePlayer(player)) { GiveVaultDoor(player); }
734
910
}
735
911
else
736
912
{
737
913
if (CanCraft(player)) { GiveVaultDoor(player); }
738
914
}
739
915
}
740
916
741
917
//Console command
742
918
[ConsoleCommand("vaultdoor.give")]
743
919
private void Cmd(ConsoleSystem.Arg arg)
744
920
{
745
921
if (arg.IsAdmin && arg.Args?.Length > 0)
746
922
{
747
923
var player = BasePlayer.Find(arg.Args[0].ToString()) ?? BasePlayer.FindSleeping(arg.Args[0].ToString());
748
924
if (player == null)
749
925
{
750
926
PrintWarning($"Can't find player with that name/ID! {arg.Args[0]}");
751
927
return;
752
928
}
753
929
GiveVaultDoor(player);
754
930
}
755
931
}
932
+
933
+
[ConsoleCommand("personalvaultdoor.toggle")]
934
+
private void CmdVaultToggle(ConsoleSystem.Arg arg)
935
+
{
936
+
BasePlayer player = arg.Player();
937
+
if (player == null) { return; }
938
+
939
+
ulong netId;
940
+
if (arg.Args == null || arg.Args.Length < 1 || !ulong.TryParse(arg.Args[0].ToString(), out netId)) { return; }
941
+
942
+
Door door = FindDoorByNetId(netId);
943
+
if (door == null || !IsVaultDoor(door.skinID)) { return; }
944
+
945
+
BaseLock baseLock = door.GetSlot(BaseEntity.Slot.Lock) as BaseLock;
946
+
if (!PlayerAuthedOnLock(player, baseLock))
947
+
{
948
+
message(player, "NotAuthed");
949
+
return;
950
+
}
951
+
952
+
VaultSlide slider = door.GetComponent<VaultSlide>();
953
+
//Ignore the toggle while a slide is already in progress - the button shouldn't have been
954
+
//shown, but guard here too in case the command is invoked directly.
955
+
if (slider != null && slider.IsMoving) { return; }
956
+
957
+
if (config.SlideDoor.Enabled && slider != null)
958
+
{
959
+
slider.Toggle();
960
+
}
961
+
else
962
+
{
963
+
door.SetOpen(!door.IsOpen());
964
+
}
965
+
}
966
+
967
+
[ConsoleCommand("personalvaultdoor.pickup")]
968
+
private void CmdVaultPickup(ConsoleSystem.Arg arg)
969
+
{
970
+
BasePlayer player = arg.Player();
971
+
if (player == null) { return; }
972
+
973
+
ulong netId;
974
+
if (arg.Args == null || arg.Args.Length < 1 || !ulong.TryParse(arg.Args[0].ToString(), out netId)) { return; }
975
+
976
+
Door door = FindDoorByNetId(netId);
977
+
if (door == null || !IsVaultDoor(door.skinID)) { return; }
978
+
979
+
//Only a public door (no lock at all) can be picked up via the menu
980
+
if (door.GetSlot(BaseEntity.Slot.Lock) != null) { return; }
981
+
982
+
if (!player.IsBuildingAuthed())
983
+
{
984
+
message(player, "NeedBuildingAuth");
985
+
return;
986
+
}
987
+
988
+
VaultSlide slider = door.GetComponent<VaultSlide>();
989
+
//Ignore pickup while a slide is in progress - avoids picking up a door mid-transition.
990
+
if (slider != null && slider.IsMoving) { return; }
991
+
992
+
bool isOpen = slider != null ? slider.IsOpen : door.IsOpen();
993
+
if (!isOpen) { return; }
994
+
995
+
VaultDoor vd = door.GetComponent<VaultDoor>();
996
+
if (vd != null)
997
+
{
998
+
vd.PickupViaMenu(player);
999
+
}
1000
+
}
756
1001
#endregion
757
1002
758
1003
#region Scripts
1004
+
1005
+
//Attached to a player while they hold the configured tool. Watches for a held right-click
1006
+
//while looking at a vault door and opens the pie menu.
1007
+
public class PersonalVaultDoorController : MonoBehaviour
1008
+
{
1009
+
private BasePlayer player;
1010
+
private bool heldPrev;
1011
+
private float pressStartTime;
1012
+
private bool menuOpenedThisPress;
1013
+
1014
+
private void Awake()
1015
+
{
1016
+
player = GetComponent<BasePlayer>();
1017
+
}
1018
+
1019
+
private void FixedUpdate()
1020
+
{
1021
+
try
1022
+
{
1023
+
if (plugin == null || player == null || player.IsDestroyed) { Destroy(this); return; }
1024
+
1025
+
var activeItem = player.GetActiveItem();
1026
+
if (activeItem == null || activeItem.info.shortname != plugin.config.PieMenu.ToolShortname)
1027
+
{
1028
+
Destroy(this);
1029
+
return;
1030
+
}
1031
+
1032
+
bool held = player.serverInput.IsDown(BUTTON.FIRE_SECONDARY);
1033
+
if (held && !heldPrev)
1034
+
{
1035
+
pressStartTime = Time.time;
1036
+
menuOpenedThisPress = false;
1037
+
}
1038
+
1039
+
if (held && !menuOpenedThisPress && Time.time - pressStartTime >= plugin.config.PieMenu.HoldTime)
1040
+
{
1041
+
menuOpenedThisPress = true;
1042
+
plugin.TryOpenVaultMenu(player);
1043
+
}
1044
+
1045
+
if (!held) { menuOpenedThisPress = false; }
1046
+
1047
+
heldPrev = held;
1048
+
}
1049
+
catch
1050
+
{
1051
+
Destroy(this);
1052
+
}
1053
+
}
1054
+
}
1055
+
759
1056
public class VaultDoor : MonoBehaviour
760
1057
{
761
1058
//Tracked so DestroyVaultDoorScript doesn't need to scan every Door on the map
762
1059
public static List<VaultDoor> ActiveVaultDoors = new List<VaultDoor>();
763
1060
764
1061
//Hold doors info
765
1062
public Door vdoor = null;
766
1063
//Holds Position that can be used after Vdoors destroyed.
767
1064
public Vector3 Position;
768
1065
//Hold door frames info
769
1066
public BaseEntity Frame = null;
770
1067
//List of players that have CUI active
771
1068
public List<BasePlayer> PlayersCUIed = new List<BasePlayer>();
772
1069
private void Awake()
773
1070
{
774
1071
//Setup
775
1072
vdoor = this.GetComponent<Door>();
776
1073
if (vdoor == null) return;
777
1074
ActiveVaultDoors.Add(this);
778
1075
Position = vdoor.transform.position;
779
1076
//Set Custom Health
780
1077
vdoor.SetMaxHealth(plugin.config.VaultMaxHealth);
781
1078
vdoor.SetHealth(plugin.config.VaultMaxHealth);
782
1079
//Safety net: make sure the slider is attached if slide mode is on
783
1080
if (plugin.config.SlideDoor.Enabled && vdoor.GetComponent<VaultSlide>() == null)
784
1081
{
785
1082
vdoor.gameObject.AddComponent<VaultSlide>();
786
1083
}
787
1084
Frame = vdoor.creatorEntity;
788
1085
//If no creatorEntity most likey a server restart so find it.
789
1086
if (Frame == null)
790
1087
{
791
1088
//Offsets scan position since where vault shows isnt where its position is.
792
1089
Vector3 scanpos = Position;
793
1090
scanpos += vdoor.transform.forward * 1.0f;
794
1091
scanpos += vdoor.transform.right * -0.54f;
795
1092
scanpos += vdoor.transform.up * 0.0f;
796
1093
if (plugin.showDebug) foreach (BasePlayer BP in BasePlayer.activePlayerList) { if (BP.IsAdmin) BP.SendConsoleCommand("ddraw.sphere", 8f, Color.red, scanpos, 0.5f); }
797
1094
List<BaseEntity> BuildingBlock = new List<BaseEntity>();
798
1095
//Scans area for players
799
1096
Vis.Entities<BaseEntity>(scanpos, 0.5f, BuildingBlock);
800
1097
foreach (BaseEntity doorframe in BuildingBlock)
801
1098
{
802
1099
if (doorframe.ShortPrefabName == "wall.doorway")
803
1100
{
804
1101
if (plugin.showDebug) plugin.Puts("Found Creator Doorway " + doorframe.ToString());
805
1102
Frame = doorframe;
806
1103
break;
807
1104
}
808
1105
}
809
1106
}
810
1107
//Setup checking if door frame has been destroyed
811
1108
InvokeRepeating("CheckFrame", 5, 8);
812
1109
if (plugin.config.HealthBarEnabled)
813
1110
{
814
1111
InvokeRepeating("VaultHealthBar", 1, 1);
815
1112
}
816
1113
else
817
1114
{ plugin.Unsubscribe(nameof(OnEntityTakeDamage)); }
818
1115
}
819
1116
820
1117
private void OnDestroy()
821
1118
{
822
1119
ActiveVaultDoors.Remove(this);
823
1120
}
824
1121
825
1122
void VaultHealthBar()
826
1123
{
827
1124
List<BasePlayer> PlayersInRange = new List<BasePlayer>();
828
1125
//Scans area for players
829
1126
Vis.Entities<BasePlayer>(vdoor.transform.position, plugin.config.vaultrange, PlayersInRange);
830
1127
831
1128
//Logic to remove CUI from players that have left area
832
1129
foreach (BasePlayer bp in PlayersCUIed.ToArray())
833
1130
{
834
1131
if (!PlayersInRange.Contains(bp))
835
1132
{
836
1133
CuiHelper.DestroyUi(bp, "VaultHealthUI");
837
1134
PlayersCUIed.Remove(bp);
838
1135
}
839
1136
}
840
1137
841
1138
//Shows CUI to each player in range
842
1139
if (PlayersInRange.Count == 0) { return; }
843
1140
844
1141
float pct = Mathf.Clamp01(vdoor._maxHealth > 0 ? vdoor._health / vdoor._maxHealth : 0f);
845
1142
var barsettings = plugin.config.HealthBar;
846
1143
string barColor = HexToRustFormat(pct >= 0.6f ? barsettings.HighColor : pct >= 0.25f ? barsettings.MidColor : barsettings.LowColor);
847
1144
string pctText = Mathf.RoundToInt(pct * 100f) + "%";
848
1145
849
1146
foreach (BasePlayer player in PlayersInRange.ToArray())
850
1147
{
851
1148
if (player.IsSleeping()) { continue; }
852
1149
853
1150
CuiHelper.DestroyUi(player, "VaultHealthUI");
854
1151
var elements = new CuiElementContainer();
855
1152
856
1153
//Backing panel
857
1154
elements.Add(new CuiPanel
858
1155
{
859
1156
Image = { Color = "0.05 0.05 0.05 0.55" },
860
1157
RectTransform = { AnchorMin = "0.79 0.93", AnchorMax = "0.995 0.975" }
861
1158
}, "Overlay", "VaultHealthUI");
862
1159
863
1160
//Title
864
1161
elements.Add(new CuiLabel
865
1162
{
866
1163
Text = { Text = "VAULT DOOR", FontSize = 11, Align = TextAnchor.MiddleLeft, Color = "0.85 0.85 0.85 0.9" },
867
1164
RectTransform = { AnchorMin = "0.04 0.5", AnchorMax = "0.45 1" }
868
1165
}, "VaultHealthUI");
869
1166
870
1167
//Bar background
871
1168
elements.Add(new CuiPanel
872
1169
{
873
1170
Image = { Color = "0.15 0.15 0.15 0.9" },
874
1171
RectTransform = { AnchorMin = "0.04 0.12", AnchorMax = "0.96 0.46" }
875
1172
}, "VaultHealthUI", "VaultHealthUI_BarBG");
876
1173
877
1174
//Bar fill, proportional to remaining health, color shifts green -> yellow -> red
878
1175
elements.Add(new CuiPanel
879
1176
{
880
1177
Image = { Color = barColor },
881
1178
RectTransform = { AnchorMin = "0 0", AnchorMax = $"{pct:F3} 1" }
882
1179
}, "VaultHealthUI_BarBG", "VaultHealthUI_BarFill");
883
1180
884
1181
//Percent readout on top of the bar
885
1182
elements.Add(new CuiLabel
886
1183
{
887
1184
Text = { Text = pctText, FontSize = 10, Align = TextAnchor.MiddleCenter, Color = "1 1 1 0.95" },
888
1185
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
889
1186
}, "VaultHealthUI_BarBG");
890
1187
891
1188
CuiHelper.AddUi(player, elements);
892
1189
if (!PlayersCUIed.Contains(player))
893
1190
{
894
1191
PlayersCUIed.Add(player);
895
1192
}
896
1193
}
897
1194
}
898
1195
899
1196
//Realistic damage indicator: a brief red flash over the health bar every time the door is hit
900
1197
public void FlashDamage()
901
1198
{
902
1199
if (PlayersCUIed.Count == 0) { return; }
903
1200
var barsettings = plugin.config.HealthBar;
904
1201
foreach (BasePlayer player in PlayersCUIed.ToArray())
905
1202
{
906
1203
if (player == null) { continue; }
907
1204
CuiHelper.DestroyUi(player, "VaultHealthUI_Flash");
908
1205
var elements = new CuiElementContainer();
909
1206
elements.Add(new CuiPanel
910
1207
{
911
1208
Image = { Color = HexToRustFormat(barsettings.FlashColor, 0.55f) },
912
1209
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
913
1210
}, "VaultHealthUI_BarBG", "VaultHealthUI_Flash");
914
1211
CuiHelper.AddUi(player, elements);
915
1212
}
916
1213
plugin.timer.Once(barsettings.FlashDuration, () =>
917
1214
{
918
1215
foreach (BasePlayer player in PlayersCUIed.ToArray())
919
1216
{
920
1217
if (player != null) { CuiHelper.DestroyUi(player, "VaultHealthUI_Flash"); }
921
1218
}
922
1219
});
923
1220
}
924
1221
925
1222
void CheckFrame()
926
1223
{
927
1224
//Frame has been destoryed so destroy vault
928
1225
try
929
1226
{
930
1227
if (Frame == null && vdoor != null)
931
1228
{
932
1229
foreach (var effect in effects) { Effect.server.Run(effect, Position); }
933
1230
vdoor.Kill();
934
1231
}
935
1232
}
936
1233
catch { }
937
1234
}
938
1235
939
1236
public void TryPickup(BasePlayer player)
940
1237
{
941
1238
//Owner has hit with hammer, Destroy frame and vault door and refund them one.
942
1239
if (vdoor.OwnerID == player.userID)
943
1240
{
944
1241
vdoor.Kill();
945
1242
if (Frame != null)
946
1243
{
947
1244
Frame.Kill();
948
1245
}
949
1246
plugin.GiveVaultDoor(player, true);
950
1247
}
951
1248
}
1249
+
1250
+
//Pie menu pickup: caller has already confirmed the door is public (no lock) and the player
1251
+
//has building privilege, so this skips the owner-only check TryPickup enforces for hammer hits.
1252
+
public void PickupViaMenu(BasePlayer player)
1253
+
{
1254
+
vdoor.Kill();
1255
+
if (Frame != null)
1256
+
{
1257
+
Frame.Kill();
1258
+
}
1259
+
plugin.GiveVaultDoor(player, true);
1260
+
}
952
1261
}
953
1262
954
1263
//Slide Mode: moves the whole vault door sideways instead of using the native swing animation
955
1264
public class VaultSlide : MonoBehaviour
956
1265
{
957
1266
public static List<VaultSlide> ActiveSliders = new List<VaultSlide>();
958
1267
959
1268
private Door entity;
960
1269
private Transform tr;
961
1270
private Vector3 closedPosition;
962
1271
private Vector3 openPosition;
963
1272
private bool opening;
964
1273
private float elapsed;
965
1274
private float duration;
966
1275
967
1276
public bool IsOpen { get { return tr != null && tr.position != closedPosition; } }
1277
+
//True while the slide animation is actively running (mid-transition), used to hide the pie
1278
+
//menu's Open/Close/Pickup buttons since IsOpen is unreliable while partway through a slide.
1279
+
public bool IsMoving { get { return enabled; } }
968
1280
969
1281
private void Awake()
970
1282
{
971
1283
entity = GetComponent<Door>();
972
1284
if (entity == null) { Destroy(this); return; }
973
1285
974
1286
tr = entity.transform;
975
1287
closedPosition = tr.position;
976
1288
openPosition = closedPosition + (-tr.forward * plugin.config.SlideDoor.Distance);
977
1289
duration = Mathf.Max(0.1f, plugin.config.SlideDoor.Duration);
978
1290
979
1291
enabled = false;
980
1292
ActiveSliders.Add(this);
981
1293
}
982
1294
983
1295
private void OnDestroy()
984
1296
{
985
1297
ActiveSliders.Remove(this);
986
1298
}
987
1299
988
1300
public void Toggle()
989
1301
{
990
1302
if (entity == null || entity.IsDestroyed) { return; }
991
1303
992
1304
CancelInvoke(nameof(AutoClose));
993
1305
elapsed = Mathf.Max(0f, duration - elapsed);
994
1306
opening = !opening;
995
1307
enabled = true;
996
1308
997
1309
Effect.server.Run("assets/prefabs/building/door.hinged/effects/door-metal-open-end.prefab", tr.position);
998
1310
}
999
1311
1000
1312
private void Update()
1001
1313
{
1002
1314
elapsed += Time.deltaTime;
1003
1315
float t = Mathf.InverseLerp(0f, duration, elapsed);
1004
1316
1005
1317
tr.position = opening ? Vector3.Lerp(closedPosition, openPosition, t) : Vector3.Lerp(openPosition, closedPosition, t);
1006
1318
tr.hasChanged = true;
1007
1319
SendPositionUpdate();
1008
1320
1009
1321
if (elapsed >= duration)
1010
1322
{
1011
1323
elapsed = duration;
1012
1324
enabled = false;
1013
1325
1014
1326
if (opening && plugin.config.SlideDoor.AutoCloseDelay > 0f)
1015
1327
{
1016
1328
Invoke(nameof(AutoClose), plugin.config.SlideDoor.AutoCloseDelay);
1017
1329
}
1018
1330
}
1019
1331
}
1020
1332
1021
1333
private void AutoClose()
1022
1334
{
1023
1335
if (opening) { Toggle(); }
1024
1336
}
1025
1337
1026
1338
public void ResetToClosed()
1027
1339
{
1028
1340
CancelInvoke(nameof(AutoClose));
1029
1341
enabled = false;
1030
1342
opening = false;
1031
1343
elapsed = 0f;
1032
1344
if (entity != null && !entity.IsDestroyed && tr != null)
1033
1345
{
1034
1346
tr.position = closedPosition;
1035
1347
tr.hasChanged = true;
1036
1348
SendPositionUpdate();
1037
1349
}
1038
1350
}
1039
1351
1040
1352
private void SendPositionUpdate()
1041
1353
{
1042
1354
if (entity == null || entity.net == null) { return; }
1043
1355
entity.SendNetworkUpdateImmediate();
1044
1356
}
1045
1357
}
1046
1358
#endregion
1047
1359
}
1048
1360
}