Exemple #1
0
        private IEnumerable <object> CreateAIHusk()
        {
            character.Enabled = false;
            Entity.Spawner.AddToRemoveQueue(character);

            string          huskedSpeciesName = GetHuskedSpeciesName(character.SpeciesName, Prefab as AfflictionPrefabHusk);
            CharacterPrefab prefab            = CharacterPrefab.FindBySpeciesName(huskedSpeciesName);

            if (prefab == null)
            {
                DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found.");
                yield return(CoroutineStatus.Success);
            }
            var husk = Character.Create(huskedSpeciesName, character.WorldPosition, ToolBox.RandomSeed(8), character.Info, isRemotePlayer: false, hasAi: true);

            foreach (Limb limb in husk.AnimController.Limbs)
            {
                if (limb.type == LimbType.None)
                {
                    limb.body.SetTransform(character.SimPosition, 0.0f);
                    continue;
                }

                var matchingLimb = character.AnimController.GetLimb(limb.type);
                if (matchingLimb?.body != null)
                {
                    limb.body.SetTransform(matchingLimb.SimPosition, matchingLimb.Rotation);
                    limb.body.LinearVelocity  = matchingLimb.LinearVelocity;
                    limb.body.AngularVelocity = matchingLimb.body.AngularVelocity;
                }
            }

            if (character.Inventory != null && husk.Inventory != null)
            {
                if (character.Inventory.Items.Length != husk.Inventory.Items.Length)
                {
                    string errorMsg = "Failed to move items from the source character's inventory into a husk's inventory (inventory sizes don't match)";
                    DebugConsole.ThrowError(errorMsg);
                    GameAnalyticsManager.AddErrorEventOnce("AfflictionHusk.CreateAIHusk:InventoryMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
                    yield return(CoroutineStatus.Success);
                }
                for (int i = 0; i < character.Inventory.Items.Length && i < husk.Inventory.Items.Length; i++)
                {
                    if (character.Inventory.Items[i] == null)
                    {
                        continue;
                    }
                    husk.Inventory.TryPutItem(character.Inventory.Items[i], i, true, false, null);
                }
            }

            husk.SetStun(5);
            yield return(new WaitForSeconds(5, false));

#if CLIENT
            husk.PlaySound(CharacterSound.SoundType.Idle);
#endif
            yield return(CoroutineStatus.Success);
        }
Exemple #2
0
        private void InitCharacters(Submarine submarine)
        {
            characters.Clear();
            characterItems.Clear();

            if (characterConfig == null)
            {
                return;
            }

            foreach (XElement element in characterConfig.Elements())
            {
                if (GameMain.NetworkMember == null && element.GetAttributeBool("multiplayeronly", false))
                {
                    continue;
                }

                int defaultCount = element.GetAttributeInt("count", -1);
                if (defaultCount < 0)
                {
                    defaultCount = element.GetAttributeInt("amount", 1);
                }
                int min   = Math.Min(element.GetAttributeInt("min", defaultCount), 255);
                int max   = Math.Min(Math.Max(min, element.GetAttributeInt("max", defaultCount)), 255);
                int count = Rand.Range(min, max + 1);

                if (element.Attribute("identifier") != null && element.Attribute("from") != null)
                {
                    string      characterIdentifier = element.GetAttributeString("identifier", "");
                    string      characterFrom       = element.GetAttributeString("from", "");
                    HumanPrefab humanPrefab         = NPCSet.Get(characterFrom, characterIdentifier);
                    if (humanPrefab == null)
                    {
                        DebugConsole.ThrowError("Couldn't spawn a character for abandoned outpost mission: character prefab \"" + characterIdentifier + "\" not found");
                        continue;
                    }
                    for (int i = 0; i < count; i++)
                    {
                        LoadHuman(humanPrefab, element, submarine);
                    }
                }
                else
                {
                    string speciesName     = element.GetAttributeString("character", element.GetAttributeString("identifier", ""));
                    var    characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
                    if (characterPrefab == null)
                    {
                        DebugConsole.ThrowError("Couldn't spawn a character for abandoned outpost mission: character prefab \"" + speciesName + "\" not found");
                        continue;
                    }
                    for (int i = 0; i < count; i++)
                    {
                        LoadMonster(characterPrefab, element, submarine);
                    }
                }
            }
        }
Exemple #3
0
            public static Character Spawn(string name, Vector2 worldPos)
            {
                Character spawnedCharacter = null;
                Vector2   spawnPosition    = worldPos;

                string    characterLowerCase = name.ToLowerInvariant();
                JobPrefab job = null;

                if (!JobPrefab.Prefabs.ContainsKey(characterLowerCase))
                {
                    job = JobPrefab.Prefabs.Find(jp => jp.Name != null && jp.Name.Equals(characterLowerCase, StringComparison.OrdinalIgnoreCase));
                }
                else
                {
                    job = JobPrefab.Prefabs[characterLowerCase];
                }
                bool human = job != null || characterLowerCase == CharacterPrefab.HumanSpeciesName;


                if (string.IsNullOrWhiteSpace(name))
                {
                    return(null);
                }

                if (human)
                {
                    var variant = job != null?Rand.Range(0, job.Variants, Rand.RandSync.Server) : 0;

                    CharacterInfo characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: job, variant: variant);
                    spawnedCharacter = Character.Create(characterInfo, spawnPosition, ToolBox.RandomSeed(8));
                    if (GameMain.GameSession != null)
                    {
                        //TODO: a way to select which team to spawn to?
                        spawnedCharacter.TeamID = Character.Controlled != null ? Character.Controlled.TeamID : CharacterTeamType.Team1;
#if CLIENT
                        GameMain.GameSession.CrewManager.AddCharacter(spawnedCharacter);
#endif
                    }
                    spawnedCharacter.GiveJobItems(null);
                    spawnedCharacter.Info.StartItemsGiven = true;
                }
                else
                {
                    if (CharacterPrefab.FindBySpeciesName(name) != null)
                    {
                        spawnedCharacter = Character.Create(name, spawnPosition, ToolBox.RandomSeed(8));
                    }
                }

                return(spawnedCharacter);
            }
