diff --git a/ARKBreedingStats/ARKBreedingStats.csproj b/ARKBreedingStats/ARKBreedingStats.csproj
index 360e832d..2545c0cf 100644
--- a/ARKBreedingStats/ARKBreedingStats.csproj
+++ b/ARKBreedingStats/ARKBreedingStats.csproj
@@ -84,6 +84,10 @@
+
+
+
+
Form
diff --git a/ARKBreedingStats/ARKOverlay.cs b/ARKBreedingStats/ARKOverlay.cs
index b853d147..784d9876 100644
--- a/ARKBreedingStats/ARKOverlay.cs
+++ b/ARKBreedingStats/ARKOverlay.cs
@@ -173,7 +173,6 @@ public void SetStatLevels(int[] wildValues, int[] tamedValues, int levelWild, in
///
/// Used to display longer texts at the top right, e.g. taming-info.
///
- ///
internal void SetInfoText(string infoText, Color textColor)
{
labelInfo.ForeColor = textColor;
diff --git a/ARKBreedingStats/Ark.cs b/ARKBreedingStats/Ark.cs
index 1117a055..12b31be6 100644
--- a/ARKBreedingStats/Ark.cs
+++ b/ARKBreedingStats/Ark.cs
@@ -106,6 +106,11 @@ public static class Ark
public const int ColorRegionCount = 6;
#endregion
+
+ ///
+ /// The name is trimmed to this length in game.
+ ///
+ public const int MaxCreatureNameLength = 24;
}
///
@@ -131,6 +136,23 @@ public static class Stats
public const int TemperatureFortitude = 10;
public const int CraftingSpeedMultiplier = 11;
+ ///
+ /// Index of additive taming multiplier in stat multipliers.
+ ///
+ public const int IndexTamingAdd = 0;
+ ///
+ /// Index of multiplicative taming multiplier in stat multipliers.
+ ///
+ public const int IndexTamingMult = 1;
+ ///
+ /// Index of domesticated level multiplier in stat multipliers.
+ ///
+ public const int IndexLevelDom = 2;
+ ///
+ /// Index of wild level multiplier in stat multipliers.
+ ///
+ public const int IndexLevelWild = 3;
+
///
/// Returns the stat-index for the given order index (like it is ordered in game).
///
diff --git a/ARKBreedingStats/CreatureInfoInput.cs b/ARKBreedingStats/CreatureInfoInput.cs
index c3c5937e..cf12626d 100644
--- a/ARKBreedingStats/CreatureInfoInput.cs
+++ b/ARKBreedingStats/CreatureInfoInput.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
+using System.Drawing.Text;
using System.Windows.Forms;
using System.Windows.Threading;
using ARKBreedingStats.Library;
@@ -47,6 +48,7 @@ public partial class CreatureInfoInput : UserControl
private bool _isNewCreature;
private readonly Debouncer _parentsChangedDebouncer = new Debouncer();
+ private readonly Debouncer _nameChangedDebouncer = new Debouncer();
///
/// The pictureBox that displays the colored species dependent on the selected region colors.
@@ -589,9 +591,8 @@ public void GenerateCreatureName(Creature creature, int[] speciesTopLevels, int[
{
SetCreatureData(creature);
CreatureName = NamePattern.GenerateCreatureName(creature, _sameSpecies, speciesTopLevels, speciesLowestLevels, customReplacings, showDuplicateNameWarning, namingPatternIndex, false, colorsExisting: ColorAlreadyExistingInformation);
- const int maxNameLengthInGame = 24;
- if (CreatureName.Length > maxNameLengthInGame)
- SetMessageLabelText?.Invoke($"The generated name is longer than {maxNameLengthInGame} characters, the name will look like this in game:\r\n" + CreatureName.Substring(0, maxNameLengthInGame), MessageBoxIcon.Error);
+ if (CreatureName.Length > Ark.MaxCreatureNameLength)
+ SetMessageLabelText?.Invoke($"The generated name is longer than {Ark.MaxCreatureNameLength} characters, the name will look like this in game:\r\n" + CreatureName.Substring(0, Ark.MaxCreatureNameLength), MessageBoxIcon.Error);
else SetMessageLabelText?.Invoke();
}
@@ -754,15 +755,22 @@ private void lblTribe_Click(object sender, EventArgs e)
}
private void textBoxName_TextChanged(object sender, EventArgs e)
+ {
+ _nameChangedDebouncer.Debounce(500, CheckIfNameAlreadyExists, Dispatcher.CurrentDispatcher);
+ }
+
+ private void CheckIfNameAlreadyExists()
{
// feedback if name already exists
if (!string.IsNullOrEmpty(textBoxName.Text) && NamesOfAllCreatures != null && NamesOfAllCreatures.Contains(textBoxName.Text))
{
textBoxName.BackColor = Color.Khaki;
+ _tt.SetToolTip(textBoxName, Loc.S("nameAlreadyExistsInLibrary"));
}
else
{
textBoxName.BackColor = SystemColors.Window;
+ _tt.SetToolTip(textBoxName, null);
}
}
diff --git a/ARKBreedingStats/FileService.cs b/ARKBreedingStats/FileService.cs
index 91d8b0c9..ce2c7a83 100644
--- a/ARKBreedingStats/FileService.cs
+++ b/ARKBreedingStats/FileService.cs
@@ -158,7 +158,7 @@ public static bool TryCreateDirectory(string path, out string error)
/// True if the file is not existing after this method ends.
public static bool TryDeleteFile(string filePath)
{
- if (!File.Exists(filePath)) return true;
+ if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) return true;
try
{
File.Delete(filePath);
diff --git a/ARKBreedingStats/Form1.Designer.cs b/ARKBreedingStats/Form1.Designer.cs
index eb6703fe..231cf7c9 100644
--- a/ARKBreedingStats/Form1.Designer.cs
+++ b/ARKBreedingStats/Form1.Designer.cs
@@ -346,7 +346,6 @@ private void InitializeComponent()
this.TsLbLabelSet = new System.Windows.Forms.ToolStripLabel();
this.TsCbbLabelSets = new System.Windows.Forms.ToolStripComboBox();
this.panelToolBar = new System.Windows.Forms.Panel();
- this.TbMessageLabel = new System.Windows.Forms.TextBox();
this.btImportLastExported = new System.Windows.Forms.Button();
this.pbSpecies = new System.Windows.Forms.PictureBox();
this.tbSpeciesGlobal = new ARKBreedingStats.uiControls.TextBoxSuggest();
@@ -354,6 +353,7 @@ private void InitializeComponent()
this.cbToggleOverlay = new System.Windows.Forms.CheckBox();
this.lbListening = new System.Windows.Forms.Label();
this.lbSpecies = new System.Windows.Forms.Label();
+ this.TbMessageLabel = new System.Windows.Forms.TextBox();
this.contextMenuStripLibraryHeader = new System.Windows.Forms.ContextMenuStrip(this.components);
this.toolStripMenuItemResetLibraryColumnWidths = new System.Windows.Forms.ToolStripMenuItem();
this.speciesSelector1 = new ARKBreedingStats.SpeciesSelector();
@@ -3397,19 +3397,6 @@ private void InitializeComponent()
this.panelToolBar.Size = new System.Drawing.Size(1878, 54);
this.panelToolBar.TabIndex = 2;
//
- // TbMessageLabel
- //
- this.TbMessageLabel.AcceptsReturn = true;
- this.TbMessageLabel.BorderStyle = System.Windows.Forms.BorderStyle.None;
- this.TbMessageLabel.Location = new System.Drawing.Point(470, 3);
- this.TbMessageLabel.Multiline = true;
- this.TbMessageLabel.Name = "TbMessageLabel";
- this.TbMessageLabel.ReadOnly = true;
- this.TbMessageLabel.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
- this.TbMessageLabel.Size = new System.Drawing.Size(889, 48);
- this.TbMessageLabel.TabIndex = 14;
- this.TbMessageLabel.Click += new System.EventHandler(this.TbMessageLabel_Click);
- //
// btImportLastExported
//
this.btImportLastExported.Location = new System.Drawing.Point(379, 3);
@@ -3488,6 +3475,19 @@ private void InitializeComponent()
this.lbSpecies.TabIndex = 0;
this.lbSpecies.Text = "Species";
//
+ // TbMessageLabel
+ //
+ this.TbMessageLabel.AcceptsReturn = true;
+ this.TbMessageLabel.BorderStyle = System.Windows.Forms.BorderStyle.None;
+ this.TbMessageLabel.Location = new System.Drawing.Point(470, 3);
+ this.TbMessageLabel.Multiline = true;
+ this.TbMessageLabel.Name = "TbMessageLabel";
+ this.TbMessageLabel.ReadOnly = true;
+ this.TbMessageLabel.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
+ this.TbMessageLabel.Size = new System.Drawing.Size(889, 48);
+ this.TbMessageLabel.TabIndex = 14;
+ this.TbMessageLabel.Click += new System.EventHandler(this.TbMessageLabel_Click);
+ //
// contextMenuStripLibraryHeader
//
this.contextMenuStripLibraryHeader.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
diff --git a/ARKBreedingStats/Form1.collection.cs b/ARKBreedingStats/Form1.collection.cs
index 568f16a8..4bcecf93 100644
--- a/ARKBreedingStats/Form1.collection.cs
+++ b/ARKBreedingStats/Form1.collection.cs
@@ -12,6 +12,7 @@
using System.Threading;
using System.Windows.Forms;
using System.Xml.Serialization;
+using ARKBreedingStats.importExportGun;
using ARKBreedingStats.uiControls;
using ARKBreedingStats.utils;
@@ -670,6 +671,9 @@ private void SaveDebugFile()
SetMessageLabelText("A File with the current library and the values in the extractor has been created and copied to the clipboard. You can paste this file to a folder to add it to an issue report.", MessageBoxIcon.Information, tempZipFilePath);
}
+ ///
+ /// Zipped library files are often error reports.
+ ///
private bool OpenZippedLibrary(string filePath)
{
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
@@ -768,5 +772,104 @@ private void OpenRecentlyUsedFile(object sender, EventArgs e)
)
LoadCollectionFile(mi.Text);
}
+
+ ///
+ /// Imports creature from file created by the export gun mod.
+ /// Returns true if the last imported creature already exists in the library.
+ ///
+ private bool ImportExportGunFiles(string[] filePaths, out bool creatureAdded, out Creature lastAddedCreature)
+ {
+ creatureAdded = false;
+ var newCreatures = new List();
+
+ var importedCounter = 0;
+ var importFailedCounter = 0;
+ string lastError = null;
+ string lastCreatureFilePath = null;
+ string serverMultipliersHash = null;
+ bool? multipliersImportSuccessful = null;
+ string serverImportResult = null;
+ bool creatureAlreadyExists = false;
+
+ foreach (var filePath in filePaths)
+ {
+ var c = ImportExportGun.ImportCreature(filePath, out lastError, out serverMultipliersHash);
+ if (c != null)
+ {
+ newCreatures.Add(c);
+ importedCounter++;
+ lastCreatureFilePath = filePath;
+ }
+ else if (lastError != null)
+ {
+ // file could be a server multiplier file, try to read it that way
+ var esm = ImportExportGun.ReadServerMultipliers(filePath, out var serverImportResultTemp);
+ if (esm != null)
+ {
+ multipliersImportSuccessful = ImportExportGun.SetServerMultipliers(_creatureCollection, esm, Path.GetFileNameWithoutExtension(filePath));
+ serverImportResult = serverImportResultTemp;
+ continue;
+ }
+
+ importFailedCounter++;
+ MessageBoxes.ShowMessageBox(lastError);
+ }
+ }
+
+ if (!string.IsNullOrEmpty(serverMultipliersHash) && _creatureCollection.ServerMultipliersHash != serverMultipliersHash)
+ {
+ // current server multipliers might be outdated, import them again
+ var serverMultiplierFilePath = Path.Combine(Path.GetDirectoryName(lastCreatureFilePath), "Servers", serverMultipliersHash + ".sav");
+ multipliersImportSuccessful = ImportExportGun.ImportServerMultipliers(_creatureCollection, serverMultiplierFilePath, serverMultipliersHash, out serverImportResult);
+ }
+
+ lastAddedCreature = newCreatures.LastOrDefault();
+ if (lastAddedCreature != null)
+ {
+ creatureAlreadyExists = IsCreatureAlreadyInLibrary(lastAddedCreature.guid, lastAddedCreature.ArkId, out _);
+ creatureAdded = true;
+ }
+
+ _creatureCollection.MergeCreatureList(newCreatures, true);
+ UpdateCreatureParentLinkingSort();
+
+ var resultText = (importedCounter > 0 || importFailedCounter > 0
+ ? $"Imported {importedCounter} creatures successfully.{(importFailedCounter > 0 ? $"Failed to import {importFailedCounter} files. Last error:{Environment.NewLine}{lastError}" : $"{Environment.NewLine}Last file: {lastCreatureFilePath}")}"
+ : string.Empty)
+ + (string.IsNullOrEmpty(serverImportResult)
+ ? string.Empty
+ : (importedCounter > 0 || importFailedCounter > 0 ? Environment.NewLine : string.Empty)
+ + serverImportResult);
+
+ SetMessageLabelText(resultText, importFailedCounter > 0 || multipliersImportSuccessful == false ? MessageBoxIcon.Error : MessageBoxIcon.Information, lastCreatureFilePath);
+ return creatureAlreadyExists;
+ }
+
+ ///
+ /// Call after creatures were added (imported) to the library. Updates parent linkings, creature lists, set collection as changed
+ ///
+ private void UpdateCreatureParentLinkingSort()
+ {
+ UpdateParents(_creatureCollection.creatures);
+
+ foreach (var creature in _creatureCollection.creatures)
+ {
+ creature.RecalculateAncestorGenerations();
+ }
+
+ UpdateIncubationParents(_creatureCollection);
+
+ // update UI
+ SetCollectionChanged(true);
+ UpdateCreatureListings();
+
+ if (_creatureCollection.creatures.Any())
+ tabControlMain.SelectedTab = tabPageLibrary;
+
+ // reapply last sorting
+ SortLibrary();
+
+ UpdateTempCreatureDropDown();
+ }
}
}
diff --git a/ARKBreedingStats/Form1.cs b/ARKBreedingStats/Form1.cs
index 233a9083..04e21e5a 100644
--- a/ARKBreedingStats/Form1.cs
+++ b/ARKBreedingStats/Form1.cs
@@ -12,6 +12,7 @@
using System.Diagnostics;
using System.Drawing;
using System.IO;
+using System.IO.Compression;
using System.Linq;
using System.Windows.Forms;
using ARKBreedingStats.mods;
@@ -1012,18 +1013,10 @@ void AddIfNotContains(List list, string name)
serverList.Sort();
// owners
- foreach (var owner in ownerList)
- {
- if (!string.IsNullOrEmpty(owner) && !tribesControl1.PlayerExists(owner))
- tribesControl1.AddPlayer(owner);
- }
+ tribesControl1.AddPlayers(ownerList);
// tribes
- foreach (var tribe in tribesList)
- {
- if (!string.IsNullOrEmpty(tribe) && !tribesControl1.TribeExists(tribe))
- tribesControl1.AddTribe(tribe);
- }
+ tribesControl1.AddTribes(tribesList);
///// Apply autocomplete lists
// owners
@@ -1174,10 +1167,26 @@ private void newToolStripMenuItem_Click(object sender, EventArgs e)
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
- if (UnsavedChanges() && CustomMessageBox.Show(Loc.S("Collection changed discard and quit?"),
- Loc.S("Discard changes?"), Loc.S("Discard changes and quit"), buttonCancel: Loc.S("Cancel quitting"),
- icon: MessageBoxIcon.Warning) != DialogResult.Yes)
- e.Cancel = true;
+ if (UnsavedChanges())
+ {
+ switch (CustomMessageBox.Show(Loc.S("Collection changed discard and quit?"),
+ Loc.S("Discard changes?"), buttonYes: Loc.S("Save and quit"), buttonNo: Loc.S("Discard changes and quit"), buttonCancel: Loc.S("Cancel quitting"),
+ icon: MessageBoxIcon.Warning))
+ {
+ case DialogResult.Yes:
+ SaveCollection();
+ break;
+ case DialogResult.No:
+ break;
+ case DialogResult.Cancel:
+ e.Cancel = true;
+ break;
+ default:
+ e.Cancel = true;
+ break;
+ }
+ }
+
}
///
@@ -1342,18 +1351,7 @@ private void SetMessageLabelText(string text = null, MessageBoxIcon icon = Messa
}
// a TextBox needs \r\n for a new line, only \n will not result in a line break.
TbMessageLabel.Text = text;
- _librarySelectionInfoClickPath = path;
-
- if (string.IsNullOrEmpty(path))
- {
- TbMessageLabel.Cursor = null;
- _tt.SetToolTip(TbMessageLabel, null);
- }
- else
- {
- TbMessageLabel.Cursor = Cursors.Hand;
- _tt.SetToolTip(TbMessageLabel, Loc.S("ClickDisplayFile"));
- }
+ SetMessageLabelLink(path);
switch (icon)
{
@@ -1372,6 +1370,25 @@ private void SetMessageLabelText(string text = null, MessageBoxIcon icon = Messa
}
}
+ ///
+ /// If valid path to file or folder, the user can click on the message to display the path in the explorer
+ ///
+ private void SetMessageLabelLink(string path = null)
+ {
+ _librarySelectionInfoClickPath = path;
+
+ if (string.IsNullOrEmpty(path))
+ {
+ TbMessageLabel.Cursor = null;
+ _tt.SetToolTip(TbMessageLabel, null);
+ }
+ else
+ {
+ TbMessageLabel.Cursor = Cursors.Hand;
+ _tt.SetToolTip(TbMessageLabel, Loc.S("ClickDisplayFile"));
+ }
+ }
+
///
/// Contains the path to open if the library selection info label is clicked, used to open the path in the explorer.
///
@@ -3269,49 +3286,117 @@ private void Form1_DragDrop(object sender, DragEventArgs e)
{
if (!(e.Data.GetData(DataFormats.FileDrop) is string[] files && files.Any()))
return;
+ ProcessDroppedFiles(files);
+ }
+ private void ProcessDroppedFiles(string[] files)
+ {
string filePath = files[0];
- string ext = Path.GetExtension(filePath).ToLower();
+ // if first item is folder, only consider all files in first folder
if (File.GetAttributes(filePath).HasFlag(FileAttributes.Directory))
{
- ShowExportedCreatureListControl();
- _exportedCreatureList.LoadFilesInFolder(filePath);
+ // if folder contains .sav files (mod dino export gun)
+ files = Directory.GetFiles(filePath);
+ if (!files.Any())
+ {
+ MessageBoxes.ShowMessageBox("No files to import in first folder");
+ return;
+ }
+ filePath = files[0];
}
- else if (ext == ".ini")
+
+ switch (Path.GetExtension(filePath).ToLower())
{
- if (files.Length == 1)
- {
+ case ".gz":
+ OpenCompressedFile(filePath, true);
+ return;
+ case ".ini" when files.Length == 1:
ExtractExportedFileInExtractor(filePath);
- }
- else
- {
+ break;
+ case ".ini":
ShowExportedCreatureListControl();
_exportedCreatureList.LoadFiles(files);
- }
+ break;
+ case ".sav":
+ ImportExportGunFiles(files, out _, out _);
+ break;
+ case ".asb":
+ case ".xml":
+ {
+ if (DiscardChangesAndLoadNewLibrary())
+ {
+ LoadCollectionFile(filePath);
+ }
+
+ break;
+ }
+ case ".zip":
+ {
+ if (DiscardChangesAndLoadNewLibrary())
+ {
+ OpenZippedLibrary(filePath);
+ }
+
+ break;
+ }
+ case ".ark":
+ {
+ if (MessageBox.Show(
+ $"Import all of the creatures in the following ARK save file to the currently opened library?\n{filePath}",
+ "Import savefile?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
+ RunSavegameImport(new ATImportFileLocation(null, null, filePath));
+ break;
+ }
+ default:
+ DoOcr(filePath);
+ break;
}
- else if (ext == ".asb" || ext == ".xml")
+ }
+
+ private bool OpenCompressedFile(string filePath, bool usegzip)
+ {
+ if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
+ return false;
+
+ try
{
- if (DiscardChangesAndLoadNewLibrary())
+ // get temp folder for zipping
+ var tempFolder = FileService.GetTempDirectory();
+ if (usegzip)
{
- LoadCollectionFile(filePath);
+ var fileName = Path.GetFileName(filePath);
+ var extractedFilePath = Path.Combine(tempFolder, fileName.Substring(0, fileName.Length - Path.GetExtension(fileName).Length));
+
+ using (FileStream compressedFileStream = File.Open(filePath, FileMode.Open))
+ using (FileStream outputFileStream = File.Create(extractedFilePath))
+ using (var decompressor = new GZipStream(compressedFileStream, CompressionMode.Decompress))
+ decompressor.CopyTo(outputFileStream);
}
- }
- else if (ext == ".zip")
- {
- if (DiscardChangesAndLoadNewLibrary())
+ else
{
- OpenZippedLibrary(filePath);
+ // unzip files
+ ZipFile.ExtractToDirectory(filePath, tempFolder);
}
+ var extractedFilePaths = Directory.GetFiles(tempFolder);
+ if (!extractedFilePaths.Any())
+ {
+ MessageBoxes.ShowMessageBox("No files in archive found: " + filePath, "Error while loading compressed file");
+ return false;
+ }
+ ProcessDroppedFiles(extractedFilePaths);
+
+ // delete temp extracted file
+ foreach (var f in extractedFilePaths)
+ FileService.TryDeleteFile(f);
+ FileService.TryDeleteDirectory(tempFolder);
}
- else if (ext == ".ark")
+ catch (Exception ex)
{
- if (MessageBox.Show(
- $"Import all of the creatures in the following ARK save file to the currently opened library?\n{filePath}",
- "Import savefile?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
- RunSavegameImport(new ATImportFileLocation(null, null, filePath));
+ MessageBoxes.ExceptionMessageBox(ex, "Error while loading compressed file " + filePath);
+ return false;
}
- else
- DoOcr(files[0]);
+
+ return true;
}
private void toolStripMenuItemCopyCreatureName_Click(object sender, EventArgs e)
diff --git a/ARKBreedingStats/Form1.importExported.cs b/ARKBreedingStats/Form1.importExported.cs
index a2978141..09d9ee45 100644
--- a/ARKBreedingStats/Form1.importExported.cs
+++ b/ARKBreedingStats/Form1.importExported.cs
@@ -170,77 +170,71 @@ private void ImportExportedAddIfPossible_WatcherThread(string filePath, importEx
///
private void ImportExportedAddIfPossible(string filePath)
{
- var loadResult = ExtractExportedFileInExtractor(filePath);
- if (!loadResult.HasValue) return;
-
- bool alreadyExists = loadResult.Value;
+ bool alreadyExists;
bool addedToLibrary = false;
- bool uniqueExtraction = _extractor.UniqueResults
- || (alreadyExists && _extractor.ValidResults);
- bool copyNameToClipboard = Properties.Settings.Default.copyNameToClipboardOnImportWhenAutoNameApplied
- && (Properties.Settings.Default.applyNamePatternOnAutoImportAlways
- || Properties.Settings.Default.applyNamePatternOnImportIfEmptyName
- || (!alreadyExists && Properties.Settings.Default.applyNamePatternOnAutoImportForNewCreatures)
- );
- Species species = speciesSelector1.SelectedSpecies;
+ bool uniqueExtraction = false;
Creature creature = null;
+ bool copiedNameToClipboard = false;
+ Creature[] creaturesOfSpecies = null;
- if (uniqueExtraction
- && Properties.Settings.Default.OnAutoImportAddToLibrary)
+ switch (Path.GetExtension(filePath))
{
- creature = AddCreatureToCollection(true, goToLibraryTab: Properties.Settings.Default.AutoImportGotoLibraryAfterSuccess);
- SetMessageLabelText($"Successful {(alreadyExists ? "updated" : "added")} {creature.name} ({species.name}) of the exported file\r\n" + filePath, MessageBoxIcon.Information, filePath);
- addedToLibrary = true;
- }
+ case ".ini":
+ var loadResult = ExtractExportedFileInExtractor(filePath);
+ if (loadResult == null) return;
+ alreadyExists = loadResult.Value;
- bool topLevels = false;
- bool newTopLevels = false;
+ uniqueExtraction = _extractor.UniqueResults
+ || (alreadyExists && _extractor.ValidResults);
+ Species species = speciesSelector1.SelectedSpecies;
- // give feedback in overlay
- string infoText;
- Color textColor;
- const int colorSaturation = 200;
- if (uniqueExtraction)
- {
- var sb = new StringBuilder();
- sb.AppendLine($"{species.name} \"{creatureInfoInputExtractor.CreatureName}\" {(alreadyExists ? "updated in " : "added to")} the library.");
- if (addedToLibrary && copyNameToClipboard)
- sb.AppendLine("Name copied to clipboard.");
-
- for (int s = 0; s < Stats.StatsCount; s++)
- {
- int statIndex = Stats.DisplayOrder[s];
- if (!species.UsesStat(statIndex)) continue;
-
- sb.Append($"{Utils.StatName(statIndex, true, species.statNames)}: { _statIOs[statIndex].LevelWild} ({_statIOs[statIndex].BreedingValue})");
- if (_statIOs[statIndex].TopLevel.HasFlag(LevelStatus.NewTopLevel))
+ if (uniqueExtraction
+ && Properties.Settings.Default.OnAutoImportAddToLibrary)
{
- sb.Append($" {Loc.S("newTopLevel")}");
- newTopLevels = true;
+ creature = AddCreatureToCollection(true, goToLibraryTab: Properties.Settings.Default.AutoImportGotoLibraryAfterSuccess);
+ SetMessageLabelText($"Successful {(alreadyExists ? "updated" : "added")} {creature.name} ({species.name}) of the exported file\r\n" + filePath, MessageBoxIcon.Information, filePath);
+ addedToLibrary = true;
}
- else if (_statIOs[statIndex].TopLevel.HasFlag(LevelStatus.TopLevel))
+
+ copiedNameToClipboard = Properties.Settings.Default.copyNameToClipboardOnImportWhenAutoNameApplied
+ && (Properties.Settings.Default.applyNamePatternOnAutoImportAlways
+ || Properties.Settings.Default.applyNamePatternOnImportIfEmptyName
+ || (!alreadyExists && Properties.Settings.Default.applyNamePatternOnAutoImportForNewCreatures)
+ );
+
+ break;
+ case ".sav":
+ alreadyExists = ImportExportGunFiles(new[] { filePath }, out addedToLibrary, out creature);
+ if (!addedToLibrary || creature == null) return;
+ uniqueExtraction = true;
+
+ if (Properties.Settings.Default.applyNamePatternOnAutoImportAlways
+ || (Properties.Settings.Default.applyNamePatternOnImportIfEmptyName
+ && string.IsNullOrEmpty(creature.name))
+ || (!alreadyExists
+ && Properties.Settings.Default.applyNamePatternOnAutoImportForNewCreatures)
+ )
{
- sb.Append($" {Loc.S("topLevel")}");
- topLevels = true;
+ creaturesOfSpecies = _creatureCollection.creatures.Where(c => c.Species == creature.Species).ToArray();
+ creature.name = NamePattern.GenerateCreatureName(creature, creaturesOfSpecies,
+ _topLevels.TryGetValue(creature.Species, out var topLevels) ? topLevels : null,
+ _lowestLevels.TryGetValue(creature.Species, out var lowestLevels) ? lowestLevels : null,
+ _customReplacingNamingPattern, false, 0);
+
+ if (Properties.Settings.Default.copyNameToClipboardOnImportWhenAutoNameApplied)
+ {
+ Clipboard.SetText(string.IsNullOrEmpty(creature.name)
+ ? ""
+ : creature.name);
+ copiedNameToClipboard = true;
+ }
}
- sb.AppendLine();
- }
- infoText = sb.ToString();
- textColor = Color.FromArgb(colorSaturation, 255, colorSaturation);
- }
- else
- {
- infoText = $"Creature \"{creatureInfoInputExtractor.CreatureName}\" couldn't be extracted uniquely, manual level selection is necessary.";
- textColor = Color.FromArgb(255, colorSaturation, colorSaturation);
+ break;
+ default: return;
}
- if (_overlay != null)
- {
- _overlay.SetInfoText(infoText, textColor);
- if (Properties.Settings.Default.DisplayInheritanceInOverlay && creature != null)
- _overlay.SetInheritanceCreatures(creature, creature.Mother, creature.Father);
- }
+ OverlayFeedbackForImport(creature, uniqueExtraction, alreadyExists, addedToLibrary, copiedNameToClipboard, out bool hasTopLevels, out bool hasNewTopLevels);
if (addedToLibrary)
{
@@ -267,7 +261,8 @@ private void ImportExportedAddIfPossible(string filePath)
string newFileName = Properties.Settings.Default.AutoImportedExportFileRename && !string.IsNullOrWhiteSpace(namePattern)
? NamePattern.GenerateCreatureName(creature,
- _creatureCollection.creatures.Where(c => c.Species == speciesSelector1.SelectedSpecies).ToArray(), null, null,
+ creaturesOfSpecies ?? _creatureCollection.creatures.Where(c => c.Species == creature.Species).ToArray(),
+ null, null,
_customReplacingNamingPattern, false, -1, false, namePattern)
: Path.GetFileName(filePath);
@@ -286,10 +281,13 @@ private void ImportExportedAddIfPossible(string filePath)
}
if (FileService.TryMoveFile(filePath, newFilePath))
+ {
_librarySelectionInfoClickPath = newFilePath;
+ SetMessageLabelLink(newFilePath);
+ }
}
}
- else if (!uniqueExtraction && copyNameToClipboard)
+ else if (!uniqueExtraction && copiedNameToClipboard)
{
// extraction failed, user might expect the name of the new creature in the clipboard
Clipboard.SetText("Automatic extraction was not possible");
@@ -301,9 +299,9 @@ private void ImportExportedAddIfPossible(string filePath)
{
if (alreadyExists)
SoundFeedback.BeepSignal(SoundFeedback.FeedbackSounds.Indifferent);
- if (newTopLevels)
+ if (hasNewTopLevels)
SoundFeedback.BeepSignal(SoundFeedback.FeedbackSounds.Great);
- else if (topLevels)
+ else if (hasTopLevels)
SoundFeedback.BeepSignal(SoundFeedback.FeedbackSounds.Good);
else
SoundFeedback.BeepSignal(SoundFeedback.FeedbackSounds.Success);
@@ -321,6 +319,59 @@ private void ImportExportedAddIfPossible(string filePath)
}
}
+ ///
+ /// Give feedback in overlay for imported creature.
+ ///
+ private void OverlayFeedbackForImport(Creature creature, bool uniqueExtraction, bool alreadyExists, bool addedToLibrary, bool copiedNameToClipboard, out bool topLevels, out bool newTopLevels)
+ {
+ topLevels = false;
+ newTopLevels = false;
+ string infoText;
+ Color textColor;
+ const int colorSaturation = 200;
+ if (uniqueExtraction)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine($"{creature.Species.name} \"{creature.name}\" {(alreadyExists ? "updated in " : "added to")} the library.");
+ if (addedToLibrary && copiedNameToClipboard)
+ sb.AppendLine("Name copied to clipboard.");
+
+ for (int s = 0; s < Stats.StatsCount; s++)
+ {
+ int statIndex = Stats.DisplayOrder[s];
+ if (!creature.Species.UsesStat(statIndex)) continue;
+
+ sb.Append($"{Utils.StatName(statIndex, true, creature.Species.statNames)}: {creature.levelsWild[statIndex]} ({creature.valuesBreeding[statIndex]})");
+ if (_statIOs[statIndex].TopLevel.HasFlag(LevelStatus.NewTopLevel))
+ {
+ sb.Append($" {Loc.S("newTopLevel")}");
+ newTopLevels = true;
+ }
+ else if (creature.topBreedingStats[statIndex])
+ {
+ sb.Append($" {Loc.S("topLevel")}");
+ topLevels = true;
+ }
+ sb.AppendLine();
+ }
+
+ infoText = sb.ToString();
+ textColor = Color.FromArgb(colorSaturation, 255, colorSaturation);
+ }
+ else
+ {
+ infoText = $"Creature \"{creature.name}\" couldn't be extracted uniquely, manual level selection is necessary.";
+ textColor = Color.FromArgb(255, colorSaturation, colorSaturation);
+ }
+
+ if (_overlay != null)
+ {
+ _overlay.SetInfoText(infoText, textColor);
+ if (Properties.Settings.Default.DisplayInheritanceInOverlay)
+ _overlay.SetInheritanceCreatures(creature, creature.Mother, creature.Father);
+ }
+ }
+
private void ExportedCreatureList_CheckGuidInLibrary(importExported.ExportedCreatureControl exportedCreatureControl)
{
Creature cr = _creatureCollection.creatures.FirstOrDefault(c => c.guid == exportedCreatureControl.creatureValues.guid);
diff --git a/ARKBreedingStats/Form1.importSave.cs b/ARKBreedingStats/Form1.importSave.cs
index 20e1e469..cc93de29 100644
--- a/ARKBreedingStats/Form1.importSave.cs
+++ b/ARKBreedingStats/Form1.importSave.cs
@@ -37,11 +37,10 @@ private async Task RunSavegameImport(ATImportFileLocation atImportFileLo
ToolStripStatusLabelImport.Text = $"{Loc.S("ImportingSavegame")} {atImportFileLocation.ConvenientName}";
ToolStripStatusLabelImport.Visible = true;
+ string workingCopyFolderPath = Properties.Settings.Default.savegameExtractionPath;
+ string workingCopyFilePath = null;
try
{
- string workingCopyFolderPath = Properties.Settings.Default.savegameExtractionPath;
- string workingCopyFilePath;
-
// working dir not configured? use temp dir
// luser configured savegame folder as working dir? use temp dir instead
if (string.IsNullOrWhiteSpace(workingCopyFolderPath) ||
@@ -99,31 +98,16 @@ private async Task RunSavegameImport(ATImportFileLocation atImportFileLo
}
}
- await ImportSavegame.ImportCollectionFromSavegame(_creatureCollection, workingCopyFilePath,
- atImportFileLocation.ServerName);
-
- FileService.TryDeleteFile(workingCopyFilePath);
-
- UpdateParents(_creatureCollection.creatures);
-
- foreach (var creature in _creatureCollection.creatures)
+ if (new FileInfo(workingCopyFilePath).Length > int.MaxValue
+ && MessageBox.Show("The file is very large (> 2 GB), importing can take some minutes. Continue?", "Importing large file", MessageBoxButtons.YesNo) != DialogResult.Yes)
{
- creature.RecalculateAncestorGenerations();
+ return "Import aborted by user because of large file size";
}
- UpdateIncubationParents(_creatureCollection);
-
- // update UI
- SetCollectionChanged(true);
- UpdateCreatureListings();
-
- if (_creatureCollection.creatures.Any())
- tabControlMain.SelectedTab = tabPageLibrary;
-
- // reapply last sorting
- SortLibrary();
+ await ImportSavegame.ImportCollectionFromSavegame(_creatureCollection, workingCopyFilePath,
+ atImportFileLocation.ServerName);
- UpdateTempCreatureDropDown();
+ UpdateCreatureParentLinkingSort();
// if unknown mods are used in the savegame-file and the user wants to load the missing mod-files, do it
if (_creatureCollection.ModValueReloadNeeded
@@ -137,6 +121,7 @@ await ImportSavegame.ImportCollectionFromSavegame(_creatureCollection, workingCo
}
finally
{
+ FileService.TryDeleteFile(workingCopyFilePath);
TsbQuickSaveGameImport.Enabled = true;
TsbQuickSaveGameImport.BackColor = SystemColors.Control;
ToolStripStatusLabelImport.Visible = false;
diff --git a/ARKBreedingStats/Form1.library.cs b/ARKBreedingStats/Form1.library.cs
index d869e293..6c48f4b0 100644
--- a/ARKBreedingStats/Form1.library.cs
+++ b/ARKBreedingStats/Form1.library.cs
@@ -533,8 +533,6 @@ private void CalculateTopStats(List creatures)
///
private bool UpdateParents(IEnumerable creatures)
{
- List placeholderAncestors = new List();
-
Dictionary creatureGuids;
bool duplicatesWereRemoved = false;
@@ -671,6 +669,8 @@ bool AreByteArraysEqual(byte[] firstArray, byte[] secondArray)
duplicatesWereRemoved = true;
}
+ var placeholderAncestors = new Dictionary();
+
foreach (Creature c in creatures)
{
if (c.motherGuid == Guid.Empty && c.fatherGuid == Guid.Empty) continue;
@@ -689,7 +689,7 @@ bool AreByteArraysEqual(byte[] firstArray, byte[] secondArray)
c.Father = father;
}
- _creatureCollection.creatures.AddRange(placeholderAncestors);
+ _creatureCollection.creatures.AddRange(placeholderAncestors.Values);
return duplicatesWereRemoved;
}
@@ -704,13 +704,12 @@ bool AreByteArraysEqual(byte[] firstArray, byte[] secondArray)
/// Name of the creature to create
/// Sex of the creature to create
///
- private Creature EnsurePlaceholderCreature(List placeholders, Creature tmpl, Guid guid, string name, Sex sex)
+ private Creature EnsurePlaceholderCreature(Dictionary placeholders, Creature tmpl, Guid guid, string name, Sex sex)
{
if (guid == Guid.Empty)
return null;
- var existing = placeholders.FirstOrDefault(ph => ph.guid == guid);
- if (existing != null)
- return existing;
+ if (placeholders.TryGetValue(guid, out var existingCreature))
+ return existingCreature;
if (string.IsNullOrEmpty(name))
name = (sex == Sex.Female ? "Mother" : "Father") + " of " + tmpl.name;
@@ -722,7 +721,7 @@ private Creature EnsurePlaceholderCreature(List placeholders, Creature
flags = CreatureFlags.Placeholder
};
- placeholders.Add(creature);
+ placeholders.Add(creature.guid, creature);
return creature;
}
diff --git a/ARKBreedingStats/ImportSavegame.cs b/ARKBreedingStats/ImportSavegame.cs
index 16cc975c..57328d9c 100644
--- a/ARKBreedingStats/ImportSavegame.cs
+++ b/ARKBreedingStats/ImportSavegame.cs
@@ -34,7 +34,7 @@ public static async Task ImportCollectionFromSavegame(CreatureCollection creatur
IEnumerable tamedCreatureObjects = gameObjectContainer
.Where(o => o.IsCreature()
&& o.IsTamed()
- && (importUnclaimedBabies || (o.IsCryo && Properties.Settings.Default.SaveImportCryo) || !o.IsUnclaimedBaby())
+ && (importUnclaimedBabies || (o.IsInCryo && Properties.Settings.Default.SaveImportCryo) || !o.IsUnclaimedBaby())
&& !ignoreClasses.Contains(o.ClassString));
if (!string.IsNullOrWhiteSpace(Properties.Settings.Default.ImportTribeNameFilter))
@@ -79,17 +79,13 @@ public static async Task ImportCollectionFromSavegame(CreatureCollection creatur
private static (GameObjectContainer, float) ReadSavegameFile(string fileName)
{
- if (new FileInfo(fileName).Length > int.MaxValue)
- {
- throw new Exception("Input file is too large.");
- }
-
ArkSavegame arkSavegame = new ArkSavegame();
bool PredicateCreatures(GameObject o) => !o.IsItem && (o.Parent != null || o.Components.Any());
- bool PredicateCreaturesAndCryopods(GameObject o) => (!o.IsItem && (o.Parent != null || o.Components.Any())) || o.ClassString.Contains("Cryopod") || o.ClassString.Contains("SoulTrap_");
+ bool PredicateCreaturesAndCryopods(GameObject o) => (!o.IsItem && (o.Parent != null || o.Components.Any())) || o.ClassString.Contains("Cryopod") || o.ClassString.Contains("SoulTrap_") || o.ClassString.Contains("Vivarium_");
- using (Stream stream = new MemoryStream(File.ReadAllBytes(fileName)))
+ var largeFile = new FileInfo(fileName).Length > int.MaxValue;
+ using (var stream = largeFile ? (Stream)new FileStream(fileName, FileMode.Open) : new MemoryStream(File.ReadAllBytes(fileName)))
using (ArkArchive archive = new ArkArchive(stream))
{
arkSavegame.ReadBinary(archive, ReadingOptions.Create()
@@ -249,7 +245,7 @@ private Creature ConvertGameObject(GameObject creatureObject, int? levelStep)
creature.Status = CreatureStatus.Dead; // dead is always dead
}
- if (creatureObject.IsCryo)
+ if (creatureObject.IsInCryo)
creature.Status = CreatureStatus.Cryopod;
creature.RecalculateCreatureValues(levelStep);
diff --git a/ARKBreedingStats/NamePatterns/NamePattern.cs b/ARKBreedingStats/NamePatterns/NamePattern.cs
index 388a5e77..68740fc1 100644
--- a/ARKBreedingStats/NamePatterns/NamePattern.cs
+++ b/ARKBreedingStats/NamePatterns/NamePattern.cs
@@ -101,9 +101,9 @@ public static string GenerateCreatureName(Creature creature, Creature[] sameSpec
{
MessageBox.Show($"The generated name for the creature\n{name}\nalready exists in the library.\n\nConsider adding {{n}} or {{sn}} in the pattern to generate unique names.", "Name already exists", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
- else if (showTooLongWarning && name.Length > 24)
+ else if (showTooLongWarning && name.Length > Ark.MaxCreatureNameLength)
{
- MessageBox.Show("The generated name is longer than 24 characters, the name will look like this in game:\n" + name.Substring(0, 24), "Name too long for game", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ MessageBox.Show($"The generated name is longer than {Ark.MaxCreatureNameLength} characters, the name will look like this in game:\n" + name.Substring(0, Ark.MaxCreatureNameLength), "Name too long for game", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
return name;
diff --git a/ARKBreedingStats/NamePatterns/NamePatternFunctions.cs b/ARKBreedingStats/NamePatterns/NamePatternFunctions.cs
index 2e0f024b..efa45ff7 100644
--- a/ARKBreedingStats/NamePatterns/NamePatternFunctions.cs
+++ b/ARKBreedingStats/NamePatterns/NamePatternFunctions.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using System.Security.Cryptography;
+using System.Text;
using System.Text.RegularExpressions;
using ARKBreedingStats.Library;
using ARKBreedingStats.species;
@@ -55,7 +57,8 @@ private static string ParametersInvalid(string specificError, string expression,
{"time", FunctionTime},
{"color", FunctionColor},
{"colornew", FunctionColorNew},
- {"indexof", FunctionIndexOf}
+ {"indexof", FunctionIndexOf},
+ {"md5", FunctionMd5}
};
private static string FunctionIf(Match m, NamePatternParameters p)
@@ -131,7 +134,7 @@ private static string FunctionExpr(Match m, NamePatternParameters p)
private static string FunctionLen(Match m, NamePatternParameters p)
{
// returns the length of the parameter
- return m.Groups[2].Value.Length.ToString();
+ return UnEscapeSpecialCharacters(m.Groups[2].Value).Length.ToString();
}
private static string FunctionSubString(Match m, NamePatternParameters p)
@@ -250,7 +253,7 @@ private static string FunctionReplace(Match m, NamePatternParameters p)
if (string.IsNullOrEmpty(m.Groups[2].Value)
|| string.IsNullOrEmpty(m.Groups[3].Value))
return m.Groups[2].Value;
- return m.Groups[2].Value.Replace(m.Groups[3].Value.Replace(" ", " "), m.Groups[4].Value.Replace(" ", " "));
+ return m.Groups[2].Value.Replace(UnEscapeSpecialCharacters(m.Groups[3].Value), UnEscapeSpecialCharacters(m.Groups[4].Value));
}
private static string FunctionRegExReplace(Match m, NamePatternParameters p)
@@ -270,7 +273,13 @@ private static string FunctionRegExReplace(Match m, NamePatternParameters p)
///
/// Functions cannot process the characters {|} directly, they have to be replaced to be used.
///
- public static string UnEscapeSpecialCharacters(string text) => text?.Replace("{", "{").Replace("&vline;", "|").Replace("}", "}");
+ public static string UnEscapeSpecialCharacters(string text) => text?
+ .Replace("{", "{")
+ .Replace("&vline;", "|")
+ .Replace("}", "}")
+ .Replace(" ", " ") // for backwards compatibility
+ .Replace("&sp;", " ")
+ ;
private static string FunctionCustomReplace(Match m, NamePatternParameters p)
{
@@ -334,6 +343,27 @@ private static string FunctionIndexOf(Match m, NamePatternParameters p)
int index = m.Groups[2].Value.IndexOf(m.Groups[3].Value);
return index >= 0 ? index.ToString() : string.Empty;
}
+
+ private static MD5 _md5;
+
+ private static string FunctionMd5(Match m, NamePatternParameters p)
+ {
+ if (_md5 == null) _md5 = MD5.Create();
+
+ var inputBytes = Encoding.ASCII.GetBytes(UnEscapeSpecialCharacters(m.Groups[2].Value));
+ var hashBytes = _md5.ComputeHash(inputBytes);
+
+ var sb = new StringBuilder();
+ foreach (var b in hashBytes)
+ sb.Append(b.ToString("X2"));
+
+ return sb.ToString();
+ }
+
+ public static void Dispose()
+ {
+ _md5?.Dispose();
+ }
}
internal struct NamePatternParameters
diff --git a/ARKBreedingStats/NamePatterns/PatternEditor.cs b/ARKBreedingStats/NamePatterns/PatternEditor.cs
index 9d9e057a..bcb8af92 100644
--- a/ARKBreedingStats/NamePatterns/PatternEditor.cs
+++ b/ARKBreedingStats/NamePatterns/PatternEditor.cs
@@ -419,9 +419,9 @@ private void InsertText(string text)
{ "spcsNm", "species name without vowels" },
{ "firstWordOfOldest", "the first word of the name of the first added creature of the species" },
- {"owner", "name of the owner of the creature" },
- {"tribe", "name of the tribe the creature belongs to" },
- {"server", "name of the server the creature is assigned to" },
+ { "owner", "name of the owner of the creature" },
+ { "tribe", "name of the tribe the creature belongs to" },
+ { "server", "name of the server the creature is assigned to" },
{ "sex", "sex (\"Male\", \"Female\", \"Unknown\")" },
{ "sex_short", "\"M\", \"F\", \"U\"" },
@@ -497,7 +497,10 @@ private void InsertText(string text)
{ "highest6s", "the name of the sixth highest stat-level of this creature (excluding torpidity)" },
};
- private static Dictionary FunctionExplanations() => new Dictionary()
+ // list of possible functions, expected format:
+ // key: name of function
+ // value: [syntax and explanation]\n[example]
+ private static Dictionary FunctionExplanations() => new Dictionary
{
{"if", "{{#if: string | if string is not empty | if string is empty }}, to check if a string is empty. E.g. you can check if a stat is a top stat of that species (i.e. highest in library).\n{{#if: {isTophp} | bestHP{hp} | notTopHP }}" },
{"ifexpr", "{{#ifexpr: expression | true | false }}, to check if an expression with two operands and one operator is true or false. Possible operators are ==, !=, <, <=, <, >=.\n{{#ifexpr: {topPercent} > 80 | true | false }}" },
@@ -518,6 +521,7 @@ private void InsertText(string text)
{"color","{{#color: regionId | return color name | return value even for unused regions }}. Returns the colorId of the region. If the second parameter is not empty, the color name will be returned. Unused regions will only return a value if the third value is not empty.\n{{#color: 0 | true }}"},
{"colorNew","{{#colorNew: regionId }}. Returns newInRegion if the region contains a color that is not yet available in that species. Returns newInSpecies if that color is not yet available in any region of that species.\n{{#colorNew: 0 }}"},
{"indexof","{{#indexof: source string | string to find }}. Returns the index of the second parameter in the first parameter. If the string is not contained, an empty string will be returned.\n{{#indexof: hello | ll }}"},
+ {"md5", "{{#md5: string }}, returns the md5 hash of a given string\n{{#md5: {hp}{st}{we} }}"}
};
private void btnClear_Click(object sender, EventArgs e)
diff --git a/ARKBreedingStats/Properties/AssemblyInfo.cs b/ARKBreedingStats/Properties/AssemblyInfo.cs
index ce447be0..62537ce8 100644
--- a/ARKBreedingStats/Properties/AssemblyInfo.cs
+++ b/ARKBreedingStats/Properties/AssemblyInfo.cs
@@ -30,6 +30,6 @@
// Revision
//
[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("0.55.0.0")]
+[assembly: AssemblyFileVersion("0.56.0.1")]
[assembly: NeutralResourcesLanguage("en")]
diff --git a/ARKBreedingStats/TribesControl.cs b/ARKBreedingStats/TribesControl.cs
index 96c6658c..14105b19 100644
--- a/ARKBreedingStats/TribesControl.cs
+++ b/ARKBreedingStats/TribesControl.cs
@@ -79,23 +79,20 @@ private void UpdatePlayerList()
{
listViewPlayer.Items.Clear();
Dictionary tribeRelColors = new Dictionary();
+
+ var tribeGroups = new Dictionary();
+ var lviPlayers = new List();
+
foreach (Player p in players)
{
// check if group of tribe exists
- ListViewGroup g = null;
- foreach (ListViewGroup lvg in listViewPlayer.Groups)
- {
- if (lvg.Header == p.Tribe)
- {
- g = lvg;
- break;
- }
- }
- if (g == null)
+ var tribeName = p.Tribe ?? string.Empty;
+ if (!tribeGroups.TryGetValue(tribeName, out var g))
{
g = new ListViewGroup(p.Tribe);
- listViewPlayer.Groups.Add(g);
+ tribeGroups[tribeName] = g;
}
+
if (p.Tribe != null && !tribeRelColors.ContainsKey(p.Tribe))
{
Color c = Color.White;
@@ -127,8 +124,11 @@ private void UpdatePlayerList()
};
if (!string.IsNullOrEmpty(p.Tribe))
lvi.SubItems[3].BackColor = tribeRelColors[p.Tribe];
- listViewPlayer.Items.Add(lvi);
+ lviPlayers.Add(lvi);
}
+
+ listViewPlayer.Groups.AddRange(tribeGroups.Values.ToArray());
+ listViewPlayer.Items.AddRange(lviPlayers.ToArray());
}
///
@@ -137,6 +137,7 @@ private void UpdatePlayerList()
private void UpdateTribeList()
{
listViewTribes.Items.Clear();
+ var tribeList = new List();
foreach (Tribe t in tribes)
{
ListViewItem lvi = new ListViewItem(new[] { t.TribeName, t.TribeRelation.ToString() })
@@ -145,8 +146,9 @@ private void UpdateTribeList()
Tag = t
};
lvi.SubItems[1].BackColor = RelationColor(t.TribeRelation);
- listViewTribes.Items.Add(lvi);
+ tribeList.Add(lvi);
}
+ listViewTribes.Items.AddRange(tribeList.ToArray());
UpdateTribeSuggestions();
}
@@ -269,6 +271,23 @@ public void AddPlayer(string name = null)
textBoxPlayerName.Focus();
}
+ ///
+ /// Add players if they aren't yet in the list.
+ ///
+ ///
+ public void AddPlayers(List playerNames)
+ {
+ if (playerNames == null) return;
+
+ var existingPlayers = players.Select(p => p.PlayerName).ToHashSet();
+ var newPlayers = playerNames
+ .Where(newPlayer => !string.IsNullOrEmpty(newPlayer) && !existingPlayers.Contains(newPlayer))
+ .Select(p => new Player { PlayerName = p }).ToArray();
+ if (!newPlayers.Any()) return;
+ players.AddRange(newPlayers);
+ UpdatePlayerList();
+ }
+
///
/// Add tribe to tribe list.
///
@@ -288,6 +307,23 @@ public void AddTribe(string name = null)
textBoxTribeName.Focus();
}
+ ///
+ /// Add tribes if they aren't yet in the list.
+ ///
+ ///
+ public void AddTribes(List tribeNames)
+ {
+ if (tribeNames == null) return;
+
+ var existingTribes = tribes.Select(t => t.TribeName).ToHashSet();
+ var newTribes = tribeNames
+ .Where(newTribe => !string.IsNullOrEmpty(newTribe) && !existingTribes.Contains(newTribe))
+ .Select(t => new Tribe { TribeName = t }).ToArray();
+ if (!newTribes.Any()) return;
+ tribes.AddRange(newTribes);
+ UpdateTribeList();
+ }
+
private void DeleteSelectedPlayer()
{
if (listViewPlayer.SelectedIndices.Count > 0 && (MessageBox.Show("Delete selected Players?", "Delete?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes))
diff --git a/ARKBreedingStats/_manifest.json b/ARKBreedingStats/_manifest.json
index 7efe0cf7..9b371275 100644
--- a/ARKBreedingStats/_manifest.json
+++ b/ARKBreedingStats/_manifest.json
@@ -4,7 +4,7 @@
"ARK Smart Breeding": {
"Id": "ARK Smart Breeding",
"Category": "main",
- "version": "0.55.0.0"
+ "version": "0.56.0.1"
},
"SpeciesColorImages": {
"Id": "SpeciesColorImages",
diff --git a/ARKBreedingStats/importExportGun/ExportGunCreatureFile.cs b/ARKBreedingStats/importExportGun/ExportGunCreatureFile.cs
new file mode 100644
index 00000000..e630f0bb
--- /dev/null
+++ b/ARKBreedingStats/importExportGun/ExportGunCreatureFile.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace ARKBreedingStats.importExportGun
+{
+ ///
+ /// Structure of the export file created by the export gun mod.
+ ///
+ [JsonObject]
+ internal class ExportGunCreatureFile
+ {
+ public string DinoName { get; set; }
+ public string SpeciesName { get; set; }
+ public string TribeName { get; set; }
+ public string TamerString { get; set; }
+ public string OwningPlayerName { get; set; }
+ public string ImprinterName { get; set; }
+ public int OwningPlayerID { get; set; }
+ public int DinoID1 { get; set; }
+ public int DinoID2 { get; set; }
+ public Ancestry Ancestry { get; set; }
+ public string BlueprintPath { get; set; }
+ public Stat[] Stats { get; set; }
+ public byte[] ColorSetIndices { get; set; }
+ public Dictionary ColorSetValues { get; set; }
+ public bool IsFemale { get; set; }
+ public float NextAllowedMatingTimeDuration { get; set; }
+ public float BabyAge { get; set; }
+ public bool MutagenApplied { get; set; }
+ public bool Neutered { get; set; }
+ public int RandomMutationsMale { get; set; }
+ public int RandomMutationsFemale { get; set; }
+ ///
+ /// Hash of the server multipliers, used to make sure the stat multipliers are from this server when importing via the export gun mod.
+ ///
+ public string ServerMultipliersHash { get; set; }
+ public float TameEffectiveness { get; set; }
+ public int BaseCharacterLevel { get; set; }
+ public float DinoImprintingQuality { get; set; }
+ }
+
+ [JsonObject]
+ internal class Ancestry
+ {
+ public string MaleName { get; set; }
+ public int MaleDinoId1 { get; set; }
+ public int MaleDinoId2 { get; set; }
+ public string FemaleName { get; set; }
+ public int FemaleDinoId1 { get; set; }
+ public int FemaleDinoId2 { get; set; }
+ }
+
+ [JsonObject]
+ internal class Stat
+ {
+ public int Wild { get; set; }
+ public int Tamed { get; set; }
+ public float Value { get; set; }
+ }
+}
diff --git a/ARKBreedingStats/importExportGun/ExportGunServerFile.cs b/ARKBreedingStats/importExportGun/ExportGunServerFile.cs
new file mode 100644
index 00000000..b1a83c0a
--- /dev/null
+++ b/ARKBreedingStats/importExportGun/ExportGunServerFile.cs
@@ -0,0 +1,32 @@
+using Newtonsoft.Json;
+
+namespace ARKBreedingStats.importExportGun
+{
+ ///
+ /// Server multipliers as exported by the export gun mod.
+ ///
+ [JsonObject]
+ internal class ExportGunServerFile
+ {
+ public double[] WildLevel { get; set; }
+ public double[] TameLevel { get; set; }
+ public double[] TameAdd { get; set; }
+ public double[] TameAff { get; set; }
+ public double WildLevelStepSize { get; set; }
+ public int MaxWildLevel { get; set; }
+ public int DestroyTamesOverLevelClamp { get; set; }
+ public double TamingSpeedMultiplier { get; set; }
+ public double DinoCharacterFoodDrainMultiplier { get; set; }
+ public double MatingSpeedMultiplier { get; set; }
+ public double MatingIntervalMultiplier { get; set; }
+ public double EggHatchSpeedMultiplier { get; set; }
+ public double BabyMatureSpeedMultiplier { get; set; }
+ public double BabyCuddleIntervalMultiplier { get; set; }
+ public double BabyImprintAmountMultiplier { get; set; }
+ public double BabyImprintingStatScaleMultiplier { get; set; }
+ public double BabyFoodConsumptionSpeedMultiplier { get; set; }
+ public double TamedDinoCharacterFoodDrainMultiplier { get; set; }
+ public bool AllowFlyerSpeedLeveling { get; set; }
+ public bool UseSingleplayerSettings { get; set; }
+ }
+}
diff --git a/ARKBreedingStats/importExportGun/ImportExportGun.cs b/ARKBreedingStats/importExportGun/ImportExportGun.cs
new file mode 100644
index 00000000..3ba88e07
--- /dev/null
+++ b/ARKBreedingStats/importExportGun/ImportExportGun.cs
@@ -0,0 +1,194 @@
+using System;
+using System.IO;
+using ARKBreedingStats.Library;
+using ARKBreedingStats.values;
+using Newtonsoft.Json;
+
+namespace ARKBreedingStats.importExportGun
+{
+ ///
+ /// Imports creature files created with the export gun (mod).
+ ///
+ internal static class ImportExportGun
+ {
+ ///
+ /// Import file created with the export gun (mod).
+ ///
+ public static Creature ImportCreature(string filePath, out string resultText, out string serverMultipliersHash)
+ {
+ resultText = null;
+ serverMultipliersHash = null;
+ if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
+ return null;
+
+ try
+ {
+ var jsonText = ReadExportFile.ReadFile(filePath, "DinoExportGunSave_C", out resultText);
+ if (jsonText == null)
+ {
+ resultText = $"Error when importing file {filePath}: {resultText}";
+ return null;
+ }
+
+ var exportedCreature = JsonConvert.DeserializeObject(jsonText);
+ if (exportedCreature == null) return null;
+
+ serverMultipliersHash = exportedCreature.ServerMultipliersHash;
+
+ return ConvertExportGunToCreature(exportedCreature, out resultText);
+ }
+ catch (Exception ex)
+ {
+ resultText = $"Error when importing file {filePath}: {ex.Message}";
+ }
+
+ return null;
+ }
+
+ private static Creature ConvertExportGunToCreature(ExportGunCreatureFile ec, out string error)
+ {
+ error = null;
+ if (ec == null) return null;
+
+ var species = Values.V.SpeciesByBlueprint(ec.BlueprintPath, true);
+ if (species == null)
+ {
+ error = $"blueprintpath {ec.BlueprintPath} couldn't be found, maybe you need to load a mod values file.";
+ return null;
+ }
+
+ var wildLevels = new int[Stats.StatsCount];
+ var domLevels = new int[Stats.StatsCount];
+ var si = 0;
+ foreach (var s in ec.Stats)
+ {
+ wildLevels[si] = s.Wild;
+ domLevels[si] = s.Tamed;
+ si++;
+ }
+
+ var arkId = Utils.ConvertArkIdsToLongArkId(ec.DinoID1, ec.DinoID2);
+
+ var isWild = string.IsNullOrEmpty(ec.DinoName)
+ && string.IsNullOrEmpty(ec.TribeName)
+ && string.IsNullOrEmpty(ec.TamerString)
+ && string.IsNullOrEmpty(ec.OwningPlayerName)
+ && string.IsNullOrEmpty(ec.ImprinterName)
+ && ec.OwningPlayerID == 0
+ ;
+
+ var c = new Creature(species, ec.DinoName, !string.IsNullOrEmpty(ec.OwningPlayerName) ? ec.OwningPlayerName : !string.IsNullOrEmpty(ec.ImprinterName) ? ec.ImprinterName : ec.TamerString,
+ ec.TribeName, species.noGender ? Sex.Unknown : ec.IsFemale ? Sex.Female : Sex.Male, wildLevels, domLevels,
+ isWild ? -3 : ec.TameEffectiveness, !string.IsNullOrEmpty(ec.ImprinterName), ec.DinoImprintingQuality,
+ CreatureCollection.CurrentCreatureCollection?.wildLevelStep)
+ {
+ ArkId = arkId,
+ guid = Utils.ConvertArkIdToGuid(arkId),
+ ArkIdImported = true,
+ ArkIdInGame = Utils.ConvertImportedArkIdToIngameVisualization(arkId),
+ colors = ec.ColorSetIndices,
+ Maturation = ec.BabyAge,
+ mutationsMaternal = ec.RandomMutationsFemale,
+ mutationsPaternal = ec.RandomMutationsMale
+ };
+
+ c.RecalculateCreatureValues(CreatureCollection.CurrentCreatureCollection?.wildLevelStep);
+ if (ec.NextAllowedMatingTimeDuration > 0)
+ c.cooldownUntil = DateTime.Now.AddSeconds(ec.NextAllowedMatingTimeDuration);
+ if (ec.MutagenApplied)
+ c.flags |= CreatureFlags.MutagenApplied;
+ if (ec.Neutered)
+ c.flags |= CreatureFlags.Neutered;
+ if (ec.Ancestry != null)
+ {
+ if (ec.Ancestry.FemaleDinoId1 != 0 || ec.Ancestry.FemaleDinoId2 != 0)
+ c.motherGuid =
+ Utils.ConvertArkIdToGuid(Utils.ConvertArkIdsToLongArkId(ec.Ancestry.FemaleDinoId1,
+ ec.Ancestry.FemaleDinoId2));
+ if (ec.Ancestry.MaleDinoId1 != 0 || ec.Ancestry.MaleDinoId2 != 0)
+ c.fatherGuid =
+ Utils.ConvertArkIdToGuid(Utils.ConvertArkIdsToLongArkId(ec.Ancestry.MaleDinoId1,
+ ec.Ancestry.MaleDinoId2));
+ }
+
+ return c;
+ }
+
+ ///
+ /// Import server multipliers file from the export gun mod.
+ ///
+ public static bool ImportServerMultipliers(CreatureCollection cc, string filePath, string newServerMultipliersHash, out string resultText)
+ {
+ var exportedServerMultipliers = ReadServerMultipliers(filePath, out resultText);
+ if (exportedServerMultipliers == null) return false;
+ return SetServerMultipliers(cc, exportedServerMultipliers, newServerMultipliersHash);
+ }
+
+ internal static ExportGunServerFile ReadServerMultipliers(string filePath, out string resultText)
+ {
+ resultText = null;
+ if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
+ return null;
+
+ try
+ {
+ var jsonText = ReadExportFile.ReadFile(filePath, "DinoExportGunServerSave_C", out resultText);
+ if (jsonText == null)
+ {
+ resultText = $"Error when importing file {filePath}: {resultText}";
+ return null;
+ }
+
+ var exportedServerMultipliers = JsonConvert.DeserializeObject(jsonText);
+ if (exportedServerMultipliers == null)
+ {
+ resultText = $"Unknown error when importing file {filePath}";
+ return null;
+ }
+
+ resultText = $"Server multipliers imported from {filePath}";
+ return exportedServerMultipliers;
+ }
+ catch (Exception ex)
+ {
+ resultText = $"Error when importing file {filePath}: {ex.Message}";
+ }
+
+ return null;
+ }
+
+ internal static bool SetServerMultipliers(CreatureCollection cc, ExportGunServerFile esm, string newServerMultipliersHash)
+ {
+ if (cc == null) return false;
+
+ const int roundToDigits = 6;
+
+ for (int s = 0; s < Stats.StatsCount; s++)
+ {
+ cc.serverMultipliers.statMultipliers[s][Stats.IndexTamingAdd] = Math.Round(esm.TameAdd[s], roundToDigits);
+ cc.serverMultipliers.statMultipliers[s][Stats.IndexTamingMult] = Math.Round(esm.TameAff[s], roundToDigits);
+ cc.serverMultipliers.statMultipliers[s][Stats.IndexLevelWild] = Math.Round(esm.WildLevel[s], roundToDigits);
+ cc.serverMultipliers.statMultipliers[s][Stats.IndexLevelDom] = Math.Round(esm.TameLevel[s], roundToDigits);
+ }
+ cc.maxWildLevel = esm.MaxWildLevel;
+ cc.maxServerLevel = esm.DestroyTamesOverLevelClamp;
+ cc.serverMultipliers.TamingSpeedMultiplier = Math.Round(esm.TamingSpeedMultiplier, roundToDigits);
+ cc.serverMultipliers.DinoCharacterFoodDrainMultiplier = Math.Round(esm.DinoCharacterFoodDrainMultiplier, roundToDigits);
+ cc.serverMultipliers.MatingSpeedMultiplier = Math.Round(esm.MatingSpeedMultiplier, roundToDigits);
+ cc.serverMultipliers.MatingIntervalMultiplier = Math.Round(esm.MatingIntervalMultiplier, roundToDigits);
+ cc.serverMultipliers.EggHatchSpeedMultiplier = Math.Round(esm.EggHatchSpeedMultiplier, roundToDigits);
+ cc.serverMultipliers.BabyMatureSpeedMultiplier = Math.Round(esm.BabyMatureSpeedMultiplier, roundToDigits);
+ cc.serverMultipliers.BabyCuddleIntervalMultiplier = Math.Round(esm.BabyCuddleIntervalMultiplier, roundToDigits);
+ cc.serverMultipliers.BabyImprintAmountMultiplier = Math.Round(esm.BabyImprintAmountMultiplier, roundToDigits);
+ cc.serverMultipliers.BabyImprintingStatScaleMultiplier = Math.Round(esm.BabyImprintingStatScaleMultiplier, roundToDigits);
+ cc.serverMultipliers.BabyFoodConsumptionSpeedMultiplier = Math.Round(esm.BabyFoodConsumptionSpeedMultiplier, roundToDigits);
+ cc.serverMultipliers.TamedDinoCharacterFoodDrainMultiplier = Math.Round(esm.TamedDinoCharacterFoodDrainMultiplier, roundToDigits);
+ cc.serverMultipliers.AllowFlyerSpeedLeveling = esm.AllowFlyerSpeedLeveling;
+ cc.singlePlayerSettings = esm.UseSingleplayerSettings;
+
+ cc.ServerMultipliersHash = newServerMultipliersHash;
+
+ return true;
+ }
+ }
+}
diff --git a/ARKBreedingStats/importExportGun/ReadExportFile.cs b/ARKBreedingStats/importExportGun/ReadExportFile.cs
new file mode 100644
index 00000000..d76b8bbd
--- /dev/null
+++ b/ARKBreedingStats/importExportGun/ReadExportFile.cs
@@ -0,0 +1,74 @@
+using System.IO;
+using System.Text;
+
+namespace ARKBreedingStats.importExportGun
+{
+ ///
+ /// Reads the content of an export files created by the export gun mod.
+ ///
+ internal static class ReadExportFile
+ {
+ ///
+ /// Reads the content of an export file and returns the containing json part as string.
+ ///
+ public static string ReadFile(string filePath, string expectedStartString, out string error)
+ {
+ error = null;
+ using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
+ {
+ using (BinaryReader br = new BinaryReader(fs))
+ {
+ br.ReadBytes(4);
+ if (Encoding.UTF8.GetString(br.ReadBytes(expectedStartString.Length))
+ != expectedStartString)
+ {
+ error = $"Expected start string {expectedStartString} not found";
+ return null;
+ }
+
+ const string strProp = "StrProperty";
+ if (!SearchBytes(br, Encoding.ASCII.GetBytes(strProp)))
+ {
+ error = $"Expected property {strProp} not found";
+ return null;
+ }
+
+ br.ReadBytes(9); // skipping to json string length
+ var jsonLength = br.ReadInt32();
+ if (jsonLength <= 0)
+ {
+ error = $"Json length {jsonLength} at position {(br.BaseStream.Position - 4)} invalid";
+ return null;
+ }
+ return Encoding.UTF8.GetString(br.ReadBytes(jsonLength));
+ }
+ }
+ }
+
+ ///
+ /// Looks for a specific byte pattern sequence, the stream position is set after that pattern.
+ ///
+ ///
+ ///
+ /// True if pattern found.
+ private static bool SearchBytes(BinaryReader br, byte[] bytesToFind)
+ {
+ if (bytesToFind == null || bytesToFind.Length == 0) return false;
+ var pi = 0; // index of pattern currently comparing, indices before already found
+ var l = br.BaseStream.Length;
+ while (br.BaseStream.Position < l)
+ {
+ if (br.ReadByte() == bytesToFind[pi])
+ {
+ pi++;
+ if (pi == bytesToFind.Length)
+ return true;
+ continue;
+ }
+
+ pi = 0;
+ }
+ return false;
+ }
+ }
+}
diff --git a/ARKBreedingStats/importExported/FileWatcherExports.cs b/ARKBreedingStats/importExported/FileWatcherExports.cs
index 993ef7f8..6b8735cd 100644
--- a/ARKBreedingStats/importExported/FileWatcherExports.cs
+++ b/ARKBreedingStats/importExported/FileWatcherExports.cs
@@ -14,7 +14,6 @@ public FileWatcherExports(string folderToWatch, Action 2 && text.Substring(text.Length - 2) == "_C")
- text = text.Substring(0, text.Length - 2); // the last two characters are "_C"
-
- cv.Species = Values.V.SpeciesByBlueprint(text);
+ cv.Species = Values.V.SpeciesByBlueprint(text,true);
if (cv.Species == null)
cv.speciesBlueprint = text; // species is unknown, check the needed mods later
break;
diff --git a/ARKBreedingStats/json/values/_manifest.json b/ARKBreedingStats/json/values/_manifest.json
index 5e1157e6..5a2c8f32 100644
--- a/ARKBreedingStats/json/values/_manifest.json
+++ b/ARKBreedingStats/json/values/_manifest.json
@@ -6,7 +6,7 @@
"mod": { "id": "1083349027", "tag": "SpeedyFlyers", "title": "Najs Speedy Flyers" }
},
"1090809604-Pyria.json": {
- "version": "357.18.1683920382",
+ "version": "358.11.1693620638",
"mod": { "id": "1090809604", "tag": "Pyria", "title": "Pyria: Mythos Evolved" }
},
"1092784125-Gryphons.json": {
@@ -72,7 +72,7 @@
"mod": { "id": "1356703358", "tag": "Primal_Fear_Noxious_Creatures", "title": "Primal Fear Noxious Creatures" }
},
"1373744537-AC2.json": {
- "version": "357.4.1679590122",
+ "version": "358.11.1693637981",
"mod": { "id": "1373744537", "tag": "AC2", "title": "Additional Creatures 2: Wild Ark" }
},
"1379111008-RealismPlus.json": {
@@ -80,7 +80,7 @@
"mod": { "id": "1379111008", "tag": "RealismPlus", "title": "Realism Plus by Storm" }
},
"1405944717-Project_Evolution.json": {
- "version": "356.5.1597323120",
+ "version": "358.10.1690909595",
"mod": { "id": "1405944717", "tag": "Project_Evolution", "title": "JP's Server Tweaks" }
},
"1420423699-ARKaeologyDinos.json": {
@@ -100,7 +100,7 @@
"mod": { "id": "1498206270", "tag": "SmallDragon", "title": "Small Dragons!" }
},
"1522327484-Additions_Pack.json": {
- "version": "356.5.1659488575",
+ "version": "358.11.1692893636",
"mod": { "id": "1522327484", "tag": "Additions_Pack", "title": "ARK Additions!" }
},
"1523045986-Paranoia.json": {
@@ -112,7 +112,7 @@
"mod": { "id": "1565015734", "tag": "BetterDinosTest", "title": "Better Dinos" }
},
"1576299694-ElementalDinos.json": {
- "version": "356.5.1667827331",
+ "version": "358.5.1685793057",
"mod": { "id": "1576299694", "tag": "ElementalDinos", "title": "Elemental Ark" }
},
"1587391872-FasterFlyers.json": {
@@ -133,7 +133,7 @@
"mod": { "id": "1633860796", "tag": "DE_Breedable_RockDrakes", "title": "Dark Edges Breedable Rock Drakes" }
},
"1652120435-AtlasPort.json": {
- "version": "356.5.1672480004",
+ "version": "358.11.1693253248",
"mod": { "id": "1652120435", "tag": "AtlasPort", "title": "Shad's Atlas Imports" }
},
"1654255131-AtlasImports.json": {
@@ -145,11 +145,11 @@
"mod": { "id": "1662691167", "tag": "Senior", "title": "Additional Creatures: Senior Class" }
},
"1675895024-NoUntameables.json": {
- "version": "357.18.1683392823",
+ "version": "358.10.1691123197",
"mod": { "id": "1675895024", "tag": "NoUntameables", "title": "No Untameables" }
},
"1676159020-Aquaria.json": {
- "version": "357.13.1681407858",
+ "version": "358.9.1690158145",
"mod": { "id": "1676159020", "tag": "Aquaria", "title": "Additional Creatures: Aquaria" }
},
"1681125667-Primal_Fear_EX.json": {
@@ -170,11 +170,11 @@
"mod": { "id": "1729512589", "tag": "Brachiosaurus", "title": "ARK Additions: Brachiosaurus!" }
},
"1734595558-Pyria2.json": {
- "version": "357.15.1681408567.1",
+ "version": "358.6.1687974983",
"mod": { "id": "1734595558", "tag": "Pyria2", "title": "Pyria: The Second Chapter" }
},
"1754846792-Zythara_Critters.json": {
- "version": "357.15.1682481287.1",
+ "version": "358.8.1688851941",
"mod": { "id": "1754846792", "tag": "Zythara_Critters", "title": "Zythara Critters" }
},
"1768499278-BalancedJPE.json": {
@@ -243,7 +243,7 @@
"mod": { "id": "2000326197", "tag": "ExtraResources", "title": "Event Assets" }
},
"2003934830-Beasts.json": {
- "version": "357.18.1683429743",
+ "version": "358.11.1692983778",
"mod": { "id": "2003934830", "tag": "Beasts", "title": "Prehistoric Beasts" }
},
"2019846325-ApexMod.json": {
@@ -271,7 +271,7 @@
"mod": { "id": "2135314513", "tag": "CI_Dinos", "title": "Crystal Isles Dino Addition" }
},
"2212177129-Hybridthing.json": {
- "version": "356.5.1669397714",
+ "version": "358.11.1692981202",
"mod": { "id": "2212177129", "tag": "Hybridthing", "title": "Sid's Hybrids" }
},
"2247209652-MonstersandMore.json": {
@@ -291,7 +291,7 @@
"mod": { "id": "2362246280", "tag": "GigaFullTame", "title": "Giga Full Tame" }
},
"2447186973-ArkOmega.json": {
- "version": "357.18.1683354231",
+ "version": "358.8.1688621034",
"mod": { "id": "2447186973", "tag": "ArkOmega", "title": "Ark Omega" }
},
"2493949846-Endemics.json": {
@@ -303,15 +303,15 @@
"mod": { "id": "2683373846", "tag": "ZazaCollection_2", "title": "Zaza's Collection" }
},
"2804332920-PaleoARKlegends.json": {
- "version": "357.15.1683080827",
+ "version": "358.11.1693676474",
"mod": { "id": "2804332920", "tag": "PaleoARKlegends", "title": "Paleo ARK: Legends Expansion!" }
},
"2869411055-SDinoVariants.json": {
- "version": "357.15.1682518886",
+ "version": "358.9.1687204735",
"mod": { "id": "2869411055", "tag": "SDinoVariants", "title": "SDinoVariants" }
},
"710880648-DinoOverHaulMODX.json": {
- "version": "357.18.1683668064",
+ "version": "358.11.1694019960",
"mod": { "id": "710880648", "tag": "DinoOverHaulMODX", "title": "DinoOverhaul X -- Hardcore PvE Experience" }
},
"729352919-IndomRex.json": {
@@ -323,7 +323,7 @@
"mod": { "id": "814833973", "tag": "Wyvern_Mating", "title": "Wyvern Mating" }
},
"839162288-Primal_Fear.json": {
- "version": "357.12.1681258396",
+ "version": "358.11.1693416578",
"mod": { "id": "839162288", "tag": "Primal_Fear", "title": "Primal Fear" }
},
"883957187-WyvernWorld.json": {
@@ -331,7 +331,7 @@
"mod": { "id": "883957187", "tag": "WyvernWorld", "title": "Wyvern World" }
},
"893735676-AE.json": {
- "version": "357.14.1681700117",
+ "version": "358.10.1691991018",
"mod": { "id": "893735676", "tag": "AE", "title": "Ark Eternal" }
},
"895711211-ClassicFlyers.json": {
@@ -361,7 +361,7 @@
}
},
"values.json": {
- "version": "356.11.10518855"
+ "version": "358.6.11410860"
}
}
}
\ No newline at end of file
diff --git a/ARKBreedingStats/json/values/values.json b/ARKBreedingStats/json/values/values.json
index d27eae95..4491774a 100644
--- a/ARKBreedingStats/json/values/values.json
+++ b/ARKBreedingStats/json/values/values.json
@@ -1,5 +1,5 @@
{
- "version": "356.11.10518855",
+ "version": "358.6.11410860",
"format": "1.14-flyerspeed",
"species": [
{
@@ -105267,6 +105267,261 @@
"TamedBaseHealthMultiplier": 1,
"displayedStats": 927
},
+ {
+ "name": "Rhyniognatha",
+ "blueprintPath": "/Game/PrimalEarth/Dinos/Rhyniognatha/Rhynio_Character_BP.Rhynio_Character_BP",
+ "isFlyer": true,
+ "fullStatsRaw": [
+ [ 1400, 0.17, 0.1755, 0.5, 0 ],
+ [ 350, 0.05, 0.06, 0, 0 ],
+ [ 800, 0.06, 0, 0.5, 0 ],
+ [ 800, 0.1, 0.1, 0, 0 ],
+ [ 1600, 0.1, 0.1, 0, 0.15 ],
+ null,
+ null,
+ [ 1200, 0.02, 0.04, 0, 0 ],
+ [ 1, 0.05, 0.1, 0.5, 0.4 ],
+ [ 1, 0, 0.01, 0, 0 ],
+ null,
+ null
+ ],
+ "statImprintMult": [ 0.2, 0, 0.2, 0, 0.2, 0.2, 0, 0.2, 0.2, 0, 0, 0 ],
+ "immobilizedBy": [ "Chain Bola", "Bear Trap", "Large Bear Trap", "Plant Species Y" ],
+ "breeding": {
+ "gestationTime": 0,
+ "incubationTime": 0,
+ "maturationTime": 666666.667,
+ "matingCooldownMin": 64800,
+ "matingCooldownMax": 172800
+ },
+ "colors": [
+ {
+ "name": "Body Main",
+ "colors": [
+ "BigFoot4",
+ "BlackSands",
+ "Coral",
+ "Cream",
+ "Custard",
+ "Dark Grey",
+ "DarkWarmGray",
+ "DarkWolfFur",
+ "Dino Albino",
+ "Dino Dark Brown",
+ "Dino Darker Grey",
+ "Dino Light Blue",
+ "Dino Light Green",
+ "Dino Light Purple",
+ "Dino Light Red",
+ "Dino Light Yellow",
+ "DragonBase0",
+ "DragonBase1",
+ "DragonGreen0",
+ "DragonGreen1",
+ "DragonGreen3",
+ "Glacial",
+ "Jade",
+ "Lavender",
+ "LeafGreen",
+ "Light Grey",
+ "LightCement",
+ "LightPink",
+ "LightWarmGray",
+ "MediumWarmGray",
+ "MidnightBlue",
+ "Mint",
+ "Peach",
+ "WolfFur",
+ "WyvernBlue0",
+ "WyvernPurple0",
+ "WyvernPurple1"
+ ]
+ },
+ {
+ "name": "Body Stripes",
+ "colors": [
+ "BlackSands",
+ "Coral",
+ "Cream",
+ "Custard",
+ "Dark Red",
+ "DarkMagenta",
+ "DarkWarmGray",
+ "Dino Albino",
+ "Dino Dark Brown",
+ "Dino Darker Grey",
+ "Dino Light Blue",
+ "Dino Light Green",
+ "Dino Light Purple",
+ "Dino Light Red",
+ "Dino Light Yellow",
+ "DragonBase0",
+ "DragonBase1",
+ "DragonFire",
+ "DragonGreen0",
+ "DragonGreen1",
+ "DragonGreen3",
+ "Glacial",
+ "Jade",
+ "Lavender",
+ "LeafGreen",
+ "Light Grey",
+ "Light Orange",
+ "Light Red",
+ "Light Yellow",
+ "LightCement",
+ "LightPink",
+ "LightWarmGray",
+ "MediumLavender",
+ "MediumTeal",
+ "MediumWarmGray",
+ "MidnightBlue",
+ "Mint",
+ "NearWhite",
+ "Orange",
+ "Peach",
+ "SpruceGreen",
+ "Teal",
+ "WyvernBlue0",
+ "WyvernPurple0",
+ "WyvernPurple1"
+ ]
+ },
+ {
+ "name": "Eyes",
+ "colors": [
+ "Coral",
+ "Cyan",
+ "Dark Green",
+ "DarkTurquoise",
+ "DeepPink",
+ "Dino Dark Green",
+ "Dino Dark Purple",
+ "Dino Deep Blue",
+ "Dino Light Blue",
+ "Dino Medium Blue",
+ "Glacial",
+ "LemonLime",
+ "Light Red",
+ "LightPink",
+ "Magenta",
+ "Mint",
+ "Orange",
+ "PowderBlue"
+ ]
+ },
+ {
+ "name": "Underbelly",
+ "colors": [
+ "BigFoot0",
+ "BigFoot4",
+ "BigFoot5",
+ "BlackSands",
+ "Cammo",
+ "Cream",
+ "Custard",
+ "DarkBlue",
+ "DarkWolfFur",
+ "Dino Albino",
+ "Dino Medium Brown",
+ "DryMoss",
+ "Glacial",
+ "Lavender",
+ "Light Grey",
+ "LightCement",
+ "LightWarmGray",
+ "MediumWarmGray",
+ "MidnightBlue",
+ "NearBlack",
+ "NearWhite",
+ "WolfFur"
+ ]
+ },
+ {
+ "name": "Stripe Highlights",
+ "colors": [
+ "BlackSands",
+ "DarkWarmGray",
+ "DarkWolfFur",
+ "Dino Dark Brown",
+ "Dino Darker Grey",
+ "DragonBase1",
+ "DragonFire",
+ "LeafGreen",
+ "MediumLavender",
+ "MediumTeal",
+ "MediumTurquoise",
+ "MediumWarmGray",
+ "MidnightBlue",
+ "Teal",
+ "Turquoise",
+ "WyvernBlue0"
+ ]
+ },
+ {
+ "name": "Leg Highlights",
+ "colors": [
+ "BigFoot4",
+ "BlackSands",
+ "Coral",
+ "Cream",
+ "Custard",
+ "Dark Grey",
+ "Dark Red",
+ "DarkWarmGray",
+ "DarkWolfFur",
+ "Dino Albino",
+ "Dino Dark Brown",
+ "Dino Darker Grey",
+ "Dino Light Blue",
+ "Dino Light Green",
+ "Dino Light Purple",
+ "Dino Light Red",
+ "Dino Light Yellow",
+ "DragonBase0",
+ "DragonBase1",
+ "DragonFire",
+ "DragonGreen0",
+ "DragonGreen1",
+ "DragonGreen3",
+ "Glacial",
+ "Jade",
+ "Lavender",
+ "LeafGreen",
+ "Light Grey",
+ "Light Orange",
+ "Light Red",
+ "LightCement",
+ "LightPink",
+ "LightWarmGray",
+ "MediumLavender",
+ "MediumTeal",
+ "MediumWarmGray",
+ "MidnightBlue",
+ "Mint",
+ "Orange",
+ "Peach",
+ "Teal",
+ "WolfFur",
+ "WyvernBlue0",
+ "WyvernPurple0",
+ "WyvernPurple1"
+ ]
+ }
+ ],
+ "taming": {
+ "nonViolent": false,
+ "violent": false,
+ "tamingIneffectiveness": 0.9375,
+ "affinityNeeded0": 6850,
+ "affinityIncreasePL": 300,
+ "foodConsumptionBase": 0.001543,
+ "foodConsumptionMult": 216.0294,
+ "babyFoodConsumptionMult": 510
+ },
+ "TamedBaseHealthMultiplier": 1,
+ "displayedStats": 927
+ },
{
"name": "Sabertooth",
"blueprintPath": "/Game/PrimalEarth/Dinos/Saber/Saber_Character_BP.Saber_Character_BP",
diff --git a/ARKBreedingStats/library/Creature.cs b/ARKBreedingStats/library/Creature.cs
index 75c3abf2..e87c9c21 100644
--- a/ARKBreedingStats/library/Creature.cs
+++ b/ARKBreedingStats/library/Creature.cs
@@ -520,7 +520,16 @@ public string GrowingLeftString
///
/// Maturation of this creature, 0: baby, 1: adult.
///
- public double Maturation => Species?.breeding == null || growingUntil == null ? 1 : 1 - growingUntil.Value.Subtract(DateTime.Now).TotalSeconds / Species.breeding.maturationTimeAdjusted;
+ public double Maturation
+ {
+ get => Species?.breeding == null || growingUntil == null
+ ? 1
+ : 1 - growingUntil.Value.Subtract(DateTime.Now).TotalSeconds /
+ Species.breeding.maturationTimeAdjusted;
+ set => growingUntil = Species?.breeding == null
+ ? default(DateTime?)
+ : DateTime.Now.AddSeconds(Species.breeding.maturationTimeAdjusted * (1 - value));
+ }
[OnDeserialized]
private void Initialize(StreamingContext ct)
diff --git a/ARKBreedingStats/library/CreatureCollection.cs b/ARKBreedingStats/library/CreatureCollection.cs
index 585b66e6..3ad4490b 100644
--- a/ARKBreedingStats/library/CreatureCollection.cs
+++ b/ARKBreedingStats/library/CreatureCollection.cs
@@ -76,6 +76,13 @@ public CreatureCollection()
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public bool AtlasSettings;
+ ///
+ /// Used for the exportGun mod.
+ /// This hash is used to determine if an imported creature file is using the current server multipliers.
+ ///
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public string ServerMultipliersHash;
+
///
/// Allow more than 100% imprinting, can happen with mods, e.g. S+ Nanny
///
@@ -194,6 +201,8 @@ public bool MergeCreatureList(IEnumerable creaturesToMerge, bool addPr
Species onlyThisSpeciesAdded = null;
bool onlyOneSpeciesAdded = true;
+ var guidDict = creatures.ToDictionary(c => c.guid);
+
foreach (Creature creatureNew in creaturesToMerge)
{
if (!addPreviouslyDeletedCreatures && DeletedCreatureGuids != null && DeletedCreatureGuids.Contains(creatureNew.guid)) continue;
@@ -206,8 +215,7 @@ public bool MergeCreatureList(IEnumerable creaturesToMerge, bool addPr
onlyOneSpeciesAdded = false;
}
- var creatureExisting = creatures.FirstOrDefault(c => c.guid == creatureNew.guid);
- if (creatureExisting == null)
+ if (!guidDict.TryGetValue(creatureNew.guid, out var creatureExisting))
{
creatures.Add(creatureNew);
creaturesWereAddedOrUpdated = true;
diff --git a/ARKBreedingStats/local/strings.de.resx b/ARKBreedingStats/local/strings.de.resx
index 9590615a..b5f06f95 100644
--- a/ARKBreedingStats/local/strings.de.resx
+++ b/ARKBreedingStats/local/strings.de.resx
@@ -1322,4 +1322,7 @@ Es ist auch möglich Tiere mit einer Farbe in mehreren möglichen Regionen zu fi
Bibliothek nach Spezies gruppieren
+
+ Dieser Name existiert bereits in der Bibliothek
+
\ No newline at end of file
diff --git a/ARKBreedingStats/local/strings.es.resx b/ARKBreedingStats/local/strings.es.resx
index 08571223..90feebd2 100644
--- a/ARKBreedingStats/local/strings.es.resx
+++ b/ARKBreedingStats/local/strings.es.resx
@@ -1037,4 +1037,7 @@ Algunas crías pueden ser peores que en el modo de estadísticas altas, pero tam
generaciones
+
+ Este nombre ya existe en la biblioteca.
+
diff --git a/ARKBreedingStats/local/strings.fr.resx b/ARKBreedingStats/local/strings.fr.resx
index 3f31e03a..d5a8e64b 100644
--- a/ARKBreedingStats/local/strings.fr.resx
+++ b/ARKBreedingStats/local/strings.fr.resx
@@ -1332,4 +1332,7 @@ Il est également possible de filtrer les créatures par couleur dans l'une des
Grouper la bibliothèque par espèces
+
+ Ce nom existe déjà dans la bibliothèque
+
\ No newline at end of file
diff --git a/ARKBreedingStats/local/strings.it.resx b/ARKBreedingStats/local/strings.it.resx
index c78c2761..e94234a2 100644
--- a/ARKBreedingStats/local/strings.it.resx
+++ b/ARKBreedingStats/local/strings.it.resx
@@ -972,4 +972,7 @@ Controlla i migliori risultati a lungo termine e se ti senti fortunato. Può ess
generazioni
+
+ Questo nome esiste già nella libreria
+
\ No newline at end of file
diff --git a/ARKBreedingStats/local/strings.ja.resx b/ARKBreedingStats/local/strings.ja.resx
index 9da6f026..349a980f 100644
--- a/ARKBreedingStats/local/strings.ja.resx
+++ b/ARKBreedingStats/local/strings.ja.resx
@@ -1303,4 +1303,7 @@ It's also possible to filter for creatures with a color in one of multiple possi
種族でライブラリを切り分ける
+
+ この名前はすでにライブラリに存在します
+
\ No newline at end of file
diff --git a/ARKBreedingStats/local/strings.pl.resx b/ARKBreedingStats/local/strings.pl.resx
index be125222..4808166e 100644
--- a/ARKBreedingStats/local/strings.pl.resx
+++ b/ARKBreedingStats/local/strings.pl.resx
@@ -1142,4 +1142,7 @@ Jeśli wyświetlona jest liczba z plusem, suma jest za wysoka i musisz wybrać i
Pogrupuj bibliotekę po gatunku
+
+ Ta nazwa już istnieje w bibliotece
+
\ No newline at end of file
diff --git a/ARKBreedingStats/local/strings.resx b/ARKBreedingStats/local/strings.resx
index 2e4897ef..a62fa970 100644
--- a/ARKBreedingStats/local/strings.resx
+++ b/ARKBreedingStats/local/strings.resx
@@ -1334,4 +1334,7 @@ It's also possible to filter for creatures with a color in one of multiple possi
Group library by species
+
+ This name already exists in the library
+
\ No newline at end of file
diff --git a/ARKBreedingStats/local/strings.ru.resx b/ARKBreedingStats/local/strings.ru.resx
index 1c6882b0..204e0706 100644
--- a/ARKBreedingStats/local/strings.ru.resx
+++ b/ARKBreedingStats/local/strings.ru.resx
@@ -1197,4 +1197,7 @@ It's also possible to filter for creatures with a color in one of multiple possi
Сгруппировать библиотеку по видам
+
+ Это имя уже существует в библиотеке
+
\ No newline at end of file
diff --git a/ARKBreedingStats/local/strings.zh-tw.resx b/ARKBreedingStats/local/strings.zh-tw.resx
index fb8cfd7d..b5a36bd1 100644
--- a/ARKBreedingStats/local/strings.zh-tw.resx
+++ b/ARKBreedingStats/local/strings.zh-tw.resx
@@ -1332,4 +1332,7 @@ It's also possible to filter for creatures with a color in one of multiple possi
按物種分類
+
+ 該名稱已存在於庫中
+
\ No newline at end of file
diff --git a/ARKBreedingStats/local/strings.zh.resx b/ARKBreedingStats/local/strings.zh.resx
index 53fc4511..6588c539 100644
--- a/ARKBreedingStats/local/strings.zh.resx
+++ b/ARKBreedingStats/local/strings.zh.resx
@@ -1042,4 +1042,7 @@
代
+
+ 该名称已存在于库中
+
\ No newline at end of file
diff --git a/ARKBreedingStats/mods/ModValuesManager.cs b/ARKBreedingStats/mods/ModValuesManager.cs
index c9cc2286..1388df53 100644
--- a/ARKBreedingStats/mods/ModValuesManager.cs
+++ b/ARKBreedingStats/mods/ModValuesManager.cs
@@ -227,7 +227,11 @@ private void FilterMods()
lbAvailableModFiles.Items.Clear();
lbAvailableModFiles.Items.AddRange(
- _modInfos.Where(mi => !mi.CurrentlyInLibrary && (filter == null || mi.mod.title.IndexOf(filter, StringComparison.OrdinalIgnoreCase) != -1)
+ _modInfos.Where(mi => !mi.CurrentlyInLibrary
+ && (filter == null
+ || mi.mod.title.IndexOf(filter, StringComparison.OrdinalIgnoreCase) != -1
+ || mi.mod.tag.IndexOf(filter, StringComparison.OrdinalIgnoreCase) != -1
+ )
).ToArray());
lbAvailableModFiles.EndUpdate();
diff --git a/ARKBreedingStats/multiplierTesting/StatsMultiplierTesting.cs b/ARKBreedingStats/multiplierTesting/StatsMultiplierTesting.cs
index e57f4770..dd90f8eb 100644
--- a/ARKBreedingStats/multiplierTesting/StatsMultiplierTesting.cs
+++ b/ARKBreedingStats/multiplierTesting/StatsMultiplierTesting.cs
@@ -6,6 +6,7 @@
using System;
using System.Drawing;
using System.Windows.Forms;
+using ARKBreedingStats.importExportGun;
using ARKBreedingStats.utils;
namespace ARKBreedingStats.multiplierTesting
@@ -455,7 +456,7 @@ private void cbSingleplayerSettings_CheckedChanged(object sender, EventArgs e)
if (spM.statMultipliers[s] == null)
_statControls[s].SetSinglePlayerSettings();
else
- _statControls[s].SetSinglePlayerSettings(spM.statMultipliers[s][3], spM.statMultipliers[s][2], spM.statMultipliers[s][0], spM.statMultipliers[s][1]);
+ _statControls[s].SetSinglePlayerSettings(spM.statMultipliers[s][Stats.IndexLevelWild], spM.statMultipliers[s][Stats.IndexLevelDom], spM.statMultipliers[s][Stats.IndexTamingAdd], spM.statMultipliers[s][Stats.IndexTamingMult]);
}
return;
}
diff --git a/ARKBreedingStats/settings/MultiplierSetting.cs b/ARKBreedingStats/settings/MultiplierSetting.cs
index d96f1c40..be71a740 100644
--- a/ARKBreedingStats/settings/MultiplierSetting.cs
+++ b/ARKBreedingStats/settings/MultiplierSetting.cs
@@ -11,6 +11,10 @@ public MultiplierSetting()
public string StatName { set => labelStatName.Text = value; }
+ ///
+ /// Stat multipliers. Setting a single value of the array won't do anything, use SetMultiplier() for that.
+ /// Indices: 0: TameAdd, 1: TameMult, 2: DomLevel, 3: WildLevel
+ ///
public double[] Multipliers
{
get => new[] { (double)nudTameAdd.Value, (double)nudTameMult.Value, (double)nudDomLevel.Value, (double)nudWildLevel.Value };
@@ -34,7 +38,31 @@ public double[] Multipliers
}
///
- /// Set the values that are considered default and are a bit lowlighted.
+ /// Set value of a stat multiplier.
+ ///
+ /// 0: TameAdd, 1: TameMult, 2: DomLevel, 3: WildLevel
+ ///
+ public void SetMultiplier(int index, double value)
+ {
+ switch (index)
+ {
+ case 0:
+ nudTameAdd.ValueSaveDouble = value;
+ return;
+ case 1:
+ nudTameMult.ValueSaveDouble = value;
+ return;
+ case 2:
+ nudDomLevel.ValueSaveDouble = value;
+ return;
+ case 3:
+ nudWildLevel.ValueSaveDouble = value;
+ return;
+ }
+ }
+
+ ///
+ /// Set the values that are considered default. These values are a bit lowlighted so non default values are spotted easier.
///
public void SetNeutralValues(double[] nv)
{
diff --git a/ARKBreedingStats/settings/Settings.cs b/ARKBreedingStats/settings/Settings.cs
index 4bc7cef5..27a15edb 100644
--- a/ARKBreedingStats/settings/Settings.cs
+++ b/ARKBreedingStats/settings/Settings.cs
@@ -9,9 +9,11 @@
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Windows.Threading;
+using ARKBreedingStats.importExportGun;
using ARKBreedingStats.library;
using ARKBreedingStats.uiControls;
using ARKBreedingStats.utils;
+using static System.Net.Mime.MediaTypeNames;
namespace ARKBreedingStats.settings
{
@@ -460,7 +462,7 @@ private void SaveSettings()
// Torpidity is handled differently by the game, IwM has no effect. Set IwM to 1.
// See https://github.com/cadon/ARKStatsExtractor/issues/942 for more infos about this.
- _cc.serverMultipliers.statMultipliers[Stats.Torpidity][3] = 1;
+ _cc.serverMultipliers.statMultipliers[Stats.Torpidity][Stats.IndexLevelWild] = 1;
_cc.singlePlayerSettings = cbSingleplayerSettings.Checked;
_cc.AtlasSettings = CbAtlasSettings.Checked;
@@ -705,7 +707,18 @@ private void tabPage2_DragDrop(object sender, DragEventArgs e)
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
- foreach (string file in files) ExtractSettingsFromFile(file);
+ foreach (string filePath in files)
+ {
+ switch (Path.GetExtension(filePath))
+ {
+ case ".sav":
+ LoadServerMultipliersFromSavFile(filePath);
+ break;
+ default:
+ ExtractSettingsFromFile(filePath);
+ break;
+ }
+ }
}
else if (e.Data.GetDataPresent(DataFormats.Text))
{
@@ -761,14 +774,13 @@ private void ExtractSettingsFromText(string text)
}
// get stat-multipliers
- // if an ini file is imported the server is most likely unofficial wit no level cap, if the server has a max level, it will be parsed.
+ // if an ini file is imported the server is most likely unofficial with no level cap, if the server has a max level, it will be parsed.
nudMaxServerLevel.ValueSave = 0;
for (int s = 0; s < Stats.StatsCount; s++)
{
ParseAndSetStatMultiplier(0, @"PerLevelStatsMultiplier_DinoTamed_Add\[" + s + @"\] ?= ?(\d*\.?\d+)");
- ParseAndSetStatMultiplier(1,
- @"PerLevelStatsMultiplier_DinoTamed_Affinity\[" + s + @"\] ?= ?(\d*\.?\d+)");
+ ParseAndSetStatMultiplier(1, @"PerLevelStatsMultiplier_DinoTamed_Affinity\[" + s + @"\] ?= ?(\d*\.?\d+)");
ParseAndSetStatMultiplier(2, @"PerLevelStatsMultiplier_DinoTamed\[" + s + @"\] ?= ?(\d*\.?\d+)");
ParseAndSetStatMultiplier(3, @"PerLevelStatsMultiplier_DinoWild\[" + s + @"\] ?= ?(\d*\.?\d+)");
@@ -778,9 +790,7 @@ void ParseAndSetStatMultiplier(int multiplierIndex, string regexPattern)
if (m.Success && double.TryParse(m.Groups[1].Value,
System.Globalization.NumberStyles.AllowDecimalPoint, cultureForStrings, out d))
{
- var multipliers = _multSetter[s].Multipliers;
- multipliers[multiplierIndex] = d == 0 ? 1 : d;
- _multSetter[s].Multipliers = multipliers;
+ _multSetter[s].SetMultiplier(multiplierIndex, d == 0 ? 1 : d);
}
}
}
@@ -819,14 +829,12 @@ void ParseAndSetStatMultiplier(int multiplierIndex, string regexPattern)
ParseAndSetValue(nudEggHatchSpeedEvent, @"ASBEvent_EggHatchSpeedMultiplier ?= ?(\d*\.?\d+)");
ParseAndSetValue(nudBabyMatureSpeedEvent, @"ASBEvent_BabyMatureSpeedMultiplier ?= ?(\d*\.?\d+)");
ParseAndSetValue(nudBabyCuddleIntervalEvent, @"ASBEvent_BabyCuddleIntervalMultiplier ?= ?(\d*\.?\d+)");
- ParseAndSetValue(nudBabyFoodConsumptionSpeedEvent,
- @"ASBEvent_BabyFoodConsumptionSpeedMultiplier ?= ?(\d*\.?\d+)");
+ ParseAndSetValue(nudBabyFoodConsumptionSpeedEvent, @"ASBEvent_BabyFoodConsumptionSpeedMultiplier ?= ?(\d*\.?\d+)");
// event multipliers taming
ParseAndSetValue(nudTamingSpeedEvent, @"ASBEvent_TamingSpeedMultiplier ?= ?(\d*\.?\d+)");
- ParseAndSetValue(nudDinoCharacterFoodDrainEvent,
- @"ASBEvent_DinoCharacterFoodDrainMultiplier ?= ?(\d*\.?\d+)");
+ ParseAndSetValue(nudDinoCharacterFoodDrainEvent, @"ASBEvent_DinoCharacterFoodDrainMultiplier ?= ?(\d*\.?\d+)");
- bool ParseAndSetValue(uiControls.Nud nud, string regexPattern)
+ bool ParseAndSetValue(Nud nud, string regexPattern)
{
m = Regex.Match(text, regexPattern);
if (m.Success && double.TryParse(m.Groups[1].Value, System.Globalization.NumberStyles.AllowDecimalPoint,
@@ -883,6 +891,40 @@ void ParseAndSetCheckbox(CheckBox cb, string regexPattern)
}
}
+ ///
+ /// Load server multipliers from a file created by the export gun mod.
+ ///
+ private void LoadServerMultipliersFromSavFile(string filePath)
+ {
+ var esm = ImportExportGun.ReadServerMultipliers(filePath, out _);
+ if (esm == null) return;
+
+ const int roundToDigits = 6;
+ for (int s = 0; s < Stats.StatsCount; s++)
+ {
+ _multSetter[s].SetMultiplier(0, Math.Round(esm.TameAdd[s], roundToDigits));
+ _multSetter[s].SetMultiplier(1, Math.Round(esm.TameAff[s], roundToDigits));
+ _multSetter[s].SetMultiplier(2, Math.Round(esm.TameLevel[s], roundToDigits));
+ _multSetter[s].SetMultiplier(3, Math.Round(esm.WildLevel[s], roundToDigits));
+ }
+
+ nudMaxWildLevels.ValueSave = esm.MaxWildLevel;
+ nudMaxServerLevel.ValueSave = esm.DestroyTamesOverLevelClamp;
+ nudTamingSpeed.ValueSaveDouble = Math.Round(esm.TamingSpeedMultiplier, roundToDigits);
+ nudDinoCharacterFoodDrain.ValueSaveDouble = Math.Round(esm.DinoCharacterFoodDrainMultiplier, roundToDigits);
+ nudMatingSpeed.ValueSaveDouble = Math.Round(esm.MatingSpeedMultiplier, roundToDigits);
+ nudMatingInterval.ValueSaveDouble = Math.Round(esm.MatingIntervalMultiplier, roundToDigits);
+ nudEggHatchSpeed.ValueSaveDouble = Math.Round(esm.EggHatchSpeedMultiplier, roundToDigits);
+ nudBabyMatureSpeed.ValueSaveDouble = Math.Round(esm.BabyMatureSpeedMultiplier, roundToDigits);
+ nudBabyCuddleInterval.ValueSaveDouble = Math.Round(esm.BabyCuddleIntervalMultiplier, roundToDigits);
+ nudBabyImprintAmount.ValueSaveDouble = Math.Round(esm.BabyImprintAmountMultiplier, roundToDigits);
+ nudBabyImprintingStatScale.ValueSaveDouble = Math.Round(esm.BabyImprintingStatScaleMultiplier, roundToDigits);
+ nudBabyFoodConsumptionSpeed.ValueSaveDouble = Math.Round(esm.BabyFoodConsumptionSpeedMultiplier, roundToDigits);
+ nudTamedDinoCharacterFoodDrain.ValueSaveDouble = Math.Round(esm.TamedDinoCharacterFoodDrainMultiplier, roundToDigits);
+ CbAllowFlyerSpeedLeveling.Checked = esm.AllowFlyerSpeedLeveling;
+ cbSingleplayerSettings.Checked = esm.UseSingleplayerSettings;
+ }
+
private void Settings_Disposed(object sender, EventArgs e)
{
_tt.RemoveAll();
@@ -1351,8 +1393,14 @@ private void CbHighlightAdjustedMultipliers_CheckedChanged(object sender, EventA
nudBabyImprintAmount.SetExtraHighlightNonDefault(highlight);
nudBabyImprintingStatScale.SetExtraHighlightNonDefault(highlight);
nudBabyFoodConsumptionSpeed.SetExtraHighlightNonDefault(highlight);
- cbSingleplayerSettings.SetBackColorAndAccordingForeColor(highlight && cbSingleplayerSettings.Checked ? Color.FromArgb(190, 40, 20) : Color.Transparent);
- CbAtlasSettings.SetBackColorAndAccordingForeColor(highlight && CbAtlasSettings.Checked ? Color.FromArgb(190, 40, 20) : Color.Transparent);
+ HighlightCheckbox(cbSingleplayerSettings);
+ HighlightCheckbox(CbAllowFlyerSpeedLeveling);
+ HighlightCheckbox(CbAtlasSettings);
+
+ void HighlightCheckbox(CheckBox cb, bool defaultUnchecked = true)
+ {
+ cb.SetBackColorAndAccordingForeColor(highlight && cb.Checked == defaultUnchecked ? Color.FromArgb(190, 40, 20) : Color.Transparent);
+ }
}
private void BExportSpreadsheetMoveUp_Click(object sender, EventArgs e)
diff --git a/ARKBreedingStats/uiControls/CreatureAnalysis.cs b/ARKBreedingStats/uiControls/CreatureAnalysis.cs
index b72c53c3..e6aeaa14 100644
--- a/ARKBreedingStats/uiControls/CreatureAnalysis.cs
+++ b/ARKBreedingStats/uiControls/CreatureAnalysis.cs
@@ -57,14 +57,14 @@ private void SetStatus(Label labelIcon, LevelStatus status, Label labelText = nu
labelIcon.ForeColor = Color.DarkGreen;
labelIcon.Text = "✓";
if (labelText != null)
- labelText.Text = "Keep this creatures!";
+ labelText.Text = "Keep this creature!";
break;
case LevelStatus.NewTopLevel:
labelIcon.BackColor = Color.LightYellow;
labelIcon.ForeColor = Color.Gold;
labelIcon.Text = "★";
if (labelText != null)
- labelText.Text = "Keep this creatures, it adds new traits to your library!";
+ labelText.Text = "Keep this creature, it adds new traits to your library!";
break;
default:
labelIcon.BackColor = Color.LightGray;
diff --git a/ARKBreedingStats/values/ServerMultipliers.cs b/ARKBreedingStats/values/ServerMultipliers.cs
index 79754c71..e50e7893 100644
--- a/ARKBreedingStats/values/ServerMultipliers.cs
+++ b/ARKBreedingStats/values/ServerMultipliers.cs
@@ -10,7 +10,7 @@ namespace ARKBreedingStats.values
public class ServerMultipliers
{
///
- /// statMultipliers[statIndex][m], m: 0:tamingAdd, 1:tamingMult, 2:levelUpDom, 3:levelUpWild
+ /// statMultipliers[statIndex][m], m: 0: Stats.IndexTamingAdd, 1: Stats.IndexTamingMult, 2: Stats.IndexLevelDom, 3: Stats.IndexLevelWild
///
[JsonProperty]
public double[][] statMultipliers;
diff --git a/ARKBreedingStats/values/Values.cs b/ARKBreedingStats/values/Values.cs
index db8624aa..775f24c2 100644
--- a/ARKBreedingStats/values/Values.cs
+++ b/ARKBreedingStats/values/Values.cs
@@ -545,23 +545,23 @@ public void ApplyMultipliers(CreatureCollection cc, bool eventMultipliers = fals
continue;
// don't apply the multiplier if AddWhenTamed is negative (e.g. Giganotosaurus, Griffin)
- sp.stats[s].AddWhenTamed *= sp.stats[s].AddWhenTamed > 0 ? singlePlayerServerMultipliers.statMultipliers[s][0] : 1;
+ sp.stats[s].AddWhenTamed *= sp.stats[s].AddWhenTamed > 0 ? singlePlayerServerMultipliers.statMultipliers[s][Stats.IndexTamingAdd] : 1;
// don't apply the multiplier if MultAffinity is negative (e.g. Aberration variants)
- sp.stats[s].MultAffinity *= sp.stats[s].MultAffinity > 0 ? singlePlayerServerMultipliers.statMultipliers[s][1] : 1;
- sp.stats[s].IncPerTamedLevel *= singlePlayerServerMultipliers.statMultipliers[s][2];
- sp.stats[s].IncPerWildLevel *= singlePlayerServerMultipliers.statMultipliers[s][3];
+ sp.stats[s].MultAffinity *= sp.stats[s].MultAffinity > 0 ? singlePlayerServerMultipliers.statMultipliers[s][Stats.IndexTamingMult] : 1;
+ sp.stats[s].IncPerTamedLevel *= singlePlayerServerMultipliers.statMultipliers[s][Stats.IndexLevelDom];
+ sp.stats[s].IncPerWildLevel *= singlePlayerServerMultipliers.statMultipliers[s][Stats.IndexLevelWild];
// troodonism values
if (sp.altStats?[s] != null)
{
sp.altStats[s].AddWhenTamed *= sp.altStats[s].AddWhenTamed > 0
- ? singlePlayerServerMultipliers.statMultipliers[s][0]
+ ? singlePlayerServerMultipliers.statMultipliers[s][Stats.IndexTamingAdd]
: 1;
sp.altStats[s].MultAffinity *= sp.altStats[s].MultAffinity > 0
- ? singlePlayerServerMultipliers.statMultipliers[s][1]
+ ? singlePlayerServerMultipliers.statMultipliers[s][Stats.IndexTamingMult]
: 1;
- sp.altStats[s].IncPerTamedLevel *= singlePlayerServerMultipliers.statMultipliers[s][2];
- sp.altStats[s].IncPerWildLevel *= singlePlayerServerMultipliers.statMultipliers[s][3];
+ sp.altStats[s].IncPerTamedLevel *= singlePlayerServerMultipliers.statMultipliers[s][Stats.IndexLevelDom];
+ sp.altStats[s].IncPerWildLevel *= singlePlayerServerMultipliers.statMultipliers[s][Stats.IndexLevelWild];
}
double GetRawStatValue(int statIndex, int statValueTypeIndex, bool customOverride)
@@ -743,8 +743,6 @@ public bool TryGetSpeciesByClassName(string speciesClassName, out Species recogn
///
/// Returns the according species to the passed blueprintPath or null if unknown.
///
- ///
- ///
public Species SpeciesByBlueprint(string blueprintPath)
{
if (string.IsNullOrEmpty(blueprintPath)) return null;
@@ -755,6 +753,16 @@ public Species SpeciesByBlueprint(string blueprintPath)
return _blueprintToSpecies.TryGetValue(blueprintPath, out var s) ? s : null;
}
+ ///
+ /// Returns the according species to the passed blueprintPath or null if unknown. Removes trailing _C if there.
+ ///
+ public Species SpeciesByBlueprint(string blueprintPath, bool removeTrailingC)
+ {
+ if (removeTrailingC && blueprintPath?.EndsWith("_C") == true)
+ return SpeciesByBlueprint(blueprintPath.Substring(0, blueprintPath.Length - 2));
+ return SpeciesByBlueprint(blueprintPath);
+ }
+
///
/// Sets the ModsManifest. If the value is null, a new default object will be created.
///
diff --git a/ArkSavegameToolkit b/ArkSavegameToolkit
index 3f600402..95c0e875 160000
--- a/ArkSavegameToolkit
+++ b/ArkSavegameToolkit
@@ -1 +1 @@
-Subproject commit 3f600402f848fa3921e2d5c864c19b2a3bb432e5
+Subproject commit 95c0e8754161cd9cb2117d592906d882c05fc879
diff --git a/translations.txt b/translations.txt
index 35d0c1a4..af8d6de2 100644
--- a/translations.txt
+++ b/translations.txt
@@ -334,6 +334,7 @@ runDefaultExtractionAndImportFileToolStripMenuItem Run default Extraction and im
runDefaultExtractionToolStripMenuItem Run default Extraction
saveAsToolStripMenuItem Save &as...
saveToolStripMenuItem &Save
+Save and quit Save and quit
SelectSpeciesBreedingPlanner Select a species to see suggestions for the chosen breeding-mode
Server Server
serverFilterFromLibrary Server filter from library