-
-
Notifications
You must be signed in to change notification settings - Fork 379
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add migrations for pre-1.6 Data/Blueprints and Data/Boots
- Loading branch information
1 parent
0258daf
commit 06f3670
Showing
4 changed files
with
394 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
292 changes: 292 additions & 0 deletions
292
ContentPatcher/Framework/Migrations/Migration_2_0.ForBlueprints.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,292 @@ | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Text; | ||
using ContentPatcher.Framework.Migrations.Internal; | ||
using ContentPatcher.Framework.Patches; | ||
using Microsoft.Xna.Framework; | ||
using StardewModdingAPI; | ||
using StardewModdingAPI.Framework.Content; | ||
using StardewValley; | ||
using StardewValley.GameData.Buildings; | ||
using StardewTokenParser = StardewValley.TokenizableStrings.TokenParser; | ||
|
||
namespace ContentPatcher.Framework.Migrations | ||
{ | ||
internal partial class Migration_2_0 : BaseRuntimeMigration | ||
{ | ||
/// <summary>The migration logic to apply pre-1.6 <c>Data/Blueprints</c> patches to <c>Data/Buildings</c>.</summary> | ||
private class BlueprintsMigrator : IEditAssetMigrator | ||
{ | ||
/********* | ||
** Fields | ||
*********/ | ||
/// <summary>The pre-1.6 asset name.</summary> | ||
private const string OldAssetName = "Data/Blueprints"; | ||
|
||
/// <summary>The 1.6 asset name.</summary> | ||
private const string NewAssetName = "Data/Buildings"; | ||
|
||
|
||
/********* | ||
** Public methods | ||
*********/ | ||
/// <inheritdoc /> | ||
public bool AppliesTo(IAssetName assetName) | ||
{ | ||
return assetName?.IsEquivalentTo(BlueprintsMigrator.OldAssetName, useBaseName: true) is true; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public IAssetName? RedirectTarget(IAssetName assetName, IPatch patch) | ||
{ | ||
return new AssetName(BlueprintsMigrator.NewAssetName, null, null); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public bool TryApplyLoadPatch<T>(LoadPatch patch, IAssetName assetName, [NotNullWhen(true)] ref T? asset, out string? error) | ||
{ | ||
Dictionary<string, string> tempData = patch.Load<Dictionary<string, string>>(this.GetOldAssetName(assetName)); | ||
Dictionary<string, BuildingData> newData = new(); | ||
this.MergeIntoNewFormat(newData, tempData, null); | ||
asset = (T)(object)newData; | ||
|
||
error = null; | ||
return true; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public bool TryApplyEditPatch<T>(EditDataPatch patch, IAssetData asset, out string? error) | ||
{ | ||
var data = asset.GetData<Dictionary<string, BuildingData>>(); | ||
Dictionary<string, string> tempData = this.GetOldFormat(data); | ||
Dictionary<string, string> tempDataBackup = new(tempData); | ||
patch.Edit<Dictionary<string, string>>(new FakeAssetData(asset, this.GetOldAssetName(asset.Name), tempData)); | ||
this.MergeIntoNewFormat(data, tempData, tempDataBackup); | ||
|
||
error = null; | ||
return true; | ||
} | ||
|
||
|
||
/********* | ||
** Private methods | ||
*********/ | ||
/// <summary>Get the old asset to edit.</summary> | ||
/// <param name="newName">The new asset name whose locale to use.</param> | ||
private IAssetName GetOldAssetName(IAssetName newName) | ||
{ | ||
return new AssetName(BlueprintsMigrator.OldAssetName, newName.LocaleCode, newName.LanguageCode); | ||
} | ||
|
||
/// <summary>Get the pre-1.6 equivalent for the new asset data.</summary> | ||
/// <param name="from">The data to convert.</param> | ||
private Dictionary<string, string> GetOldFormat(IDictionary<string, BuildingData> from) | ||
{ | ||
var data = new Dictionary<string, string>(); | ||
|
||
string[] fields = new string[19]; | ||
foreach ((string key, BuildingData entry) in from) | ||
{ | ||
fields[0] = this.GetOldItemsRequiredField(entry); | ||
fields[1] = entry.Size.X.ToString(); | ||
fields[2] = entry.Size.Y.ToString(); | ||
fields[3] = entry.HumanDoor.X.ToString(); | ||
fields[4] = entry.HumanDoor.Y.ToString(); | ||
fields[5] = entry.AnimalDoor.X.ToString(); | ||
fields[6] = entry.AnimalDoor.Y.ToString(); | ||
fields[7] = entry.IndoorMap; | ||
fields[8] = StardewTokenParser.ParseText(entry.Name); | ||
fields[9] = StardewTokenParser.ParseText(entry.Description); | ||
fields[10] = "Buildings"; // unused (blueprintType) | ||
fields[11] = "none"; // unused (nameOfBuildingToUpgrade) | ||
fields[12] = "0"; // unused (sourceRectForMenuView.X) | ||
fields[13] = "0"; // unused (sourceRectForMenuView.Y) | ||
fields[14] = entry.MaxOccupants.ToString(); | ||
fields[15] = "null"; // unused (actionBehavior) | ||
fields[16] = "Farm"; // unused (locations) | ||
fields[17] = entry.BuildCost.ToString(); | ||
fields[18] = entry.MagicalConstruction.ToString().ToLowerInvariant(); | ||
|
||
data[key] = string.Join('/', fields); | ||
} | ||
|
||
return data; | ||
} | ||
|
||
/// <summary>Merge pre-1.6 data into the new asset.</summary> | ||
/// <param name="asset">The asset data to update.</param> | ||
/// <param name="from">The pre-1.6 data to merge into the asset.</param> | ||
/// <param name="fromBackup">A copy of <paramref name="from"/> before edits were applied.</param> | ||
private void MergeIntoNewFormat(IDictionary<string, BuildingData> asset, IDictionary<string, string> from, IDictionary<string, string>? fromBackup) | ||
{ | ||
// remove deleted entries | ||
foreach (string key in asset.Keys) | ||
{ | ||
if (!from.ContainsKey(key)) | ||
asset.Remove(key); | ||
} | ||
|
||
// apply entries | ||
foreach ((string key, string fromEntry) in from) | ||
{ | ||
// get/add target record | ||
bool isNew = false; | ||
if (!asset.TryGetValue(key, out BuildingData? entry)) | ||
{ | ||
isNew = true; | ||
entry = new BuildingData() | ||
{ | ||
Name = key, | ||
Description = "...", | ||
Texture = $"Buildings\\{key}" | ||
}; | ||
} | ||
|
||
// get backup | ||
string[]? backupFields = null; | ||
if (fromBackup is not null) | ||
{ | ||
if (fromBackup.TryGetValue(key, out string? prevRow) && prevRow == fromEntry) | ||
continue; // no changes | ||
backupFields = prevRow?.Split('/'); | ||
} | ||
|
||
// merge fields into new asset | ||
{ | ||
string[] fields = fromEntry.Split('/'); | ||
|
||
/* | ||
fields[14] = entry.MaxOccupants.ToString(); | ||
fields[17] = entry.BuildCost.ToString(); | ||
fields[18] = entry.MagicalConstruction.ToString().ToLowerInvariant(); | ||
*/ | ||
|
||
string rawItemsRequired = ArgUtility.Get(fields, 0); | ||
if (rawItemsRequired != ArgUtility.Get(backupFields, 0)) | ||
this.MergeItemsRequiredFieldIntoNewFormat(entry, rawItemsRequired); | ||
|
||
entry.Size = new Point( | ||
ArgUtility.GetInt(fields, 1, entry.Size.X), | ||
ArgUtility.GetInt(fields, 2, entry.Size.Y) | ||
); | ||
entry.HumanDoor = new Point( | ||
ArgUtility.GetInt(fields, 3, entry.HumanDoor.X), | ||
ArgUtility.GetInt(fields, 4, entry.HumanDoor.Y) | ||
); | ||
entry.AnimalDoor = new Rectangle( | ||
ArgUtility.GetInt(fields, 5, entry.AnimalDoor.X), | ||
ArgUtility.GetInt(fields, 6, entry.AnimalDoor.Y), | ||
1, | ||
1 | ||
); | ||
|
||
entry.IndoorMap = ArgUtility.Get(fields, 7, entry.IndoorMap, allowBlank: false); | ||
if (string.IsNullOrWhiteSpace(entry.IndoorMap) || entry.IndoorMapType == "null") | ||
entry.IndoorMap = null; | ||
|
||
{ | ||
string displayName = ArgUtility.Get(fields, 8); | ||
if (!string.IsNullOrWhiteSpace(displayName) && displayName != ArgUtility.Get(backupFields, 8) && displayName != StardewTokenParser.ParseText(entry.Name)) | ||
entry.Name = displayName; | ||
} | ||
|
||
{ | ||
string description = ArgUtility.Get(fields, 9); | ||
if (!string.IsNullOrWhiteSpace(description) && description != ArgUtility.Get(backupFields, 9) && description != StardewTokenParser.ParseText(entry.Description)) | ||
entry.Description = description; | ||
} | ||
|
||
entry.MaxOccupants = ArgUtility.GetInt(fields, 14, entry.MaxOccupants); | ||
entry.BuildCost = ArgUtility.GetInt(fields, 17, entry.BuildCost); | ||
entry.MagicalConstruction = ArgUtility.GetBool(fields, 18, entry.MagicalConstruction); | ||
} | ||
|
||
// set value | ||
if (isNew) | ||
asset[key] = entry; | ||
} | ||
} | ||
|
||
/// <summary>Get the pre-1.6 'items required' field for the new asset data.</summary> | ||
/// <param name="data">The building data.</param> | ||
private string GetOldItemsRequiredField(BuildingData data) | ||
{ | ||
if (data.BuildMaterials?.Count is not > 0) | ||
return string.Empty; | ||
|
||
StringBuilder result = new(); | ||
|
||
foreach (BuildingMaterial material in data.BuildMaterials) | ||
{ | ||
result | ||
.Append(RuntimeMigrationHelper.ParseObjectId(material.ItemId) ?? material.ItemId) | ||
.Append(' ') | ||
.Append(material.Amount) | ||
.Append(' '); | ||
} | ||
|
||
return result.ToString(0, result.Length - 1); | ||
} | ||
|
||
/// <summary>Merge a pre-1.6 'items required' field into the new asset data.</summary> | ||
/// <param name="data">The asset entry.</param> | ||
/// <param name="field">The field value.</param> | ||
private void MergeItemsRequiredFieldIntoNewFormat(BuildingData data, string field) | ||
{ | ||
string[] fields = field.Split(' '); | ||
|
||
// build list | ||
Dictionary<string, int> materials = new(); | ||
for (int i = 0; i < fields.Length - 1; i += 2) | ||
{ | ||
string itemId = ArgUtility.Get(fields, i, allowBlank: false); | ||
int count = ArgUtility.GetInt(fields, i + 1, 1); | ||
|
||
if (itemId != null) | ||
materials[itemId] = count; | ||
} | ||
|
||
// step 1: remove or update existing entries | ||
if (data.BuildMaterials?.Count > 0) | ||
{ | ||
for (int i = 0; i < data.BuildMaterials.Count; i++) | ||
{ | ||
var material = data.BuildMaterials[i]; | ||
string itemId = RuntimeMigrationHelper.ParseObjectId(material.ItemId) ?? ItemRegistry.QualifyItemId(material.ItemId); | ||
|
||
// remove if deleted | ||
if (!materials.TryGetValue(itemId, out int count)) | ||
{ | ||
data.BuildMaterials.RemoveAt(i); | ||
i--; | ||
} | ||
|
||
// else update | ||
else | ||
{ | ||
material.Amount = count; | ||
materials.Remove(itemId); | ||
} | ||
} | ||
} | ||
|
||
// step 2: add any remaining as new entries | ||
if (materials.Count > 0) | ||
{ | ||
data.BuildMaterials ??= new(); | ||
|
||
foreach ((string itemId, int amount) in materials) | ||
{ | ||
string qualifiedItemId = ItemRegistry.ManuallyQualifyItemId(itemId, ItemRegistry.type_object); | ||
|
||
data.BuildMaterials.Add(new() | ||
{ | ||
ItemId = qualifiedItemId, | ||
Amount = amount | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
ContentPatcher/Framework/Migrations/Migration_2_0.ForBoots.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using ContentPatcher.Framework.Migrations.Internal; | ||
using ContentPatcher.Framework.Patches; | ||
using StardewModdingAPI; | ||
|
||
namespace ContentPatcher.Framework.Migrations | ||
{ | ||
internal partial class Migration_2_0 : BaseRuntimeMigration | ||
{ | ||
/// <summary>The migration logic to apply pre-1.6 <c>Data/Boots</c> patches to the new format.</summary> | ||
private class BootsMigrator : IEditAssetMigrator | ||
{ | ||
/********* | ||
** Fields | ||
*********/ | ||
/// <summary>The asset name.</summary> | ||
private const string AssetName = "Data/Boots"; | ||
|
||
|
||
/********* | ||
** Public methods | ||
*********/ | ||
/// <inheritdoc /> | ||
public bool AppliesTo(IAssetName assetName) | ||
{ | ||
return assetName?.IsEquivalentTo(BootsMigrator.AssetName, useBaseName: true) is true; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public IAssetName? RedirectTarget(IAssetName assetName, IPatch patch) | ||
{ | ||
return null; // same asset name | ||
} | ||
|
||
/// <inheritdoc /> | ||
public bool TryApplyLoadPatch<T>(LoadPatch patch, IAssetName assetName, [NotNullWhen(true)] ref T? asset, out string? error) | ||
{ | ||
var data = patch.Load<Dictionary<string, string>>(assetName)!; | ||
this.MigrateData(data); | ||
asset = (T)(object)data; | ||
|
||
error = null; | ||
return true; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public bool TryApplyEditPatch<T>(EditDataPatch patch, IAssetData asset, out string? error) | ||
{ | ||
var data = (Dictionary<string, string>)asset.Data; | ||
patch.Edit<Dictionary<string, string>>(asset); | ||
this.MigrateData(data); | ||
|
||
error = null; | ||
return true; | ||
} | ||
|
||
|
||
/********* | ||
** Private methods | ||
*********/ | ||
/// <summary>Migrate pre-1.6 data to the new format.</summary> | ||
/// <param name="asset">The asset data to update.</param> | ||
private void MigrateData(IDictionary<string, string> asset) | ||
{ | ||
foreach ((string key, string fromEntry) in asset) | ||
{ | ||
int fieldCount = RuntimeMigrationHelper.CountFields(fromEntry, '/'); | ||
|
||
if (fieldCount == 6) | ||
{ | ||
string name = fromEntry[..fromEntry.IndexOf('/')]; | ||
asset[key] = fromEntry + '/' + name; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.