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