2 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
41 [
Serialize(
"",
IsPropertySaveable.Yes, description:
"The spawned entity will be assigned this tag. The tag can be used to refer to the entity by other actions of the event.")]
59 [
Serialize(
false,
IsPropertySaveable.Yes, description:
"Should we spawn the entity even when no spawn points with matching tags were found?")]
62 private readonly HashSet<Identifier> targetModuleTags =
new HashSet<Identifier>();
64 [
Serialize(
true,
IsPropertySaveable.Yes, description:
"If false, we won't spawn another character if one with the same identifier has already been spawned.")]
76 get =>
string.Join(
",", targetModuleTags);
79 targetModuleTags.Clear();
80 if (!
string.IsNullOrWhiteSpace(value))
82 string[] splitTags = value.Split(
',');
83 foreach (var s
in splitTags)
85 targetModuleTags.Add(s.ToIdentifier());
91 [
Serialize(
false,
IsPropertySaveable.Yes, description:
"Should the AI ignore this item. This will prevent outpost NPCs cleaning up or otherwise using important items intended to be left for the players.")]
94 [
Serialize(
true,
IsPropertySaveable.Yes, description:
"If disabled, the action will choose a spawn position away from players' views if one is available.")]
98 private Entity spawnedEntity;
100 private readonly
bool ignoreSpawnPointType;
104 ignoreSpawnPointType = element.
GetAttribute(
"spawnpointtype") ==
null;
106 TeamID = element.GetAttributeEnum(
"teamtag", element.GetAttributeEnum(
"team",
TeamID));
109 DebugConsole.ThrowError(
110 $
"Error in even \"{(parentEvent.Prefab?.Identifier.ToString() ?? "unknown
")}\". " +
111 $
"The attribute \"submarinetype\" is not valid in {nameof(SpawnAction)}. Did you mean {nameof(SpawnLocation)}?",
118 if (spawnedEntity !=
null)
131 spawnedEntity =
null;
134 public override void Update(
float deltaTime)
136 if (spawned) {
return; }
144 TryFindHumanPrefab(startLocation.Faction) ??
145 TryFindHumanPrefab(startLocation.SecondaryFaction);
149 if (faction ==
null) {
return null; }
156 NPCIdentifier.Replace(
"[faction]".ToIdentifier(),
"coalition".ToIdentifier()),
162 if (humanPrefab !=
null)
171 if (spawnPos !=
null)
173 for (
int i = 0; i <
Amount; i++)
177 if (newCharacter == null) { return; }
178 newCharacter.HumanPrefab = humanPrefab;
179 newCharacter.TeamID =
TeamID;
180 newCharacter.EnableDespawn =
false;
181 humanPrefab.
GiveItems(newCharacter, newCharacter.Submarine, spawnPos as
WayPoint);
184 foreach (
Item item
in newCharacter.Inventory.FindAllItems(recursive:
true))
186 item.SpawnedInCurrentOutpost = true;
187 item.AllowStealing = false;
191 if (!
TargetTag.IsEmpty && newCharacter !=
null)
193 ParentEvent.AddTarget(TargetTag, newCharacter);
195 spawnedEntity = newCharacter;
198 outPostInfo.AddOutpostNPCIdentifierOrTag(newCharacter, humanPrefab.Identifier);
199 foreach (Identifier tag in humanPrefab.GetTags())
201 outPostInfo.AddOutpostNPCIdentifierOrTag(newCharacter, tag);
205 newCharacter.LoadTalents();
213 else if (!SpeciesName.IsEmpty)
215 if (!AllowDuplicates && Character.CharacterList.Any(c => c.SpeciesName == SpeciesName))
220 ISpatialEntity spawnPos = GetSpawnPos();
221 if (spawnPos !=
null)
223 for (
int i = 0; i < Amount; i++)
225 Entity.Spawner.AddCharacterToSpawnQueue(SpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, Rand.Range(0.0f, Offset)), onSpawn: newCharacter =>
227 if (!TargetTag.IsEmpty && newCharacter != null)
229 ParentEvent.AddTarget(TargetTag, newCharacter);
231 spawnedEntity = newCharacter;
236 else if (!ItemIdentifier.IsEmpty)
238 if (MapEntityPrefab.FindByIdentifier(ItemIdentifier) is not ItemPrefab itemPrefab)
240 DebugConsole.ThrowError(
"Error in SpawnAction (item prefab \"" + ItemIdentifier +
"\" not found)",
241 contentPackage: ParentEvent.Prefab.ContentPackage);
245 Inventory spawnInventory =
null;
246 if (!TargetInventory.IsEmpty)
248 var targets = ParentEvent.GetTargets(TargetInventory);
251 var target = targets.First(t => t is Item || t is Character);
252 if (target is Character character)
254 spawnInventory = character.Inventory;
256 else if (target is Item item)
258 spawnInventory = item.OwnInventory;
262 if (spawnInventory ==
null)
264 DebugConsole.ThrowError($
"Could not spawn \"{ItemIdentifier}\" in target inventory \"{TargetInventory}\" - matching target not found.",
265 contentPackage: ParentEvent.Prefab.ContentPackage);
269 if (spawnInventory ==
null)
271 ISpatialEntity spawnPos = GetSpawnPos();
272 if (spawnPos !=
null)
274 for (
int i = 0; i < Amount; i++)
276 Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(spawnPos.WorldPosition, Rand.Range(0.0f, Offset)), onSpawned: onSpawned);
282 for (
int i = 0; i < Amount; i++)
284 Entity.Spawner.AddItemToSpawnQueue(itemPrefab, spawnInventory, onSpawned: onSpawned);
288 void onSpawned(Item newItem)
292 if (!TargetTag.IsEmpty)
294 ParentEvent.AddTarget(TargetTag, newItem);
298 newItem.AddTag(
"ignorebyai");
301 spawnedEntity = newItem;
312 pos += Rand.Vector(offset);
315 float margin = 50.0f;
325 if (!SpawnPointTag.IsEmpty)
327 IEnumerable<Item> potentialItems =
Item.
ItemList.Where(it => IsValidSubmarineType(SpawnLocation, it.Submarine));
328 if (!AllowInPlayerView)
330 potentialItems = GetEntitiesNotInPlayerView(potentialItems);
332 var item = potentialItems.Where(it => it.HasTag(SpawnPointTag)).GetRandomUnsynced();
333 if (item !=
null) {
return item; }
335 var potentialTargets = ParentEvent.GetTargets(SpawnPointTag).Where(t => IsValidSubmarineType(SpawnLocation, t.Submarine));
336 if (!AllowInPlayerView)
338 potentialTargets = GetEntitiesNotInPlayerView(potentialTargets);
340 var target = potentialTargets.GetRandomUnsynced();
341 if (target !=
null) {
return target; }
345 if (!ignoreSpawnPointType) { spawnPointType = SpawnPointType; }
347 return GetSpawnPos(SpawnLocation, spawnPointType, targetModuleTags, SpawnPointTag.ToEnumerable(), requireTaggedSpawnPoint: RequireSpawnPointTag, allowInPlayerView: AllowInPlayerView);
350 private static bool IsValidSubmarineType(SpawnLocationType spawnLocation, Submarine submarine)
352 return spawnLocation
switch
354 SpawnLocationType.Any =>
true,
355 SpawnLocationType.MainSub => submarine ==
Submarine.MainSub,
356 SpawnLocationType.NearMainSub => submarine ==
null,
357 SpawnLocationType.MainPath => submarine ==
null,
358 SpawnLocationType.Outpost => submarine is { Info.IsOutpost:
true },
359 SpawnLocationType.Wreck => submarine is { Info.IsWreck:
true },
360 SpawnLocationType.Ruin => submarine is { Info.IsRuin:
true },
361 SpawnLocationType.BeaconStation => submarine?.Info?.BeaconStationInfo !=
null,
362 _ =>
throw new NotImplementedException(),
369 private static IEnumerable<T> GetEntitiesNotInPlayerView<T>(IEnumerable<T> entities) where T : ISpatialEntity
371 if (entities.Any(e => !IsInPlayerView(e)))
373 return entities.Where(e => !IsInPlayerView(e));
378 private static bool IsInPlayerView(ISpatialEntity entity)
380 foreach (var character
in Character.CharacterList)
382 if (!character.IsPlayer || character.IsDead) {
continue; }
383 if (character.CanSeeTarget(entity)) {
return true; }
388 public static WayPoint GetSpawnPos(
SpawnLocationType spawnLocation,
SpawnType? spawnPointType, IEnumerable<Identifier> moduleFlags =
null, IEnumerable<Identifier> spawnpointTags =
null,
bool asFarAsPossibleFromAirlock =
false,
bool requireTaggedSpawnPoint =
false,
bool allowInPlayerView =
true)
391 List<WayPoint> potentialSpawnPoints =
WayPoint.
WayPointList.FindAll(wp => IsValidSubmarineType(spawnLocation, wp.Submarine) && (wp.CurrentHull !=
null || !requireHull));
392 potentialSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.ConnectedDoor ==
null && wp.Ladders ==
null && wp.IsTraversable);
393 if (moduleFlags !=
null && moduleFlags.Any())
395 var spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull is
Hull h && h.
OutpostModuleTags.Any(moduleFlags.Contains));
396 if (spawnPoints.Any())
398 potentialSpawnPoints = spawnPoints.ToList();
401 if (spawnpointTags !=
null && spawnpointTags.Any())
403 var spawnPoints = potentialSpawnPoints.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag) && wp.ConnectedDoor ==
null && wp.IsTraversable));
404 if (requireTaggedSpawnPoint || spawnPoints.Any())
406 potentialSpawnPoints = spawnPoints.ToList();
409 if (potentialSpawnPoints.None())
411 if (requireTaggedSpawnPoint && spawnpointTags !=
null && spawnpointTags.Any())
413 DebugConsole.NewMessage($
"Could not find a spawn point for a SpawnAction (spawn location: {spawnLocation} (tag: {string.Join(",
", spawnpointTags)}), skipping.", color: Color.White);
417 DebugConsole.ThrowError($
"Could not find a spawn point for a SpawnAction (spawn location: {spawnLocation})");
422 IEnumerable<WayPoint> validSpawnPoints;
423 if (spawnPointType.HasValue)
425 validSpawnPoints = potentialSpawnPoints.FindAll(wp => spawnPointType.Value.HasFlag(wp.SpawnType));
429 validSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.SpawnType !=
SpawnType.Path);
430 if (!validSpawnPoints.Any()) { validSpawnPoints = potentialSpawnPoints; }
434 var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags.Contains(
"airlock".ToIdentifier()) ??
false);
435 if (airlockSpawnPoints.Count() < validSpawnPoints.Count())
437 validSpawnPoints = validSpawnPoints.Except(airlockSpawnPoints);
440 if (validSpawnPoints.None())
442 DebugConsole.ThrowError($
"Could not find a spawn point of the correct type for a SpawnAction (spawn location: {spawnLocation}, type: {spawnPointType}, module flags: {((moduleFlags == null || !moduleFlags.Any()) ? "none
" : string.Join(",
", moduleFlags))})");
443 return potentialSpawnPoints.GetRandomUnsynced();
448 validSpawnPoints = validSpawnPoints.Where(p =>
449 Submarine.
Loaded.None(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(p.WorldPosition)));
453 if (validSpawnPoints.Any(wp => wp.SpawnType !=
SpawnType.Path))
455 validSpawnPoints = validSpawnPoints.Where(wp => wp.SpawnType !=
SpawnType.Path);
459 if (spawnpointTags ==
null || spawnpointTags.None())
461 var spawnPoints = validSpawnPoints.Where(wp => !wp.Tags.Any());
462 if (spawnPoints.Any())
464 validSpawnPoints = spawnPoints.ToList();
468 if (!allowInPlayerView)
470 validSpawnPoints = GetEntitiesNotInPlayerView(validSpawnPoints);
475 WayPoint closestPoint = validSpawnPoints.First();
476 float closestDist =
float.PositiveInfinity;
477 foreach (
WayPoint wp
in validSpawnPoints)
480 if (dist < closestDist)
488 else if (asFarAsPossibleFromAirlock && airlockSpawnPoints.Any())
490 WayPoint furthestPoint = validSpawnPoints.First();
491 float furthestDist = 0.0f;
492 foreach (
WayPoint waypoint
in validSpawnPoints)
494 float dist = Vector2.DistanceSquared(waypoint.
WorldPosition, airlockSpawnPoints.First().WorldPosition);
495 if (dist > furthestDist)
498 furthestPoint = waypoint;
501 return furthestPoint;
505 return validSpawnPoints.GetRandomUnsynced();
511 return $
"{ToolBox.GetDebugSymbol(spawned)} {nameof(SpawnAction)} -> (Spawned entity: {spawnedEntity.ColorizeObject()})";
static readonly List< Character > CharacterList
static readonly Identifier HumanSpeciesName
XAttribute? GetAttribute(string name)
static EntitySpawner Spawner
virtual Vector2 WorldPosition
void AddCharacterToSpawnQueue(Identifier speciesName, Vector2 worldPosition, Action< Character > onSpawn=null)
readonly ScriptedEvent ParentEvent
static NetworkMember NetworkMember
IEnumerable< Identifier > OutpostModuleTags
Inherited flags from outpost generation.
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
CharacterInfo CreateCharacterInfo(Rand.RandSync randSync=Rand.RandSync.Unsynced)
Creates a character info from the human prefab. If there are custom character infos defined,...
void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn=null)
bool GiveItems(Character character, Submarine submarine, WayPoint spawnPoint, Rand.RandSync randSync=Rand.RandSync.Unsynced, bool createNetworkEvents=true)
static readonly List< Item > ItemList
ContentPackage? ContentPackage
Spawns an entity (e.g. item, NPC, monster).
static Vector2 OffsetSpawnPos(Vector2 pos, float offset)
bool RequireSpawnPointTag
override bool IsFinished(ref string goTo)
Has the action finished.
override string ToDebugString()
Rich test to display in debugdraw
Identifier ItemIdentifier
Identifier NPCSetIdentifier
SpawnAction(ScriptedEvent parentEvent, ContentXElement element)
static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable< Identifier > moduleFlags=null, IEnumerable< Identifier > spawnpointTags=null, bool asFarAsPossibleFromAirlock=false, bool requireTaggedSpawnPoint=false, bool allowInPlayerView=true)
override void Update(float deltaTime)
Identifier TargetInventory
SpawnLocationType SpawnLocation
override Vector2? WorldPosition
static List< Submarine > Loaded
static List< WayPoint > WayPointList