· 4 years ago · Jul 12, 2021, 06:58 PM
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Text.RegularExpressions;
5using Oxide.Core;
6using Oxide.Core.Libraries.Covalence;
7using Oxide.Core.Plugins;
8using UnityEngine;
9
10/*
11Exposed ArenaTerritory(Vector3) API
12Added option `Block Decay Damage (true)`
13*/
14
15namespace Oxide.Plugins
16{
17 [Info("ArenaWallGenerator", "nivex", "1.0.5")]
18 [Description("An easy to use arena wall generator.")]
19 class ArenaWallGenerator : RustPlugin
20 {
21 private static string hewwPrefab;
22 private static string heswPrefab;
23 private readonly int wallMask = LayerMask.GetMask("Terrain", "World", "Default", "Construction", "Deployed");
24 private readonly int worldMask = LayerMask.GetMask("World");
25 private StoredData storedData = new StoredData();
26 List<SimpleBuildingBlock> buildingBlocks = new List<SimpleBuildingBlock>();
27
28 private bool newSave;
29
30 public class StoredData
31 {
32 public int Seed = 0;
33 public readonly Dictionary<string, float> Arenas = new Dictionary<string, float>();
34 public StoredData() { }
35 }
36
37 private void OnNewSave(string filename) => newSave = true;
38
39 private void OnServerSave()
40 {
41 SaveData();
42 }
43
44 private void Unload()
45 {
46 SaveData();
47 }
48
49 private void Init()
50 {
51 Unsubscribe(nameof(OnEntityDeath));
52 Unsubscribe(nameof(OnEntityKill));
53 Unsubscribe(nameof(OnEntityTakeDamage));
54 }
55
56 private void OnServerInitialized(bool isStartup)
57 {
58 try
59 {
60 storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
61 }
62 catch { }
63
64 if (storedData?.Arenas == null)
65 storedData = new StoredData();
66
67 if (newSave || BuildingManager.server.buildingDictionary.Count == 1)
68 {
69 if (respawnOnWipe && storedData.Seed == ConVar.Server.seed)
70 {
71 foreach (var entry in storedData.Arenas)
72 {
73 API_CreateZoneWalls(entry.Key.ToVector3(), entry.Value);
74 }
75 }
76
77 storedData.Seed = ConVar.Server.seed;
78 newSave = false;
79 SaveData();
80 }
81
82 if (noDecay)
83 {
84 Subscribe(nameof(OnEntityTakeDamage));
85 }
86
87 Subscribe(nameof(OnEntityKill));
88 Subscribe(nameof(OnEntityDeath));
89 Subscribe(nameof(OnEntitySpawned));
90 hewwPrefab = StringPool.Get(1745077396);
91 heswPrefab = StringPool.Get(1585379529);
92 buildingBlocks.AddRange(BaseNetworkable.serverEntities.OfType<SimpleBuildingBlock>());
93 }
94
95 private void OnEntityKill(SimpleBuildingBlock e)
96 {
97 buildingBlocks.Remove(e);
98
99 if (respawnWalls)
100 {
101 RecreateZoneWall(e.PrefabName, e.transform.position, e.transform.rotation, e.OwnerID);
102 }
103 }
104
105 private void OnEntityDeath(SimpleBuildingBlock e, HitInfo hitInfo) => OnEntityKill(e);
106
107 private void OnEntitySpawned(SimpleBuildingBlock e) => buildingBlocks.Add(e);
108
109 private void OnEntityTakeDamage(SimpleBuildingBlock e, HitInfo hitInfo)
110 {
111 if (e == null || hitInfo == null || !hitInfo.damageTypes.Has(Rust.DamageType.Decay) || !ArenaTerritory(e.transform.position))
112 {
113 return;
114 }
115
116 hitInfo.damageTypes = new Rust.DamageTypeList();
117 }
118
119 public void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, storedData);
120
121 public void RecreateZoneWall(string prefab, Vector3 pos, Quaternion rot, ulong ownerId)
122 {
123 if (ArenaTerritory(pos))
124 {
125 var e = GameManager.server.CreateEntity(prefab, pos, rot, false);
126
127 if (e != null)
128 {
129 e.OwnerID = ownerId;
130 e.Spawn();
131 e.gameObject.SetActive(true);
132 }
133 }
134 }
135
136 [HookMethod("ArenaTerritory")]
137 public bool ArenaTerritory(Vector3 position)
138 {
139 foreach (var zone in storedData.Arenas)
140 {
141 if (Vector3Ex.Distance2D(position, zone.Key.ToVector3()) <= zone.Value + 5f)
142 {
143 return true;
144 }
145 }
146
147 return false;
148 }
149
150 public ulong GetHashId(string uid)
151 {
152 return Convert.ToUInt64(Math.Abs(uid.GetHashCode()));
153 }
154
155 public bool RemoveCustomZoneWalls(Vector3 center)
156 {
157 var list = new List<Vector3>();
158
159 foreach (var zone in storedData.Arenas)
160 {
161 if (Vector3Ex.Distance2D(center, zone.Key.ToVector3()) <= zone.Value)
162 {
163 list.Add(zone.Key.ToVector3());
164 }
165 }
166
167 if (list.Count > 0)
168 {
169 list.Sort((x, y) => Vector3.Distance(y, x).CompareTo(Vector3.Distance(x, y)));
170
171 string key = list.First().ToString();
172
173 if (RemoveZoneWalls(GetHashId(key)) > 0)
174 {
175 storedData.Arenas.Remove(key);
176 return true;
177 }
178 }
179
180 return false;
181 }
182
183 public int RemoveZoneWalls(ulong hashId)
184 {
185 int removed = 0;
186
187 if (respawnWalls)
188 {
189 Unsubscribe(nameof(OnEntityKill));
190 }
191
192 foreach (var e in buildingBlocks.ToList())
193 {
194 if (e.OwnerID == hashId)
195 {
196 buildingBlocks.Remove(e);
197 e.Kill();
198 removed++;
199 }
200 }
201
202 if (respawnWalls)
203 {
204 Subscribe(nameof(OnEntityKill));
205 }
206
207 return removed;
208 }
209
210 public List<Vector3> GetCircumferencePositions(Vector3 center, float radius, float next, float y)
211 {
212 var positions = new List<Vector3>();
213 float degree = 0f;
214
215 while (degree < 360)
216 {
217 float angle = (float)(2 * Math.PI / 360) * degree;
218 float x = center.x + radius * (float)Math.Cos(angle);
219 float z = center.z + radius * (float)Math.Sin(angle);
220 var position = new Vector3(x, 0f, z);
221
222 position.y = y == 0f ? TerrainMeta.HeightMap.GetHeight(position) : y;
223 positions.Add(position);
224
225 degree += next;
226 }
227
228 return positions;
229 }
230
231 public bool CreateZoneWalls(Vector3 center, float radius, string prefab, IPlayer p)
232 {
233 var tick = DateTime.Now;
234 ulong hashId = GetHashId(center.ToString());
235 float maxHeight = -200f;
236 float minHeight = 200f;
237 int spawned = 0;
238 int raycasts = Mathf.CeilToInt(360 / radius * 0.1375f);
239
240 foreach (var position in GetCircumferencePositions(center, radius, raycasts, 0f))
241 {
242 RaycastHit hit;
243 if (Physics.Raycast(new Vector3(position.x, position.y + 200f, position.z), Vector3.down, out hit, Mathf.Infinity, wallMask))
244 {
245 maxHeight = Mathf.Max(hit.point.y, maxHeight);
246 minHeight = Mathf.Min(hit.point.y, minHeight);
247 center.y = minHeight;
248 }
249 }
250
251 float gap = prefab == heswPrefab ? 0.3f : 0.5f;
252 int stacks = Mathf.CeilToInt((maxHeight - minHeight) / 6f) + extraWallStacks;
253 float next = 360 / radius - gap;
254
255 for (int i = 0; i < stacks; i++)
256 {
257 foreach (var position in GetCircumferencePositions(center, radius, next, center.y))
258 {
259 float groundHeight = TerrainMeta.HeightMap.GetHeight(new Vector3(position.x, position.y + 6f, position.z));
260
261 if (groundHeight > position.y + 9f)
262 continue;
263
264 if (useLeastAmount && position.y - groundHeight > 6f + extraWallStacks * 6f)
265 continue;
266
267 var entity = GameManager.server.CreateEntity(prefab, position, default(Quaternion), false);
268
269 if (entity != null)
270 {
271 entity.OwnerID = hashId;
272 entity.transform.LookAt(center, Vector3.up);
273 entity.Spawn();
274 entity.gameObject.SetActive(true);
275 spawned++;
276 }
277 else
278 return false;
279
280 if (stacks == i - 1)
281 {
282 RaycastHit hit;
283 if (Physics.Raycast(new Vector3(position.x, position.y + 6f, position.z), Vector3.down, out hit, 12f, worldMask))
284 stacks++;
285 }
286 }
287
288 center.y += 6f;
289 }
290
291 string strPos = $"{center.x.ToString("N2")} {center.y.ToString("N2")} {center.z.ToString("N2")}";
292
293 if (p == null)
294 Puts(msg("GeneratedWalls", null, spawned, stacks, strPos, (DateTime.Now - tick).TotalSeconds));
295 else
296 p.Reply(msg("GeneratedWalls", p.Id, spawned, stacks, strPos, (DateTime.Now - tick).TotalSeconds));
297
298 return true;
299 }
300
301 private bool API_CreateZoneWalls(Vector3 center, float radius)
302 {
303 if (CreateZoneWalls(center, radius, useWoodenWalls ? hewwPrefab : heswPrefab, null))
304 {
305 storedData.Arenas[center.ToString()] = radius;
306 return true;
307 }
308
309 return false;
310 }
311
312 private bool API_RemoveZoneWalls(Vector3 center)
313 {
314 return RemoveCustomZoneWalls(center);
315 }
316
317 private void CommandWalls(IPlayer p, string command, string[] args)
318 {
319 if (!p.IsAdmin || p.IsServer)
320 {
321 return;
322 }
323
324 var player = p.Object as BasePlayer;
325
326 if (args.Length >= 1)
327 {
328 float radius;
329 if (float.TryParse(args[0], out radius) && radius > 2f)
330 {
331 if (radius > maxCustomWallRadius)
332 radius = maxCustomWallRadius;
333
334 RaycastHit hit;
335 if (Physics.Raycast(player.eyes.HeadRay(), out hit, Mathf.Infinity, wallMask))
336 {
337 string prefab = useWoodenWalls ? hewwPrefab : heswPrefab;
338
339 if (args.Any(arg => arg.ToLower().Contains("stone")))
340 prefab = heswPrefab;
341 else if (args.Any(arg => arg.ToLower().Contains("wood")))
342 prefab = hewwPrefab;
343
344 storedData.Arenas[hit.point.ToString()] = radius;
345 CreateZoneWalls(hit.point, radius, prefab, p);
346 }
347 else
348 p.Reply(msg("FailedRaycast", p.Id));
349 }
350 else
351 p.Reply(msg("InvalidNumber", p.Id, args[0]));
352 }
353 else
354 {
355 if (!RemoveCustomZoneWalls(player.transform.position))
356 p.Reply(msg("WallSyntax", player.UserIDString, chatCommandName));
357
358 foreach (var entry in storedData.Arenas)
359 player.SendConsoleCommand("ddraw.text", 30f, Color.yellow, entry.Key.ToVector3(), entry.Value);
360 }
361 }
362
363 #region Config
364
365 private bool Changed;
366 private int extraWallStacks;
367 private bool useLeastAmount;
368 private float maxCustomWallRadius;
369 private bool useWoodenWalls;
370 private string chatCommandName;
371 private bool respawnWalls;
372 private bool respawnOnWipe;
373 private bool noDecay;
374
375 protected override void LoadDefaultMessages()
376 {
377 lang.RegisterMessages(new Dictionary<string, string>
378 {
379 ["GeneratedWalls"] = "Generated {0} arena walls {1} high at {2} in {3}ms",
380 ["FailedRaycast"] = "Look towards the ground, and try again.",
381 ["InvalidNumber"] = "Invalid number: {0}",
382 ["WallSyntax"] = "Use <color=orange>/{0} [radius] <wood|stone></color>, or stand inside of an existing arena and use <color=orange>/{0}</color> to remove it.",
383 }, this);
384 }
385
386 protected override void LoadConfig()
387 {
388 base.LoadConfig();
389 extraWallStacks = Convert.ToInt32(GetConfig("Settings", "Extra High External Wall Stacks", 2));
390 useLeastAmount = Convert.ToBoolean(GetConfig("Settings", "Create Least Amount Of Walls", false));
391 maxCustomWallRadius = Convert.ToSingle(GetConfig("Settings", "Maximum Arena Radius", 300f));
392 useWoodenWalls = Convert.ToBoolean(GetConfig("Settings", "Use Wooden Walls", false));
393 chatCommandName = Convert.ToString(GetConfig("Settings", "Chat Command", "awg"));
394 respawnWalls = Convert.ToBoolean(GetConfig("Settings", "Respawn Zone Walls On Death", false));
395 respawnOnWipe = Convert.ToBoolean(GetConfig("Settings", "Respawn Arenas On Wipe If Same Seed", false));
396 noDecay = Convert.ToBoolean(GetConfig("Settings", "Block Decay Damage", true));
397
398 AddCovalenceCommand(chatCommandName, nameof(CommandWalls));
399
400 if (!Changed) return;
401 SaveConfig();
402 Changed = false;
403 }
404
405 protected override void LoadDefaultConfig()
406 {
407 PrintWarning("Creating a new configuration file");
408 Config.Clear();
409 }
410
411 private object GetConfig(string menu, string datavalue, object defaultValue)
412 {
413 var data = Config[menu] as Dictionary<string, object>;
414 if (data == null)
415 {
416 data = new Dictionary<string, object>();
417 Config[menu] = data;
418 Changed = true;
419 }
420 object value;
421 if (!data.TryGetValue(datavalue, out value))
422 {
423 value = defaultValue;
424 data[datavalue] = value;
425 Changed = true;
426 }
427 return value;
428 }
429
430 public string msg(string key, string id = null, params object[] args)
431 {
432 string message = id == null ? RemoveFormatting(lang.GetMessage(key, this, id)) : lang.GetMessage(key, this, id);
433
434 return args.Length > 0 ? string.Format(message, args) : message;
435 }
436
437 public string RemoveFormatting(string source)
438 {
439 return source.Contains(">") ? Regex.Replace(source, "<.*?>", string.Empty) : source;
440 }
441
442 #endregion
443 }
444}