Skip to content

Commit

Permalink
Don't convert artifact display name to enum value
Browse files Browse the repository at this point in the history
Up until now, DFU was converting an artifact's display name into an enum type to determine offset for Info popups.
This is not compatible with localized artifact names.
Added artifactIndexBitfield to items. This is equal to artifactIndex << 1 | 1 when set. A 0 in lowest bit means this value is not valid / not set.
DFU will attempt to generate this bitfield when loading an older DFU save or importing classic save when this value is missing for artifact items.
This requires item name in older/classic save to be unchanged, but this was already an issue in DFU prior to now. This is the problem we're trying to move past.
Any artifacts generated during play will now set their artifactIndexBitfield at creation time.
This field is also serialized/deserialized with saves as normal.
This approach should handle majority of past saves in addition to resolving core problem in new saves.
If loading an older/classic save with an edited artifact name then Info text simply won't display properly as is already the case.
  • Loading branch information
Interkarma committed Apr 20, 2023
1 parent 0047824 commit 95209c0
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 6 deletions.
42 changes: 42 additions & 0 deletions Assets/Scripts/Game/Items/DaggerfallUnityItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public partial class DaggerfallUnityItem
int worldTextureArchive;
int worldTextureRecord;
ItemGroups itemGroup;
int artifactIndexBitfield;
int groupIndex;
int currentVariant = 0;
ulong uid;
Expand Down Expand Up @@ -173,6 +174,18 @@ public virtual int GroupIndex
set { SetItem(itemGroup, value); }
}

/// <summary>
/// Bitfield used to determine both artifact index and if bitfield is set.
/// (ArtifactIndexBitfield & 1) ...check if this bitfield has been set.
/// (ArtifactIndexBitfield >> 1) ...to get artifact index or cast to ArtifactsSubTypes.
/// Only valid for items where IsArtifact is true.
/// If this item is an artifact and this bitfield is 0 then item most likely imported from edited legacy/classic save data.
/// </summary>
public virtual int ArtifactIndexBitfield
{
get { return artifactIndexBitfield; }
}

/// <summary>
/// Get this item's template data.
/// </summary>
Expand Down Expand Up @@ -589,6 +602,7 @@ public void SetArtifact(ItemGroups itemGroup, int groupIndex)
shortName = TextManager.Instance.GetLocalizedMagicItemName((int)magicItemTemplate.index, magicItemTemplate.name);
this.itemGroup = (ItemGroups)magicItemTemplate.group;
this.groupIndex = magicItemTemplate.groupIndex;
artifactIndexBitfield = groupIndex << 1 | 1;
playerTextureArchive = archive;
playerTextureRecord = record;
worldTextureArchive = archive; // Not sure about artifact world textures, just using player texture for now
Expand Down Expand Up @@ -797,6 +811,7 @@ public virtual ItemData_v1 GetSaveData()
data.repairData = repairData.GetSaveData();
data.timeForItemToDisappear = timeForItemToDisappear;
data.timeHealthLeechLastUsed = timeHealthLeechLastUsed;
data.artifactIndexBitfield = artifactIndexBitfield;

return data;
}
Expand Down Expand Up @@ -1497,6 +1512,7 @@ void FromItem(DaggerfallUnityItem other)
message = other.message;
potionRecipeKey = other.potionRecipeKey;
timeHealthLeechLastUsed = other.timeHealthLeechLastUsed;
artifactIndexBitfield = other.artifactIndexBitfield;

isQuestItem = other.isQuestItem;
questUID = other.questUID;
Expand Down Expand Up @@ -1561,6 +1577,9 @@ void FromItemRecord(ItemRecord itemRecord)
if ((IsPotion || IsPotionRecipe) && typeDependentData < MagicAndEffects.PotionRecipe.classicRecipeKeys.Length)
potionRecipeKey = MagicAndEffects.PotionRecipe.classicRecipeKeys[typeDependentData];

// Try to generate artifactIndexBitfield if this data is missing from save
LegacyArtifactIndexBitfieldCheck();

currentVariant = 0;
enchantmentPoints = itemRecord.ParsedData.enchantmentPoints;
message = (int)itemRecord.ParsedData.message;
Expand Down Expand Up @@ -1649,15 +1668,38 @@ internal void FromItemData(ItemData_v1 data)
poisonType = Poisons.None;
potionRecipeKey = data.potionRecipe;
timeHealthLeechLastUsed = data.timeHealthLeechLastUsed;
artifactIndexBitfield = data.artifactIndexBitfield;
// Convert any old classic recipe items in saves to DFU recipe key.
if (potionRecipeKey == 0 && (IsPotion || IsPotionRecipe) && typeDependentData < MagicAndEffects.PotionRecipe.classicRecipeKeys.Length)
potionRecipeKey = MagicAndEffects.PotionRecipe.classicRecipeKeys[typeDependentData];

