← Back to changelog

What changed

boatcommands · v1.0.1 → v1.0.2 (current)

Diff 2 added · 2 removed · 463 → 463 total lines
1 1 using Oxide.Game.Rust.Cui;
2 2 using UnityEngine;
3 3 using System.Collections.Generic;
4 4 using System.Linq;
5 5 using Facepunch;
6 6
7 7 namespace Oxide.Plugins
8 8 {
9 - [Info("boatcommands", "bmgjet", "1.0.1")]
9 + [Info("boatcommands", "bmgjet", "1.0.2")]
10 10 [Description("Boat control UI")]
11 11 public class boatcommands : RustPlugin
12 12 {
13 13 private const string BoatUI = "BoatCommandsUI";
14 14 private const string CannonUI = "BoatCannonsUI";
15 15 private const string PermUse = "boatcommands.use";
16 16
17 17 private const float CommandCooldown = 1f;
18 18 private const float UIRefreshInterval = 3f;
19 19 private const float AnchorDelay = 3f;
20 20
21 21 private readonly Dictionary<ulong, float> lastUse = new();
22 22 private readonly Dictionary<ulong, Timer> refreshTimers = new();
23 23
24 24 private class ManCannonData
25 25 {
26 26 public ulong OwnerId;
27 27 public PlayerBoat Boat;
28 28 public Cannon Cannon;
29 29 public BasePlayer Bot;
30 30 }
31 31
32 32 private readonly Dictionary<int, ManCannonData> manCannons = new();
33 33 private int cannonUidCounter = 0;
34 34
35 35 #region Oxide
36 36
37 37 void Init()
38 38 {
39 39 permission.RegisterPermission(PermUse, this);
40 40 }
41 41
42 42 void Unload()
43 43 {
44 44 if (manCannons.Count != 0)
45 45 {
46 46 foreach (var b in manCannons)
47 47 {
48 48 if (b.Value != null)
49 49 {
50 50 if (b.Value.Cannon != null)
51 51 {
52 52 b.Value.Cannon.DismountAllPlayers();
53 53 }
54 54 if (b.Value.Bot != null)
55 55 {
56 56 b.Value.Bot.Kill();
57 57 }
58 58 }
59 59 }
60 60 }
61 61
62 62 foreach (var player in BasePlayer.activePlayerList)
63 63 {
64 64 StopRefresh(player);
65 65 DestroyBoatUI(player);
66 66 }
67 67 }
68 68
69 69 #endregion
70 70
71 71 private void KitPlayer(BasePlayer npc)
72 72 {
73 73 Item item = ItemManager.CreateByName("hazmatsuit", 1, 0);
74 74 timer.Once(1, () =>
75 75 {
76 76 if (item != null && npc != null && npc.net != null && npc.net.group != null && npc?.inventory?.containerWear != null)
77 77 {
78 78 npc.inventory.containerWear.onItemAddedRemoved = null;
79 79 npc.inventory.containerWear.Insert(item);
80 80 npc?.SendNetworkUpdateImmediate();
81 81 npc.inventory.ServerUpdate(0f);
82 82 item.MarkDirty();
83 83 }
84 84 });
85 85 }
86 86
87 87 private int CreateNPC(Cannon cannon, BasePlayer player, PlayerBoat boat)
88 88 {
89 89 if (cannon.IsMounted()) { return 0; }
90 90 var bot = GameManager.server.CreateEntity("assets/prefabs/player/player.prefab", cannon.mountAnchor.position, cannon.mountAnchor.rotation) as BasePlayer;
91 91 if (bot == null) { return 0; }
92 92 bot.enableSaving = false;
93 93 bot.Spawn();
94 94 bot.displayName = RandomUsernames.Get(bot.userID);
95 95 KitPlayer(bot);
96 96 cannon.MountPlayer(bot);
97 97 cannon.AdminReload(1);
98 98 cannon.SendNetworkUpdate();
99 99 int uid = ++cannonUidCounter;
100 100 manCannons[uid] = new ManCannonData
101 101 {
102 102 OwnerId = player.userID,
103 103 Boat = boat,
104 104 Cannon = cannon,
105 105 Bot = bot
106 106 };
107 107 return uid;
108 108 }
109 109
110 110 #region Commands
111 111
112 112 [ConsoleCommand("boat.firecannon")]
113 113 private void CmdFireCannon(ConsoleSystem.Arg arg)
114 114 {
115 115 CannonFire(arg.Player(), "firecannon", arg.Args);
116 116 }
117 117
118 118 [ChatCommand("manallcannons")]
119 119 void ManAllCannons(BasePlayer player, string cmd, string[] args)
120 120 {
121 121 if (!player.IsAdmin) { return; }
122 122 if (player.GetParentEntity() is PlayerBoat boat)
123 123 {
124 124 foreach (var ent in boat.Deployables.Cached)
125 125 {
126 126 if (ent is Cannon cannon) { CreateNPC(cannon, player, boat); }
127 127 }
128 128 }
129 129 }
130 130
131 131 [ChatCommand("mancannon")]
132 132 void ManCannon(BasePlayer player, string cmd, string[] args)
133 133 {
134 134 if (!player.IsAdmin) { return; }
135 135
136 136 RaycastHit hit;
137 137 if (!Physics.Raycast(player.eyes.HeadRay(), out hit, 4f))
138 138 {
139 139 player.ChatMessage("Look at a cannon");
140 140 return;
141 141 }
142 142
143 143 Cannon cannon = hit.GetEntity() as Cannon;
144 144 if (cannon == null)
145 145 {
146 146 player.ChatMessage("Look at a cannon");
147 147 return;
148 148 }
149 149
150 150 if (!(cannon.GetParentEntity() is PlayerBoat boat))
151 151 {
152 152 player.ChatMessage("Cannon not on a player boat");
153 153 return;
154 154 }
155 155
156 156 if (cannon.IsMounted())
157 157 {
158 158 player.ChatMessage("This cannon already has a gunner");
159 159 return;
160 160 }
161 161 var uid = CreateNPC(cannon, player, boat);
162 162 if (uid != 0) { player.ChatMessage($"Man cannon installed. Fire with /firecannon {uid}"); }
163 163 }
164 164
165 165 [ChatCommand("firecannon")]
166 166 void CannonFire(BasePlayer player, string cmd, StringView[] args)
167 167 {
168 168 if (!player.IsAdmin) { return; }
169 169
170 170 if (args.Length == 0 || !int.TryParse(args[0].ToString(), out int uid))
171 171 {
172 172 return;
173 173 }
174 174
175 175 if (!manCannons.TryGetValue(uid, out var data))
176 176 {
177 177 return;
178 178 }
179 179
180 180 if (!data.Cannon.IsLoaded())
181 181 {
182 182 return;
183 183 }
184 184
185 185 if (data.Bot == null || data.Bot.IsDead())
186 186 {
187 187 manCannons.Remove(uid);
188 188 player.ChatMessage("Cannon Man Dead");
189 189 return;
190 190 }
191 191
192 192 ServerProjectile serverProjectile;
193 193 Cannon cannon = data.Cannon;
194 194 if (cannon.FireProjectile(cannon.AmmoPrefab, cannon.FirePoint.position, cannon.FirePoint.forward, player, 0.25f, 100f, out serverProjectile))
195 195 {
196 196 cannon.SERVER_OnProjectileFired(player.Connection, player);
197 197 }
198 198 if (data.Cannon != null && !data.Cannon.IsDestroyed)
199 199 {
200 200 data.Cannon.AdminReload(1);
201 201 data.Cannon.SendNetworkUpdate();
202 202 }
203 203 }
204 204
205 205 [ChatCommand("engine")]
206 206 void ToggleEngine(BasePlayer player, string cmd, string[] args)
207 207 {
208 208 if (!CanUse(player)) return;
209 209 if (!GetBoat(player, out PlayerBoat boat)) return;
210 210 bool engineOn = boat.EnginesOn();
211 211
212 212 boat.SetAllEnginesOn(!engineOn);
213 213 RefreshUI(player);
214 214 }
215 215
216 216 [ChatCommand("sails")]
217 217 void ToggleSails(BasePlayer player, string cmd, string[] args)
218 218 {
219 219 if (!CanUse(player)) return;
220 220 if (!GetBoat(player, out PlayerBoat boat)) return;
221 221
222 222 boat.SetAllSailsOpen(!boat.SailsOpen());
223 223 RefreshUI(player);
224 224 }
225 225
226 226 [ChatCommand("reverse")]
227 227 void ToggleReverse(BasePlayer player, string cmd, string[] args)
228 228 {
229 229 if (!CanUse(player)) return;
230 230 if (!GetBoat(player, out PlayerBoat boat)) return;
231 231
232 232 bool newReverse = !boat.AnyEngineReverse();
233 233
234 234 // Engines
235 235 foreach (var engine in boat.Engines.Cached)
236 - engine.SetFlag(BaseEntity.Flags.Reserved3, newReverse);
236 + engine.SetFlagLocal(BaseEntity.Flags.Reserved3, newReverse);
237 237
238 238 // Sails rotate with reverse
239 239 foreach (var sail in boat.Sails.Cached)
240 240 {
241 241 if (sail.CanRotate(player))
242 242 {
243 243 sail.RotateSail(player);
244 244 }
245 245 }
246 246
247 247 RefreshUI(player);
248 248 }
249 249
250 250 [ChatCommand("boatanchor")]
251 251 void ToggleAnchor(BasePlayer player, string cmd, string[] args)
252 252 {
253 253 if (!CanUse(player)) return;
254 254 if (!GetBoat(player, out PlayerBoat boat)) return;
255 255
256 256 foreach (var anchor in boat.Anchors.Cached)
257 257 {
258 258 if (anchor.Lowered)
259 259 anchor.RaiseAnchor(player);
260 260 else
261 261 anchor.LowerAnchor(player, false);
262 262 }
263 263
264 264 timer.Once(AnchorDelay, () =>
265 265 {
266 266 if (player != null && player.IsConnected)
267 267 RefreshUI(player);
268 268 });
269 269 }
270 270
271 271 #endregion
272 272
273 273 #region UI
274 274
275 275 void CreateBoatUI(BasePlayer player)
276 276 {
277 277 DestroyBoatUI(player);
278 278 if (!HasPerm(player)) return;
279 279 if (!GetBoat(player, out PlayerBoat boat)) return;
280 280
281 281 var container = new CuiElementContainer();
282 282 container.Add(new CuiPanel
283 283 {
284 284 Image = { Color = "0 0 0 0.35" },
285 285 RectTransform =
286 286 {
287 287 AnchorMin = "0.925 0.855",
288 288 AnchorMax = "0.995 0.995"
289 289 }
290 290 }, "Overlay", BoatUI);
291 291
292 292 AddBtn(container, BoatUI, "ENGINE", "engine", boat.EnginesOn(), 0, "0.2 0.7 0.2 0.9");
293 293 AddBtn(container, BoatUI, "SAILS", "sails", boat.SailsOpen(), 1, "0.2 0.6 0.9 0.9");
294 294 AddBtn(container, BoatUI, "REVERSE", "reverse", boat.AnyEngineReverse(), 2, "0.9 0.6 0.1 0.9");
295 295 AddBtn(container, BoatUI, "ANCHOR", "boatanchor", boat.AnchorLowered(), 3, "0.2 0.5 0.8 0.9");
296 296
297 297 if (player.IsAdmin)
298 298 {
299 299 container.Add(new CuiPanel
300 300 {
301 301 Image = { Color = "0 0 0 0.35" },
302 302 RectTransform =
303 303 {
304 304 AnchorMin = "0.20 0.001",
305 305 AnchorMax = "0.80 0.020"
306 306 }
307 307 }, "Overlay", CannonUI);
308 308
309 309 var cannons = manCannons
310 310 .Where(x => x.Value.Boat == boat)
311 311 .OrderBy(x => x.Key)
312 312 .ToList();
313 313
314 314 float width = cannons.Count > 0 ? 1f / cannons.Count : 1f;
315 315
316 316 for (int i = 0; i < cannons.Count; i++)
317 317 {
318 318 float xmin = i * width;
319 319 float xmax = xmin + width;
320 320
321 321 container.Add(new CuiButton
322 322 {
323 323 Button =
324 324 {
325 325 Color = "0.75 0.25 0.25 0.9",
326 326 Command = $"boat.firecannon {cannons[i].Key}"
327 327 },
328 328 RectTransform =
329 329 {
330 330 AnchorMin = $"{xmin} 0.05",
331 331 AnchorMax = $"{xmax} 0.95"
332 332 },
333 333 Text =
334 334 {
335 335 Text = $"CANNON {cannons[i].Key}",
336 336 FontSize = 9,
337 337 Align = TextAnchor.MiddleCenter
338 338 }
339 339 }, CannonUI);
340 340 }
341 341 }
342 342 CuiHelper.AddUi(player, container);
343 343 }
344 344
345 345 void AddBtn(CuiElementContainer c, string p, string t, string cmd, bool active, int i, string onColor)
346 346 {
347 347 float h = 0.17f;
348 348 float spacing = 0.055f;
349 349 float anchorMaxY = 0.93f;
350 350 float yMax = anchorMaxY - i * (h + spacing); float yMin = yMax - h;
351 351 c.Add(new CuiButton { Button = { Color = active ? onColor : "0.2 0.2 0.2 0.8", Command = $"chat.say /{cmd}" }, RectTransform = { AnchorMin = $"0.07 {yMin}", AnchorMax = $"0.93 {yMax}" }, Text = { Text = t, FontSize = 11, Align = TextAnchor.MiddleCenter } }, p);
352 352 }
353 353
354 354 void DestroyBoatUI(BasePlayer p)
355 355 {
356 356 CuiHelper.DestroyUi(p, BoatUI);
357 357 CuiHelper.DestroyUi(p, CannonUI);
358 358 }
359 359
360 360 void RefreshUI(BasePlayer p) => CreateBoatUI(p);
361 361
362 362
363 363
364 364 #endregion
365 365
366 366 #region Hooks
367 367
368 368 void OnEntityMounted(BaseMountable e, BasePlayer p)
369 369 {
370 370 if (e.prefabID != 1346716961) return;
371 371 CreateBoatUI(p);
372 372 StartRefresh(p);
373 373 }
374 374
375 375 void OnEntityDismounted(BaseMountable e, BasePlayer p)
376 376 {
377 377 if (e.prefabID != 1346716961) return;
378 378 StopRefresh(p);
379 379 DestroyBoatUI(p);
380 380 }
381 381
382 382 #endregion
383 383
384 384 #region Refresh Loop
385 385
386 386 void StartRefresh(BasePlayer p)
387 387 {
388 388 StopRefresh(p);
389 389 refreshTimers[p.userID] = timer.Every(UIRefreshInterval, () =>
390 390 {
391 391 if (p == null || !p.IsConnected || !GetBoat(p, out _))
392 392 {
393 393 StopRefresh(p);
394 394 return;
395 395 }
396 396 RefreshUI(p);
397 397 });
398 398 }
399 399
400 400 void StopRefresh(BasePlayer p)
401 401 {
402 402 if (refreshTimers.TryGetValue(p.userID, out var t))
403 403 t.Destroy();
404 404 refreshTimers.Remove(p.userID);
405 405 }
406 406
407 407 #endregion
408 408
409 409 #region Helpers
410 410
411 411 bool HasPerm(BasePlayer p) =>
412 412 permission.UserHasPermission(p.UserIDString, PermUse);
413 413
414 414 bool CanUse(BasePlayer p)
415 415 {
416 416 float now = Time.realtimeSinceStartup;
417 417 if (lastUse.TryGetValue(p.userID, out float last) && now - last < CommandCooldown)
418 418 return false;
419 419
420 420 lastUse[p.userID] = now;
421 421 return true;
422 422 }
423 423
424 424 bool GetBoat(BasePlayer p, out PlayerBoat boat)
425 425 {
426 426 boat = p?.GetMounted()?.GetParentEntity() as PlayerBoat;
427 427 return boat != null && boat.IsPlayerAuthed(p, true);
428 428 }
429 429
430 430 #endregion
431 431 }
432 432
433 433 static class BoatExtensions
434 434 {
435 435 public static bool EnginesOn(this PlayerBoat b)
436 436 {
437 437 foreach (var e in b.Engines.Cached)
438 438 if (e.IsOn()) return true;
439 439 return false;
440 440 }
441 441
442 442 public static bool AnyEngineReverse(this PlayerBoat b)
443 443 {
444 444 foreach (var e in b.Engines.Cached)
445 445 if (e.InReverse) return true;
446 446 return false;
447 447 }
448 448
449 449 public static bool SailsOpen(this PlayerBoat b)
450 450 {
451 451 foreach (var s in b.Sails.Cached)
452 452 if (s.Lowered) return true;
453 453 return false;
454 454 }
455 455
456 456 public static bool AnchorLowered(this PlayerBoat b)
457 457 {
458 458 foreach (var a in b.Anchors.Cached)
459 459 if (a.Lowered) return true;
460 460 return false;
461 461 }
462 462 }
463 463 }