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;
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);
142 Rectangle shrinkedBounds = ToolBox.GetWorldBounds(wreck.
WorldPosition.ToPoint(),
new Point(wreck.
Borders.Width - 500, wreck.
Borders.Height));
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;
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
override IReadOnlyList< Client > ConnectedClients
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)
void ServerEventWrite(IWriteMessage msg, Client client, NetEntityEvent.IData extraData=null)
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)
@ Character
Characters only
@ Structure
Structures and hulls, but also items (for backwards support)!