public static bool Prefix(LevelUpState state, UnitDescriptor unit) { if (!settings.toggleMulticlass) { return(false); } if ((UnityEngine.Object)state.SelectedClass == (UnityEngine.Object)null) { return(true); } SkipLevelsForSpellProgression component1 = state.SelectedClass.GetComponent <SkipLevelsForSpellProgression>(); if ((UnityEngine.Object)component1 != (UnityEngine.Object)null && ((IEnumerable <int>)component1.Levels).Contains <int>(state.NextClassLevel)) { return(true); } ClassData classData = unit.Progression.GetClassData(state.SelectedClass); if (classData == null || !((UnityEngine.Object)classData.Spellbook != (UnityEngine.Object)null)) { return(true); } Spellbook spellbook1 = unit.DemandSpellbook(classData.Spellbook); if ((bool)(UnityEngine.Object)state.SelectedClass.Spellbook && (UnityEngine.Object)state.SelectedClass.Spellbook != (UnityEngine.Object)classData.Spellbook) { Spellbook spellbook2 = unit.Spellbooks.FirstOrDefault <Spellbook>((Func <Spellbook, bool>)(s => (UnityEngine.Object)s.Blueprint == (UnityEngine.Object)state.SelectedClass.Spellbook)); if (spellbook2 != null) { foreach (AbilityData allKnownSpell in spellbook2.GetAllKnownSpells()) { spellbook1.AddKnown(allKnownSpell.SpellLevel, allKnownSpell.Blueprint); } unit.DeleteSpellbook(state.SelectedClass.Spellbook); } } int casterLevelBefore = spellbook1.CasterLevel; spellbook1.AddLevelFromClass(classData.CharacterClass); int casterLevelAfter = spellbook1.CasterLevel; if (casterLevelBefore == casterLevelAfter) { return(true); // Mod line } SpellSelectionData spellSelectionData = state.DemandSpellSelection(spellbook1.Blueprint, spellbook1.Blueprint.SpellList); if ((UnityEngine.Object)spellbook1.Blueprint.SpellsKnown != (UnityEngine.Object)null) { for (int index = 0; index <= 10; ++index) { BlueprintSpellsTable spellsKnown = spellbook1.Blueprint.SpellsKnown; int?count = spellsKnown.GetCount(casterLevelBefore, index); int num1 = count ?? 0; count = spellsKnown.GetCount(casterLevelAfter, index); int num2 = count ?? 0; spellSelectionData.SetLevelSpells(index, Math.Max(0, num2 - num1)); } } int maxSpellLevel = spellbook1.MaxSpellLevel; if (spellbook1.Blueprint.SpellsPerLevel > 0) { if (casterLevelBefore == 0) { spellSelectionData.SetExtraSpells(0, maxSpellLevel); spellSelectionData.ExtraByStat = true; spellSelectionData.UpdateMaxLevelSpells(unit); } else { spellSelectionData.SetExtraSpells(spellbook1.Blueprint.SpellsPerLevel, maxSpellLevel); } } foreach (AddCustomSpells component2 in spellbook1.Blueprint.GetComponents <AddCustomSpells>()) { ApplySpellbook.TryApplyCustomSpells(spellbook1, component2, state, unit); } return(true); }
// TODO: this unfortunately duplicates a lot of ApplySpellbook.Apply, but it's needed because: // - Apply checks the skip spell levels internally, so we can't prevent the skip without mutating data structures. // - Apply only knows how to add one level, but sometimes we need to gain 2 (if Prestigious Spellcaster fills in // a previous missing level, and another one is gained normally). internal static void IncreaseCasterLevel(LevelUpState state, UnitDescriptor unit) { var blueprintSpellbook = unit.Progression.GetClassData(state.SelectedClass)?.Spellbook; Log.Append($"IncreaseCasterLevel {unit.CharacterName}, class {state.SelectedClass}, blueprintSpellbook {blueprintSpellbook}"); if (blueprintSpellbook == null) { return; } var spellbook = unit.DemandSpellbook(blueprintSpellbook); var selectedClassSpellbook = state.SelectedClass.Spellbook; if (selectedClassSpellbook != null && selectedClassSpellbook != blueprintSpellbook) { var oldSpellbook = unit.Spellbooks.FirstOrDefault((s) => s.Blueprint == selectedClassSpellbook); if (oldSpellbook != null) { foreach (var known in oldSpellbook.GetAllKnownSpells()) { spellbook.AddKnown(known.SpellLevel, known.Blueprint); } unit.DeleteSpellbook(selectedClassSpellbook); } } int oldCasterLevel = spellbook.CasterLevel; spellbook.AddCasterLevel(); int newCasterLevel = spellbook.CasterLevel; Log.Write($"Level up from {oldCasterLevel} to {newCasterLevel}"); var spellSelection = state.DemandSpellSelection(spellbook.Blueprint, spellbook.Blueprint.SpellList); var spellsKnown = spellbook.Blueprint.SpellsKnown; if (spellsKnown != null) { for (int i = 0; i <= 9; i++) { int?oldSpellsKnown = spellsKnown.GetCount(oldCasterLevel, i); int?newSpellsKnown = spellsKnown.GetCount(newCasterLevel, i); var newSpells = newSpellsKnown.GetValueOrDefault() - oldSpellsKnown.GetValueOrDefault(); // Log.Write($" gain spells {newSpells} at level {i}"); int existingNewSpells = spellSelection.LevelCount[i]?.SpellSelections.Length ?? 0; spellSelection.SetLevelSpells(i, newSpells + existingNewSpells); } } int maxSpellLevel = spellbook.MaxSpellLevel; int spellsPerLevel = spellbook.Blueprint.SpellsPerLevel; if (spellsPerLevel > 0) { if (oldCasterLevel == 0) { spellSelection.SetExtraSpells(0, maxSpellLevel); spellSelection.ExtraByStat = true; spellSelection.UpdateMaxLevelSpells(unit); } else { spellSelection.ExtraMaxLevel = maxSpellLevel; var existingExtra = spellSelection.ExtraSelected?.Length ?? 0; spellSelection.ExtraSelected = new BlueprintAbility[spellsPerLevel + existingExtra]; } } foreach (var customSpells in spellbook.Blueprint.GetComponents <AddCustomSpells>()) { if (customSpells.CasterLevel == newCasterLevel) { var customSelection = state.DemandSpellSelection(spellbook.Blueprint, customSpells.SpellList); customSelection.SetExtraSpells(customSpells.Count, customSpells.MaxSpellLevel); } } ReplaceSpells.HandleUpdateCasterLevel(unit, state, spellbook); }
static void Postfix(MethodBase __originalMethod, ApplySpellbook __instance, LevelUpState state, UnitDescriptor unit) { if (!IsAvailable()) { return; } if (!Core.Mod.LockedPatchedMethods.Contains(__originalMethod)) { Core.Mod.LockedPatchedMethods.Add(__originalMethod); Dictionary <Spellbook, int> spellbookCasterLevels = new Dictionary <Spellbook, int>(); // simulate the internal caster level foreach (ClassData classData in unit.Progression.Classes) { if (classData != null && classData.Spellbook != null) { int[] skipLevelList = classData.CharacterClass.GetComponent <SkipLevelsForSpellProgression>()?.Levels; int skipLevels = 0; // Eldritch Knight || Dragon Disciple if (classData.CharacterClass.AssetGuid == "de52b73972f0ed74c87f8f6a8e20b542" || classData.CharacterClass.AssetGuid == "72051275b1dbb2d42ba9118237794f7c") { skipLevels = 1; } Spellbook spellbook = unit.DemandSpellbook(classData.Spellbook); if (!spellbookCasterLevels.TryGetValue(spellbook, out int casterLevel)) { casterLevel = 0; } for (int i = 1 + skipLevels; i <= classData.Level; i++) { if (skipLevelList == null || !skipLevelList.Contains(i)) { casterLevel++; } } spellbookCasterLevels[spellbook] = casterLevel; } } // f*****g Mystic foreach (Feature feature in unit.Progression.Features.Enumerable .Where(feature => feature.Components.Any(item => item is AddSpellbookLevel))) { foreach (AddSpellbookLevel fact in feature.Components .Where(item => item is AddSpellbookLevel)) { ClassData classData = unit.Progression.GetClassData(feature.GetSourceClass()); int[] skipLevelList = classData.CharacterClass.GetComponent <SkipLevelsForSpellProgression>()?.Levels; Spellbook spellbook = fact.Owner.DemandSpellbook(fact.Spellbook); if (!spellbookCasterLevels.TryGetValue(spellbook, out int casterLevel)) { casterLevel = 0; } for (int i = 1; i <= classData.Level; i++) { if (skipLevelList == null || !skipLevelList.Contains(i)) { casterLevel++; } } spellbookCasterLevels[spellbook] = casterLevel; } } // simulate the caster level foreach (Spellbook spellbook in new List <Spellbook>(spellbookCasterLevels.Keys)) { int casterLevel = spellbookCasterLevels[spellbook]; int maxLevel = spellbook.Blueprint.SpellsPerDay.Levels.Length - 1; if (RestrictCasterLevelToCharacterLevel.IsAvailable()) { casterLevel = Math.Min(casterLevel, unit.Progression.CharacterLevel); } casterLevel = Math.Max(0, Math.Min(maxLevel, casterLevel + spellbook.Blueprint.CasterLevelModifier)); spellbookCasterLevels[spellbook] = casterLevel; Core.Debug($" - Recalculated spellbook: {spellbook.Blueprint.Name}, target level: {casterLevel}, current level: {spellbook.CasterLevel}"); } // check and increase the caster level of the spellbooks StateReplacer stateReplacer = new StateReplacer(state); foreach (Spellbook spellbook in spellbookCasterLevels.Keys) { BlueprintCharacterClass characterClass = spellbook.Blueprint.GetOwnerClass(unit); stateReplacer.Replace(characterClass, unit.Progression.GetClassLevel(characterClass)); // We need __instance.Apply(state, unit) to always lead to an increase in the caster level SkipLevelsForSpellProgression skipLevels = characterClass.GetComponent <SkipLevelsForSpellProgression>(); int[] backup = skipLevels?.Levels; if (skipLevels != null) { skipLevels.Levels = new int[0]; } int casterLevel = spellbookCasterLevels[spellbook]; while (spellbook.CasterLevel < casterLevel) { Core.Debug($" - Applying caster level: {spellbook.CasterLevel} => {casterLevel}"); __instance.Apply(state, unit); } if (skipLevels != null) { skipLevels.Levels = backup; } } stateReplacer.Restore(); Core.Mod.LockedPatchedMethods.Remove(__originalMethod); } }
public static bool Prefix(LevelUpState state, UnitDescriptor unit) { if (!settings.toggleMulticlass) { return(true); } if (state.SelectedClass == null) { return(false); } var component1 = state.SelectedClass.GetComponent <SkipLevelsForSpellProgression>(); if (component1 != null && component1.Levels.Contains(state.NextClassLevel)) { return(false); } var classData = unit.Progression.GetClassData(state.SelectedClass); if (classData?.Spellbook == null) { return(false); } var spellbook1 = unit.DemandSpellbook(classData.Spellbook); if (state.SelectedClass.Spellbook && state.SelectedClass.Spellbook != classData.Spellbook) { var spellbook2 = unit.Spellbooks.FirstOrDefault(s => s.Blueprint == state.SelectedClass.Spellbook); if (spellbook2 != null) { foreach (var allKnownSpell in spellbook2.GetAllKnownSpells()) { spellbook1.AddKnown(allKnownSpell.SpellLevel, allKnownSpell.Blueprint); } unit.DeleteSpellbook(state.SelectedClass.Spellbook); } } var casterLevelAfter = CasterHelpers.GetRealCasterLevel(unit, spellbook1.Blueprint); // Calculates based on progression which includes class selected in level up screen spellbook1.AddLevelFromClass(classData.CharacterClass); // This only adds one class at a time and will only ever increase by 1 or 2 var casterLevelBefore = casterLevelAfter - (classData.CharacterClass.IsMythic ? 2 : 1); // Technically only needed to see if this is our first level of a casting class var spellSelectionData = state.DemandSpellSelection(spellbook1.Blueprint, spellbook1.Blueprint.SpellList); if (spellbook1.Blueprint.SpellsKnown != null) { for (var index = 0; index <= 10; ++index) { var spellsKnown = spellbook1.Blueprint.SpellsKnown; var expectedCount = spellsKnown.GetCount(casterLevelAfter, index); var actual = CasterHelpers.GetCachedSpellsKnown(unit, spellbook1, index); #if DEBUG //Mod.Trace($"Spellbook {spellbook1.Blueprint.Name}: Granting {expectedCount-actual} spells of spell level:{index} based on expected={expectedCount} and actual={actual}"); #endif spellSelectionData.SetLevelSpells(index, Math.Max(0, expectedCount - actual)); } } var maxSpellLevel = spellbook1.MaxSpellLevel; if (spellbook1.Blueprint.SpellsPerLevel > 0) { if (casterLevelBefore == 0) { spellSelectionData.SetExtraSpells(0, maxSpellLevel); spellSelectionData.ExtraByStat = true; spellSelectionData.UpdateMaxLevelSpells(unit); } else { spellSelectionData.SetExtraSpells(spellbook1.Blueprint.SpellsPerLevel, maxSpellLevel); } } foreach (var component2 in spellbook1.Blueprint.GetComponents <AddCustomSpells>()) { ApplySpellbook.TryApplyCustomSpells(spellbook1, component2, state, unit); } return(false); }
static public bool Prefix(ApplySpellbook __instance, LevelUpState state, UnitDescriptor unit) { if (state.SelectedClass != ArcanistClass.arcanist) { return(true); } if (state.SelectedClass == null) { return(false); } SkipLevelsForSpellProgression component = state.SelectedClass.GetComponent <SkipLevelsForSpellProgression>(); if (component != null && component.Levels.Contains(state.NextClassLevel)) { return(false); } ClassData classData = unit.Progression.GetClassData(state.SelectedClass); if (classData == null) { return(false); } if (classData.Spellbook != null) { Spellbook spellbook = unit.DemandSpellbook(classData.Spellbook); if (state.SelectedClass.Spellbook && state.SelectedClass.Spellbook != classData.Spellbook) { Spellbook spellbook2 = unit.Spellbooks.FirstOrDefault((Spellbook s) => s.Blueprint == state.SelectedClass.Spellbook); if (spellbook2 != null) { foreach (AbilityData abilityData in spellbook2.GetAllKnownSpells()) { spellbook.AddKnown(abilityData.SpellLevel, abilityData.Blueprint, false); } unit.DeleteSpellbook(state.SelectedClass.Spellbook); } } int casterLevel = spellbook.CasterLevel; spellbook.AddCasterLevel(); int casterLevel2 = spellbook.CasterLevel; SpellSelectionData spellSelectionData = state.DemandSpellSelection(spellbook.Blueprint, spellbook.Blueprint.SpellList); if (spellbook.Blueprint.SpellsKnown != null && 1 == 0) { // SelectedClass must be arcanist, arcanist requires wizard-like spell selection, // while its SpellsKnown is not null for (int i = 0; i <= 9; i++) { int?count = spellbook.Blueprint.SpellsKnown.GetCount(casterLevel, i); int num = (count == null) ? 0 : count.Value; int?count2 = spellbook.Blueprint.SpellsKnown.GetCount(casterLevel2, i); int num2 = (count2 == null) ? 0 : count2.Value; spellSelectionData.SetLevelSpells(i, num2 - num); } } int maxSpellLevel = spellbook.MaxSpellLevel; if (spellbook.Blueprint.SpellsPerLevel > 0) { if (casterLevel == 0) { spellSelectionData.SetExtraSpells(0, maxSpellLevel); spellSelectionData.ExtraByStat = true; spellSelectionData.UpdateMaxLevelSpells(unit); } else { spellSelectionData.SetExtraSpells(spellbook.Blueprint.SpellsPerLevel, maxSpellLevel); } } foreach (AddCustomSpells customSpells in spellbook.Blueprint.GetComponents <AddCustomSpells>()) { ApplySpellbook_TryApplyCustomSpells(__instance, spellbook, customSpells, state, unit); } } return(false); }