Exemple #4
0
        public override IEnumerable <ContentFile> GetFilesToPreload()
        {
            string path = CharacterPrefab.FindBySpeciesName(speciesName)?.FilePath;

            if (string.IsNullOrWhiteSpace(path))
            {
                DebugConsole.ThrowError($"Failed to find config file for species \"{speciesName}\"");
                yield break;
            }
            else
            {
                yield return(new ContentFile(path, ContentType.Character));
            }
        }
Exemple #5
0
        public MonsterMission(MissionPrefab prefab, Location[] locations)
            : base(prefab, locations)
        {
            string speciesName = prefab.ConfigElement.GetAttributeString("monsterfile", null);

            if (!string.IsNullOrEmpty(speciesName))
            {
                var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
                if (characterPrefab != null)
                {
                    int monsterCount = Math.Min(prefab.ConfigElement.GetAttributeInt("monstercount", 1), 255);
                    monsterPrefabs.Add(new Tuple <CharacterPrefab, Point>(characterPrefab, new Point(monsterCount)));
                }
                else
                {
                    DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
                }
            }

            maxSonarMarkerDistance = prefab.ConfigElement.GetAttributeFloat("maxsonarmarkerdistance", 10000.0f);

            foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
            {
                speciesName = monsterElement.GetAttributeString("character", string.Empty);
                int defaultCount = monsterElement.GetAttributeInt("count", -1);
                if (defaultCount < 0)
                {
                    defaultCount = monsterElement.GetAttributeInt("amount", 1);
                }
                int min             = Math.Min(monsterElement.GetAttributeInt("min", defaultCount), 255);
                int max             = Math.Min(Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount)), 255);
                var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
                if (characterPrefab != null)
                {
                    monsterPrefabs.Add(new Tuple <CharacterPrefab, Point>(characterPrefab, new Point(min, max)));
                }
                else
                {
                    DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
                }
            }

            if (monsterPrefabs.Any())
            {
                var characterParams = new CharacterParams(monsterPrefabs.First().Item1.FilePath);
                description = description.Replace("[monster]",
                                                  TextManager.Get("character." + characterParams.SpeciesTranslationOverride, returnNull: true) ??
                                                  TextManager.Get("character." + characterParams.SpeciesName));
            }
        }