// Try to generate artifactIndexBitfield if this data is missing from save
LegacyArtifactIndexBitfieldCheck();

repairData.RestoreRepairData(data.repairData);

timeForItemToDisappear = data.timeForItemToDisappear;
}

/// <summary>
/// Older DFU versions matched artifacts for Info text by name to determine index - this is not compatible with localized artifact names.
/// Newly created artifacts store their artifact index in artifactIndexBitfield as artifactIndex << 1 | 1.
/// When importing an artifact from older DFU saves or classic save the artifactIndexBitfield must be generated for the first time.
/// This requires artifact shortName in older/classic saves to be unchanged, but this is no different to older versions.
/// Once artifact has artifactIndexBitfield set it will be used to get correct index of Info text for artifact.
/// </summary>
void LegacyArtifactIndexBitfieldCheck()
{
// If this item has artifact flag raised and artifactIndexBitfield & 1 not set then item is missing this value from legacy save data
// Attempt to generate artifactIndexBitfield by reversing name to enum as used in older versions
if (IsArtifact && (artifactIndexBitfield & 1) == 0)
{
ArtifactsSubTypes artifactSubType = ItemHelper.LegacyGetArtifactSubType(shortName);
if (artifactSubType != ArtifactsSubTypes.None)
artifactIndexBitfield = (int)artifactSubType << 1 | 1;
}
}

/// <summary>
/// Caches item template.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion Assets/Scripts/Game/Items/DaggerfallUnityItemMCP.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ public override TextFile.Token[] MagicPowers(TextFile.Formatting format)
{
// Use appropriate artifact description message. (8700-8721)
try {
ArtifactsSubTypes artifactType = ItemHelper.GetArtifactSubType(parent.shortName);
ArtifactsSubTypes artifactType = ItemHelper.GetArtifactSubType(parent);
return DaggerfallUnity.Instance.TextProvider.GetRSCTokens(8700 + (int)artifactType);
} catch (KeyNotFoundException e) {
Debug.Log(e.Message);
Expand Down
1 change: 1 addition & 0 deletions Assets/Scripts/Game/Items/ItemEnums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ public enum MagicItemSubTypes // Not mapped to a specific item

public enum ArtifactsSubTypes // Mapped to artifact definitions in MAGIC.DEF
{
None = -1,
Masque_of_Clavicus = 0,
Mehrunes_Razor = 1,
Mace_of_Molag_Bal = 2,
Expand Down
30 changes: 25 additions & 5 deletions Assets/Scripts/Game/Items/ItemHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -533,19 +533,39 @@ public void GetArtifactTextureIndices(ArtifactsSubTypes type, out int textureArc
}

/// <summary>
/// Gets an artifact sub type from an items' short name. (throws exception if no match)
/// Gets artifact sub type by converting short name to enum type.
/// Not compatible with localized artifact names.
/// Should only be used when importing classic saves or older save data where artifactIndexBitfield is not present.
/// </summary>
/// <param name="itemShortName">Item short name</param>
/// <returns>Artifact sub type.</returns>
public static ArtifactsSubTypes GetArtifactSubType(string itemShortName)
/// <param name="itemShortName">Item short name.</param>
/// <returns>Artifact sub type or ArtifactsSubTypes.None.</returns>
public static ArtifactsSubTypes LegacyGetArtifactSubType(string itemShortName)
{
itemShortName = itemShortName.Replace("\'", "").Replace(' ', '_');
foreach (var artifactName in Enum.GetNames(typeof(ArtifactsSubTypes)))
{
if (itemShortName.Contains(artifactName))
return (ArtifactsSubTypes)Enum.Parse(typeof(ArtifactsSubTypes), artifactName);
}
throw new KeyNotFoundException("No match found for: " + itemShortName);
return ArtifactsSubTypes.None;
}

/// <summary>
/// Gets artifact sub type using ArtifactIndexBitfield on item.
/// This method is compatible with localized artifact names.
/// Throws exception if not an artifact or item's ArtifactIndexBitfield not properly set.
/// </summary>
/// <param name="item">DaggerfalUnityItem.</param>
/// <returns>ArtifactsSubTypes.</returns>
public static ArtifactsSubTypes GetArtifactSubType(DaggerfallUnityItem item)
{
if (!item.IsArtifact)
throw new Exception("GetArtifactSubType() item is not an artifact.");

if ((item.ArtifactIndexBitfield & 1) == 0)
throw new Exception("GetArtifactSubType() item does not have an artifact index. Item most likely imported from old save data where item shortName was changed.");

return (ArtifactsSubTypes)(item.ArtifactIndexBitfield >> 1);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ public class ItemData_v1
public ItemRepairData_v1 repairData;
public uint timeForItemToDisappear;
public uint timeHealthLeechLastUsed;
public int artifactIndexBitfield;
}

[fsObject("v1")]
Expand Down

0 comments on commit 95209c0

Please sign in to comment.