Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ARKBreedingStats/ARKBreedingStats.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@


<ItemGroup>
<PackageReference Include="pythonnet" Version="3.0.5" />
<PackageReference Include="System.Windows.Forms.DataVisualization" Version="1.0.0-prerelease.20110.1" />
<PackageReference Include="System.Speech" Version="10.0.3" />
<PackageReference Include="FluentFTP" Version="51.0.0" />
Expand Down
3 changes: 1 addition & 2 deletions ARKBreedingStats/Form1.importSave.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,7 @@ private async Task<string> 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
Expand Down
240 changes: 221 additions & 19 deletions ARKBreedingStats/ImportSavegame.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using ARKBreedingStats.Library;
using ARKBreedingStats.species;
using ARKBreedingStats.values;
using Python.Runtime;
using SavegameToolkit;
using SavegameToolkit.Arrays;
using SavegameToolkit.Structs;
Expand All @@ -26,6 +27,81 @@ 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<Creature[]> ImportCollectionFromAsaSavegame(CreatureCollection creatureCollection, string filename, string serverName)
{
var importUnclaimedBabies = Properties.Settings.Default.SaveFileImportUnclaimedBabies;
var saveImportCryo = Properties.Settings.Default.SaveImportCryo;

// TODO: properly set up python engine & make sure arkparse is installed
if (!PythonEngine.IsInitialized) PythonEngine.Initialize();
Creature[] creatures;
using (Py.GIL())
{
dynamic pathlib = Py.Import("pathlib");
dynamic asa_save = Py.Import("arkparse.saves.asa_save");
dynamic path = pathlib.Path(filename);
dynamic save = asa_save.AsaSave(path);
float gameTime = save.save_context.game_time;
dynamic dino_api = Py.Import("arkparse.api.dino_api");
dynamic DinoApi = dino_api.DinoApi(save);
PyDict creatureObjects = DinoApi.get_all_tamed();

IEnumerable<dynamic> tamedCreatures = creatureObjects.Values().Where(o => (importUnclaimedBabies || !IsUnclaimedBaby(o)) && (saveImportCryo || !IsInCryo(o)));

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())
{
tamedCreatures = tamedCreatures.Where(c =>
{
return filters.Any(filter => c.owner.tribe.Contains(filter));
});
}
}

ImportSavegame importSavegame = new ImportSavegame(gameTime);
int? wildLevelStep = creatureCollection.getWildLevelStep();
creatures = tamedCreatures.Select(o => (Creature)importSavegame.ConvertTamedDino(o, wildLevelStep)).Where(c => c != null).ToArray();
}

return creatures;
}

private static async Task<Creature[]> ImportCollectionFromAseSavegame(CreatureCollection creatureCollection, string filename, string serverName)
{
(GameObjectContainer gameObjectContainer, float gameTime) = await Task.Run(() => ReadSavegameFile(filename));
var ignoreClasses = Values.V.IgnoreSpeciesClassesOnImport;
Expand Down Expand Up @@ -56,25 +132,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)
Expand Down Expand Up @@ -254,5 +312,149 @@ private Creature ConvertGameObject(GameObject creatureObject, int? levelStep)

return creature;
}