Exemple #6
0
        public NestMission(MissionPrefab prefab, Location[] locations, Submarine sub)
            : base(prefab, locations, sub)
        {
            itemConfig = prefab.ConfigElement.Element("Items");

            itemSpawnRadius     = prefab.ConfigElement.GetAttributeFloat("itemspawnradius", 800.0f);
            approachItemsRadius = prefab.ConfigElement.GetAttributeFloat("approachitemsradius", itemSpawnRadius * 2.0f);
            monsterSpawnRadius  = prefab.ConfigElement.GetAttributeFloat("monsterspawnradius", approachItemsRadius * 2.0f);

            nestObjectRadius = prefab.ConfigElement.GetAttributeFloat("nestobjectradius", itemSpawnRadius * 2.0f);
            nestObjectAmount = prefab.ConfigElement.GetAttributeInt("nestobjectamount", 10);

            requireDelivery = prefab.ConfigElement.GetAttributeBool("requiredelivery", false);

            string spawnPositionTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");

            if (string.IsNullOrWhiteSpace(spawnPositionTypeStr) ||
                !Enum.TryParse(spawnPositionTypeStr, true, out spawnPositionType))
            {
                spawnPositionType = Level.PositionType.Cave | Level.PositionType.Ruin;
            }

            foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
            {
                string speciesName  = monsterElement.GetAttributeString("character", string.Empty);
                int    defaultCount = monsterElement.GetAttributeInt("count", -1);
                if (defaultCount < 0)
                {
                    defaultCount = monsterElement.GetAttributeInt("amount", 1);
                }
                int min             = Math.Min(monsterElement.GetAttributeInt("min", defaultCount), 255);
                int max             = Math.Min(Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount)), 255);
                var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
                if (characterPrefab != null)
                {
                    monsterPrefabs.Add(new Tuple <CharacterPrefab, Point>(characterPrefab, new Point(min, max)));
                }
                else
                {
                    DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
                }
            }
        }
        public bool Load()
        {
            bool success = base.Load(File);

            if (doc.Root.IsCharacterVariant())
            {
                VariantFile = doc;
                var original = CharacterPrefab.FindBySpeciesName(doc.Root.GetAttributeString("inherit", string.Empty));
                success = Load(original.FilePath);
                CreateSubParams();
                TryLoadOverride(this, VariantFile.Root, SerializableProperties);
                foreach (XElement subElement in VariantFile.Root.Elements())
                {
                    var matchingParams = SubParams.FirstOrDefault(p => p.Name.Equals(subElement.Name.ToString(), StringComparison.OrdinalIgnoreCase));
                    if (matchingParams != null)
                    {
                        TryLoadOverride(matchingParams, subElement, matchingParams.SerializableProperties);
                        // TODO: Make recursive? In practice we don't have to go deeper than this, but the implementation would be a lot cleaner with recursion.
                        foreach (XElement subSubElement in subElement.Elements())
                        {
                            if (subSubElement.Name.ToString().Equals("item", StringComparison.OrdinalIgnoreCase))
                            {
                                continue;
                            }
                            var matchingSubParams = matchingParams.SubParams.FirstOrDefault(p => p.Name.Equals(subSubElement.Name.ToString(), StringComparison.OrdinalIgnoreCase));
                            if (matchingSubParams != null)
                            {
                                TryLoadOverride(matchingSubParams, subSubElement, matchingSubParams.SerializableProperties);
                            }
                        }
                    }
                }
                return(success);
            }
            if (string.IsNullOrEmpty(SpeciesName) && MainElement != null)
            {
                //backwards compatibility
                SpeciesName = MainElement.GetAttributeString("name", "");
            }
            CreateSubParams();
            return(success);
        }
