272 lines
11 KiB
C#
272 lines
11 KiB
C#
using Barotrauma.Extensions;
|
|
using Barotrauma.Items.Components;
|
|
using Microsoft.Xna.Framework;
|
|
using System.Collections.Generic;
|
|
using Barotrauma.Networking;
|
|
using System.Linq;
|
|
using System;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
class WreckAI : IServerSerializable
|
|
{
|
|
public Submarine Wreck { get; private set; }
|
|
|
|
public bool IsAlive { get; private set; }
|
|
|
|
private readonly List<Item> allItems;
|
|
private readonly List<Item> thalamusItems;
|
|
private readonly List<Turret> turrets = new List<Turret>();
|
|
private readonly List<WayPoint> wayPoints = new List<WayPoint>();
|
|
private readonly List<Hull> hulls = new List<Hull>();
|
|
private readonly List<Item> spawnOrgans = new List<Item>();
|
|
private readonly Item brain;
|
|
|
|
private bool initialCellsSpawned;
|
|
|
|
public readonly WreckAIConfig Config;
|
|
|
|
private bool IsClient => GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
|
|
|
public WreckAI(Submarine wreck, Item brain, List<Item> items = null)
|
|
{
|
|
Config = WreckAIConfig.GetRandom();
|
|
if (Config == null)
|
|
{
|
|
DebugConsole.ThrowError("WreckAI: No wreck AI config found!");
|
|
Kill();
|
|
return;
|
|
}
|
|
allItems = items ?? wreck.GetItems(false);
|
|
thalamusItems = allItems.FindAll(i => i.Prefab.Category == MapEntityCategory.Thalamus || i.HasTag("thalamus"));
|
|
foreach (Item item in allItems)
|
|
{
|
|
if (thalamusItems.Contains(item))
|
|
{
|
|
// Ensure that thalamus items are visible
|
|
item.HiddenInGame = false;
|
|
}
|
|
else
|
|
{
|
|
// Load regular turrets
|
|
var turret = item.GetComponent<Turret>();
|
|
if (turret != null)
|
|
{
|
|
foreach (var linkedItem in item.GetLinkedEntities<Item>())
|
|
{
|
|
var container = linkedItem.GetComponent<ItemContainer>();
|
|
if (container == null) { continue; }
|
|
for (int i = 0; i < container.Inventory.Capacity; i++)
|
|
{
|
|
if (container.Inventory.Items[i] != null) { continue; }
|
|
if (MapEntityPrefab.List.GetRandom(e => e is ItemPrefab i && container.CanBeContained(i) &&
|
|
Config.ForbiddenAmmunition.None(id => id.Equals(i.Identifier, StringComparison.OrdinalIgnoreCase)), Rand.RandSync.Server) is ItemPrefab ammoPrefab)
|
|
{
|
|
Item ammo = new Item(ammoPrefab, container.Item.WorldPosition, wreck);
|
|
if (!container.Inventory.TryPutItem(ammo, i, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false))
|
|
{
|
|
item.Remove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.brain = brain;
|
|
Wreck = wreck;
|
|
foreach (var item in Wreck.GetItems(false))
|
|
{
|
|
var turret = item.GetComponent<Turret>();
|
|
if (turret != null)
|
|
{
|
|
turrets.Add(turret);
|
|
}
|
|
if (item.HasTag("cellspawnorgan"))
|
|
{
|
|
if (!spawnOrgans.Contains(item))
|
|
{
|
|
spawnOrgans.Add(item);
|
|
}
|
|
}
|
|
}
|
|
wayPoints.AddRange(Wreck.GetWaypoints(false));
|
|
hulls.AddRange(Wreck.GetHulls(false));
|
|
IsAlive = true;
|
|
}
|
|
|
|
private readonly List<Item> destroyedOrgans = new List<Item>();
|
|
public void Update(float deltaTime)
|
|
{
|
|
if (!IsAlive || Wreck == null || Wreck.Removed)
|
|
{
|
|
cells.ForEach(c => c.OnDeath -= OnCellDeath);
|
|
return;
|
|
}
|
|
if (brain == null || brain.Removed || brain.Condition <= 0)
|
|
{
|
|
Kill();
|
|
}
|
|
destroyedOrgans.Clear();
|
|
foreach (var organ in spawnOrgans)
|
|
{
|
|
if (organ.Condition <= 0)
|
|
{
|
|
destroyedOrgans.Add(organ);
|
|
}
|
|
}
|
|
destroyedOrgans.ForEach(o => spawnOrgans.Remove(o));
|
|
|
|
bool someoneNearby = false;
|
|
float minDist = Sonar.DefaultSonarRange * 2.0f;
|
|
foreach (Submarine submarine in Submarine.Loaded)
|
|
{
|
|
if (submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
|
|
if (Vector2.DistanceSquared(submarine.WorldPosition, Wreck.WorldPosition) < minDist * minDist)
|
|
{
|
|
someoneNearby = true;
|
|
break;
|
|
}
|
|
}
|
|
foreach (Character c in Character.CharacterList)
|
|
{
|
|
if (c != Character.Controlled && !c.IsRemotePlayer) { continue; }
|
|
if (Vector2.DistanceSquared(c.WorldPosition, Wreck.WorldPosition) < minDist * minDist)
|
|
{
|
|
someoneNearby = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!someoneNearby) { return; }
|
|
|
|
OperateTurrets(deltaTime);
|
|
if (!IsClient)
|
|
{
|
|
if (!initialCellsSpawned) { SpawnInitialCells(); }
|
|
UpdateReinforcements(deltaTime);
|
|
}
|
|
}
|
|
|
|
private void SpawnInitialCells()
|
|
{
|
|
int brainRoomCells = Rand.Range(MinCellsPerBrainRoom, MaxCellsPerRoom);
|
|
if (brain.CurrentHull?.WaterPercentage >= MinWaterLevel)
|
|
{
|
|
for (int i = 0; i < brainRoomCells; i++)
|
|
{
|
|
if (!TrySpawnCell(out _, brain.CurrentHull)) { break; }
|
|
}
|
|
}
|
|
int cellsInside = Rand.Range(MinCellsInside, MaxCellsInside);
|
|
for (int i = 0; i < cellsInside; i++)
|
|
{
|
|
if (!TrySpawnCell(out _)) { break; }
|
|
}
|
|
int cellsOutside = Rand.Range(MinCellsOutside, MaxCellsOutside);
|
|
// If we failed to spawn some of the cells in the brainroom/inside, spawn some extra cells outside.
|
|
cellsOutside = Math.Clamp(cellsOutside + brainRoomCells + cellsInside - cells.Count, cellsOutside, MaxCellsOutside);
|
|
for (int i = 0; i < cellsOutside; i++)
|
|
{
|
|
ISpatialEntity targetEntity = wayPoints.GetRandom(wp => wp.CurrentHull == null);
|
|
if (targetEntity == null) { break; }
|
|
if (!TrySpawnCell(out _, targetEntity)) { break; }
|
|
}
|
|
initialCellsSpawned = true;
|
|
}
|
|
|
|
public void Kill()
|
|
{
|
|
if (!IsClient)
|
|
{
|
|
brain.Condition = 0;
|
|
}
|
|
IsAlive = false;
|
|
}
|
|
|
|
// The client doesn't use these, so we don't have to sync them.
|
|
private readonly List<Character> cells = new List<Character>();
|
|
// Intentionally contains duplicates.
|
|
private readonly List<Hull> populatedHulls = new List<Hull>();
|
|
private float cellSpawnTimer;
|
|
|
|
private float CellSpawnTime => Config.CellSpawnTime;
|
|
private float CellSpawnRandomFactor => Config.CellSpawnRandomFactor;
|
|
private int MinCellsPerBrainRoom => Config.MinCellsPerBrainRoom;
|
|
private int MaxCellsPerRoom => Config.MaxCellsPerRoom;
|
|
private int MinCellsOutside => Config.MinCellsOutside;
|
|
private int MaxCellsOutside => Config.MaxCellsOutside;
|
|
private int MinCellsInside => Config.MinCellsInside;
|
|
private int MaxCellsInside => Config.MaxCellsInside;
|
|
private int MaxCellCount => Config.MaxCellCount;
|
|
private float MinWaterLevel => Config.MinWaterLevel;
|
|
|
|
void UpdateReinforcements(float deltaTime)
|
|
{
|
|
if (cells.Count >= MaxCellCount) { return; }
|
|
cellSpawnTimer -= deltaTime;
|
|
if (cellSpawnTimer < 0)
|
|
{
|
|
TrySpawnCell(out _, spawnOrgans.GetRandom());
|
|
cellSpawnTimer = CellSpawnTime * Rand.Range(CellSpawnRandomFactor, 1 + CellSpawnRandomFactor);
|
|
}
|
|
}
|
|
|
|
bool TrySpawnCell(out Character cell, ISpatialEntity targetEntity = null)
|
|
{
|
|
cell = null;
|
|
if (cells.Count >= MaxCellCount) { return false; }
|
|
if (targetEntity == null)
|
|
{
|
|
targetEntity =
|
|
wayPoints.GetRandom(wp => wp.CurrentHull != null && populatedHulls.Count(h => h == wp.CurrentHull) < MaxCellsPerRoom && wp.CurrentHull.WaterPercentage >= MinWaterLevel) ??
|
|
hulls.GetRandom(h => populatedHulls.Count(h2 => h2 == h) < MaxCellsPerRoom && h.WaterPercentage >= MinWaterLevel) as ISpatialEntity;
|
|
}
|
|
if (targetEntity == null) { return false; }
|
|
if (targetEntity is Hull h)
|
|
{
|
|
populatedHulls.Add(h);
|
|
}
|
|
else if (targetEntity is WayPoint wp && wp.CurrentHull != null)
|
|
{
|
|
populatedHulls.Add(wp.CurrentHull);
|
|
}
|
|
// Don't add items in the list, because we want to be able to ignore the restrictions for spawner organs.
|
|
cell = Character.Create("Leucocyte", targetEntity.WorldPosition, ToolBox.RandomSeed(8), hasAi: true, createNetworkEvent: true);
|
|
cells.Add(cell);
|
|
cell.OnDeath += OnCellDeath;
|
|
cellSpawnTimer = CellSpawnTime * Rand.Range(CellSpawnRandomFactor, 1 + CellSpawnRandomFactor);
|
|
return true;
|
|
}
|
|
|
|
void OperateTurrets(float deltaTime)
|
|
{
|
|
foreach (var turret in turrets)
|
|
{
|
|
// Never target other creatures than humans with the turrets.
|
|
turret.ThalamusOperate(deltaTime,
|
|
!turret.Item.HasTag("ignorecharacters"),
|
|
targetOtherCreatures: false,
|
|
!turret.Item.HasTag("ignoresubmarines"),
|
|
turret.Item.HasTag("ignoreaimdelay"));
|
|
}
|
|
}
|
|
|
|
void OnCellDeath(Character character, CauseOfDeath causeOfDeath)
|
|
{
|
|
cells.Remove(character);
|
|
}
|
|
|
|
#if SERVER
|
|
public void ServerWrite(IWriteMessage msg, Client client, object[] extraData = null)
|
|
{
|
|
msg.Write(IsAlive);
|
|
}
|
|
#endif
|
|
#if CLIENT
|
|
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
|
{
|
|
IsAlive = msg.ReadBoolean();
|
|
}
|
|
#endif
|
|
}
|
|
} |