267 lines
12 KiBLFS
C#
Executable File
267 lines
12 KiBLFS
C#
Executable File
using Barotrauma;
|
|
using Barotrauma.Items.Components;
|
|
using Barotrauma.MoreLevelContent.Shared.Utils;
|
|
using Barotrauma.Networking;
|
|
using FarseerPhysics.Dynamics;
|
|
using Microsoft.Xna.Framework;
|
|
using MoreLevelContent.Shared;
|
|
using MoreLevelContent.Shared.Data;
|
|
using MoreLevelContent.Shared.Utils;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
|
|
namespace MoreLevelContent.Missions
|
|
{
|
|
// Shared
|
|
partial class MissionNPCCollection
|
|
{
|
|
internal readonly List<Character> characters = new();
|
|
internal readonly Dictionary<Character, List<Item>> characterItems = new();
|
|
|
|
internal bool AllHumansAlive => characters.All(c => IsAlive(c));
|
|
internal bool AnyHumanAlive => characters.Any(c => IsAlive(c));
|
|
internal bool AnyHumanSurvived => characters.Any(c => Survived(c));
|
|
internal int AliveHumans => characters.Where(c => IsAlive(c)).Count();
|
|
internal delegate void OnCharacterCreated(Character character, XElement missionCharacterConfig);
|
|
internal Character this[int index] => characters[index];
|
|
|
|
internal MissionNPCCollection(Mission mission, XElement characterConfig)
|
|
{
|
|
this.mission = mission;
|
|
this.characterConfig = characterConfig;
|
|
}
|
|
private readonly Mission mission;
|
|
private readonly XElement characterConfig;
|
|
|
|
|
|
public void Clear()
|
|
{
|
|
characters.Clear();
|
|
characterItems.Clear();
|
|
}
|
|
|
|
public void End(bool completed)
|
|
{
|
|
foreach (Character character in characters)
|
|
{
|
|
if (character.Inventory == null) { continue; }
|
|
foreach (Item item in character.Inventory.AllItemsMod)
|
|
{
|
|
//item didn't spawn with the characters -> drop it
|
|
if (!characterItems.Any(c => c.Value.Contains(item)))
|
|
{
|
|
item.Drop(character);
|
|
}
|
|
}
|
|
}
|
|
|
|
// characters that survived will take their items with them, in case players tried to be crafty and steal them
|
|
// this needs to run here in case players abort the mission by going back home
|
|
foreach (var characterItem in characterItems)
|
|
{
|
|
if (Survived(characterItem.Key) || !completed)
|
|
{
|
|
foreach (Item item in characterItem.Value)
|
|
{
|
|
if (!item.Removed)
|
|
{
|
|
item.Remove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
internal void CreateHumansInSubmarine(Submarine submarine, CharacterTeamType team = CharacterTeamType.FriendlyNPC, OnCharacterCreated onCharacterCreated = null)
|
|
{
|
|
if (characterConfig == null)
|
|
{
|
|
Log.Warn("No characters");
|
|
return;
|
|
}
|
|
WayPoint explicitStayInHullPos = WayPoint.GetRandom(SpawnType.Human, null, submarine);
|
|
Rand.RandSync randSync = Rand.RandSync.Unsynced;
|
|
List<(HumanPrefab prefab, XElement config)> humanPrefabsToSpawn = new List<(HumanPrefab, XElement)>();
|
|
foreach (XElement element in characterConfig.Elements())
|
|
{
|
|
var humanPrefab = GetHumanPrefabFromElement(element);
|
|
humanPrefabsToSpawn.Add((humanPrefab, element));
|
|
}
|
|
foreach (var (prefab, config) in humanPrefabsToSpawn)
|
|
{
|
|
var humanPrefab = prefab;
|
|
XElement characterSpecificConfig = config;
|
|
if (humanPrefab == null || humanPrefab.Job.IsEmpty || humanPrefab.Job == "any") { continue; }
|
|
var jobPrefab = humanPrefab.GetJobPrefab(randSync);
|
|
var stayPos = explicitStayInHullPos;
|
|
if (jobPrefab != null)
|
|
{
|
|
stayPos = WayPoint.GetRandom(SpawnType.Human, jobPrefab, submarine) ?? explicitStayInHullPos;
|
|
}
|
|
XElement additionalItemsElement = config?.GetChildElement("additionalitems");
|
|
ContentXElement additionalItems = new ContentXElement(null, additionalItemsElement);
|
|
|
|
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, submarine, team, stayPos, additionalItems: additionalItemsElement != null ? additionalItems.Elements() : null);
|
|
spawnedCharacter.EnableDespawn = false; // don't let mission npcs despawn
|
|
spawnedCharacter.GiveIdCardTags(stayPos, false);
|
|
onCharacterCreated?.Invoke(spawnedCharacter, characterSpecificConfig);
|
|
spawnedCharacter.MLC().NPCElement = characterSpecificConfig;
|
|
#if CLIENT
|
|
if (GameMain.IsSingleplayer)
|
|
{
|
|
if (characterSpecificConfig.GetAttributeBool("allowordering", false))
|
|
{
|
|
_ = GameMain.GameSession.CrewManager.AddCharacterToCrewList(spawnedCharacter);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
Log.Debug("end");
|
|
InitCharacters();
|
|
}
|
|
|
|
internal Character CreateHuman(HumanPrefab humanPrefab, List<Character> characters, Dictionary<Character, List<Item>> characterItems, Submarine submarine, CharacterTeamType teamType, ISpatialEntity positionToStayIn = null, bool giveTags = true, IEnumerable<ContentXElement> additionalItems = null)
|
|
{
|
|
var characterInfo = humanPrefab.CreateCharacterInfo(Rand.RandSync.Unsynced);
|
|
characterInfo.TeamID = teamType;
|
|
|
|
if (positionToStayIn == null)
|
|
{
|
|
positionToStayIn =
|
|
WayPoint.GetRandom(SpawnType.Human, characterInfo.Job?.Prefab, submarine) ??
|
|
WayPoint.GetRandom(SpawnType.Human, null, submarine);
|
|
}
|
|
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
|
|
spawnedCharacter.HumanPrefab = humanPrefab;
|
|
humanPrefab.InitializeCharacter(spawnedCharacter, positionToStayIn);
|
|
_ = humanPrefab.GiveItems(spawnedCharacter, submarine, null, createNetworkEvents: false);
|
|
foreach (var item in spawnedCharacter.Inventory.AllItems)
|
|
{
|
|
IdCard card = item.GetComponent<IdCard>();
|
|
if (card == null) continue;
|
|
card.SubmarineSpecificID = submarine.SubmarineSpecificIDTag;
|
|
}
|
|
|
|
if (additionalItems != null)
|
|
{
|
|
foreach (var additionalItem in additionalItems)
|
|
{
|
|
int amount = additionalItem.GetAttributeInt("amount", 1);
|
|
for (int i = 0; i < amount; i++)
|
|
{
|
|
HumanPrefab.InitializeItem(spawnedCharacter, additionalItem, submarine, humanPrefab, createNetworkEvents: false);
|
|
}
|
|
}
|
|
}
|
|
characters.Add(spawnedCharacter);
|
|
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
|
|
|
|
return spawnedCharacter;
|
|
}
|
|
|
|
internal void GiveCharacterItem(Character character, ContentXElement itemElement, bool createNetworkEvents = true)
|
|
{
|
|
HumanPrefab.InitializeItem(character, itemElement, null, character.HumanPrefab, createNetworkEvents: createNetworkEvents);
|
|
characterItems[character] = character.Inventory.FindAllItems(recursive: true);
|
|
}
|
|
|
|
internal void CreateHumansAtPosition(CharacterTeamType team, Vector2 position, OnCharacterCreated onCharacterCreated)
|
|
{
|
|
List<(HumanPrefab, XElement)> humanPrefabsToSpawn = new List<(HumanPrefab, XElement)>();
|
|
foreach (XElement element in characterConfig?.Elements())
|
|
{
|
|
var humanPrefab = GetHumanPrefabFromElement(element);
|
|
humanPrefabsToSpawn.Add((humanPrefab, element));
|
|
}
|
|
|
|
foreach (var prefabToSpawn in humanPrefabsToSpawn)
|
|
{
|
|
var humanPrefab = prefabToSpawn.Item1;
|
|
XElement characterMissionConfig = prefabToSpawn.Item2;
|
|
Character character = CreateHumanAtPosition(humanPrefab, team, position);
|
|
character.EnableDespawn = false; // don't let mission npcs despawn
|
|
onCharacterCreated.Invoke(character, characterMissionConfig);
|
|
character.MLC().NPCElement = characterMissionConfig;
|
|
}
|
|
InitCharacters();
|
|
}
|
|
|
|
internal Character CreateHumanAtPosition(HumanPrefab humanPrefab, CharacterTeamType team, Vector2 spawnPosition)
|
|
{
|
|
Character character = CharacterUtils.CreateHuman(humanPrefab, characters, characterItems, team, spawnPosition, false);
|
|
return character;
|
|
}
|
|
|
|
private HumanPrefab GetHumanPrefabFromElement(XElement element)
|
|
{
|
|
if (element.Attribute("name") != null)
|
|
{
|
|
DebugConsole.ThrowError("Error in mission \"" + mission.Prefab.Identifier + "\" - use character identifiers instead of names to configure the characters.");
|
|
|
|
return null;
|
|
}
|
|
|
|
Identifier characterIdentifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
|
|
Identifier characterFrom = element.GetAttributeIdentifier("from", Identifier.Empty);
|
|
HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
|
|
if (humanPrefab == null)
|
|
{
|
|
DebugConsole.ThrowError("Couldn't spawn character for mission: character prefab \"" + characterIdentifier + "\" not found");
|
|
return null;
|
|
}
|
|
|
|
return humanPrefab;
|
|
}
|
|
|
|
private void InitCharacters()
|
|
{
|
|
Log.Debug("characterCount");
|
|
int i = 0;
|
|
foreach (XElement element in characterConfig.Elements())
|
|
{
|
|
characters[i].IsEscorted = false;
|
|
Color col = element.GetAttributeColor("color", Color.LightGreen);
|
|
characters[i].UniqueNameColor = col;
|
|
Log.Debug($"Set color to {col} for character {characters[i].Name}");
|
|
i++;
|
|
}
|
|
}
|
|
|
|
internal static bool IsAlive(Character character) => character != null && !character.Removed && !character.IsDead;
|
|
|
|
internal static bool IsCaptured(Character character) => character.LockHands;
|
|
|
|
internal static bool Survived(Character character)
|
|
{
|
|
return IsAlive(character) && character.CurrentHull?.Submarine != null &&
|
|
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine));
|
|
}
|
|
|
|
internal static bool Close(Character target, Character ai, float closeDistance)
|
|
{
|
|
float dist = Vector2.DistanceSquared(ai.WorldPosition, target.WorldPosition);
|
|
return dist < closeDistance * closeDistance;
|
|
}
|
|
|
|
internal static Character GetClosest(Character ai)
|
|
{
|
|
float closest = float.MaxValue;
|
|
Character target = null;
|
|
foreach (Character player in Character.CharacterList.Where(c => c.IsPlayer))
|
|
{
|
|
if (player.IsDead) continue; // skip dead players
|
|
float dist = Vector2.DistanceSquared(player.WorldPosition, ai.WorldPosition);
|
|
if (dist < closest)
|
|
{
|
|
closest = dist;
|
|
target = player;
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
}
|
|
}
|