Exemple #8
0
        public void PreloadContent(IEnumerable <ContentFile> contentFiles)
        {
            var filesToPreload = new List <ContentFile>(contentFiles);

            foreach (Submarine sub in Submarine.Loaded)
            {
                if (sub.WreckAI == null)
                {
                    continue;
                }

                if (!string.IsNullOrEmpty(sub.WreckAI.Config.DefensiveAgent))
                {
                    var prefab = CharacterPrefab.FindBySpeciesName(sub.WreckAI.Config.DefensiveAgent);
                    if (prefab != null && !filesToPreload.Any(f => f.Path == prefab.FilePath))
                    {
                        filesToPreload.Add(new ContentFile(prefab.FilePath, ContentType.Character));
                    }
                }
                foreach (Item item in Item.ItemList)
                {
                    if (item.Submarine != sub)
                    {
                        continue;
                    }
                    foreach (Items.Components.ItemComponent component in item.Components)
                    {
                        if (component.statusEffectLists == null)
                        {
                            continue;
                        }
                        foreach (var statusEffectList in component.statusEffectLists.Values)
                        {
                            foreach (StatusEffect statusEffect in statusEffectList)
                            {
                                foreach (var spawnInfo in statusEffect.SpawnCharacters)
                                {
                                    var prefab = CharacterPrefab.FindBySpeciesName(spawnInfo.SpeciesName);
                                    if (prefab != null && !filesToPreload.Any(f => f.Path == prefab.FilePath))
                                    {
                                        filesToPreload.Add(new ContentFile(prefab.FilePath, ContentType.Character));
                                    }
                                }
                            }
                        }
                    }
                }
            }

            foreach (ContentFile file in filesToPreload)
            {
                switch (file.Type)
                {
                case ContentType.Character:
#if CLIENT
                    CharacterPrefab characterPrefab = CharacterPrefab.FindByFilePath(file.Path);
                    if (characterPrefab?.XDocument == null)
                    {
                        throw new Exception($"Failed to load the character config file from {file.Path}!");
                    }
                    var doc         = characterPrefab.XDocument;
                    var rootElement = doc.Root;
                    var mainElement = rootElement.IsOverride() ? rootElement.FirstElement() : rootElement;

                    foreach (var soundElement in mainElement.GetChildElements("sound"))
                    {
                        var sound = Submarine.LoadRoundSound(soundElement);
                    }
                    string speciesName = mainElement.GetAttributeString("speciesname", null);
                    if (string.IsNullOrWhiteSpace(speciesName))
                    {
                        speciesName = mainElement.GetAttributeString("name", null);
                        if (!string.IsNullOrWhiteSpace(speciesName))
                        {
                            DebugConsole.NewMessage($"Error in {file.Path}: 'name' is deprecated! Use 'speciesname' instead.", Color.Orange);
                        }
                        else
                        {
                            throw new Exception($"Species name null in {file.Path}");
                        }
                    }

                    bool          humanoid = mainElement.GetAttributeBool("humanoid", false);
                    RagdollParams ragdollParams;
                    if (humanoid)
                    {
                        ragdollParams = RagdollParams.GetRagdollParams <HumanRagdollParams>(speciesName);
                    }
                    else
                    {
                        ragdollParams = RagdollParams.GetRagdollParams <FishRagdollParams>(speciesName);
                    }
                    if (ragdollParams != null)
                    {
                        HashSet <string> texturePaths = new HashSet <string>
                        {
                            ragdollParams.Texture
                        };
                        foreach (RagdollParams.LimbParams limb in ragdollParams.Limbs)
                        {
                            if (!string.IsNullOrEmpty(limb.normalSpriteParams?.Texture))
                            {
                                texturePaths.Add(limb.normalSpriteParams.Texture);
                            }
                            if (!string.IsNullOrEmpty(limb.deformSpriteParams?.Texture))
                            {
                                texturePaths.Add(limb.deformSpriteParams.Texture);
                            }
                            if (!string.IsNullOrEmpty(limb.damagedSpriteParams?.Texture))
                            {
                                texturePaths.Add(limb.damagedSpriteParams.Texture);
                            }
                            foreach (var decorativeSprite in limb.decorativeSpriteParams)
                            {
                                if (!string.IsNullOrEmpty(decorativeSprite.Texture))
                                {
                                    texturePaths.Add(decorativeSprite.Texture);
                                }
                            }
                        }
                        foreach (string texturePath in texturePaths)
                        {
                            preloadedSprites.Add(new Sprite(texturePath, Vector2.Zero));
                        }
                    }
#endif
                    break;
                }
            }
        }