private Creature ConvertTamedDino(dynamic dino, int? levelStep)
{
if (!Values.V.TryGetSpeciesByBlueprint(dino.@object.blueprint.As<string>(), out Species species))
{
// species is unknown, creature cannot be imported.
// use name-field to temporarily save the unknown blueprint name to display in a messageBox
return new Creature { name = dino.@object.blueprint.As<string>().Split('.').Last() };
}

string imprinterName = dino.owner.imprinter;
string owner = string.IsNullOrWhiteSpace(imprinterName) ? dino.owner.tamer_string : imprinterName;

int[] wildLevels = ExtractStatLevels(dino.stats.base_stat_points);
wildLevels[Stats.Torpidity] = dino.stats.base_level.As<int>() - 1;
int[] tamedLevels = ExtractStatLevels(dino.stats.added_stat_points);
int[] mutatedLevels = ExtractStatLevels(dino.stats.mutated_stat_points);

float ti = dino.@object.get_property_value("TamedIneffectivenessModifier", 0).As<float>();
double te = 1f / (1 + (ti == 0 ? dino.@object.get_property_value("TameIneffectivenessModifier", 0).As<float>() : ti));

var arkId = Utils.ConvertArkIdsToLongArkId(dino.id_.id1.As<int>(), dino.id_.id2.As<int>());

string tamedName = dino.tamed_name;
string tribeName = dino.owner.tribe;
bool isFemale = dino.is_female.As<bool>();
float imprintingQuality = dino.stats.@object.get_property_value("DinoImprintingQuality", 0).As<float>();
int mutationsFemale = dino.@object.get_property_value("RandomMutationsFemale", 0).As<int>();
int mutationsMale = dino.@object.get_property_value("RandomMutationsMale", 0).As<int>();
bool neutered = dino.@object.get_property_value("bNeutered", false).As<bool>();
bool mutagenApplied = dino.@object.get_property_value("MutagenApplied", false).As<bool>();

Creature creature = new Creature(species,
tamedName, owner, tribeName,
isFemale ? Sex.Female : Sex.Male,
wildLevels, tamedLevels, mutatedLevels, te,
!string.IsNullOrWhiteSpace(imprinterName),
imprintingQuality,
levelStep
)
{
imprinterName = imprinterName,
guid = Utils.ConvertArkIdToGuid(arkId),
ArkId = arkId,
ArkIdImported = true,
ArkIdInGame = Utils.ConvertImportedArkIdToIngameVisualization(arkId),
domesticatedAt = DateTime.Now, // TODO: possible to convert ingame-time to realtime?
addedToLibrary = DateTime.Now,
mutationsMaternal = mutationsFemale,
mutationsPaternal = mutationsMale,
flags = (neutered ? CreatureFlags.Neutered : CreatureFlags.None)
| (mutagenApplied ? CreatureFlags.MutagenApplied : CreatureFlags.None)
};

float babyAge = dino.@object.get_property_value("BabyAge", 1).As<float>();
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 = dino.@object.get_property_value("NextAllowedMatingTime", 0).As<double>();
if (_gameTime < nextMatingPossible)
{
creature.cooldownUntil = DateTime.Now.Add(TimeSpan.FromSeconds(nextMatingPossible - _gameTime));
}
}

dynamic ancestorsFemale = dino.@object.get_property_value("DinoAncestors");
if (ancestorsFemale != null)
{
dynamic femaleParent = ancestorsFemale[-1].female;
creature.motherGuid = Utils.ConvertArkIdToGuid(Utils.ConvertArkIdsToLongArkId(
femaleParent.id_.id1.As<int>(),
femaleParent.id_.id2.As<int>()
));
creature.motherName = femaleParent.name;
creature.isBred = true;
}
dynamic ancestorsMale = dino.@object.get_property_value("DinoAncestorsMale");
if (ancestorsMale != null)
{
dynamic maleParent = ancestorsMale[-1].male;
creature.fatherGuid = Utils.ConvertArkIdToGuid(Utils.ConvertArkIdsToLongArkId(
maleParent.id_.id1.As<int>(),
maleParent.id_.id2.As<int>()
));
creature.fatherName = maleParent.name;
creature.isBred = true;
}

creature.colors = new byte[Ark.ColorRegionCount];
dynamic colorSetIndices = dino.get_color_set_indices();
for (int i = 0; i < Ark.ColorRegionCount; i++)
{
creature.colors[i] = colorSetIndices[i].As<byte>();
}

bool isDead = dino.is_dead.As<bool>();
if (isDead)
{
creature.Status = CreatureStatus.Dead;
}
bool isInCryo = dino.is_cryopodded.As<bool>();
if (isInCryo)
{
creature.Status = CreatureStatus.Cryopod;
}

creature.RecalculateCreatureValues(levelStep);

return creature;
}

private static bool IsUnclaimedBaby(dynamic dino)
{
TeamType teamType = TeamTypes.ForTeam(dino.@object.get_property_value("TargetingTeam", 0).As<int>());
return teamType == TeamType.Breeding;
}
private static bool IsInCryo(dynamic dino)
{
return dino.is_cryopodded.As<bool>();
}

private static int[] ExtractStatLevels(dynamic statPoints)
{
int[] stats = new int[Stats.StatsCount];
stats[Stats.Health] = statPoints.health.As<int>();
stats[Stats.Stamina] = statPoints.stamina.As<int>();
stats[Stats.Torpidity] = statPoints.torpidity.As<int>();
stats[Stats.Oxygen] = statPoints.oxygen.As<int>();
stats[Stats.Food] = statPoints.food.As<int>();
stats[Stats.Water] = statPoints.water.As<int>();
stats[Stats.Temperature] = statPoints.temperature.As<int>();
stats[Stats.Weight] = statPoints.weight.As<int>();
stats[Stats.MeleeDamageMultiplier] = statPoints.melee_damage.As<int>();
stats[Stats.SpeedMultiplier] = statPoints.movement_speed.As<int>();
stats[Stats.TemperatureFortitude] = statPoints.fortitude.As<int>();
stats[Stats.CraftingSpeedMultiplier] = statPoints.crafting_speed.As<int>();
return stats;
}
}
}
6 changes: 6 additions & 0 deletions ARKBreedingStats/values/Values.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/// <summary>
/// Sets the ModsManifest. If the value is null, a new default object will be created.
/// </summary>
Expand Down