diff --git a/.gitmodules b/.gitmodules index 24edb462..0b5e2363 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,7 @@ path = ArkSavegameToolkit url = https://github.com/cadon/ArkSavegameToolkit.git branch = master +[submodule "AsaSavegameToolkit"] + path = AsaSavegameToolkit + url = git@github.com:Olfi01/AsaSavegameToolkit.git + branch = asb diff --git a/ARKBreedingStats.sln b/ARKBreedingStats.sln index cdb3de61..aca44ef2 100644 --- a/ARKBreedingStats.sln +++ b/ARKBreedingStats.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.32002.261 +# Visual Studio Version 18 +VisualStudioVersion = 18.6.11806.211 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ARKBreedingStats", "ARKBreedingStats\ARKBreedingStats.csproj", "{991563CE-6B2C-40AE-BC80-A14F090A4D26}" ProjectSection(ProjectDependencies) = postProject @@ -25,6 +25,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_meta", "_meta", "{5DAADC66 translations.txt = translations.txt EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsaSavegameToolkit", "AsaSavegameToolkit\src\AsaSavegameToolkit.csproj", "{DC4E684E-1A84-2D33-95CA-154FECA26A10}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,6 +53,10 @@ Global {E7C8F9A2-D4B3-4C1E-9F2A-1A8B5C3D4E5F}.Debug|Any CPU.Build.0 = Debug|Any CPU {E7C8F9A2-D4B3-4C1E-9F2A-1A8B5C3D4E5F}.Release|Any CPU.ActiveCfg = Release|Any CPU {E7C8F9A2-D4B3-4C1E-9F2A-1A8B5C3D4E5F}.Release|Any CPU.Build.0 = Release|Any CPU + {DC4E684E-1A84-2D33-95CA-154FECA26A10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC4E684E-1A84-2D33-95CA-154FECA26A10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC4E684E-1A84-2D33-95CA-154FECA26A10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC4E684E-1A84-2D33-95CA-154FECA26A10}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ARKBreedingStats/ARKBreedingStats.csproj b/ARKBreedingStats/ARKBreedingStats.csproj index 202df7ec..cc449068 100644 --- a/ARKBreedingStats/ARKBreedingStats.csproj +++ b/ARKBreedingStats/ARKBreedingStats.csproj @@ -39,6 +39,14 @@ + + + + + + + + diff --git a/ARKBreedingStats/Form1.importSave.cs b/ARKBreedingStats/Form1.importSave.cs index d3a1a9d4..2eb77234 100644 --- a/ARKBreedingStats/Form1.importSave.cs +++ b/ARKBreedingStats/Form1.importSave.cs @@ -155,8 +155,7 @@ private async Task RunSavegameImport(string fileLocation, string conveni } catch (Exception ex) { - var noAsaSupportInfo = ex.Message.StartsWith("Found unknown Version 20819") ? "Importing save games from ARK: Survival Ascended (ASA) is not yet supported, currently only ARK: Survival Evolved (ASE) is supported for save file import.\n\n" : null; - MessageBoxes.ExceptionMessageBox(ex, $"{noAsaSupportInfo}An error occurred while importing the file {fileLocation}.", "Save file import error"); + MessageBoxes.ExceptionMessageBox(ex, $"An error occurred while importing the file {fileLocation}.", "Save file import error"); return string.Empty; } finally diff --git a/ARKBreedingStats/ImportSavegame.cs b/ARKBreedingStats/ImportSavegame.cs index 9f486c0c..3bb8f8c7 100644 --- a/ARKBreedingStats/ImportSavegame.cs +++ b/ARKBreedingStats/ImportSavegame.cs @@ -1,6 +1,7 @@ using ARKBreedingStats.Library; using ARKBreedingStats.species; using ARKBreedingStats.values; +using AsaSavegameToolkit.Porcelain; using SavegameToolkit; using SavegameToolkit.Arrays; using SavegameToolkit.Structs; @@ -12,6 +13,16 @@ using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; +using Creature = ARKBreedingStats.Library.Creature; +using SavegameCreature = AsaSavegameToolkit.Porcelain.Creature; +using GameObjectExtensions = SavegameToolkitAdditions.GameObjectExtensions; +using AsaSavegameToolkit.Plumbing.Utilities; +#if DEBUG +using AsaSavegameToolkit.Plumbing.Records; +#else +using AsaSavegameToolkit.Plumbing.Properties; +#endif +using Microsoft.Extensions.Logging.Abstractions; namespace ARKBreedingStats { @@ -26,6 +37,77 @@ private ImportSavegame(float gameTime) } public static async Task ImportCollectionFromSavegame(CreatureCollection creatureCollection, string filename, string serverName) + { + byte[] first16Bytes = new byte[16]; + using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read)) + { + fs.ReadExactly(first16Bytes, 0, 16); + } + string checkString = System.Text.ASCIIEncoding.ASCII.GetString(first16Bytes); + bool isAsaSavegame = checkString.Contains("SQLite format"); + var creatures = await (isAsaSavegame ? ImportCollectionFromAsaSavegame(creatureCollection, filename, serverName) : ImportCollectionFromAseSavegame(creatureCollection, filename, serverName)); + + ArkName.ClearCache(); + + // if there are creatures with unknown species, check if the according mod-file is available + var unknownSpeciesCreatures = creatures.Where(c => c.Species == null).ToArray(); + + if (!unknownSpeciesCreatures.Any() + || Properties.Settings.Default.IgnoreUnknownBlueprintsOnSaveImport + || MessageBox.Show("The species of " + unknownSpeciesCreatures.Length + " creature" + (unknownSpeciesCreatures.Length != 1 ? "s" : "") + " is not recognized, probably because they are from a mod that is not loaded.\n" + + "The unrecognized species-classes are as follows, all the according creatures cannot be imported:\n\n" + string.Join("\n", unknownSpeciesCreatures.Select(c => c.name).Distinct().ToArray()) + + "\n\nTo import the unrecognized creatures, you first need mod values-files, see Settings - Mod value manager… if the mod value is available\n\n" + + "Do you want to import the recognized creatures? If you click no, nothing is imported.", + "Unrecognized species while importing savegame", MessageBoxButtons.YesNo, MessageBoxIcon.Question + ) == DialogResult.Yes + ) + { + ImportCollection(creatureCollection, creatures.Where(c => c.Species != null).ToList(), serverName); + } + } + + private static async Task ImportCollectionFromAsaSavegame(CreatureCollection creatureCollection, string filename, string serverName) + { + var ignoreClasses = Values.V.IgnoreSpeciesClassesOnImport; + var importUnclaimedBabies = Properties.Settings.Default.SaveFileImportUnclaimedBabies; + var saveImportCryo = Properties.Settings.Default.SaveImportCryo; + + var save = AsaSaveGame.ReadFrom(filename, logger: NullLogger.Instance); +#if DEBUG + var gameTime = save.GameTime; +#else + var gameTime = 0d; // wait for https://github.com/flintthatchwood/AsaSavegameToolkit/pull/3 to be merged and published +#endif + + var tamedCreatureObjects = save.TamedCreatures.Values.Where(o => (importUnclaimedBabies || !o.Record.IsUnclaimedBaby()) && !ignoreClasses.Contains(o.ClassName.Split('.').Last())); + if (saveImportCryo) + { + tamedCreatureObjects = tamedCreatureObjects.Concat(save.CryopoddedCreatures.Values.Where(o => !ignoreClasses.Contains(o.ClassName.Split('.').Last()))); + } + + if (!string.IsNullOrWhiteSpace(Properties.Settings.Default.ImportTribeNameFilter)) + { + string[] filters = Properties.Settings.Default.ImportTribeNameFilter.Split(',') + .Select(s => s.Trim()) + .Where(s => !string.IsNullOrEmpty(s)) + .ToArray(); + + if (filters.Any()) + { + tamedCreatureObjects = tamedCreatureObjects.Where(o => + { + string tribeName = o.TribeName ?? string.Empty; + return filters.Any(filter => tribeName.Contains(filter)); + }); + } + } + + ImportSavegame importSavegame = new ImportSavegame(Convert.ToSingle(gameTime)); + int? wildLevelStep = creatureCollection.getWildLevelStep(); + return tamedCreatureObjects.Select(o => importSavegame.ConvertCreature(o, wildLevelStep)).Where(c => c != null).ToArray(); + } + + private static async Task ImportCollectionFromAseSavegame(CreatureCollection creatureCollection, string filename, string serverName) { (GameObjectContainer gameObjectContainer, float gameTime) = await Task.Run(() => ReadSavegameFile(filename)); var ignoreClasses = Values.V.IgnoreSpeciesClassesOnImport; @@ -56,25 +138,7 @@ public static async Task ImportCollectionFromSavegame(CreatureCollection creatur ImportSavegame importSavegame = new ImportSavegame(gameTime); int? wildLevelStep = creatureCollection.getWildLevelStep(); - var creatures = tamedCreatureObjects.Select(o => importSavegame.ConvertGameObject(o, wildLevelStep)).Where(c => c != null).ToArray(); - - ArkName.ClearCache(); - - // if there are creatures with unknown species, check if the according mod-file is available - var unknownSpeciesCreatures = creatures.Where(c => c.Species == null).ToArray(); - - if (!unknownSpeciesCreatures.Any() - || Properties.Settings.Default.IgnoreUnknownBlueprintsOnSaveImport - || MessageBox.Show("The species of " + unknownSpeciesCreatures.Length + " creature" + (unknownSpeciesCreatures.Length != 1 ? "s" : "") + " is not recognized, probably because they are from a mod that is not loaded.\n" - + "The unrecognized species-classes are as follows, all the according creatures cannot be imported:\n\n" + string.Join("\n", unknownSpeciesCreatures.Select(c => c.name).Distinct().ToArray()) - + "\n\nTo import the unrecognized creatures, you first need mod values-files, see Settings - Mod value manager… if the mod value is available\n\n" - + "Do you want to import the recognized creatures? If you click no, nothing is imported.", - "Unrecognized species while importing savegame", MessageBoxButtons.YesNo, MessageBoxIcon.Question - ) == DialogResult.Yes - ) - { - ImportCollection(creatureCollection, creatures.Where(c => c.Species != null).ToList(), serverName); - } + return tamedCreatureObjects.Select(o => importSavegame.ConvertGameObject(o, wildLevelStep)).Where(c => c != null).ToArray(); } private static (GameObjectContainer, float) ReadSavegameFile(string fileName) @@ -254,5 +318,146 @@ private Creature ConvertGameObject(GameObject creatureObject, int? levelStep) return creature; } + + private Creature ConvertCreature(SavegameCreature creatureObject, int? levelStep) + { + if (!Values.V.TryGetSpeciesByBlueprint(creatureObject.ClassName, out Species species)) + { + // species is unknown, creature cannot be imported. + // use name-field to temporarily save the unknown classString to display in a messageBox + return new Creature { name = creatureObject.ClassName }; + } + + string imprinterName = creatureObject.ImprinterName; + string owner = string.IsNullOrWhiteSpace(imprinterName) ? creatureObject.TamerString : imprinterName; + + int[] wildLevels = Enumerable.Repeat(-1, Stats.StatsCount).ToArray(); // -1 is unknown + int[] tamedLevels = new int[Stats.StatsCount]; + int[] mutatedLevels = new int[Stats.StatsCount]; + + for (int i = 0; i < Stats.StatsCount; i++) + { + wildLevels[i] = creatureObject.WildLevels[i] ?? 0; + } + wildLevels[Stats.Torpidity] = (creatureObject.BaseLevel ?? 1) - 1; // torpor + + for (int i = 0; i < Stats.StatsCount; i++) + { + tamedLevels[i] = creatureObject.TamedLevelsApplied[i] ?? 0; + mutatedLevels[i] = creatureObject.MutationLevelsApplied[i] ?? 0; + } + + if (!creatureObject.Record.Properties.TryGet("TamedIneffectivenessModifier", out float ti)) + { + ti = creatureObject.Record.Properties.Get("TameIneffectivenessModifier"); + } + double te = 1f / (1 + ti); + + Guid creatureGuid = creatureObject.Id; + long arkId = Utils.ConvertCreatureGuidToArkId(creatureGuid); + Creature creature = new Creature(species, + creatureObject.TamedName, owner, creatureObject.TribeName, + creatureObject.IsFemale ? Sex.Female : Sex.Male, + wildLevels, tamedLevels, mutatedLevels, te, + !string.IsNullOrWhiteSpace(creatureObject.ImprinterName), + creatureObject.ImprintQuality, + levelStep + ) + { + imprinterName = creatureObject.ImprinterName, + guid = creatureGuid, + ArkId = arkId, + ArkIdImported = true, + ArkIdInGame = Utils.ConvertImportedArkIdToIngameVisualization(arkId), + domesticatedAt = DateTime.Now, + addedToLibrary = DateTime.Now, + mutationsMaternal = creatureObject.MutationsFemale, + mutationsPaternal = creatureObject.MutationsMale, + flags = (creatureObject.Record.Properties.Get("bNeutered") ? CreatureFlags.Neutered : CreatureFlags.None) + | (creatureObject.Record.Properties.Get("MutagenApplied") ? CreatureFlags.MutagenApplied : CreatureFlags.None) + }; + + // If it's a baby and still growing, work out growingUntil + float babyAge = creatureObject.BabyAge ?? 1; + if (babyAge < 1) + { + double maturationDuration = species.breeding?.maturationTimeAdjusted ?? 0; + float bornSecondsAgo = (float)maturationDuration * babyAge; + if (bornSecondsAgo < maturationDuration - 120) // there seems to be a slight offset of one of these saved values, so don't display a creature as being in cooldown if it is about to leave it in the next 2 minutes + creature.growingUntil = DateTime.Now.Add(TimeSpan.FromSeconds(maturationDuration - bornSecondsAgo)); + } + else + { + double nextMatingPossible = creatureObject.Record.Properties.Get("NextAllowedMatingTime"); + if (_gameTime < nextMatingPossible) + { + creature.cooldownUntil = DateTime.Now.Add(TimeSpan.FromSeconds(nextMatingPossible - _gameTime)); + } + } + + // Ancestor linking is done later after entire collection is formed - here we just set the guids +#if DEBUG + IEnumerable femaleAncestors = creatureObject.Record.Properties.Get("DinoAncestors")?.OfType(); + DinoAncestorsEntryRecord femaleAncestor = femaleAncestors?.LastOrDefault(); + if (femaleAncestor != null) + { + creature.motherGuid = Utils.ConvertArkIdToGuid(Utils.ConvertArkIdsToLongArkId( + (int)femaleAncestor.FemaleId1, + (int)femaleAncestor.FemaleId2)); + creature.motherName = femaleAncestor.FemaleName; + creature.isBred = true; + } + IEnumerable maleAncestors = creatureObject.Record.Properties.Get("DinoAncestors")?.OfType(); + DinoAncestorsEntryRecord maleAncestor = maleAncestors?.LastOrDefault(); + if (maleAncestor != null) + { + creature.fatherGuid = Utils.ConvertArkIdToGuid(Utils.ConvertArkIdsToLongArkId( + (int)maleAncestor.MaleId1, + (int)maleAncestor.MaleId2)); + creature.fatherName = maleAncestor.MaleName; + creature.isBred = true; + } +#else + IEnumerable> femaleAncestors = creatureObject.Record.Properties.Get("DinoAncestors")?.OfType>(); // for now, this has to use the raw properties until https://github.com/flintthatchwood/AsaSavegameToolkit/pull/4 is merged and published. + List femaleAncestor = femaleAncestors?.LastOrDefault(); + if (femaleAncestor != null) + { + creature.motherGuid = Utils.ConvertArkIdToGuid(Utils.ConvertArkIdsToLongArkId( + (int)femaleAncestor.Get("FemaleDinoID1"), + (int)femaleAncestor.Get("FemaleDinoID2"))); + creature.motherName = femaleAncestor.Get("FemaleName"); + creature.isBred = true; + } + IEnumerable> maleAncestors = creatureObject.Record.Properties.Get("DinoAncestorsMale")?.OfType>(); + List maleAncestor = maleAncestors?.LastOrDefault(); + if (maleAncestor != null) + { + creature.fatherGuid = Utils.ConvertArkIdToGuid(GameObjectExtensions.CreateDinoId( + (int)maleAncestor.Get("MaleDinoID1"), + (int)maleAncestor.Get("MaleDinoID2"))); + creature.fatherName = maleAncestor.Get("MaleName"); + creature.isBred = true; + } +#endif + + creature.colors = new byte[Ark.ColorRegionCount]; + for (int i = 0; i < 6; i++) + { + creature.colors[i] = creatureObject.ColorRegions[i] ?? 0; + } + + bool isDead = creatureObject.Record.Properties.Get("bIsDead"); + if (isDead) + { + creature.Status = CreatureStatus.Dead; // dead is always dead + } + + if (creatureObject.IsInCryo) + creature.Status = CreatureStatus.Cryopod; + + creature.RecalculateCreatureValues(levelStep); + + return creature; + } } } diff --git a/ARKBreedingStats/values/Values.cs b/ARKBreedingStats/values/Values.cs index 412a0f0e..3f4cfe8e 100644 --- a/ARKBreedingStats/values/Values.cs +++ b/ARKBreedingStats/values/Values.cs @@ -845,6 +845,12 @@ public Species SpeciesByBlueprint(string blueprintPath, bool removeTrailingC) => ? blueprintPath.Substring(0, blueprintPath.Length - 2) : blueprintPath); + public bool TryGetSpeciesByBlueprint(string blueprintPath, out Species species, bool removeTrailingC = true) + { + species = SpeciesByBlueprint(blueprintPath, removeTrailingC); + return species != null; + } + /// /// Sets the ModsManifest. If the value is null, a new default object will be created. /// diff --git a/AsaSavegameToolkit b/AsaSavegameToolkit new file mode 160000 index 00000000..2006dc63 --- /dev/null +++ b/AsaSavegameToolkit @@ -0,0 +1 @@ +Subproject commit 2006dc638f2217eb8aa7e3226d0a1ecaea56773f