Exemple #9
0
        public void PreloadContent(IEnumerable <ContentFile> contentFiles)
        {
            var filesToPreload = new List <ContentFile>(contentFiles);

            foreach (Submarine sub in Submarine.Loaded)
            {
                if (sub.WreckAI == null)
                {
                    continue;
                }

                if (!string.IsNullOrEmpty(sub.WreckAI.Config.DefensiveAgent))
                {
                    var prefab = CharacterPrefab.FindBySpeciesName(sub.WreckAI.Config.DefensiveAgent);
                    if (prefab != null && !filesToPreload.Any(f => f.Path == prefab.FilePath))
                    {
                        filesToPreload.Add(new ContentFile(prefab.FilePath, ContentType.Character));
                    }
                }
                foreach (Item item in Item.ItemList)
                {
                    if (item.Submarine != sub)
                    {
                        continue;
                    }
                    foreach (Items.Components.ItemComponent component in item.Components)
                    {
                        if (component.statusEffectLists == null)
                        {
                            continue;
                        }
                        foreach (var statusEffectList in component.statusEffectLists.Values)
                        {
                            foreach (StatusEffect statusEffect in statusEffectList)
                            {
                                foreach (var spawnInfo in statusEffect.SpawnCharacters)
                                {
                                    var prefab = CharacterPrefab.FindBySpeciesName(spawnInfo.SpeciesName);
                                    if (prefab != null && !filesToPreload.Any(f => f.Path == prefab.FilePath))
                                    {
                                        filesToPreload.Add(new ContentFile(prefab.FilePath, ContentType.Character));
                                    }
                                }
                            }
                        }
                    }
                }
            }

            foreach (ContentFile file in filesToPreload)
            {
                switch (file.Type)
                {
                case ContentType.Character:
#if CLIENT
                    CharacterPrefab characterPrefab = CharacterPrefab.FindByFilePath(file.Path);
                    if (characterPrefab?.XDocument == null)
                    {
                        throw new Exception($"Failed to load the character config file from {file.Path}!");
                    }
                    var doc         = characterPrefab.XDocument;
                    var rootElement = doc.Root;
                    var mainElement = rootElement.IsOverride() ? rootElement.FirstElement() : rootElement;
                    mainElement.GetChildElements("sound").ForEach(e => Submarine.LoadRoundSound(e));
                    if (!CharacterPrefab.CheckSpeciesName(mainElement, file.Path, out string speciesName))
                    {
                        continue;
                    }
                    bool            humanoid = mainElement.GetAttributeBool("humanoid", false);
                    CharacterPrefab originalCharacter;
                    if (characterPrefab.VariantOf != null)
                    {
                        originalCharacter = CharacterPrefab.FindBySpeciesName(characterPrefab.VariantOf);
                        var originalRoot        = originalCharacter.XDocument.Root;
                        var originalMainElement = originalRoot.IsOverride() ? originalRoot.FirstElement() : originalRoot;
                        originalMainElement.GetChildElements("sound").ForEach(e => Submarine.LoadRoundSound(e));
                        if (!CharacterPrefab.CheckSpeciesName(mainElement, file.Path, out string name))
                        {
                            continue;
                        }
                        speciesName = name;
                        if (mainElement.Attribute("humanoid") == null)
                        {
                            humanoid = originalMainElement.GetAttributeBool("humanoid", false);
                        }
                    }
                    RagdollParams ragdollParams;
                    try
                    {
                        if (humanoid)
                        {
                            ragdollParams = RagdollParams.GetRagdollParams <HumanRagdollParams>(characterPrefab.VariantOf ?? speciesName);
                        }
                        else
                        {
                            ragdollParams = RagdollParams.GetRagdollParams <FishRagdollParams>(characterPrefab.VariantOf ?? speciesName);
                        }
                    }
                    catch (Exception e)
                    {
                        DebugConsole.ThrowError($"Failed to preload a ragdoll file for the character \"{characterPrefab.Name}\"", e);
                        continue;
                    }

                    if (ragdollParams != null)
                    {
                        HashSet <string> texturePaths = new HashSet <string>
                        {
                            ragdollParams.Texture
                        };
                        foreach (RagdollParams.LimbParams limb in ragdollParams.Limbs)
                        {
                            if (!string.IsNullOrEmpty(limb.normalSpriteParams?.Texture))
                            {
                                texturePaths.Add(limb.normalSpriteParams.Texture);
                            }
                            if (!string.IsNullOrEmpty(limb.deformSpriteParams?.Texture))
                            {
                                texturePaths.Add(limb.deformSpriteParams.Texture);
                            }
                            if (!string.IsNullOrEmpty(limb.damagedSpriteParams?.Texture))
                            {
                                texturePaths.Add(limb.damagedSpriteParams.Texture);
                            }
                            foreach (var decorativeSprite in limb.decorativeSpriteParams)
                            {
                                if (!string.IsNullOrEmpty(decorativeSprite.Texture))
                                {
                                    texturePaths.Add(decorativeSprite.Texture);
                                }
                            }
                        }
                        foreach (string texturePath in texturePaths)
                        {
                            preloadedSprites.Add(new Sprite(texturePath, Vector2.Zero));
                        }
                    }
#endif
                    break;
                }
            }
        }
