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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,5 @@ EmAssetPackages/
Tools/TranslationTool/translation_issues_report_*
.claude
tmpclaude-*
CLAUDE.md
INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@ bool EnchanterCondition(string culture)

void OpenEnchantmentShop(List<string> prefixList, string culture)
{
EnchantmentHelper.OpenEnchantmentRecipeShop(prefixList, culture, false);
EnchantmentShopHelper.OpenEnchantmentRecipeShop(prefixList, culture, false);
}
}
}
Expand Down
242 changes: 12 additions & 230 deletions CSharpSourceCode/CampaignMechanics/Crafting/EnchantmentHelper.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
using HarmonyLib;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using TaleWorlds.CampaignSystem;
using TaleWorlds.Core;
using TaleWorlds.Core.ImageIdentifiers;
using TaleWorlds.LinQuick;
using TaleWorlds.Localization;
using TaleWorlds.ObjectSystem;
using TOR_Core.AbilitySystem.Spells;
using TOR_Core.CharacterDevelopment;
using TOR_Core.Extensions;
using TOR_Core.Items;
using TOR_Core.Utilities;

namespace TOR_Core.CampaignMechanics.Crafting;

/// <summary>
/// Enchantment blueprint data and item creation: what blueprints exist, who in the party
/// is eligible to learn one, and building the actual enchanted <see cref="ItemObject"/>.
/// For the town-service shop UI built on top of this data, see <see cref="EnchantmentShopHelper"/>.
/// </summary>
public static class EnchantmentHelper
{
public static ItemObject CreateEnchantedItem(ItemObject original, List<string> traits = null, string newName = null, bool playerCrafted = false, ItemModifier itemModifier = null)
Expand Down Expand Up @@ -53,7 +53,7 @@ private static ItemObject CreateItemCopy(ItemObject copyFrom, string newId, stri
return newItem;
}

private static List<ItemObject> GetBlueprintItems(List<string> prefixList)
internal static List<ItemObject> GetBlueprintItems(List<string> prefixList)
{
return MBObjectManager.Instance.GetObjectTypeList<ItemObject>()
.Where(item =>
Expand All @@ -65,7 +65,7 @@ private static List<ItemObject> GetBlueprintItems(List<string> prefixList)
.ToList();
}

private static bool TryGetBlueprintData(ItemObject item, out string blueprintId, out SkillObject requiredSkill, out int requiredSkillValue, out string restriction)
internal static bool TryGetBlueprintData(ItemObject item, out string blueprintId, out SkillObject requiredSkill, out int requiredSkillValue, out string restriction)
{
blueprintId = null;
requiredSkill = null;
Expand Down Expand Up @@ -104,19 +104,13 @@ private static bool TryGetBlueprintData(ItemObject item, out string blueprintId,
return true;
}

private static bool IsBlueprintCurrentlyApplicableToParty(string blueprintId)
{
if (Hero.MainHero.PartyBelongedTo.GetMemberHeroes().Any(hero => hero.HasKnownEnchantmentBlueprint(blueprintId)))
{
return true;
}
internal static bool IsBlueprintKnownByParty(string blueprintId) => Hero.MainHero.PartyBelongedTo.GetMemberHeroes().Any(hero => hero.HasKnownEnchantmentBlueprint(blueprintId));

return Hero.MainHero.PartyBelongedTo.ItemRoster.Any(rosterElement =>
internal static bool IsBlueprintInInventory(string blueprintId) => Hero.MainHero.PartyBelongedTo.ItemRoster.Any(rosterElement =>
TryGetBlueprintData(rosterElement.EquipmentElement.Item, out var inventoryBlueprintId, out _, out _, out _) &&
inventoryBlueprintId == blueprintId);
}

private static List<Hero> GetEligibleHeroesForBlueprint(string blueprintId, SkillObject requiredSkill, int requiredSkillValue, string restriction, bool requireRequiredSkill)
internal static List<Hero> GetEligibleHeroesForBlueprint(string blueprintId, SkillObject requiredSkill, int requiredSkillValue, string restriction, bool requireRequiredSkill)
{
var eligibleHeroes = new List<Hero>();

Expand Down Expand Up @@ -158,7 +152,7 @@ public static bool HasAnyLearnableEnchantmentRecipe(List<string> prefixList)
continue;
}

if (IsBlueprintCurrentlyApplicableToParty(blueprintId))
if (IsBlueprintKnownByParty(blueprintId) || IsBlueprintInInventory(blueprintId))
{
continue;
}
Expand All @@ -171,216 +165,4 @@ public static bool HasAnyLearnableEnchantmentRecipe(List<string> prefixList)

return false;
}

public static void OpenEnchantmentRecipeShop(List<string> prefixList, string culture, bool blessings = false)
{
var blueprints = GetBlueprintItems(prefixList);

var list = new List<ItemObject>();
foreach (var item in blueprints)
{
if (!TryGetBlueprintData(item, out var blueprintId, out var requiredSkill, out var requiredSkillValue, out var restriction))
{
continue;
}

if (IsBlueprintCurrentlyApplicableToParty(blueprintId))
{
continue;
}

if (GetEligibleHeroesForBlueprint(blueprintId, requiredSkill, requiredSkillValue, restriction, false).Any())
{
list.Add(item);
}
}

var selectableItems = new List<InquiryElement>();
foreach (var item in list)
{

var trait = item.GetTraits().FirstOrDefault();
if (trait == null)
{
TORCommon.Log($"Enchantment blueprint {item.StringId} has no traits. Skipping this item.", LogLevel.Error);
continue;
}

if (trait.OnInventoryUseScript == null)
{
TORCommon.Log($"Enchantment blueprint {item.StringId} has no inventory use script. Skipping this item.", LogLevel.Error);
continue;
}

var arguments = trait.OnInventoryUseScript.InventoryScriptArguments;
if (arguments == null || arguments.Count < 3)
{
var argCount = arguments?.Count ?? 0;
TORCommon.Log($"Enchantment blueprint {item.StringId} has insufficient arguments (expected at least 3, got {argCount})", LogLevel.Error);
continue;
}

var included = false;

var hintText = new TextObject("{TRAIT_EFFECT}\n\n{REQUIREMENT_TEXT}\n\n{COMPLETE_COST}");

if (!TryGetBlueprintData(item, out var id, out var skill, out var skillValue, out var restriction))
{
continue;
}

var eligableHeroes = GetEligibleHeroesForBlueprint(id, skill, skillValue, restriction, false);
if (!eligableHeroes.Any())
{
continue;
}

var learnableHeroes = eligableHeroes.Where(hero => hero.GetSkillValue(skill) >= skillValue).ToList();
var enabled = learnableHeroes.Any();

var requirementPrefix = "";

if (!string.IsNullOrEmpty(restriction))
{
var lore = LoreObject.GetAll().FirstOrDefault(x => x.StringId == restriction);
if (lore != null)
{
requirementPrefix = "This enchantment is bound to the Lore of " + lore.Name + ". ";
}
else
{
requirementPrefix = "This enchantment requires " + restriction + ". ";
}
}

if (!enabled)
{
if (eligableHeroes.Count == 1)
{
var hero = eligableHeroes[0];
if (hero == Hero.MainHero)
{
hintText.SetTextVariable("REQUIREMENT_TEXT", requirementPrefix + "You don't have enough " + skill.Name + ". Requires " + skillValue + ".");
}
else
{
hintText.SetTextVariable("REQUIREMENT_TEXT", requirementPrefix + hero.Name + " doesn't have enough " + skill.Name + ". Requires " + skillValue + ".");
}
}
else
{
hintText.SetTextVariable("REQUIREMENT_TEXT", requirementPrefix + "None of your eligible characters have enough " + skill.Name + ". Requires " + skillValue + ".");
}
}
else
{
hintText.SetTextVariable("REQUIREMENT_TEXT", "");
}

var crCost = 0;
var goldCost = 0;
var cr = Hero.MainHero.GetCultureSpecificCustomResource();
var factor = cr.GetCustomResourceGeneralizedFactor();
crCost = (int)factor * skillValue;

goldCost = (int)item.Value;

if (enabled)
{
if (!hintText.GetVariableValue("REQUIREMENT_TEXT", out var requirementText) ||
requirementText != null && requirementText.ToString().IsEmpty())
{
if (crCost >= Hero.MainHero.GetCultureSpecificCustomResourceValue())
{
enabled = false;

hintText.SetTextVariable("REQUIREMENT_TEXT", "Not enough {CUSTOMRESOURCE}");
}

if (goldCost >= Hero.MainHero.Gold)
{
enabled = false;

hintText.SetTextVariable("REQUIREMENT_TEXT", "Not enough {GOLD_ICON}.");
}
}

}


var underlyingTrait = ItemTrait.All.FirstOrDefault(x => x.ItemTraitStringId == id);

if (underlyingTrait != null)
{
string typeRestriction = GameTexts.FindText("tor_enchantmentshop_restriction", underlyingTrait.ValidItemType.ToString()).ToString();
GameTexts.SetVariable("VALIDTYPE_RESTRICTION", typeRestriction);
}



if (enabled)
{

hintText = new TextObject(trait.ItemTraitDescription + "\n {GOLD_VALUE}{GOLD_ICON} , {CR_VALUE}{CUSTOMRESOURCE},\n {VALIDTYPE_RESTRICTION}");
}
hintText.SetTextVariable("TRAIT_EFFECT", trait.ItemTraitDescription);
hintText.SetTextVariable("COMPLETE_COST", "{GOLD_VALUE}{GOLD_ICON} , {CR_VALUE}{CUSTOMRESOURCE}");
GameTexts.SetVariable("CR_VALUE", crCost);
GameTexts.SetVariable("CUSTOMRESOURCE", Hero.MainHero.GetCultureSpecificCustomResource().GetCustomResourceIconAsText());
GameTexts.SetVariable("GOLD_VALUE", item.Value);


selectableItems.Add(new InquiryElement(new Tuple<List<Hero>, ItemObject>(eligableHeroes, item), item.Name.ToString(), new ItemImageIdentifier(item), enabled, hintText.ToString()));
}

var shopvariation = "";
if (blessings)
{
shopvariation = "blessings";
}
else
{
shopvariation = culture;
}

var title = GameTexts.FindText("tor_enchantmentshop_title", shopvariation).ToString();
var description = GameTexts.FindText("tor_enchantmentshop_description", shopvariation).ToString();

var inquirydata = new MultiSelectionInquiryData(title, description, selectableItems, true, 1, 1, "Accept", "Cancel",
AddEnchantment, null, "", true);
MBInformationManager.ShowMultiSelectionInquiry(inquirydata, true);
}


private static void AddEnchantment(List<InquiryElement> inquiryElements)
{
var element = (Tuple<List<Hero>, ItemObject>)inquiryElements.FirstOrDefault()?.Identifier;
if (element == null) return;

var heroes = element.Item1;
var item = element.Item2;
var trait = item.GetTraits().FirstOrDefault();

var arguments = trait.OnInventoryUseScript.InventoryScriptArguments;
var skillValue = 0;

int.TryParse(arguments[2], out skillValue);
var candidateHero = heroes.Count == 1 ? heroes[0] : heroes.FirstOrDefault(x => x == Hero.MainHero);

if (candidateHero != null)
{
candidateHero.AddEnchantmentBlueprint(arguments[0], true); // convenience, only one character can learn it, so we instantly apply the trait
}
else
{
Hero.MainHero.PartyBelongedTo.ItemRoster.Add(new ItemRosterElement(item, 1)); // we dont know, so we just add it to the inventory
var itemAddedText = TORTextHelper.GetTextObject("tor_item_added_to_inventory_text", "{ITEM_NAME} was added to the inventory");
itemAddedText.SetTextVariable("ITEM_NAME", item.Name);
MBInformationManager.AddQuickInformation(itemAddedText, 0);
}

var crCost = skillValue * Hero.MainHero.GetCultureSpecificCustomResource().GetCustomResourceGeneralizedFactor();
Hero.MainHero.AddCultureSpecificCustomResource(-crCost);
Hero.MainHero.ChangeHeroGold(-item.Value);
}
}
}
Loading