3 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
11 internal class SubmarineTurretAI
14 protected readonly List<Turret> turrets =
new List<Turret>();
15 public Identifier FriendlyTag;
17 public SubmarineTurretAI(Submarine submarine, Identifier friendlyTag =
default)
19 FriendlyTag = friendlyTag;
21 foreach (Item item
in Item.ItemList)
23 if (item.Submarine != Submarine) {
continue; }
24 var turret = item.GetComponent<
Turret>();
31 turret.Item.Condition = turret.Item.MaxCondition;
32 foreach (MapEntity linkedEntity
in turret.Item.linkedTo)
34 if (linkedEntity is Item linkedItem)
36 linkedItem.Condition = linkedItem.MaxCondition;
44 public virtual void Update(
float deltaTime)
46 if (Submarine ==
null ||
Submarine.Removed) {
return; }
47 OperateTurrets(deltaTime, FriendlyTag);
50 protected virtual void LoadAllTurrets()
52 foreach (var turret
in turrets)
58 protected void LoadTurret(
Turret turret, Func<ItemPrefab, bool> ammoFilter =
null)
60 foreach (var linkedItem
in turret.
Item.GetLinkedEntities<Item>())
63 if (container ==
null) {
continue; }
64 for (
int i = 0; i < container.Inventory.Capacity; i++)
66 if (container.Inventory.GetItemAt(i) !=
null) {
continue; }
67 if (MapEntityPrefab.List.GetRandom(e => e is ItemPrefab ip && container.CanBeContained(ip, i) && (ammoFilter ==
null || ammoFilter(ip)), Rand.RandSync.ServerAndClient) is ItemPrefab ammoPrefab)
69 Item ammo =
new Item(ammoPrefab, container.Item.WorldPosition, Submarine);
70 if (!container.Inventory.TryPutItem(ammo, i, allowSwapping:
false, allowCombine:
false, user:
null, createNetworkEvent:
false))
79 protected void OperateTurrets(
float deltaTime, Identifier friendlyTag)
81 foreach (var turret
in turrets)
83 turret.UpdateAutoOperate(deltaTime, ignorePower:
true, friendlyTag);
92 private readonly List<Item> allItems;
93 private readonly List<Item> thalamusItems;
94 private readonly List<Structure> thalamusStructures;
95 private readonly List<WayPoint> wayPoints =
new List<WayPoint>();
96 private readonly List<Hull> hulls =
new List<Hull>();
97 private readonly List<Item> spawnOrgans =
new List<Item>();
98 private readonly
Item brain;
100 private bool initialCellsSpawned;
108 private static IEnumerable<T> GetThalamusEntities<T>(
Submarine wreck, Identifier tag) where T :
MapEntity => GetThalamusEntities(wreck, tag).Where(e => e is T).Select(e => e as T);
110 private static IEnumerable<MapEntity> GetThalamusEntities(
Submarine wreck, Identifier tag) =>
MapEntity.
MapEntityList.Where(e => e.Submarine == wreck && e.Prefab !=
null && IsThalamus(e.Prefab, tag));
112 public static bool IsThalamus(
MapEntityPrefab entityPrefab, Identifier tag) => entityPrefab.HasSubCategory(
"thalamus") || entityPrefab.Tags.Contains(tag);
116 var wreckAI =
new WreckAI(wreck);
117 if (wreckAI.Config ==
null) {
return null; }
124 if (
Config ==
null) {
return; }
125 var thalamusPrefabs = ItemPrefab.Prefabs.Where(p => IsThalamus(p));
126 var brainPrefab = thalamusPrefabs.GetRandom(i => i.Tags.Contains(
Config.
Brain), Rand.RandSync.ServerAndClient);
127 if (brainPrefab ==
null)
129 DebugConsole.ThrowError($
"WreckAI: Could not find any brain prefab with the tag {Config.Brain}! Cannot continue. Failed to create wreck AI.");
133 thalamusItems = allItems.FindAll(i => IsThalamus(((MapEntity)i).Prefab));
134 hulls.AddRange(wreck.
GetHulls(
false));
135 var potentialBrainHulls =
new List<(Hull hull, float weight)>();
136 brain =
new Item(brainPrefab, Vector2.Zero, wreck);
137 thalamusItems.Add(brain);
138 Point minSize = brain.Rect.Size.Multiply(brain.Scale);
140 Vector2 sufficentSize =
new Vector2(minSize.X * 2, minSize.Y * 1.1f);
143 foreach (Hull hull
in hulls)
145 float distanceFromCenter = Vector2.Distance(wreck.
WorldPosition, hull.WorldPosition);
146 float distanceFactor = MathHelper.Lerp(1.0f, 0.5f, MathUtils.InverseLerp(0, Math.Max(shrinkedBounds.Width, shrinkedBounds.Height) / 2, distanceFromCenter));
147 float horizontalSizeFactor = MathHelper.Lerp(0.5f, 1.0f, MathUtils.InverseLerp(minSize.X, sufficentSize.X, hull.Rect.Width));
148 float verticalSizeFactor = MathHelper.Lerp(0.5f, 1.0f, MathUtils.InverseLerp(minSize.Y, sufficentSize.Y, hull.Rect.Height));
149 float weight = verticalSizeFactor * horizontalSizeFactor * distanceFactor;
150 if (hull.GetLinkedEntities<Hull>().Any())
155 else if (hull.ConnectedGaps.Any(g => g.Open > 0 && (!g.IsRoomToRoom || g.Position.Y < hull.Position.Y)))
160 else if (thalamusItems.Any(i => i.CurrentHull == hull))
165 else if (hull.Rect.Width < minSize.X || hull.Rect.Height < minSize.Y)
172 potentialBrainHulls.Add((hull, weight));
175 Hull brainHull = ToolBox.SelectWeightedRandom(potentialBrainHulls.Select(pbh => pbh.hull).ToList(), potentialBrainHulls.Select(pbh => pbh.weight).ToList(), Rand.RandSync.ServerAndClient);
176 var thalamusStructurePrefabs = StructurePrefab.Prefabs.Where(IsThalamus);
177 if (brainHull ==
null)
179 DebugConsole.AddWarning(
"Wreck AI: Cannot find a proper room for the brain. Using a random room.");
180 brainHull = hulls.GetRandom(Rand.RandSync.ServerAndClient);
182 if (brainHull ==
null)
184 DebugConsole.ThrowError(
"Wreck AI: Cannot find any room for the brain! Failed to create the Thalamus.");
187 brainHull.WaterVolume = brainHull.Volume;
188 brain.SetTransform(brainHull.SimPosition, rotation: 0, findNewHull:
false);
189 brain.CurrentHull = brainHull;
190 var backgroundPrefab = thalamusStructurePrefabs.GetRandom(i => i.Tags.Contains(
Config.
BrainRoomBackground), Rand.RandSync.ServerAndClient);
191 if (backgroundPrefab !=
null)
193 new Structure(brainHull.Rect, backgroundPrefab, wreck);
195 var horizontalWallPrefab = thalamusStructurePrefabs.GetRandom(p => p.Tags.Contains(
Config.
BrainRoomHorizontalWall), Rand.RandSync.ServerAndClient);
196 if (horizontalWallPrefab !=
null)
198 int height = (int)horizontalWallPrefab.Size.Y;
199 int halfHeight = height / 2;
200 int quarterHeight = halfHeight / 2;
201 new Structure(
new Rectangle(brainHull.Rect.Left, brainHull.Rect.Top + quarterHeight, brainHull.Rect.Width, height), horizontalWallPrefab, wreck);
202 new Structure(
new Rectangle(brainHull.Rect.Left, brainHull.Rect.Top - brainHull.Rect.Height + halfHeight + quarterHeight, brainHull.Rect.Width, height), horizontalWallPrefab, wreck);
204 var verticalWallPrefab = thalamusStructurePrefabs.GetRandom(p => p.Tags.Contains(
Config.
BrainRoomVerticalWall), Rand.RandSync.ServerAndClient);
205 if (verticalWallPrefab !=
null)
207 int width = (int)verticalWallPrefab.Size.X;
208 int halfWidth = width / 2;
209 int quarterWidth = halfWidth / 2;
210 new Structure(
new Rectangle(brainHull.Rect.Left - quarterWidth, brainHull.Rect.Top, width, brainHull.Rect.Height), verticalWallPrefab, wreck);
211 new Structure(
new Rectangle(brainHull.Rect.Right - halfWidth - quarterWidth, brainHull.Rect.Top, width, brainHull.Rect.Height), verticalWallPrefab, wreck);
213 foreach (Item item
in thalamusItems)
216 item.IsLayerHidden =
false;
219 if (!spawnOrgans.Contains(item))
221 spawnOrgans.Add(item);
222 if (item.CurrentHull !=
null)
225 item.CurrentHull.WaterVolume = item.CurrentHull.Volume;
232 thalamusStructures = GetThalamusEntities<Structure>(wreck,
Config.
Entity).ToList();
235 private void GetConfig()
240 DebugConsole.ThrowError(
"WreckAI: No wreck AI config found!");
247 foreach (var turret
in turrets)
253 private readonly List<Item> destroyedOrgans =
new List<Item>();
254 public override void Update(
float deltaTime)
262 if (brain ==
null || brain.Removed || brain.Condition <= 0)
267 destroyedOrgans.Clear();
268 foreach (var organ
in spawnOrgans)
270 if (organ.Condition <= 0)
272 destroyedOrgans.Add(organ);
275 destroyedOrgans.ForEach(o => spawnOrgans.Remove(o));
278 if (!initialCellsSpawned) { SpawnInitialCells(); }
280 bool isSomeoneNearby =
false;
283 foreach (var client
in GameMain.Server.ConnectedClients)
285 var spectatePos = client.SpectatePos;
286 if (spectatePos.HasValue)
288 if (IsCloseEnough(spectatePos.Value, minDist))
290 isSomeoneNearby =
true;
298 isSomeoneNearby =
true;
301 if (!isSomeoneNearby)
308 isSomeoneNearby =
true;
313 if (!isSomeoneNearby)
320 isSomeoneNearby =
true;
325 if (!isSomeoneNearby) {
return; }
329 UpdateReinforcements(deltaTime);
332 private bool IsCloseEnough(Vector2 targetPos,
float minDist) => Vector2.DistanceSquared(targetPos,
Submarine.
WorldPosition) < minDist * minDist;
334 private void SpawnInitialCells()
336 int brainRoomCells = Rand.Range(MinCellsPerBrainRoom, MaxCellsPerRoom + 1);
337 if (brain.CurrentHull?.WaterPercentage >= MinWaterLevel)
339 for (
int i = 0; i < brainRoomCells; i++)
341 if (!TrySpawnCell(out _, brain.CurrentHull)) {
break; }
344 int cellsInside = Rand.Range(MinCellsInside, MaxCellsInside + 1);
345 for (
int i = 0; i < cellsInside; i++)
347 if (!TrySpawnCell(out _)) {
break; }
349 int cellsOutside = Rand.Range(MinCellsOutside, MaxCellsOutside + 1);
351 cellsOutside = Math.Clamp(cellsOutside + brainRoomCells + cellsInside - protectiveCells.Count, cellsOutside, MaxCellsOutside);
352 for (
int i = 0; i < cellsOutside; i++)
354 ISpatialEntity targetEntity = wayPoints.GetRandomUnsynced(wp => wp.CurrentHull ==
null);
355 if (targetEntity ==
null) {
break; }
356 if (!TrySpawnCell(out _, targetEntity)) {
break; }
358 initialCellsSpawned =
true;
363 thalamusItems.ForEach(i => i.Condition = 0);
364 foreach (var turret
in turrets)
367 foreach (
Item item
in turret.ActiveProjectiles)
369 if (item.GetComponent<
Projectile>() is { IsStuckToTarget: true })
376 protectiveCells.ForEach(c => c.OnDeath -= OnCellDeath);
396 if (Vector2.DistanceSquared(character.WorldPosition,
Submarine.
WorldPosition) < maxDistance * maxDistance)
406 protectiveCells.Clear();
410 partial
void FadeOutColors();
416 thalamusItems?.Clear();
417 thalamusStructures?.Clear();
422 List<MapEntity> thalamusItems =
new List<MapEntity>();
425 thalamusItems.AddRange(GetThalamusEntities(wreck, wreckAiConfig.
Entity));
427 thalamusItems = thalamusItems.Distinct().ToList();
428 foreach (
MapEntity thalamusItem
in thalamusItems)
436 private readonly List<Character> protectiveCells =
new List<Character>();
438 private readonly List<Hull> populatedHulls =
new List<Hull>();
439 private float cellSpawnTimer;
450 private int CalculateCellCount(
int minValue,
int maxValue)
452 if (maxValue == 0) {
return 0; }
453 float difficulty = Level.Loaded?.Difficulty ?? 0.0f;
455 return (
int)Math.Round(MathHelper.Lerp(minValue, maxValue, t));
458 private float GetSpawnTime()
463 float max = delay * 6;
464 float difficulty = Level.Loaded?.Difficulty ?? 0.0f;
466 return MathHelper.Lerp(max, min, MathUtils.InverseLerp(0, 100, t));
469 private void UpdateReinforcements(
float deltaTime)
471 if (spawnOrgans.Count == 0) {
return; }
472 cellSpawnTimer -= deltaTime;
473 if (cellSpawnTimer < 0)
475 TrySpawnCell(out _, spawnOrgans.GetRandomUnsynced());
476 cellSpawnTimer = GetSpawnTime();
480 private bool TrySpawnCell(out Character cell, ISpatialEntity targetEntity =
null)
483 if (protectiveCells.Count >= MaxCellCount) {
return false; }
484 if (targetEntity ==
null)
487 wayPoints.GetRandomUnsynced(wp => wp.CurrentHull !=
null && populatedHulls.Count(h => h == wp.CurrentHull) < MaxCellsPerRoom && wp.CurrentHull.WaterPercentage >= MinWaterLevel) ??
488 hulls.GetRandomUnsynced(h => populatedHulls.Count(h2 => h2 == h) < MaxCellsPerRoom && h.WaterPercentage >= MinWaterLevel) as ISpatialEntity;
490 if (targetEntity ==
null) {
return false; }
491 if (targetEntity is Hull h)
493 populatedHulls.Add(h);
495 else if (targetEntity is WayPoint wp && wp.CurrentHull !=
null)
497 populatedHulls.Add(wp.CurrentHull);
501 protectiveCells.Add(cell);
502 cell.OnDeath += OnCellDeath;
503 cellSpawnTimer = GetSpawnTime();
507 void OnCellDeath(Character character, CauseOfDeath causeOfDeath)
509 protectiveCells.Remove(character);
static readonly List< Character > CharacterList
virtual Vector2 WorldPosition
Entity(Submarine submarine, ushort id)
static GameScreen GameScreen
static NetworkMember NetworkMember
const float DefaultSonarRange
static readonly List< MapEntity > MapEntityList
List< Item > GetItems(bool alsoFromConnectedSubs)
override Vector2? WorldPosition
List< WayPoint > GetWaypoints(bool alsoFromConnectedSubs)
static List< Submarine > Loaded
Rectangle? Borders
Extents of the solid items/structures (ones with a physics body) and hulls
List< Hull > GetHulls(bool alsoFromConnectedSubs)
float AgentSpawnDelayRandomFactor
string BrainRoomVerticalWall
float AgentSpawnCountDifficultyMultiplier
bool KillAgentsWhenEntityDies
Identifier DefensiveAgent
string BrainRoomHorizontalWall
static readonly PrefabCollection< WreckAIConfig > Prefabs
readonly Identifier[] ForbiddenAmmunition
string BrainRoomBackground
int MinAgentsPerBrainRoom
float AgentSpawnDelayDifficultyMultiplier
static WreckAIConfig GetRandom()
static bool IsThalamus(MapEntityPrefab entityPrefab, Identifier tag)
static void RemoveThalamusItems(Submarine wreck)
static WreckAI Create(Submarine wreck)
override void LoadAllTurrets()
override void Update(float deltaTime)
Interface for entities that the server can send events to the clients
void WriteBoolean(bool val)