Exemple #10
0
        public static List <Limb> AttachHuskAppendage(Character character, string afflictionIdentifier, XElement appendageDefinition = null, Ragdoll ragdoll = null)
        {
            var appendage = new List <Limb>();

            if (!(AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier) is AfflictionPrefabHusk matchingAffliction))
            {
                DebugConsole.ThrowError($"Could not find an affliction of type 'huskinfection' that matches the affliction '{afflictionIdentifier}'!");
                return(appendage);
            }
            string          nonhuskedSpeciesName = GetNonHuskedSpeciesName(character.SpeciesName, matchingAffliction);
            string          huskedSpeciesName    = GetHuskedSpeciesName(nonhuskedSpeciesName, matchingAffliction);
            CharacterPrefab huskPrefab           = CharacterPrefab.FindBySpeciesName(huskedSpeciesName);

            if (huskPrefab?.XDocument == null)
            {
                DebugConsole.ThrowError($"Failed to find the config file for the husk infected species with the species name '{huskedSpeciesName}'!");
                return(appendage);
            }
            var mainElement = huskPrefab.XDocument.Root.IsOverride() ? huskPrefab.XDocument.Root.FirstElement() : huskPrefab.XDocument.Root;
            var element     = appendageDefinition;

            if (element == null)
            {
                element = mainElement.GetChildElements("huskappendage").FirstOrDefault(e => e.GetAttributeString("affliction", string.Empty).Equals(afflictionIdentifier));
            }
            if (element == null)
            {
                DebugConsole.ThrowError($"Error in '{huskPrefab.FilePath}': Failed to find a huskappendage that matches the affliction with an identifier '{afflictionIdentifier}'!");
                return(appendage);
            }
            string    pathToAppendage = element.GetAttributeString("path", string.Empty);
            XDocument doc             = XMLExtensions.TryLoadXml(pathToAppendage);

            if (doc == null)
            {
                return(appendage);
            }
            if (ragdoll == null)
            {
                ragdoll = character.AnimController;
            }
            if (ragdoll.Dir < 1.0f)
            {
                ragdoll.Flip();
            }
            var limbElements = doc.Root.Elements("limb").ToDictionary(e => e.GetAttributeString("id", null), e => e);

            foreach (var jointElement in doc.Root.Elements("joint"))
            {
                if (limbElements.TryGetValue(jointElement.GetAttributeString("limb2", null), out XElement limbElement))
                {
                    var  jointParams = new RagdollParams.JointParams(jointElement, ragdoll.RagdollParams);
                    Limb attachLimb  = null;
                    if (matchingAffliction.AttachLimbId > -1)
                    {
                        attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == matchingAffliction.AttachLimbId);
                    }
                    else if (matchingAffliction.AttachLimbName != null)
                    {
                        attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Name == matchingAffliction.AttachLimbName);
                    }
                    else if (matchingAffliction.AttachLimbType != LimbType.None)
                    {
                        attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.type == matchingAffliction.AttachLimbType);
                    }
                    if (attachLimb == null)
                    {
                        attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == jointParams.Limb1);
                    }
                    if (attachLimb != null)
                    {
                        jointParams.Limb1 = attachLimb.Params.ID;
                        var appendageLimbParams = new RagdollParams.LimbParams(limbElement, ragdoll.RagdollParams)
                        {
                            // Ensure that we have a valid id for the new limb
                            ID = ragdoll.Limbs.Length
                        };
                        jointParams.Limb2 = appendageLimbParams.ID;
                        Limb huskAppendage = new Limb(ragdoll, character, appendageLimbParams);
                        huskAppendage.body.Submarine = character.Submarine;
                        huskAppendage.body.SetTransform(attachLimb.SimPosition, attachLimb.Rotation);
                        ragdoll.AddLimb(huskAppendage);
                        ragdoll.AddJoint(jointParams);
                        appendage.Add(huskAppendage);
                    }
                }
            }
            return(appendage);
        }
        protected override void StartMissionSpecific(Level level)
        {
            existingTargets.Clear();
            spawnedTargets.Clear();
            allTargets.Clear();
            if (IsClient)
            {
                return;
            }
            TargetRuin = Level.Loaded?.Ruins?.GetRandom(randSync: Rand.RandSync.Server);
            if (TargetRuin == null)
            {
                DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): level contains no alien ruins");
                return;
            }
            if (targetItemIdentifiers.Length < 1 && targetEnemyIdentifiers.Length < 1)
            {
                DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): no target identifiers set in the mission definition");
                return;
            }
            foreach (var item in Item.ItemList)
            {
                if (!targetItemIdentifiers.Contains(item.Prefab.Identifier))
                {
                    continue;
                }
                if (item.Submarine != TargetRuin.Submarine)
                {
                    continue;
                }
                existingTargets.Add(item);
                allTargets.Add(item);
            }
            int existingEnemyCount = 0;

            foreach (var character in Character.CharacterList)
            {
                if (string.IsNullOrEmpty(character.SpeciesName))
                {
                    continue;
                }
                if (!targetEnemyIdentifiers.Contains(character.SpeciesName.ToLowerInvariant()))
                {
                    continue;
                }
                if (character.Submarine != TargetRuin.Submarine)
                {
                    continue;
                }
                existingTargets.Add(character);
                allTargets.Add(character);
                existingEnemyCount++;
            }
            if (existingEnemyCount < minEnemyCount)
            {
                var enemyPrefabs = new HashSet <CharacterPrefab>();
                foreach (string identifier in targetEnemyIdentifiers)
                {
                    var prefab = CharacterPrefab.FindBySpeciesName(identifier);
                    if (prefab != null)
                    {
                        enemyPrefabs.Add(prefab);
                    }
                    else
                    {
                        DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): could not find a character prefab with the species \"{identifier}\"");
                    }
                }
                if (enemyPrefabs.None())
                {
                    DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no enemy species defined that could be used to spawn more ({minEnemyCount - existingEnemyCount}) enemies");
                    return;
                }
                for (int i = 0; i < (minEnemyCount - existingEnemyCount); i++)
                {
                    var prefab   = enemyPrefabs.GetRandom();
                    var spawnPos = TargetRuin.Submarine.GetWaypoints(false).GetRandom(w => w.CurrentHull != null)?.WorldPosition;
                    if (!spawnPos.HasValue)
                    {
                        DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no valid spawn positions could be found for the additional ({minEnemyCount - existingEnemyCount}) enemies to be spawned");
                        return;
                    }
                    var newEnemy = Character.Create(prefab.Identifier, spawnPos.Value, ToolBox.RandomSeed(8), createNetworkEvent: false);
                    spawnedTargets.Add(newEnemy);
                    allTargets.Add(newEnemy);
                }
            }
#if DEBUG
            DebugConsole.NewMessage("********** CLEAR RUIN MISSION INFO **********");
            DebugConsole.NewMessage($"Existing item targets: {existingTargets.Count - existingEnemyCount}");
            DebugConsole.NewMessage($"Existing enemy targets: {existingEnemyCount}");
            DebugConsole.NewMessage($"Spawned enemy targets: {spawnedTargets.Count}");
#endif
        }