diff --git a/src/Activatable/Activatable_Active.re b/src/Activatable/Activatable_Active.re index e69de29b..b321c5a3 100644 --- a/src/Activatable/Activatable_Active.re +++ b/src/Activatable/Activatable_Active.re @@ -0,0 +1,50 @@ +type t = { + naming: Activatable_Active_Name.combinedName, + active: Activatable_Convert.singleWithId, + apValue: int, + isAutomatic: bool, + validation: Activatable_Active_Validation.t, + staticEntry: Static.activatable, + heroEntry: Hero.Activatable.t, +}; + +let getActive = + ( + ~isEntryToAdd, + ~automaticAdvantages, + ~addLevelToName, + staticData, + hero, + staticEntry, + heroEntry, + singleEntry, + ) => { + let naming = + Activatable_Active_Name.getName( + ~addLevelToName, + staticData, + staticEntry, + singleEntry, + ); + + let {Activatable_Active_AdventurePointValue.apValue, isAutomatic} = + Activatable_Active_AdventurePointValue.getApValue( + ~isEntryToAdd, + ~automaticAdvantages, + staticData, + hero, + staticEntry, + heroEntry, + singleEntry, + ); + (); + // { + // naming, + // active: singleEntry, + // apValue, + // isAutomatic, + // validation: , + // staticEntry, + // heroEntry, + // }; +}; diff --git a/src/Activatable/Activatable_Active.rei b/src/Activatable/Activatable_Active.rei index e69de29b..def0f686 100644 --- a/src/Activatable/Activatable_Active.rei +++ b/src/Activatable/Activatable_Active.rei @@ -0,0 +1,26 @@ +type t = { + naming: Activatable_Active_Name.combinedName, + active: Activatable_Convert.singleWithId, + apValue: int, + isAutomatic: bool, + validation: Activatable_Active_Validation.t, + staticEntry: Static.activatable, + heroEntry: Hero.Activatable.t, +} /* t*/; + +// /** +// * `getActive cache staticData hero staticEntry maybeHeroEntry` validates an +// * inactive entry, filters possible options and then either returns a valid +// * entry with additional and filtered options or an invalid entry. +// * +// * This can be used to display valid as well as invalid entries in a single +// * list. +// */ +// let getActive: +// ( +// Activatable_Inactive_Cache.t, +// Static.t, +// Hero.t, +// Static.activatable, +// option(Hero.Activatable.t) +// ) => diff --git a/src/Activatable/Activatable_AdventurePoints.bs.js b/src/Activatable/Activatable_Active_AdventurePointValue.bs.js similarity index 99% rename from src/Activatable/Activatable_AdventurePoints.bs.js rename to src/Activatable/Activatable_Active_AdventurePointValue.bs.js index 3c716715..80f9b0eb 100644 --- a/src/Activatable/Activatable_AdventurePoints.bs.js +++ b/src/Activatable/Activatable_Active_AdventurePointValue.bs.js @@ -364,7 +364,7 @@ function getEntrySpecificCost(isEntryToAdd, staticData, hero, staticEntry, heroE return Ley_Option$OptolithClient.Monad.liftM2((function (prim, prim$1) { return prim + prim$1 | 0; }), getCostFromHeroEntry(singleHeroEntry), Ley_Option$OptolithClient.Monad.$great$great$eq(Ley_Option$OptolithClient.listToOption(wissensdurst.active), (function (fst) { - return getCostFromHeroEntry(Activatable_Convert$OptolithClient.singleToSingleWithId(heroEntry, fst)); + return getCostFromHeroEntry(Activatable_Convert$OptolithClient.singleToSingleWithId(heroEntry, 0, fst)); }))); })); })); diff --git a/src/Activatable/Activatable_AdventurePoints.re b/src/Activatable/Activatable_Active_AdventurePointValue.re similarity index 99% rename from src/Activatable/Activatable_AdventurePoints.re rename to src/Activatable/Activatable_Active_AdventurePointValue.re index b6e39962..fc93c889 100644 --- a/src/Activatable/Activatable_AdventurePoints.re +++ b/src/Activatable/Activatable_Active_AdventurePointValue.re @@ -394,7 +394,7 @@ let getEntrySpecificCost = >>= ( fst => fst - |> singleToSingleWithId(heroEntry) + |> singleToSingleWithId(heroEntry, 0) |> getCostFromHeroEntry ), ); diff --git a/src/Activatable/Activatable_AdventurePoints.rei b/src/Activatable/Activatable_Active_AdventurePointValue.rei similarity index 100% rename from src/Activatable/Activatable_AdventurePoints.rei rename to src/Activatable/Activatable_Active_AdventurePointValue.rei diff --git a/src/Activatable/Activatable_Names.bs.js b/src/Activatable/Activatable_Active_Name.bs.js similarity index 100% rename from src/Activatable/Activatable_Names.bs.js rename to src/Activatable/Activatable_Active_Name.bs.js diff --git a/src/Activatable/Activatable_Names.re b/src/Activatable/Activatable_Active_Name.re similarity index 100% rename from src/Activatable/Activatable_Names.re rename to src/Activatable/Activatable_Active_Name.re diff --git a/src/Activatable/Activatable_Names.rei b/src/Activatable/Activatable_Active_Name.rei similarity index 100% rename from src/Activatable/Activatable_Names.rei rename to src/Activatable/Activatable_Active_Name.rei diff --git a/src/Activatable/Activatable_Active_Validation.bs.js b/src/Activatable/Activatable_Active_Validation.bs.js index e2865610..c625499b 100644 --- a/src/Activatable/Activatable_Active_Validation.bs.js +++ b/src/Activatable/Activatable_Active_Validation.bs.js @@ -1,6 +1,310 @@ // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE +import * as Curry from "bs-platform/lib/es6/curry.js"; +import * as Id$OptolithClient from "../Misc/Id.bs.js"; +import * as Skills$OptolithClient from "../Increasable/Skills.bs.js"; import * as Ley_Int$OptolithClient from "../Data/Ley_Int.bs.js"; +import * as Ley_List$OptolithClient from "../Data/Ley_List.bs.js"; +import * as Tradition$OptolithClient from "../Misc/Tradition.bs.js"; +import * as Ley_IntMap$OptolithClient from "../Data/Ley_IntMap.bs.js"; +import * as Ley_IntSet$OptolithClient from "../Data/Ley_IntSet.bs.js"; +import * as Ley_Option$OptolithClient from "../Data/Ley_Option.bs.js"; +import * as EntryGroups$OptolithClient from "../Misc/EntryGroups.bs.js"; +import * as Dependencies$OptolithClient from "../Prerequisites/Dependencies.bs.js"; +import * as Prerequisites$OptolithClient from "../Prerequisites/Prerequisites.bs.js"; +import * as CombatTechniques$OptolithClient from "../Increasable/CombatTechniques.bs.js"; +import * as ActivatableSkills$OptolithClient from "../Increasable/ActivatableSkills.bs.js"; +import * as Activatable_Convert$OptolithClient from "./Activatable_Convert.bs.js"; +import * as Activatable_Accessors$OptolithClient from "./Activatable_Accessors.bs.js"; +import * as Activatable_ExtendedStyle$OptolithClient from "./Activatable_ExtendedStyle.bs.js"; +import * as Activatable_SelectOptions$OptolithClient from "./Activatable_SelectOptions.bs.js"; + +function getActivesById(mp, id) { + return Ley_Option$OptolithClient.option(/* [] */0, (function (param) { + return param.active; + }), Curry._2(Ley_IntMap$OptolithClient.lookup, id, mp)); +} + +function isNotRequiredByDependencies(hero, staticEntry, heroEntry, singleEntry) { + var tmp; + switch (staticEntry.TAG | 0) { + case /* Advantage */0 : + var partial_arg = hero.advantages; + tmp = (function (param) { + return getActivesById(partial_arg, param); + }); + break; + case /* Disadvantage */1 : + var partial_arg$1 = hero.disadvantages; + tmp = (function (param) { + return getActivesById(partial_arg$1, param); + }); + break; + case /* SpecialAbility */2 : + var partial_arg$2 = hero.specialAbilities; + tmp = (function (param) { + return getActivesById(partial_arg$2, param); + }); + break; + + } + return Ley_List$OptolithClient.Foldable.all((function (dep) { + if (Dependencies$OptolithClient.Activatable.isDependencyMatched(dep, Activatable_Convert$OptolithClient.singleWithIdToSingle(singleEntry))) { + return Ley_List$OptolithClient.Foldable.any((function (param) { + return Dependencies$OptolithClient.Activatable.isDependencyMatched(dep, param); + }), Ley_List$OptolithClient.Index.deleteAt(singleEntry.index, heroEntry.active)); + } else { + return true; + } + }), Dependencies$OptolithClient.Flatten.flattenActivatableDependencies(tmp, singleEntry.id, heroEntry.dependencies)); +} + +function isEntrySpecificRemovalValid(cache, staticData, hero, staticEntry, heroEntry, singleEntry) { + if (Tradition$OptolithClient.Magical.isTraditionId(staticData, singleEntry.id)) { + var activeTraditions = cache.magicalTraditions; + var hasMultipleTraditions = Ley_List$OptolithClient.Foldable.length(activeTraditions) > 1; + if (hasMultipleTraditions) { + return true; + } else if (ActivatableSkills$OptolithClient.hasActiveSkillEntries(/* Spells */0, hero)) { + return false; + } else { + return Curry._1(Ley_IntSet$OptolithClient.IntSet.Foldable.$$null, hero.cantrips); + } + } + if (Tradition$OptolithClient.Blessed.isTraditionId(staticData, singleEntry.id)) { + if (ActivatableSkills$OptolithClient.hasActiveSkillEntries(/* LiturgicalChants */1, hero)) { + return false; + } else { + return Curry._1(Ley_IntSet$OptolithClient.IntSet.Foldable.$$null, hero.blessings); + } + } + switch (staticEntry.TAG | 0) { + case /* Advantage */0 : + var match = Id$OptolithClient.Advantage.fromInt(staticEntry._0.id); + if (typeof match !== "number") { + return true; + } + if (match !== 4) { + if (match !== 5) { + return true; + } + var match$1 = singleEntry.options; + if (!match$1) { + return true; + } + var match$2 = match$1.hd; + if (typeof match$2 === "number") { + return true; + } + if (match$2.HASH !== -920806756) { + return true; + } + var value = CombatTechniques$OptolithClient.getValueDef(Curry._2(Ley_IntMap$OptolithClient.lookup, match$2.VAL, hero.combatTechniques)); + return value >= (cache.startExperienceLevel.maxCombatTechniqueRating + 1 | 0); + } + var match$3 = singleEntry.options; + if (!match$3) { + return true; + } + var match$4 = match$3.hd; + if (typeof match$4 === "number") { + return true; + } + if (match$4.HASH !== 290194801) { + return true; + } + var id = match$4.VAL; + var value$1 = Skills$OptolithClient.getValueDef(Curry._2(Ley_IntMap$OptolithClient.lookup, id, hero.skills)); + var countSameSkill = Ley_List$OptolithClient.countBy((function (active) { + var match = active.options; + if (!match) { + return false; + } + var match$1 = match.hd; + if (typeof match$1 === "number" || match$1.HASH !== 290194801) { + return false; + } else { + return match$1.VAL === id; + } + }), heroEntry.active); + return value$1 >= (cache.startExperienceLevel.maxSkillRating + countSameSkill | 0); + case /* Disadvantage */1 : + return true; + case /* SpecialAbility */2 : + var match$5 = Id$OptolithClient.SpecialAbility.fromInt(staticEntry._0.id); + if (typeof match$5 !== "number") { + return true; + } + if (match$5 >= 42) { + if (match$5 >= 45) { + return true; + } + var match$6 = cache.blessedTradition; + if (match$6 === undefined) { + return true; + } + var blessedTradition = match$6[2]; + return !Curry._2(Ley_IntMap$OptolithClient.IntMap.Foldable.any, (function (param) { + return Ley_List$OptolithClient.notElem(blessedTradition.numId, param.traditions); + }), Curry._2(Ley_IntMap$OptolithClient.mapMaybe, (function (param) { + if (param.value) { + return Curry._2(Ley_IntMap$OptolithClient.lookup, param.id, staticData.liturgicalChants); + } + + }), hero.liturgicalChants)); + } + if (match$5 >= 21) { + return true; + } + switch (match$5) { + case /* Literacy */5 : + var match$7 = cache.matchingLanguagesScripts.isEntryActiveRequiringMatch; + var match$8 = cache.matchingLanguagesScripts.scriptsWithMatchingLanguages; + var match$9 = singleEntry.options; + if (!match$7) { + return true; + } + if (!match$8) { + return true; + } + if (match$8.tl) { + return true; + } + if (!match$9) { + return true; + } + var match$10 = match$9.hd; + if (typeof match$10 === "number" || match$10.HASH !== 61643255) { + return true; + } else { + return match$8.hd !== match$10.VAL; + } + case /* Language */6 : + var match$11 = cache.matchingLanguagesScripts.isEntryActiveRequiringMatch; + var match$12 = cache.matchingLanguagesScripts.languagesWithMatchingScripts; + var match$13 = singleEntry.options; + if (!match$11) { + return true; + } + if (!match$12) { + return true; + } + if (match$12.tl) { + return true; + } + if (!match$13) { + return true; + } + var match$14 = match$13.hd; + if (typeof match$14 === "number" || match$14.HASH !== 61643255) { + return true; + } else { + return match$12.hd !== match$14.VAL; + } + case /* PropertyKnowledge */10 : + var match$15 = singleEntry.options; + if (!match$15) { + return true; + } + var match$16 = match$15.hd; + if (typeof match$16 === "number") { + return true; + } + if (match$16.HASH !== 61643255) { + return true; + } + var propertyId = match$16.VAL; + return !Curry._2(Ley_IntMap$OptolithClient.IntMap.Foldable.any, (function (heroSpell) { + if (ActivatableSkills$OptolithClient.valueToInt(heroSpell.value) > 14) { + return Ley_Option$OptolithClient.option(true, (function (param) { + return param.property === propertyId; + }), Curry._2(Ley_IntMap$OptolithClient.lookup, heroSpell.id, staticData.spells)); + } else { + return false; + } + }), hero.spells); + case /* AspectKnowledge */12 : + var activeAspects = Activatable_SelectOptions$OptolithClient.mapActiveOptions1((function (param) { + if (typeof param === "number" || param.HASH !== 61643255) { + return ; + } else { + return param.VAL; + } + }), heroEntry); + var match$17 = singleEntry.options; + if (!match$17) { + return true; + } + var match$18 = match$17.hd; + if (typeof match$18 === "number") { + return true; + } + if (match$18.HASH !== 61643255) { + return true; + } + var aspectId = match$18.VAL; + var otherAspects = Ley_List$OptolithClient.$$delete(aspectId, activeAspects); + return !Curry._2(Ley_IntMap$OptolithClient.IntMap.Foldable.any, (function (heroLiturgicalChant) { + if (ActivatableSkills$OptolithClient.valueToInt(heroLiturgicalChant.value) > 14) { + return Ley_Option$OptolithClient.option(true, (function (param) { + var aspects = param.aspects; + if (Ley_List$OptolithClient.elem(aspectId, aspects)) { + return Ley_List$OptolithClient.disjoint(otherAspects, aspects); + } else { + return false; + } + }), Curry._2(Ley_IntMap$OptolithClient.lookup, heroLiturgicalChant.id, staticData.liturgicalChants)); + } else { + return false; + } + }), hero.liturgicalChants); + case /* CombatStyleCombination */15 : + var activeArmedStyles = cache.armedCombatStylesCount; + var activeUnarmedStyles = cache.unarmedCombatStylesCount; + var totalActive = activeArmedStyles + activeUnarmedStyles | 0; + if (totalActive < 3) { + if (activeArmedStyles < 2) { + return true; + } else { + return activeUnarmedStyles < 2; + } + } else { + return false; + } + case /* SkillSpecialization */0 : + case /* TerrainKnowledge */1 : + case /* CraftInstruments */2 : + case /* Hunter */3 : + case /* AreaKnowledge */4 : + case /* CombatReflexes */7 : + case /* ImprovedDodge */8 : + case /* TraditionGuildMages */9 : + case /* PropertyFocus */11 : + case /* TraditionChurchOfPraios */13 : + case /* Feuerschlucker */14 : + case /* AdaptionZauber */16 : + case /* Exorzist */17 : + case /* FavoriteSpellwork */18 : + case /* TraditionWitches */19 : + return true; + case /* MagicStyleCombination */20 : + return cache.magicalStylesCount < 2; + + } + + } +} + +function isStyleSpecialAbilityRemovalValid(hero, staticEntry) { + switch (staticEntry.TAG | 0) { + case /* Advantage */0 : + case /* Disadvantage */1 : + return true; + case /* SpecialAbility */2 : + return Activatable_ExtendedStyle$OptolithClient.isStyleValidToRemove(hero, staticEntry._0); + + } +} function getMinLevelForIncreaseEntry(defaultAmount, currentAmount) { if (currentAmount > defaultAmount) { @@ -13,9 +317,116 @@ function getMaxLevelForDecreaseEntry(maxDecrease, current) { return Ley_Int$OptolithClient.max(0, maxDecrease - current | 0); } +function getEntrySpecificMinLevel(cache, staticData, hero, staticEntry) { + switch (staticEntry.TAG | 0) { + case /* Advantage */0 : + var match = Id$OptolithClient.Advantage.fromInt(staticEntry._0.id); + if (typeof match !== "number") { + return ; + } + if (match < 20) { + return ; + } + switch (match - 20 | 0) { + case /* Aptitude */0 : + return getMinLevelForIncreaseEntry(3, ActivatableSkills$OptolithClient.countActiveSkillEntries(/* Spells */0, hero)); + case /* ExceptionalSkill */4 : + return getMinLevelForIncreaseEntry(3, EntryGroups$OptolithClient.SpecialAbility.countActiveFromGroup(/* Predigten */23, cache.specialAbilityPairs)); + case /* ExceptionalCombatTechnique */5 : + return getMinLevelForIncreaseEntry(3, EntryGroups$OptolithClient.SpecialAbility.countActiveFromGroup(/* Visionen */26, cache.specialAbilityPairs)); + case /* Nimble */1 : + case /* Blessed */2 : + case /* Luck */3 : + case /* IncreasedAstralPower */6 : + case /* IncreasedKarmaPoints */7 : + return ; + + } + case /* Disadvantage */1 : + return ; + case /* SpecialAbility */2 : + var match$1 = Id$OptolithClient.SpecialAbility.fromInt(staticEntry._0.id); + if (typeof match$1 === "number" && match$1 === 75) { + return Ley_Option$OptolithClient.ensure((function (count) { + return count > 0; + }), Curry._2(Ley_IntMap$OptolithClient.countWith, (function (param) { + return Ley_Option$OptolithClient.option(false, (function (param) { + return param.gr === Curry._1(Id$OptolithClient.Spell.Group.toInt, /* Spells */0); + }), Curry._2(Ley_IntMap$OptolithClient.lookup, param.id, staticData.spells)); + }), hero.spells)); + } else { + return ; + } + + } +} + +function getEntrySpecificMaxLevel(cache, hero, staticEntry) { + switch (staticEntry.TAG | 0) { + case /* Advantage */0 : + return ; + case /* Disadvantage */1 : + var match = Id$OptolithClient.Disadvantage.fromInt(staticEntry._0.id); + if (typeof match !== "number") { + return ; + } + if (match < 22) { + return ; + } + switch (match - 22 | 0) { + case /* AfraidOf */0 : + return Ley_Option$OptolithClient.Monad.$$return(getMaxLevelForDecreaseEntry(3, ActivatableSkills$OptolithClient.countActiveSkillEntries(/* Spells */0, hero))); + case /* Poor */1 : + return Ley_Option$OptolithClient.Monad.$$return(getMaxLevelForDecreaseEntry(3, EntryGroups$OptolithClient.SpecialAbility.countActiveFromGroup(/* Predigten */23, cache.specialAbilityPairs))); + case /* Slow */2 : + return Ley_Option$OptolithClient.Monad.$$return(getMaxLevelForDecreaseEntry(3, EntryGroups$OptolithClient.SpecialAbility.countActiveFromGroup(/* Visionen */26, cache.specialAbilityPairs))); + + } + case /* SpecialAbility */2 : + var match$1 = Id$OptolithClient.SpecialAbility.fromInt(staticEntry._0.id); + if (typeof match$1 === "number" && match$1 === 47) { + return Ley_Option$OptolithClient.Functor.$less$amp$great(hero.pact, (function (pact) { + return pact.level; + })); + } else { + return ; + } + + } +} + +function getMinLevel(cache, staticData, hero, staticEntry, heroEntry, singleEntry) { + var maybeMinLevel = getEntrySpecificMinLevel(cache, staticData, hero, staticEntry); + return Ley_List$OptolithClient.Foldable.foldr((function (dependency, maybeMinLevel) { + var definedLevel = dependency.level; + if (definedLevel !== undefined && definedLevel > Ley_Option$OptolithClient.Foldable.sum(maybeMinLevel) && Dependencies$OptolithClient.Activatable.areOptionDependenciesMatched(dependency, Activatable_Convert$OptolithClient.singleWithIdToSingle(singleEntry))) { + return definedLevel; + } else { + return maybeMinLevel; + } + }), maybeMinLevel, heroEntry.dependencies); +} + +function getMaxLevel(cache, staticData, hero, staticEntry) { + var entrySpecificMaxLevel = getEntrySpecificMaxLevel(cache, hero, staticEntry); + var maxPossibleWithPrerequisites = Prerequisites$OptolithClient.Validation.getMaxLevel(staticData, hero, Id$OptolithClient.Activatable.generalize(Activatable_Accessors$OptolithClient.id(staticEntry)), Prerequisites$OptolithClient.Activatable.getLevelPrerequisites(staticEntry)); + return Ley_Option$OptolithClient.Alternative.$less$pipe$great(Ley_Option$OptolithClient.Alternative.$less$pipe$great(Ley_Option$OptolithClient.Monad.liftM2(Ley_Int$OptolithClient.min, entrySpecificMaxLevel, maxPossibleWithPrerequisites), entrySpecificMaxLevel), maxPossibleWithPrerequisites); +} + +function isRemovalOrModificationValid(cache, staticData, hero, staticEntry, heroEntry, singleEntry) { + var minLevel = getMinLevel(cache, staticData, hero, staticEntry, heroEntry, singleEntry); + var maxLevel = getMaxLevel(cache, staticData, hero, staticEntry); + return { + minLevel: minLevel, + maxLevel: maxLevel, + disabled: Ley_Option$OptolithClient.isSome(minLevel) || !isNotRequiredByDependencies(hero, staticEntry, heroEntry, singleEntry) || !isStyleSpecialAbilityRemovalValid(hero, staticEntry) || !isEntrySpecificRemovalValid(cache, staticData, hero, staticEntry, heroEntry, singleEntry) + }; +} + export { getMinLevelForIncreaseEntry , getMaxLevelForDecreaseEntry , + isRemovalOrModificationValid , } -/* No side effect */ +/* Skills-OptolithClient Not a pure module */ diff --git a/src/Activatable/Activatable_Active_Validation.re b/src/Activatable/Activatable_Active_Validation.re index 0013ce45..c2944b37 100644 --- a/src/Activatable/Activatable_Active_Validation.re +++ b/src/Activatable/Activatable_Active_Validation.re @@ -1,364 +1,248 @@ module I = Ley_Int; +module IM = Ley_IntMap; +module IS = Ley_IntSet; +module L = Ley_List; +module O = Ley_Option; + +open Activatable_Cache; + +type t = { + minLevel: option(int), + maxLevel: option(int), + disabled: bool, +}; // const hasRequiredMinimumLevel = // (min_level: Maybe) => (max_level: Maybe): boolean => // isJust (max_level) && isJust (min_level) -// -// const isRequiredByOthers = -// (current_active: Record) => -// (state_entry: Record): boolean => -// pipe ( -// ADA.dependencies, -// any ( -// ifElse -// (isBoolean) -// (e => e && flength (ADA.active (state_entry)) === 1) -// (e => ( -// equals (DOA.sid (e)) (AOWIA.sid (current_active)) -// && equals (AOWIA.sid2 (current_active)) (DOA.sid2 (e)) -// && equals (AOWIA.tier (current_active)) (DOA.tier (e)) -// ) -// || ( -// isJust (DOA.tier (e)) -// && isJust (AOWIA.tier (current_active)) -// && or (fmap (equals (Maybe.gte (DOA.tier (e)) -// (AOWIA.tier (current_active)))) -// (DOA.active (e))) -// )) -// ) -// ) -// (state_entry) -// -// /** -// * Checks if you can somehow remove an ActiveObject from the given entry. -// */ -// const isRemovalDisabledEntrySpecific = -// (wiki: StaticDataRecord) => -// (hero: HeroModelRecord) => -// (matching_script_and_lang_related: Tuple<[boolean, List, List]>) => -// (wiki_entry: Activatable) => -// (hero_entry: Record) => -// -// // tslint:disable-next-line: cyclomatic-complexity -// (active: Record): boolean => { -// const mstart_el = -// lookupF (SDA.experienceLevels (wiki)) -// (HA.experienceLevel (hero)) -// -// if (isMagicalTradId (AAL.id (wiki_entry))) { -// // All active tradition entries -// const traditions = -// getMagicalTraditionsHeroEntries (HA.specialAbilities (hero)) -// -// const multiple_traditions = flength (traditions) > 1 -// -// // multiple traditions are currently not supported and there must be no -// // active spell or cantrip -// return multiple_traditions -// || countActiveSkillEntries ("spells") (hero) > 0 -// || size (HA.cantrips (hero)) > 0 -// } -// else if (isBlessedTradId (AAL.id (wiki_entry))) { -// // there must be no active liturgical chant or blessing -// return countActiveSkillEntries ("liturgicalChants") (hero) > 0 -// || size (HA.blessings (hero)) > 0 -// } -// -// switch (AAL.id (wiki_entry)) { -// case AdvantageId.exceptionalSkill: { -// // value of target skill -// const mvalue = -// pipe_ ( -// active, -// AOWIA.sid, -// misStringM, -// bindF (lookupF (HA.skills (hero))), -// fmap (SkillDependent.AL.value) -// ) -// -// // amount of active Exceptional Skill advantages for the same skill -// const counter = countWith (pipe (AOA.sid, equals (AOWIA.sid (active)))) -// (ADA.active (hero_entry)) -// -// // if the maximum value is reached removal needs to be disabled -// return or (liftM2 (gte) -// (mvalue) -// (fmap (pipe (ELA.maxSkillRating, add (counter))) -// (mstart_el))) -// } -// -// case AdvantageId.exceptionalCombatTechnique: { -// // value of target combat technique -// const value = -// pipe_ ( -// active, -// AOWIA.sid, -// misStringM, -// bindF (lookupF (HeroModel.A.combatTechniques (hero))), -// maybe (6) (SkillDependent.A.value) -// ) -// -// // if the maximum value is reached removal needs to be disabled -// return maybe (true) (pipe (ELA.maxCombatTechniqueRating, inc, lte (value))) (mstart_el) -// } -// -// case SpecialAbilityId.literacy: { -// if (sel1 (matching_script_and_lang_related)) { -// const active_matching_scripts = sel2 (matching_script_and_lang_related) -// -// return flength (active_matching_scripts) === 1 -// && pipe_ ( -// AOWIA.sid (active), -// misNumberM, -// maybe (false) (elemF (active_matching_scripts)) -// ) -// } -// else { -// return false -// } -// } -// -// case SpecialAbilityId.language: { -// if (sel1 (matching_script_and_lang_related)) { -// const active_matching_languages = sel3 (matching_script_and_lang_related) -// -// return flength (active_matching_languages) === 1 -// && pipe_ ( -// AOWIA.sid (active), -// misNumberM, -// maybe (false) (elemF (active_matching_languages)) -// ) -// } -// else { -// return false -// } -// } -// -// case SpecialAbilityId.propertyKnowledge: -// return pipe_ ( -// active, -// AOWIA.sid, -// misNumberM, -// maybe (false) -// (prop_id => OrderedMap.any ((spell: Record) => -// ASDA.value (spell) > 14 -// && pipe_ ( -// spell, -// ASDA.id, -// lookupF (SDA.spells (wiki)), -// maybe (true) -// (pipe (SA.property, equals (prop_id))) -// )) -// (HA.spells (hero))) -// ) -// -// case SpecialAbilityId.aspectKnowledge: { -// const all_aspcs = getActiveSelections (hero_entry) -// -// return pipe_ ( -// active, -// AOWIA.sid, -// misNumberM, -// maybe (false) -// (aspc_id => { -// const other_aspcs = sdelete (aspc_id) (all_aspcs) -// -// return OrderedMap.any ((chant: Record) => -// ASDA.value (chant) > 14 -// && pipe_ ( -// chant, -// ASDA.id, -// lookupF (SDA.liturgicalChants (wiki)), -// maybe (true) -// (pipe ( -// LCA.aspects, -// aspcs => elem (aspc_id) (aspcs) -// && all (notElemF (other_aspcs)) -// (aspcs) -// )) -// )) -// (HA.liturgicalChants (hero)) -// }) -// ) -// } -// -// case SpecialAbilityId.combatStyleCombination: { -// const armedStyleActive = countActiveGroupEntries (wiki) -// (hero) -// (SpecialAbilityGroup.CombatStylesArmed) -// -// const unarmedStyleActive = countActiveGroupEntries (wiki) -// (hero) -// (SpecialAbilityGroup.CombatStylesUnarmed) -// -// const totalActive = armedStyleActive + unarmedStyleActive -// -// // default is 1 per group (armed/unarmed), but with this SA 1 more in -// // one group: maximum of 3, but max 2 per group. If max is reached, this -// // SA cannot be removed -// return totalActive >= 3 -// || armedStyleActive >= 2 -// || unarmedStyleActive >= 2 -// } -// -// case SpecialAbilityId.magicStyleCombination: { -// const totalActive = countActiveGroupEntries (wiki) (hero) (13) -// -// // default is 1, but with this SA its 2. If it's 2 this SA is neccessary -// // and cannot be removed -// return totalActive >= 2 -// } -// -// // Extended Blessed Special Abilities that allow to learn liturgical -// // chants of different traditions -// case SpecialAbilityId.zugvoegel: -// case SpecialAbilityId.jaegerinnenDerWeissenMaid: -// case SpecialAbilityId.anhaengerDesGueldenen: { -// const mblessed_tradition = -// getBlessedTraditionFromWiki (SDA.specialAbilities (wiki)) -// (HA.specialAbilities (hero)) -// -// // Wiki entries for all active liturgical chants -// const active_chants = -// pipe_ ( -// hero, -// HA.liturgicalChants, -// elems, -// filter> (ASDA.active), -// mapByIdKeyMap (SDA.liturgicalChants (wiki)) -// ) -// -// // If there are chants active that do not belong to the own tradition -// const mactive_unfamiliar_chants = -// fmap (pipe (isOwnTradition, notP, any, thrush (active_chants))) -// (mblessed_tradition) -// -// return or (mactive_unfamiliar_chants) -// } -// -// default: -// return false -// } -// } -// -// const isEntryDisabledByDependencies = -// (wiki: StaticDataRecord) => -// (hero: HeroModelRecord) => -// (wiki_entry: Activatable) => -// (hero_entry: Record) => -// (active: Record) => -// -// // if there is any dependency that disables the possibility to remove -// // the entry -// any ((dep: ActivatableDependency) => { -// // If there is a top-level dependency for the whole entry, -// // it must be `true` because `false` would have prevented -// // the entry from being added. -// if (isBoolean (dep)) { -// return true -// } -// -// // A Just if the depedency exists because of a list of ids -// // in a prerequiste. Contains the source id of the object -// // where the prerequisite is from. -// const current_origin = DOA.origin (dep) -// -// if (isJust (current_origin)) { -// return pipe_ ( -// fromJust (current_origin), -// getWikiEntry (wiki), -// bindF -// (ensure (isActivatableWikiEntry)), -// -// // Get flat prerequisites for origin entry -// fmap (origin_entry => -// flattenPrerequisites (Nothing) -// (alt (AAL.tiers (origin_entry)) (Just (1))) -// (AAL.prerequisites (origin_entry))), -// -// // Get the prerequisite that matches this entry -// // to get all other options from list -// bindF (find ((req): req is PrerequisitesWithIds => { -// if (typeof req === "string" || SocialPrerequisite.is (req)) { -// return false -// } -// -// const current_id = RAAL.id (req) -// -// // the id must be a list of ids -// // because otherwise no options in -// // terms of fulfilling the -// // prerequisite would be possible -// return isList (current_id) -// -// // check if the current entry's -// // id is actually a member of -// // the prerequisite -// && elem (AAL.id (wiki_entry)) -// (current_id) -// })), -// -// // Check if there are other entries that would -// // match the prerequisite so that this entry -// // could be removed -// fmap (req => -// !any ((x: string) => -// validateObject (wiki) -// (hero) -// (setPrerequisiteId (x) (req)) -// (AAL.id (wiki_entry))) -// (sdelete (AAL.id (wiki_entry)) -// (RAAL.id (req) as List))), -// or -// ) -// } -// -// // sid of the current dependency -// const current_dep_sid = DOA.sid (dep) -// const mcurrent_sid = AOWIA.sid (active) -// -// if (Maybe.any (isList) (current_dep_sid) && isJust (mcurrent_sid)) { -// // list of possible sids to fulfill the prerequisite -// // and thus to fulfill the dependency -// const xs = fromJust (current_dep_sid) -// const current_sid = fromJust (mcurrent_sid) -// -// if (notElem (current_sid) (xs)) { -// return false -// } -// -// const current_active_selections = getActiveSelections (hero_entry) -// -// // there must be at least two active sids being contained -// // in the list of possible sids so that one of them can -// // be removed -// const multiple_valid_for_dependency = -// flength (intersect (current_active_selections) (xs)) > 1 -// -// // must be negated because it must return `false` if -// // the dependency says it can be removed -// return !multiple_valid_for_dependency -// } -// -// return false -// }) -// (ADA.dependencies (hero_entry)) -// -// const isStyleSpecialAbilityRemovalDisabled = -// (hero: HeroModelRecord) => -// (wiki_entry: Activatable): boolean => -// SpecialAbility.is (wiki_entry) -// && !isStyleValidToRemove (hero) -// (Just (wiki_entry)) -// -// -// export const getMaxLevelForDecreaseEntry: (def: number) => (count: number) => Maybe = -// -// // the more entries the user buys, the less levels are -// // possible. If the user has 3 or more entries, the decrease entry cannot -// // be used at all. In those cases (which should not happen), the maximum -// // will be 0. -// def => pipe (subtract (def), max (0), Just) + +let getActivesById = (mp, id) => + IM.lookup(id, mp) + |> O.option([], ({active, _}: Hero.Activatable.t) => active); + +let isNotRequiredByDependencies = + ( + hero: Hero.t, + staticEntry, + heroEntry: Hero.Activatable.t, + singleEntry: Activatable_Convert.singleWithId, + ) => + heroEntry.dependencies + // Filter out every dependency that can be matched by another entry + |> Dependencies.Flatten.flattenActivatableDependencies( + switch (staticEntry) { + | Static.Advantage(_) => getActivesById(hero.advantages) + | Disadvantage(_) => getActivesById(hero.disadvantages) + | SpecialAbility(_) => getActivesById(hero.specialAbilities) + }, + singleEntry.id, + ) + |> L.Foldable.all((dep: Hero.Activatable.dependency) => + ! + Dependencies.Activatable.isDependencyMatched( + dep, + Activatable_Convert.singleWithIdToSingle(singleEntry), + ) + // If the entry matched the dependency, try if other activations of the + // same entry may still satisfy the dependency, so that it can still be + // removed + || L.Index.deleteAt(singleEntry.index, heroEntry.active) + |> L.Foldable.any(Dependencies.Activatable.isDependencyMatched(dep)) + ); + +let isEntrySpecificRemovalValid = + ( + cache, + staticData, + hero, + staticEntry, + heroEntry: Hero.Activatable.t, + singleEntry: Activatable_Convert.singleWithId, + ) => + if (Tradition.Magical.isTraditionId(staticData, singleEntry.id)) { + let activeTraditions = cache.magicalTraditions; + + let hasMultipleTraditions = L.Foldable.length(activeTraditions) > 1; + + hasMultipleTraditions + || !ActivatableSkills.hasActiveSkillEntries(Spells, hero) + && IS.Foldable.null(hero.cantrips); + } else if (Tradition.Blessed.isTraditionId(staticData, singleEntry.id)) { + !ActivatableSkills.hasActiveSkillEntries(LiturgicalChants, hero) + && IS.Foldable.null(hero.blessings); + } else { + switch (staticEntry) { + | Static.Advantage(staticAdvantage) => + [@warning "-4"] + Id.Advantage.( + switch (fromInt(staticAdvantage.id)) { + | ExceptionalSkill => + switch (singleEntry.options) { + | [`Skill(id), ..._] => + // value of target skill + let value = IM.lookup(id, hero.skills) |> Skills.getValueDef; + + // amount of active Exceptional Skill advantages for the same skill + let countSameSkill = + L.countBy( + (active: Hero.Activatable.single) => + switch (active.options) { + | [`Skill(otherId), ..._] => otherId === id + | _ => false + }, + heroEntry.active, + ); + + // if the maximum skill rating is reached removal needs to be + // disabled + value >= cache.startExperienceLevel.maxSkillRating + countSameSkill; + | _ => true + } + | ExceptionalCombatTechnique => + switch (singleEntry.options) { + | [`CombatTechnique(id), ..._] => + // value of target combat technique + let value = + IM.lookup(id, hero.combatTechniques) + |> CombatTechniques.getValueDef; + + // if the maximum combat technique rating is reached removal needs + // to be disabled + value >= cache.startExperienceLevel.maxCombatTechniqueRating + 1; + | _ => true + } + | _ => true + } + ) + | Disadvantage(_) => true + | SpecialAbility(staticSpecialAbility) => + [@warning "-4"] + Id.SpecialAbility.( + switch (fromInt(staticSpecialAbility.id)) { + | Literacy => + switch ( + cache.matchingLanguagesScripts.isEntryActiveRequiringMatch, + cache.matchingLanguagesScripts.scriptsWithMatchingLanguages, + singleEntry.options, + ) { + // requiring entry must be active, the list of scripts must contain + // only one element and that element has to match the current script + // to prohibit removing the entry + | (true, [onlyScriptWithLanguage], [`Generic(scriptId), ..._]) => + onlyScriptWithLanguage !== scriptId + | _ => true + } + | Language => + switch ( + cache.matchingLanguagesScripts.isEntryActiveRequiringMatch, + cache.matchingLanguagesScripts.languagesWithMatchingScripts, + singleEntry.options, + ) { + // requiring entry must be active, the list of languages must contain + // only one element and that element has to match the current language + // to prohibit removing the entry + | (true, [onlyLanguageWithScript], [`Generic(languageId), ..._]) => + onlyLanguageWithScript !== languageId + | _ => true + } + | PropertyKnowledge => + switch (singleEntry.options) { + | [`Generic(propertyId), ..._] => + hero.spells + // If there is any spell with matching property above SR 14... + |> IM.Foldable.any((heroSpell: Hero.ActivatableSkill.t) => + ActivatableSkills.valueToInt(heroSpell.value) > 14 + && IM.lookup(heroSpell.id, staticData.spells) + |> O.option(true, ({Spell.property, _}) => + property === propertyId + ) + ) + // ...prohibit removal + |> (!) + | _ => true + } + | AspectKnowledge => + let activeAspects = + Activatable_SelectOptions.mapActiveOptions1( + fun + | `Generic(aspectId) => Some(aspectId) + | _ => None, + heroEntry, + ); + + switch (singleEntry.options) { + | [`Generic(aspectId), ..._] => + let otherAspects = L.delete(aspectId, activeAspects); + + hero.liturgicalChants + // If there is any liturgical chant with matching aspect + // above SR 14 and no other aspect knowledges for that entry... + |> IM.Foldable.any((heroLiturgicalChant: Hero.ActivatableSkill.t) => + ActivatableSkills.valueToInt(heroLiturgicalChant.value) > 14 + && IM.lookup( + heroLiturgicalChant.id, + staticData.liturgicalChants, + ) + |> O.option(true, ({LiturgicalChant.aspects, _}) => + L.elem(aspectId, aspects) + && L.disjoint(otherAspects, aspects) + ) + ) + // ...prohibit removal + |> (!); + | _ => true + }; + | CombatStyleCombination => + let activeArmedStyles = cache.armedCombatStylesCount; + let activeUnarmedStyles = cache.unarmedCombatStylesCount; + + let totalActive = activeArmedStyles + activeUnarmedStyles; + + // default is 1 per group (armed/unarmed), but with this SA 1 more in + // one group: maximum of 3, but max 2 per group. If max is reached, + // this SA cannot be removed + totalActive < 3 && (activeArmedStyles < 2 || activeUnarmedStyles < 2); + | MagicStyleCombination => + // default is 1, but with this SA its 2. If it's 2 this SA is + // neccessary and cannot be removed + cache.magicalStylesCount < 2 + | Zugvoegel + | JaegerinnenDerWeissenMaid + | AnhaengerDesGueldenen => + switch (cache.blessedTradition) { + | Some((_, _, blessedTradition)) => + hero.liturgicalChants + // Filter liturgical chants... + |> IM.mapMaybe( + fun + // ...so that only the active ones remain... + | {Hero.ActivatableSkill.id, value: Active(_), _} => + // ...and replace with their static entry. + IM.lookup(id, staticData.liturgicalChants) + | _ => None, + ) + // Check if there is any active entry that does not belong to the + // active tradition... + |> IM.Foldable.any(({LiturgicalChant.traditions, _}) => + L.notElem(blessedTradition.numId, traditions) + ) + // ...which would prohibit removal + |> (!) + | None => true + } + | _ => true + } + ) + }; + }; + +let isStyleSpecialAbilityRemovalValid = (hero, staticEntry) => + switch (staticEntry) { + | Static.Advantage(_) + | Disadvantage(_) => true + | SpecialAbility(specialAbility) => + Activatable_ExtendedStyle.isStyleValidToRemove(hero, specialAbility) + }; /** * `getMinLevelForIncreaseEntry defaultAmount currentAmount` returns the minimum @@ -380,218 +264,173 @@ let getMinLevelForIncreaseEntry = (defaultAmount, currentAmount) => let getMaxLevelForDecreaseEntry = (maxDecrease, current) => I.max(0, maxDecrease - current); -// -// const getEntrySpecificMinimumLevel = -// (staticData: StaticDataRecord) => -// (hero: HeroModelRecord) => -// (x: Record): Maybe => { -// switch (AOWIA.id (x)) { -// case AdvantageId.largeSpellSelection: -// return pipe_ ( -// hero, -// countActiveSkillEntries ("spells"), -// getMinLevelForIncreaseEntry (3) -// ) -// -// case AdvantageId.zahlreichePredigten: -// return pipe_ ( -// 24, -// getSermonsAndVisionsCount (staticData) (hero), -// getMinLevelForIncreaseEntry (3) -// ) -// -// case AdvantageId.zahlreicheVisionen: -// return pipe_ ( -// 27, -// getSermonsAndVisionsCount (staticData) (hero), -// getMinLevelForIncreaseEntry (3) -// ) -// -// case SpecialAbilityId.imitationszauberei: -// return pipe_ ( -// hero, -// HA.spells, -// elems, -// countWith (pipe ( -// ASDA.id, -// lookupF (SDA.spells (staticData)), -// maybe (false) -// (pipe (SA.gr, gr => gr === MagicalGroup.Spells)) -// )), -// ensure (gt (0)) -// ) -// -// default: -// return Nothing -// } -// } -// -// const getEntrySpecificMaximumLevel = -// (wiki: StaticDataRecord) => -// (hero: HeroModelRecord) => -// (entry_id: string): Maybe => { -// switch (entry_id) { -// case DisadvantageId.smallSpellSelection: -// return pipe_ (hero, countActiveSkillEntries ("spells"), getMaxLevelForDecreaseEntry (3)) -// -// case DisadvantageId.wenigePredigten: -// return pipe_ (24, getSermonsAndVisionsCount (wiki) (hero), getMaxLevelForDecreaseEntry (3)) -// -// case DisadvantageId.wenigeVisionen: -// return pipe_ (27, getSermonsAndVisionsCount (wiki) (hero), getMaxLevelForDecreaseEntry (3)) -// -// case SpecialAbilityId.dunklesAbbildDerBuendnisgabe: -// return pipe_ (hero, HA.pact, fmap (PA.level)) -// -// default: -// return Nothing -// } -// } -// -// type MinLevelDepSid = string | number | List -// -// const adjustMinimumLevelByDependencies = -// (entry: Record) => -// flip (foldl ((min_level: Maybe): (dep: ActivatableDependency) => Maybe => -// pipe ( -// -// // dependency must include a minimum level, which only occurs -// // in a DependencyObject -// ensure (DependencyObject.is), -// -// // get the level dependency from the object and ensure it's -// // greater than the current minimum level and that -// bindF (dep => bind (DOA.tier (dep)) -// -// // new min must be lower than current -// // min level -// (ensure (dep_level => sum (min_level) < dep_level -// -// // if the DependencyObject defines a -// // sid, too, the entry must match the -// // sid as well. A DependencyObject -// // without a sid is valid for all -// // entries (in case of calculating a -// // minimum level) -// && maybe (true) -// (flip (Maybe.elem) -// -// (AOWIA.sid (entry))) -// (DOA.sid (dep))))), -// -// // if the current dependency's level is not valid, return -// // the current minimum -// flip (alt) (min_level) -// ))) -// -// /** -// * Get minimum valid tier. -// */ -// export const getMinTier = -// (wiki: StaticDataRecord) => -// (hero: HeroModelRecord) => -// (entry: Record) => -// (entry_dependencies: List): Maybe => -// adjustMinimumLevelByDependencies (entry) -// (entry_dependencies) -// (getEntrySpecificMinimumLevel (wiki) -// (hero) -// (entry)) -// -// const minMaybe: (mx: Maybe) => (my: Maybe) => Maybe = -// mx => my => isNothing (mx) ? my : isNothing (my) ? mx : Just (min (fromJust (mx)) (fromJust (my))) -// -// /** -// * Get maximum valid tier. -// */ -// export const getMaxTier = -// (wiki: StaticDataRecord) => -// (hero: HeroModelRecord) => -// (entry_prerequisites: LevelAwarePrerequisites) => -// (entry_dependencies: List) => -// (entry_id: string): Maybe => { -// const entry_specific_max_level = getEntrySpecificMaximumLevel (wiki) (hero) (entry_id) -// -// if (isOrderedMap (entry_prerequisites)) { -// return minMaybe (entry_specific_max_level) -// (validateLevel (wiki) -// (hero) -// (entry_prerequisites) -// (entry_dependencies) -// (entry_id)) -// } -// -// return entry_specific_max_level -// } -// -// /** -// * Checks if the given ActiveObject can be removed or changed in tier. -// * @param obj The ActiveObject with origin id. -// * @param state The current hero's state. -// */ -// export const getIsRemovalOrChangeDisabled = -// (wiki: StaticDataRecord) => -// (hero: HeroModelRecord) => -// (matching_script_and_lang_related: Tuple<[boolean, List, List]>) => -// (entry: Record): Maybe> => -// pipe ( -// getWikiEntry (wiki), -// bindF (ensure (isActivatableWikiEntry)), -// bindF ( -// wiki_entry => -// pipe_ ( -// entry, -// AOWIA.id, -// getHeroStateItem (hero), -// bindF> -// (ensure (isActivatableDependent)), -// fmap (hero_entry => { -// const minimum_level = getMinTier (wiki) -// (hero) -// (entry) -// (ADA.dependencies (hero_entry)) -// -// return ActivatableActivationValidation ({ -// disabled: -// -// // Disable if a minimum level is required -// hasRequiredMinimumLevel (minimum_level) -// (AAL.tiers (wiki_entry)) -// -// // Disable if other entries depend on this entry -// || isRequiredByOthers (entry) -// (hero_entry) -// -// // Disable if style special ability is required for -// // extended special abilities -// || isStyleSpecialAbilityRemovalDisabled (hero) -// (wiki_entry) -// -// // Disable if dependencies disable remove -// // (overlap with `isRequiredByOther`? => merge?) -// || isEntryDisabledByDependencies (wiki) -// (hero) -// (wiki_entry) -// (hero_entry) -// (entry) -// -// // Disable if specific entry conditions disallow -// // remove -// || isRemovalDisabledEntrySpecific (wiki) -// (hero) -// (matching_script_and_lang_related) -// (wiki_entry) -// (hero_entry) -// (entry), -// minLevel: minimum_level, -// maxLevel: getMaxTier (wiki) -// (hero) -// (AAL.prerequisites (wiki_entry)) -// (ADA.dependencies (hero_entry)) -// (AOWIA.id (entry)), -// }) -// }) -// ) -// ) -// ) -/* (AOWIA.id (entry)*/ +let getEntrySpecificMinLevel = + (cache, staticData: Static.t, hero, staticEntry) => + switch (staticEntry) { + | Static.Advantage(staticAdvantage) => + [@warning "-4"] + Id.Advantage.( + switch (fromInt(staticAdvantage.id)) { + | LargeSpellSelection => + hero + |> ActivatableSkills.countActiveSkillEntries(Spells) + |> getMinLevelForIncreaseEntry(3) + | ZahlreichePredigten => + EntryGroups.SpecialAbility.countActiveFromGroup( + Predigten, + cache.specialAbilityPairs, + ) + |> getMinLevelForIncreaseEntry(3) + | ZahlreicheVisionen => + EntryGroups.SpecialAbility.countActiveFromGroup( + Visionen, + cache.specialAbilityPairs, + ) + |> getMinLevelForIncreaseEntry(3) + | _ => None + } + ) + | Disadvantage(_) => None + | SpecialAbility(staticSpecialAbility) => + [@warning "-4"] + Id.SpecialAbility.( + switch (fromInt(staticSpecialAbility.id)) { + | Imitationszauberei => + hero.spells + |> IM.countWith(({Hero.ActivatableSkill.id, _}) => + IM.lookup(id, staticData.spells) + |> O.option(false, ({Spell.gr, _}) => + gr === Id.Spell.Group.toInt(Spells) + ) + ) + // Minimum level must be the number of active spells, if there are any + |> O.ensure(count => count > 0) + | _ => None + } + ) + }; + +let getEntrySpecificMaxLevel = (cache, hero, staticEntry) => + switch (staticEntry) { + | Static.Advantage(_) => None + | Disadvantage(staticDisadvantage) => + [@warning "-4"] + Id.Disadvantage.( + switch (fromInt(staticDisadvantage.id)) { + | SmallSpellSelection => + hero + |> ActivatableSkills.countActiveSkillEntries(Spells) + |> getMaxLevelForDecreaseEntry(3) + |> O.Monad.return + | WenigePredigten => + EntryGroups.SpecialAbility.countActiveFromGroup( + Predigten, + cache.specialAbilityPairs, + ) + |> getMaxLevelForDecreaseEntry(3) + |> O.Monad.return + | WenigeVisionen => + EntryGroups.SpecialAbility.countActiveFromGroup( + Visionen, + cache.specialAbilityPairs, + ) + |> getMaxLevelForDecreaseEntry(3) + |> O.Monad.return + | _ => None + } + ) + | SpecialAbility(staticSpecialAbility) => + [@warning "-4"] + Id.SpecialAbility.( + switch (fromInt(staticSpecialAbility.id)) { + | DunklesAbbildDerBuendnisgabe => + O.Functor.(hero.pact <&> (pact => pact.level)) + | _ => None + } + ) + }; + +let adjustMinLevelByDependencies = + ({Hero.Activatable.dependencies, _}, singleEntry, maybeMinLevel) => + L.Foldable.foldr( + (dependency: Hero.Activatable.dependency, maybeMinLevel) => + switch (dependency.level) { + | Some(definedLevel) => + // get the level dependency from the object and ensure it's + // greater than the current minimum level and that... + definedLevel > O.Foldable.sum(maybeMinLevel) + // ...if the dependency defines options, too, the entry must match them + // as well. A dependency without options is valid for all entries (in + // case of calculating a minimum level) + && Dependencies.Activatable.areOptionDependenciesMatched( + dependency, + Activatable_Convert.singleWithIdToSingle(singleEntry), + ) + ? Some(definedLevel) : maybeMinLevel + | None => maybeMinLevel + }, + maybeMinLevel, + dependencies, + ); + +/** + * Get minimum valid level. + */ +let getMinLevel = + (cache, staticData, hero, staticEntry, heroEntry, singleEntry) => + getEntrySpecificMinLevel(cache, staticData, hero, staticEntry) + |> adjustMinLevelByDependencies(heroEntry, singleEntry); + +let getMaxLevel = (cache, staticData, hero, staticEntry) => { + let entrySpecificMaxLevel = + getEntrySpecificMaxLevel(cache, hero, staticEntry); + + let maxPossibleWithPrerequisites = + Prerequisites.Validation.getMaxLevel( + staticData, + hero, + staticEntry |> Activatable_Accessors.id |> Id.Activatable.generalize, + Prerequisites.Activatable.getLevelPrerequisites(staticEntry), + ); + + O.Alternative.( + O.Monad.liftM2(I.min, entrySpecificMaxLevel, maxPossibleWithPrerequisites) + <|> entrySpecificMaxLevel + <|> maxPossibleWithPrerequisites + ); +}; + +/** + * Checks if the given active entry can be removed or changed in level. + */ +let isRemovalOrModificationValid = + (cache, staticData, hero, staticEntry, heroEntry, singleEntry) => { + let minLevel = + getMinLevel(cache, staticData, hero, staticEntry, heroEntry, singleEntry); + + let maxLevel = getMaxLevel(cache, staticData, hero, staticEntry); + + { + disabled: + O.isSome(minLevel) + || ! + isNotRequiredByDependencies( + hero, + staticEntry, + heroEntry, + singleEntry, + ) + || !isStyleSpecialAbilityRemovalValid(hero, staticEntry) + || ! + isEntrySpecificRemovalValid( + cache, + staticData, + hero, + staticEntry, + heroEntry, + singleEntry, + ), + maxLevel, + minLevel, + }; +}; diff --git a/src/Activatable/Activatable_Active_Validation.rei b/src/Activatable/Activatable_Active_Validation.rei index d1862242..27e9e400 100644 --- a/src/Activatable/Activatable_Active_Validation.rei +++ b/src/Activatable/Activatable_Active_Validation.rei @@ -1,3 +1,9 @@ +type t = { + minLevel: option(int), + maxLevel: option(int), + disabled: bool, +}; + /** * `getMinLevelForIncreaseEntry defaultAmount currentAmount` returns the minimum * level for an "increase entry", which is an entry that allows to get more @@ -15,3 +21,19 @@ let getMinLevelForIncreaseEntry: (int, int) => option(int); * amount allowed. */ let getMaxLevelForDecreaseEntry: (int, int) => int; + +/** + * `isRemovalOrModificationValid cache staticData hero staticEntry heroEntry + * singleEntry` checks if the given active entry can be removed or if it's level + * can be changed. + */ +let isRemovalOrModificationValid: + ( + Activatable_Cache.t, + Static.t, + Hero.t, + Static.activatable, + Hero.Activatable.t, + Activatable_Convert.singleWithId + ) => + t; diff --git a/src/Activatable/Activatable_Inactive_Cache.bs.js b/src/Activatable/Activatable_Cache.bs.js similarity index 100% rename from src/Activatable/Activatable_Inactive_Cache.bs.js rename to src/Activatable/Activatable_Cache.bs.js diff --git a/src/Activatable/Activatable_Inactive_Cache.re b/src/Activatable/Activatable_Cache.re similarity index 88% rename from src/Activatable/Activatable_Inactive_Cache.re rename to src/Activatable/Activatable_Cache.re index 222c3642..f8afcc67 100644 --- a/src/Activatable/Activatable_Inactive_Cache.re +++ b/src/Activatable/Activatable_Cache.re @@ -8,9 +8,11 @@ type matchingLanguagesScripts = { }; /** - * The cache of computed values to optimize performance of `isAdditionValid`. + * The cache of computed values to optimize performance of activatable list + * generation. */ type t = { + startExperienceLevel: ExperienceLevel.t, combatStyleCombination: option(Hero.Activatable.t), armedCombatStylesCount: int, unarmedCombatStylesCount: int, diff --git a/src/Activatable/Activatable_Convert.bs.js b/src/Activatable/Activatable_Convert.bs.js index ca779707..8bc469ac 100644 --- a/src/Activatable/Activatable_Convert.bs.js +++ b/src/Activatable/Activatable_Convert.bs.js @@ -2,20 +2,24 @@ import * as Ley_List$OptolithClient from "../Data/Ley_List.bs.js"; +function singleToSingleWithId(x, index, s) { + return { + id: x.id, + index: index, + options: s.options, + level: s.level, + customCost: s.customCost + }; +} + function heroEntryToSingles(x) { - return Ley_List$OptolithClient.map((function (s) { - return { - id: x.id, - options: s.options, - level: s.level, - customCost: s.customCost - }; + return Ley_List$OptolithClient.Index.imap((function (param, param$1) { + return singleToSingleWithId(x, param, param$1); }), x.active); } -function singleToSingleWithId(x, s) { +function singleWithIdToSingle(s) { return { - id: x.id, options: s.options, level: s.level, customCost: s.customCost @@ -34,6 +38,7 @@ export { heroEntryToSingles , singleToSingleWithId , activatableOptionToSelectOptionId , + singleWithIdToSingle , } /* No side effect */ diff --git a/src/Activatable/Activatable_Convert.re b/src/Activatable/Activatable_Convert.re index d0334e33..73b772c2 100644 --- a/src/Activatable/Activatable_Convert.re +++ b/src/Activatable/Activatable_Convert.re @@ -3,28 +3,29 @@ */ type singleWithId = { id: int, + index: int, options: list(Id.Activatable.Option.t), level: option(int), customCost: option(int), }; -let heroEntryToSingles = (x: Hero.Activatable.t) => - x.active - |> Ley_List.map((s: Hero.Activatable.single) => - { - id: x.id, - options: s.options, - level: s.level, - customCost: s.customCost, - } - ); - -let singleToSingleWithId = (x: Hero.Activatable.t, s: Hero.Activatable.single) => { +let singleToSingleWithId = + (x: Hero.Activatable.t, index, s: Hero.Activatable.single) => { id: x.id, + index, options: s.options, level: s.level, customCost: s.customCost, }; + +let heroEntryToSingles = (x: Hero.Activatable.t) => + x.active |> Ley_List.Index.imap(singleToSingleWithId(x)); + +let singleWithIdToSingle = s => { + Hero.Activatable.options: s.options, + level: s.level, + customCost: s.customCost, +}; /* /** * Converts the object generated by the list item to an object that can be * inserted into an array of ActiveObjects. diff --git a/src/Activatable/Activatable_Convert.rei b/src/Activatable/Activatable_Convert.rei index bd2a73c0..57e372c0 100644 --- a/src/Activatable/Activatable_Convert.rei +++ b/src/Activatable/Activatable_Convert.rei @@ -11,6 +11,7 @@ */ type singleWithId = { id: int, + index: int, options: list(Id.Activatable.Option.t), level: option(int), customCost: option(int), @@ -23,7 +24,9 @@ type singleWithId = { let heroEntryToSingles: Hero.Activatable.t => list(singleWithId); let singleToSingleWithId: - (Hero.Activatable.t, Hero.Activatable.single) => singleWithId; + (Hero.Activatable.t, int, Hero.Activatable.single) => singleWithId; let activatableOptionToSelectOptionId: Id.Activatable.Option.t => option(Id.SelectOption.t); + +let singleWithIdToSingle: singleWithId => Hero.Activatable.single; diff --git a/src/Activatable/Activatable_Inactive.re b/src/Activatable/Activatable_Inactive.re index 57e03767..655f591e 100644 --- a/src/Activatable/Activatable_Inactive.re +++ b/src/Activatable/Activatable_Inactive.re @@ -2,9 +2,9 @@ module IM = Ley_IntMap; module L = Ley_List; module O = Ley_Option; -open Activatable_Inactive_Cache; +open Activatable_Cache; -type valid = { +type t = { id: int, name: string, apValue: option(OneOrMany.t(int)), @@ -17,8 +17,8 @@ type valid = { isAutomatic: bool, }; -type t = - | Valid(valid) +type result = + | Valid(t) | Invalid(Static.activatable); let getSermonOrVisionCountMax = (hero: Hero.t, advantageId, disadvantageId) => diff --git a/src/Activatable/Activatable_Inactive.rei b/src/Activatable/Activatable_Inactive.rei index 71cf0b78..85b5ddbb 100644 --- a/src/Activatable/Activatable_Inactive.rei +++ b/src/Activatable/Activatable_Inactive.rei @@ -1,4 +1,4 @@ -type valid = { +type t = { id: int, name: string, apValue: option(OneOrMany.t(int)), @@ -11,8 +11,8 @@ type valid = { isAutomatic: bool, }; -type t = - | Valid(valid) +type result = + | Valid(t) | Invalid(Static.activatable); /** @@ -25,10 +25,10 @@ type t = */ let getInactive: ( - Activatable_Inactive_Cache.t, + Activatable_Cache.t, Static.t, Hero.t, Static.activatable, option(Hero.Activatable.t) ) => - t; + result; diff --git a/src/Activatable/Activatable_Inactive_Validation.re b/src/Activatable/Activatable_Inactive_Validation.re index 5138e7be..c503ab20 100644 --- a/src/Activatable/Activatable_Inactive_Validation.re +++ b/src/Activatable/Activatable_Inactive_Validation.re @@ -4,7 +4,7 @@ module IM = Ley_IntMap; module L = Ley_List; module O = Ley_Option; -open Activatable_Inactive_Cache; +open Activatable_Cache; let getActivePactGiftsCount = specialAbilityPairs => specialAbilityPairs diff --git a/src/Activatable/Activatable_Inactive_Validation.rei b/src/Activatable/Activatable_Inactive_Validation.rei index 021c1b17..1c053058 100644 --- a/src/Activatable/Activatable_Inactive_Validation.rei +++ b/src/Activatable/Activatable_Inactive_Validation.rei @@ -20,7 +20,7 @@ let getActivePactGiftsCount: */ let isAdditionValid: ( - Activatable_Inactive_Cache.t, + Activatable_Cache.t, Static.t, Hero.t, option(int), diff --git a/src/Activatable/Activatable_SelectOptions.bs.js b/src/Activatable/Activatable_SelectOptions.bs.js index 07d91a47..82851131 100644 --- a/src/Activatable/Activatable_SelectOptions.bs.js +++ b/src/Activatable/Activatable_SelectOptions.bs.js @@ -34,12 +34,16 @@ function getActiveOptions1(x) { }), x.active); } -function getActiveSelectOptions1(x) { +function mapActiveOptions1(f, x) { return Ley_Option$OptolithClient.mapOption((function (y) { - return Ley_Option$OptolithClient.Monad.$great$great$eq(Ley_Option$OptolithClient.listToOption(y.options), Activatable_Convert$OptolithClient.activatableOptionToSelectOptionId); + return Ley_Option$OptolithClient.Monad.$great$great$eq(Ley_Option$OptolithClient.listToOption(y.options), f); }), x.active); } +function getActiveSelectOptions1(param) { + return mapActiveOptions1(Activatable_Convert$OptolithClient.activatableOptionToSelectOptionId, param); +} + function getActiveOptions2(param) { var index = 1; return Ley_Option$OptolithClient.mapOption((function (y) { @@ -105,6 +109,7 @@ export { getSelectOptionName , getSelectOptionCost , getActiveOptions1 , + mapActiveOptions1 , getActiveSelectOptions1 , getActiveOptions2 , getActiveOptions2Map , diff --git a/src/Activatable/Activatable_SelectOptions.re b/src/Activatable/Activatable_SelectOptions.re index 7e2f96ab..ee30f2d5 100644 --- a/src/Activatable/Activatable_SelectOptions.re +++ b/src/Activatable/Activatable_SelectOptions.re @@ -37,6 +37,12 @@ let getActiveOptions = (index, x: Hero.Activatable.t) => Ley_List.Safe.atMay(y.options, index) ); +let mapActiveOptions = (f, index, x: Hero.Activatable.t) => + x.active + |> O.mapOption((y: Hero.Activatable.single) => + Ley_List.Safe.atMay(y.options, index) >>= f + ); + /** * Get all first option IDs from the given entry. */ @@ -44,16 +50,17 @@ let getActiveOptions1 = (x: Hero.Activatable.t) => x.active |> O.mapOption((y: Hero.Activatable.single) => y.options |> O.listToOption); +let mapActiveOptions1 = (f, x: Hero.Activatable.t) => + x.active + |> O.mapOption((y: Hero.Activatable.single) => + y.options |> O.listToOption >>= f + ); + /** * Get all first select option IDs from the given entry. */ -let getActiveSelectOptions1 = (x: Hero.Activatable.t) => - x.active - |> O.mapOption((y: Hero.Activatable.single) => - y.options - |> O.listToOption - >>= Activatable_Convert.activatableOptionToSelectOptionId - ); +let getActiveSelectOptions1 = + mapActiveOptions1(Activatable_Convert.activatableOptionToSelectOptionId); /** * Get all second option IDs from the given entry. diff --git a/src/Activatable/Activatable_SelectOptions.rei b/src/Activatable/Activatable_SelectOptions.rei index 6bc8f2fa..6ee6ab56 100644 --- a/src/Activatable/Activatable_SelectOptions.rei +++ b/src/Activatable/Activatable_SelectOptions.rei @@ -33,6 +33,14 @@ let getSelectOptionCost: */ let getActiveOptions1: Hero.Activatable.t => list(Id.Activatable.Option.t); +/** + * `mapActiveOptions1 f entry` takes all first option ids from the given entry + * and returns all values where the passed function, applied to each id, + * returned a `Some`, which is then unwrapped. + */ +let mapActiveOptions1: + (Id.Activatable.Option.t => option('a), Hero.Activatable.t) => list('a); + /** * Get all first select option IDs from the given entry. */ diff --git a/src/App/Utilities/Activatable/activatableInactiveUtils.ts b/src/App/Utilities/Activatable/activatableInactiveUtils.ts deleted file mode 100644 index 8bbef51d..00000000 --- a/src/App/Utilities/Activatable/activatableInactiveUtils.ts +++ /dev/null @@ -1,959 +0,0 @@ -/** - * Get the needed options for `Activatable` entries that are available to - * activate. - * - * @file src/Utilities/activatableInactiveUtils.ts - * @author Lukas Obermann - * @since 1.1.0 - */ - -import { notP } from "../../../Data/Bool" -import { equals, notEquals } from "../../../Data/Eq" -import { cnst, flip, ident } from "../../../Data/Function" -import { fmap, fmapF, mapReplace } from "../../../Data/Functor" -import * as IntMapJS from "../../../Data/IntMap.bs" -import * as IntMap from "../../../Data/IntMap.gen" -import { over, set } from "../../../Data/Lens" -import { consF, countWith, elem, elemF, filter, find, flength, fnull, foldr, isList, List, map, mapByIdKeyMap, notElem, notElemF, notNull, nub, subscript } from "../../../Data/List" -import { all, ap, bind, bindF, ensure, fromJust, fromMaybe, guard, isJust, isNothing, join, Just, liftM2, listToMaybe, Maybe, maybe, maybeToList, Nothing, or, thenF } from "../../../Data/Maybe" -import { add, gt, gte, inc, multiply } from "../../../Data/Num" -import { alter, elems, foldrWithKey, isOrderedMap, lookup, lookupF, member, OrderedMap } from "../../../Data/OrderedMap" -import { Record, RecordI } from "../../../Data/Record" -import { filterMapListT, filterT, mapT } from "../../../Data/Transducer" -import { fst, Pair, snd } from "../../../Data/Tuple" -import { curryN4, uncurryN } from "../../../Data/Tuple/All" -import { CombatTechniqueGroupId, MagicalTradition, SkillGroup, SpecialAbilityGroup } from "../../Constants/Groups" -import { AdvantageId, DisadvantageId, SkillId, SpecialAbilityId } from "../../Constants/Ids.gen" -import { ActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent" -import { ActiveObject } from "../../Models/ActiveEntries/ActiveObject" -import { SkillDependent } from "../../Models/ActiveEntries/SkillDependent" -import { HeroModelRecord } from "../../Models/Hero/Hero" -import { Pact } from "../../Models/Hero/Pact" -import { AdventurePointsCategories } from "../../Models/View/AdventurePointsCategories" -import { InactiveActivatable, InactiveActivatableL } from "../../Models/View/InactiveActivatable" -import { Advantage } from "../../Models/Wiki/Advantage" -import { LiturgicalChant } from "../../Models/Wiki/LiturgicalChant" -import { Skill } from "../../Models/Wiki/Skill" -import { Spell } from "../../Models/Wiki/Spell" -import { Application } from "../../Models/Wiki/sub/Application" -import { SelectOption, SelectOptionL } from "../../Models/Wiki/sub/SelectOption" -import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel" -import { Activatable } from "../../Models/Wiki/wikiTypeHelpers" -import { composeT } from "../compose" -import { filterUnfamiliar } from "../Dependencies/TransferredUnfamiliarUtils" -import { countActiveGroupEntries } from "../entryGroupUtils" -import { getAllEntriesByGroup } from "../heroStateUtils" -import { prefixSA } from "../IDUtils" -import { getTraditionOfAspect } from "../Increasable/liturgicalChantUtils" -import { isUnfamiliarSpell } from "../Increasable/spellUtils" -import { pipe, pipe_ } from "../pipe" -import { validateLevel, validatePrerequisites } from "../Prerequisites/validatePrerequisitesUtils" -import { isEntryAvailable } from "../RulesUtils" -import { sortRecordsByName } from "../sortBy" -import { isNumber, isString, isStringM, misNumberM, misStringM } from "../typeCheckUtils" -import { getMaxLevelForDecreaseEntry, getSermonsAndVisionsCount } from "./activatableActiveValidationUtils" -import { isAdditionDisabled } from "./activatableInactiveValidationUtils" -import { modifyByLevel } from "./activatableModifierUtils" -import { countActiveSkillEntries } from "./activatableSkillUtils" -import { isMaybeActive } from "./isActive" -import { getActiveSecondarySelections, getActiveSelections, getActiveSelectionsMaybe, getRequiredSelections } from "./selectionUtils" -import { getBlessedTradition, getMagicalTraditionsHeroEntries, mapBlessedNumIdToTradId } from "./traditionUtils" - -const SDA = StaticData.A -const HA = HeroModel.A -const AAL = Advantage.AL -const LCA = LiturgicalChant.A -const SpA = Spell.A -const ADA = ActivatableDependent.A -const ASDA = ActivatableSkillDependent.A -const SkDA = SkillDependent.A -const SOA = SelectOption.A -const AppA = Application.A -const AOA = ActiveObject.A -const SpAL = Spell.AL -const SA = Skill.A -const RA = Rules.A -const SOL = SelectOptionL -const IAA = InactiveActivatable.A -const IAL = InactiveActivatableL -const PA = Pact.A -const APCA = AdventurePointsCategories.A - -/** - * `isNotActive :: Maybe ActivatableDependent -> SelectOption -> Bool` - * - * Test if the id of the passed select option is activated for the passed - * `ActivatableDependent`. - */ -const isNotActive = - pipe ( - getActiveSelectionsMaybe, - fromMaybe> (List.empty), - activeSelections => pipe ( - SOA.id, - notElemF (activeSelections) - ) - ) - -/** - * `areNoSameActive :: Maybe ActivatableDependent -> SelectOption -> Bool` - * - * Test if a select option is not activated more than once for the passed - * `ActivatableDependent`. - */ -const areNoSameActive = - pipe ( - getActiveSelectionsMaybe, - fromMaybe> (List.empty), - activeSelections => pipe ( - SOA.id, - current_id => countWith (equals (current_id)) (activeSelections) < 2 - ) - ) - -/** - * `isNotRequired :: Maybe ActivatableDependent -> SelectOption -> Bool` - * - * Test if the id of the passed select option is required for the passed - * `ActivatableDependent`. - */ -const isNotRequired = - pipe ( - getRequiredSelections, - fromMaybe>> (List.empty), - requiredSelections => pipe ( - SOA.id, - notElemF (requiredSelections) - ) - ) - - -/** - * `isNotRequiredNotActive :: Maybe ActivatableDependent -> SelectOption -> Bool` - * - * Test if the id of the passed select option is neither required nor activated - * for the passed `ActivatableDependent`. - */ -const isNotRequiredNotActive = - (mhero_entry: Maybe>) => { - const isNoActiveSelection = isNotActive (mhero_entry) - const isNoRequiredSelection = isNotRequired (mhero_entry) - - return (e: Record) => - isNoActiveSelection (e) && isNoRequiredSelection (e) - } - -/** - * Increment the value at the specified key by `1`. If there is no value at that - * key, the value will be set to `0`. - */ -const incMapVal = alter (pipe (maybe (1) (inc), Just)) - -const addChantToCounter = - (chant: Record) => - flip (foldr (flip ) => IntMap.t> - (curryN4 (IntMap.insertWith) (uncurryN (add))) - (1))) - (LCA.aspects (chant)) - -const addSpellToCounter = pipe (SpA.property, incMapVal) - -const filterSkillsGte10 = filter (pipe (ASDA.value, gte (10))) - -const foldCounter = - foldrWithKey> (k => x => x >= 3 ? consF (k) : ident) - (List.empty) - -/** - * `getPropsWith3Gte10 :: Wiki -> Hero -> [Int]` - * - * Returns a list containing all properties where at least 3 spells with at - * least SR 10 belong to. - */ -const getPropsWith3Gte10 = - (staticData: StaticDataRecord) => - pipe ( - HA.spells, - elems, - filterSkillsGte10, - mapByIdKeyMap (SDA.spells (staticData)), - foldr (addSpellToCounter) (OrderedMap.empty), - foldCounter - ) - -/** - * `getAspectsWith3Gte10 :: Wiki -> Hero -> [Int]` - * - * Returns a list containing all aspects where at least 3 chants with at least - * SR 10 belong to. - */ -const getAspectsWith3Gte10 = - (staticData: StaticDataRecord) => - pipe ( - HA.liturgicalChants, - elems, - filterSkillsGte10, - mapByIdKeyMap (SDA.liturgicalChants (staticData)), - foldr (addChantToCounter) (IntMapJS.empty), - mp => IntMap.foldrWithKey> ( - (k, x, xs) => x >= 3 ? consF (k) (xs) : xs, - List.empty, - mp - ) - ) - -const isSocialSkill = - (staticData: StaticDataRecord) => - pipe ( - SOA.id, - ensure (isString), - bindF (lookupF (SDA.skills (staticData))), - fmap (pipe (SA.gr, equals (SkillGroup.Social))), - or - ) - -const isAddExistSkillSpecAllowed = - (hero: HeroModelRecord) => - (counter: OrderedMap>) => - (curr_select_id: string | number) => - pipe_ ( - curr_select_id, - ensure (isString), - bindF (lookupF (HA.skills (hero))), - liftM2 ((apps: List) => - (skill: Record) => - flength (apps) < 3 && SkDA.value (skill) >= (flength (apps) + 1) * 6) - (lookupF (counter) (curr_select_id)), - or - ) - -const isAddNotExistSkillSpecAllowed = - (hero: HeroModelRecord) => - (curr_select_id: string | number) => - pipe_ ( - curr_select_id, - ensure (isString), - bindF (lookupF (HA.skills (hero))), - fmap (skill => SkDA.value (skill) >= 6), - or - ) - -const is3or4 = (x: string | number): x is number => x === 3 || x === 4 - -/** - * Modifies the select options of specific entries to match current conditions. - */ -const modifySelectOptions = - (staticData: StaticDataRecord) => - (hero: HeroModelRecord) => - (hero_magical_traditions: List>) => - (wiki_entry: Activatable) => - (mhero_entry: Maybe>): ident>>> => { - const current_id = AAL.id (wiki_entry) - - const isAvailable = - composeT ( - filterT (isEntryAvailable (SDA.books (staticData)) - (RA.enabledRuleBooks (HA.rules (hero))) - (RA.enableAllRuleBooks (HA.rules (hero))) - (SOA.src)), - filterT (pipe ( - SOA.prerequisites, - Maybe.all (reqs => validatePrerequisites (staticData) - (hero) - (reqs) - (current_id)) - )) - ) - - const isNoRequiredOrActiveSelection = - composeT (isAvailable, filterT (isNotRequiredNotActive (mhero_entry))) - - const isNoRequiredSelection = - composeT (isAvailable, filterT (isNotRequired (mhero_entry))) - - switch (current_id) { - case AdvantageId.exceptionalSkill: { - const hasLessThanTwoSameIdActiveSelections = filterT (areNoSameActive (mhero_entry)) - - return fmap (filterMapListT (composeT ( - isNoRequiredSelection, - hasLessThanTwoSameIdActiveSelections - ))) - } - - case DisadvantageId.personalityFlaw: { - const unique_selections = maybe (List ()) - (pipe ( - getActiveSelections, - filter (isNumber), - nub - )) - (mhero_entry) - - const isInfiniteActive: (id: number) => (x: Record) => boolean = - id => x => SOA.id (x) === id && elem (id) (unique_selections) - - const isPrejudiceAndActive = isInfiniteActive (7) - - const isUnworldlyAndActive = isInfiniteActive (8) - - const isNotActiveAndMaxNotReached: (x: Record) => boolean = - x => isNotActive (mhero_entry) (x) - && isNotRequired (mhero_entry) (x) - && flength (unique_selections) < 2 - - const filterOptions = - composeT ( - isAvailable, - filterT (a => isPrejudiceAndActive (a) - || isUnworldlyAndActive (a) - || isNotActiveAndMaxNotReached (a)) - ) - - return fmap (filterMapListT (filterOptions)) - } - - case DisadvantageId.negativeTrait: - case DisadvantageId.maimed: - return fmap (filterMapListT (isNoRequiredOrActiveSelection)) - - case DisadvantageId.incompetent: { - const isAdvActive = - pipe (lookupF (HA.advantages (hero)), isMaybeActive) - - const isNotSocialSkill = notP (isSocialSkill (staticData)) - - return fmap (filterMapListT (composeT ( - isNoRequiredOrActiveSelection, - filterT (e => - - // Socially Adaptable and Inspire Confidence - // require no Incompetence in social skills - (isAdvActive (AdvantageId.sociallyAdaptable) - || isAdvActive (AdvantageId.inspireConfidence) - ? isNotSocialSkill (e) - : true)) - ))) - } - - case SpecialAbilityId.skillSpecialization: { - const mcounter = getActiveSecondarySelections (mhero_entry) - - return fmap (filterMapListT (composeT ( - isNoRequiredSelection, - filterT (e => { - const curr_select_id = SOA.id (e) - - // if mcounter is available, mhero_entry must be a Just and thus - // there can be active selections - if (isJust (mcounter)) { - const counter = fromJust (mcounter) - - if (member (curr_select_id) (counter)) { - return isAddExistSkillSpecAllowed (hero) - (counter) - (curr_select_id) - } - } - - // otherwise we only need to check if the skill rating is at - // least 6, as there can't be an activated selection. - return isAddNotExistSkillSpecAllowed (hero) (curr_select_id) - }), - mapT (e => { - const curr_select_id = SOA.id (e) - - const mcounts = bind (mcounter) (lookup (curr_select_id)) - - const adjustSelectOption = - pipe ( - over (SOL.cost) - (isJust (mcounts) - - // Increase cost if there are active specializations - // for the same skill - ? fmap (multiply (flength (fromJust (mcounts)) + 1)) - - // otherwise return current cost - : ident), - over (SOL.applications) - (fmap (filter (app => { - const isInactive = - all (notElem - (AppA.id (app))) - (mcounts) - - const arePrerequisitesMet = - validatePrerequisites (staticData) - (hero) - (maybeToList (AppA.prerequisite (app))) - (current_id) - - return isInactive - && arePrerequisitesMet - }))) - ) - - return adjustSelectOption (e) - }) - ))) - } - - case prefixSA (SpecialAbilityId.traditionGuildMages): { - return fmap (filterUnfamiliar (pipe ( - SpA.tradition, - trads => notElem (MagicalTradition.General) (trads) - && notElem (MagicalTradition.GuildMages) (trads) - )) - (staticData)) - } - - case SpecialAbilityId.propertyKnowledge: { - const isValidProperty = - filterT (pipe (SOA.id, elemF (getPropsWith3Gte10 (staticData) (hero)))) - - return fmap (filterMapListT (composeT ( - isNoRequiredOrActiveSelection, - isValidProperty - ))) - } - - case SpecialAbilityId.propertyFocus: { - const isActivePropertyKnowledge = - filterT (notP (pipe_ ( - hero, - HA.specialAbilities, - lookup (SpecialAbilityId.propertyKnowledge), - isNotActive - ))) - - return fmap (filterMapListT (composeT ( - isNoRequiredOrActiveSelection, - isActivePropertyKnowledge - ))) - } - - case SpecialAbilityId.aspectKnowledge: { - const valid_aspects = getAspectsWith3Gte10 (staticData) (hero) - - const isAspectValid = pipe (SOA.id, elemF (valid_aspects)) - - return maybe (ident as ident>>>) - ((blessed_trad: Record) => - fmap (filterMapListT (composeT ( - filterT (pipe ( - SOA.id, - ensure (isNumber), - bindF (pipe ( - getTraditionOfAspect, - mapBlessedNumIdToTradId - )), - Maybe.elem (AAL.id (blessed_trad)) - )), - isNoRequiredOrActiveSelection, - filterT (isAspectValid) - )))) - (getBlessedTradition (HA.specialAbilities (hero))) - } - - case SpecialAbilityId.adaptionZauber: { - const isWikiEntryFromUnfamiliarTrad = - isUnfamiliarSpell (HA.transferredUnfamiliarSpells (hero)) - (hero_magical_traditions) - - const isSpellAbove10 = - pipe ( - SOA.id, - ensure (isString), - bindF (lookupF (HA.spells (hero))), - maybe (false) (pipe (ASDA.value, gte (10))) - ) - - const isFromUnfamiliarTrad = - pipe ( - SOA.id, - ensure (isString), - bindF (lookupF (SDA.spells (staticData))), - maybe (false) (isWikiEntryFromUnfamiliarTrad) - ) - - return fmap (filterMapListT (composeT ( - isNoRequiredOrActiveSelection, - filterT (isSpellAbove10), - filterT (isFromUnfamiliarTrad) - ))) - } - - case prefixSA (SpecialAbilityId.spellEnhancement): - case prefixSA (SpecialAbilityId.chantEnhancement): { - const getTargetHeroEntry = current_id === prefixSA (SpecialAbilityId.spellEnhancement) - ? bindF (lookupF (HA.spells (hero))) - : bindF (lookupF (HA.liturgicalChants (hero))) - - const getTargetWikiEntry: - ((x: Maybe) => Maybe | Record>) = - current_id === prefixSA (SpecialAbilityId.spellEnhancement) - ? bindF (lookupF (SDA.spells (staticData))) - : bindF (lookupF (SDA.liturgicalChants (staticData))) - - const isNotUnfamiliar = - (x: Record | Record) => - LiturgicalChant.is (x) - || !isUnfamiliarSpell (HA.transferredUnfamiliarSpells (hero)) - (hero_magical_traditions) - (x) - - return fmap (foldr (isNoRequiredOrActiveSelection (e => { - const mtarget_hero_entry = getTargetHeroEntry (SOA.target (e)) - const mtarget_wiki_entry = getTargetWikiEntry (SOA.target (e)) - - if ( - isJust (mtarget_wiki_entry) - && isJust (mtarget_hero_entry) - && isNotUnfamiliar (fromJust (mtarget_wiki_entry)) - && ASDA.value (fromJust (mtarget_hero_entry)) - >= maybe (0) - (pipe (multiply (4), add (4))) - (SOA.level (e)) - ) { - const target_wiki_entry = fromJust (mtarget_wiki_entry) - - return consF ( - set (SOL.name) - (`${SpAL.name (target_wiki_entry)}: ${SOA.name (e)}`) - (e) - ) - } - - return ident as ident>> - })) - (List ())) - } - - case SpecialAbilityId.languageSpecializations: { - return pipe_ ( - staticData, - SDA.specialAbilities, - lookup (SpecialAbilityId.language), - bindF (AAL.select), - maybe (cnst (Nothing) as ident>>>) - (current_select => { - const available_langs = - - // Pair: fst = sid, snd = current_level - maybe (List> ()) - (pipe ( - ADA.active, - foldr ((obj: Record) => - pipe_ ( - obj, - AOA.tier, - bindF (current_level => - pipe_ ( - guard (is3or4 (current_level)), - thenF (AOA.sid (obj)), - misNumberM, - fmap (current_sid => - consF (Pair ( - current_sid, - current_level - ))) - )), - fromMaybe ( - ident as ident>> - ) - )) - (List ()) - )) - (pipe_ ( - hero, - HA.specialAbilities, - lookup (SpecialAbilityId.language) - )) - - const filterLanguages = - foldr (isNoRequiredOrActiveSelection - (e => { - const lang = - find ((l: Pair) => - fst (l) === SOA.id (e)) - (available_langs) - - if (isJust (lang)) { - const isMotherTongue = - snd (fromJust (lang)) === 4 - - if (isMotherTongue) { - return consF (set (SOL.cost) (Just (0)) (e)) - } - - return consF (e) - } - - return ident as ident>> - })) - (List ()) - - return cnst (Just (filterLanguages (current_select))) - }) - ) - } - - case SpecialAbilityId.madaschwesternStil: { - return fmap (filterUnfamiliar (pipe ( - SpA.tradition, - trads => notElem (MagicalTradition.General) (trads) - && notElem (MagicalTradition.Witches) (trads) - )) - (staticData)) - } - - case SpecialAbilityId.scholarDesMagierkollegsZuHoningen: { - const allowed_traditions = List ( - MagicalTradition.Druids, - MagicalTradition.Elves, - MagicalTradition.Witches - ) - - const mtransferred_spell_trads = pipe_ ( - HA.specialAbilities (hero), - lookup (prefixSA (SpecialAbilityId.traditionGuildMages)), - bindF (pipe (ADA.active, listToMaybe)), - bindF (pipe (AOA.sid, isStringM)), - bindF (lookupF (SDA.spells (staticData))), - fmap (SpA.tradition) - ) - - if (isNothing (mtransferred_spell_trads)) { - return ident - } - - const transferred_spell_trads = fromJust (mtransferred_spell_trads) - - // Contains all allowed trads the first spell does not have - const trad_diff = filter (notElemF (transferred_spell_trads)) - (allowed_traditions) - - const has_transferred_all_traditions_allowed = fnull (trad_diff) - - return fmap (filterUnfamiliar (pipe ( - SpA.tradition, - has_transferred_all_traditions_allowed - ? trads => - notElem (MagicalTradition.General) (trads) - && List.any (elemF (allowed_traditions)) (trads) - : trads => - notElem (MagicalTradition.General) (trads) - && List.any (elemF (trad_diff)) (trads) - )) - (staticData)) - } - - default: - return fmap (filterMapListT (isNoRequiredOrActiveSelection)) - } - } - -type OtherOptionsModifier = ident> - -const getSermonOrVisionCountMax = - (hero: HeroModelRecord) => - (adv_id: string) => - (disadv_id: string) => - modifyByLevel (3) - (lookup (adv_id) (HA.advantages (hero))) - (lookup (disadv_id) (HA.disadvantages (hero))) - -const modifyOtherOptions = - (wiki: StaticDataRecord) => - (hero: HeroModelRecord) => - (adventure_points: Record) => - (wiki_entry: Activatable) => - (mhero_entry: Maybe>): Maybe => { - const current_id = AAL.id (wiki_entry) - - switch (current_id) { - case DisadvantageId.wenigePredigten: - return pipe_ ( - SpecialAbilityGroup.Predigten, - getSermonsAndVisionsCount (wiki) (hero), - getMaxLevelForDecreaseEntry (3), - set (IAL.maxLevel), - Just - ) - - case DisadvantageId.wenigeVisionen: - return pipe_ ( - SpecialAbilityGroup.Visionen, - getSermonsAndVisionsCount (wiki) (hero), - getMaxLevelForDecreaseEntry (3), - set (IAL.maxLevel), - Just - ) - - case DisadvantageId.smallSpellSelection: { - return pipe_ ( - hero, - countActiveSkillEntries ("spells"), - getMaxLevelForDecreaseEntry (3), - set (IAL.maxLevel), - Just - ) - } - - case SpecialAbilityId.craftInstruments: { - return join (liftM2 ( - (woodworking: Record) => - (metalworking: Record) => - SkDA.value (woodworking) + SkDA.value (metalworking) >= 12 - ? Just (ident) - : Nothing - ) - (pipe (HA.skills, lookup (SkillId.woodworking)) (hero)) - (pipe (HA.skills, lookup (SkillId.metalworking)) (hero))) - } - - case SpecialAbilityId.hunter: { - return pipe_ ( - CombatTechniqueGroupId.Ranged, - getAllEntriesByGroup (SDA.combatTechniques (wiki)) - (HA.combatTechniques (hero)), - filter (pipe (SkDA.value, gte (10))), - flength, - ensure (gt (0)), - mapReplace (ident) - ) - } - - case prefixSA (SpecialAbilityId.traditionGuildMages): - case SpecialAbilityId.traditionWitches: - case SpecialAbilityId.traditionElves: - case SpecialAbilityId.traditionDruids: - case SpecialAbilityId.traditionIllusionist: - case SpecialAbilityId.traditionQabalyaMage: - case SpecialAbilityId.traditionGeoden: - case SpecialAbilityId.traditionZauberalchimisten: - case SpecialAbilityId.traditionSchelme: - case SpecialAbilityId.traditionBrobimGeoden: { - return pipe_ ( - hero, - HA.specialAbilities, - getMagicalTraditionsHeroEntries, - ensure (List.fnull), - mapReplace (ident) - ) - } - - case SpecialAbilityId.propertyKnowledge: - case SpecialAbilityId.aspectKnowledge: { - return pipe_ ( - wiki_entry, - AAL.cost, - bindF, List> (ensure (isList)), - bindF (costs => subscript (costs) - (maybe (0) - (pipe (ADA.active, flength)) - (mhero_entry))), - fmap (pipe (Just, set (IAL.cost))) - ) - } - - case SpecialAbilityId.traditionChurchOfPraios: - case SpecialAbilityId.traditionChurchOfRondra: - case SpecialAbilityId.traditionChurchOfBoron: - case SpecialAbilityId.traditionChurchOfHesinde: - case SpecialAbilityId.traditionChurchOfPhex: - case SpecialAbilityId.traditionChurchOfPeraine: - case SpecialAbilityId.traditionChurchOfEfferd: - case SpecialAbilityId.traditionChurchOfTravia: - case SpecialAbilityId.traditionChurchOfFirun: - case SpecialAbilityId.traditionChurchOfTsa: - case SpecialAbilityId.traditionChurchOfIngerimm: - case SpecialAbilityId.traditionChurchOfRahja: - case SpecialAbilityId.traditionCultOfTheNamelessOne: - case SpecialAbilityId.traditionChurchOfAves: - case SpecialAbilityId.traditionChurchOfIfirn: - case SpecialAbilityId.traditionChurchOfKor: - case SpecialAbilityId.traditionChurchOfNandus: - case SpecialAbilityId.traditionChurchOfSwafnir: - case SpecialAbilityId.traditionCultOfNuminoru: { - return pipe_ ( - hero, - HA.specialAbilities, - getBlessedTradition, - x => isJust (x) ? Nothing : Just (ident) - ) - } - - case SpecialAbilityId.recherchegespuer: { - return pipe_ ( - hero, - HA.specialAbilities, - lookup (SpecialAbilityId.wissensdurst), - fmap (ADA.active), - bindF (listToMaybe), - bindF (AOA.sid), - misStringM, - bindF (lookupF (SDA.skills (wiki))), - bindF (skill => pipe ( - bindF, List> (ensure (isList)), - fmap (pipe ( - map (add (SA.ic (skill))), - Just, - set (IAL.cost) - )) - ) - (AAL.cost (wiki_entry))) - ) - } - - case SpecialAbilityId.predigtDerGemeinschaft: - case SpecialAbilityId.predigtDerZuversicht: - case SpecialAbilityId.predigtDesGottvertrauens: - case SpecialAbilityId.predigtDesWohlgefallens: - case SpecialAbilityId.predigtWiderMissgeschicke: { - const isAdvActive = - pipe (lookupF (HA.advantages (hero)), isMaybeActive) - - const max = - getSermonOrVisionCountMax (hero) - (AdvantageId.zahlreichePredigten) - (DisadvantageId.wenigePredigten) - - const isLessThanMax = - countActiveGroupEntries (wiki) - (hero) - (SpecialAbilityGroup.Predigten) < max - - return (isAdvActive (AdvantageId.prediger) && isLessThanMax) - || isAdvActive (AdvantageId.blessed) - ? Just (ident) - : Nothing - } - - case SpecialAbilityId.visionDerBestimmung: - case SpecialAbilityId.visionDerEntrueckung: - case SpecialAbilityId.visionDerGottheit: - case SpecialAbilityId.visionDesSchicksals: - case SpecialAbilityId.visionDesWahrenGlaubens: { - const isAdvActive = - pipe (lookupF (HA.advantages (hero)), isMaybeActive) - - const max = - getSermonOrVisionCountMax (hero) - (AdvantageId.zahlreicheVisionen) - (DisadvantageId.wenigeVisionen) - - const isLessThanMax = - countActiveGroupEntries (wiki) - (hero) - (SpecialAbilityGroup.Visionen) < max - - return (isAdvActive (AdvantageId.visionaer) && isLessThanMax) - || isAdvActive (AdvantageId.blessed) - ? Just (ident) - : Nothing - } - - case SpecialAbilityId.dunklesAbbildDerBuendnisgabe: { - return pipe_ ( - hero, - HA.pact, - fmap (pipe (PA.level, Just, set (IAL.maxLevel))) - ) - } - - case SpecialAbilityId.traditionArcaneBard: - case SpecialAbilityId.traditionArcaneDancer: - case SpecialAbilityId.traditionIntuitiveMage: - case SpecialAbilityId.traditionSavant: - case SpecialAbilityId.traditionAnimisten: { - return APCA.spentOnMagicalAdvantages (adventure_points) <= 25 - && APCA.spentOnMagicalDisadvantages (adventure_points) <= 25 - && pipe_ ( - hero, - HA.specialAbilities, - getMagicalTraditionsHeroEntries, - fnull - ) - ? Just (ident) - : Nothing - } - - default: - return Just (ident) - } - } - -/** - * Calculates whether an Activatable is valid to add or not and, if valid, - * calculates and filters necessary properties and selection lists. Returns a - * Maybe of the result or `undefined` if invalid. - */ -export const getInactiveView = - (staticData: StaticDataRecord) => - (hero: HeroModelRecord) => - (automatic_advantages: List) => - (required_apply_to_mag_actions: boolean) => - (matching_script_and_lang_related: Tuple<[boolean, List, List]>) => - (adventure_points: Record) => - (validExtendedSpecialAbilities: List) => - (hero_magical_traditions: List>) => - (wiki_entry: Activatable) => - (mhero_entry: Maybe>): Maybe> => { - const current_id = AAL.id (wiki_entry) - const current_prerequisites = AAL.prerequisites (wiki_entry) - - const max_level = isOrderedMap (current_prerequisites) - ? validateLevel (staticData) - (hero) - (current_prerequisites) - (maybe (List ()) - (ADA.dependencies) - (mhero_entry)) - (current_id) - : Nothing - - const isNotValid = isAdditionDisabled (staticData) - (hero) - (required_apply_to_mag_actions) - (validExtendedSpecialAbilities) - (matching_script_and_lang_related) - (wiki_entry) - (mhero_entry) - (max_level) - - if (!isNotValid) { - return pipe_ ( - wiki_entry, - AAL.select, - modifySelectOptions (staticData) - (hero) - (hero_magical_traditions) - (wiki_entry) - (mhero_entry), - ensure (maybe (true) (notNull)), - fmap (select_options => InactiveActivatable ({ - id: current_id, - name: SpAL.name (wiki_entry), - cost: AAL.cost (wiki_entry), - maxLevel: max_level, - heroEntry: mhero_entry, - wikiEntry: wiki_entry as Record>, - selectOptions: fmapF (select_options) - (sortRecordsByName (staticData)), - isAutomatic: List.elem (AAL.id (wiki_entry)) - (automatic_advantages), - })), - ap (modifyOtherOptions (staticData) - (hero) - (adventure_points) - (wiki_entry) - (mhero_entry)), - bindF (ensure (pipe (IAA.maxLevel, maybe (true) (notEquals (0))))) - ) - } - - return Nothing - } diff --git a/src/App/Utilities/Activatable/activatableInactiveValidationUtils.ts b/src/App/Utilities/Activatable/activatableInactiveValidationUtils.ts deleted file mode 100644 index b525d8f1..00000000 --- a/src/App/Utilities/Activatable/activatableInactiveValidationUtils.ts +++ /dev/null @@ -1,257 +0,0 @@ -/** - * Checks if an `Activatable` entry is available to be activated. - * - * @file src/Utilities/activatableInactiveValidationUtils.ts - * @author Lukas Obermann - * @since 1.1.0 - */ - -import { not } from "../../../Data/Bool" -import { ident } from "../../../Data/Function" -import { fmap } from "../../../Data/Functor" -import { elem, flength, foldr, isList, List, notElem } from "../../../Data/List" -import { all, any, bind, bindF, fromMaybe, isJust, listToMaybe, Maybe, sum } from "../../../Data/Maybe" -import { add, inc, lte } from "../../../Data/Num" -import { isOrderedMap, lookup } from "../../../Data/OrderedMap" -import { Record } from "../../../Data/Record" -import { sel2, sel3 } from "../../../Data/Tuple/Select" -import { SpecialAbilityGroup } from "../../Constants/Groups" -import { SpecialAbilityId } from "../../Constants/Ids" -import { ActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent" -import { HeroModelRecord } from "../../Models/Hero/Hero" -import { ActivatableDependency } from "../../Models/Hero/heroTypeHelpers" -import { Pact } from "../../Models/Hero/Pact" -import { Advantage } from "../../Models/Wiki/Advantage" -import { SpecialAbility } from "../../Models/Wiki/SpecialAbility" -import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel" -import { Activatable } from "../../Models/Wiki/wikiTypeHelpers" -import { countActiveGroupEntries, hasActiveGroupEntry } from "../entryGroupUtils" -import { getAllEntriesByGroup } from "../heroStateUtils" -import { pipe, pipe_ } from "../pipe" -import { getFirstLevelPrerequisites } from "../Prerequisites/flattenPrerequisites" -import { validatePrerequisites } from "../Prerequisites/validatePrerequisitesUtils" -import * as CheckStyleUtils from "./checkStyleUtils" -import { isActive, isMaybeActive } from "./isActive" - -const SDA = StaticData.A -const AAL = Advantage.AL -const SAA = SpecialAbility.A -const HA = HeroModel.A -const ADA = ActivatableDependent.A -const AOA = ActiveObject.A -const PA = Pact.A -const RA = Rules.A - -const isAdditionDisabledForCombatStyle = - (staticData: StaticDataRecord) => - (hero: HeroModelRecord) => - (wiki_entry: Record): boolean => { - const combination_hero_entry = lookup (SpecialAbilityId.CombatStyleCombination) - (HA.specialAbilities (hero)) - - // Combination-SA is active, which allows 3 styles to be active, - // but only a maximum of 2 from one type (armed/unarmed). - if (isMaybeActive (combination_hero_entry)) { - const totalActive = - countActiveGroupEntries (staticData) - (hero) - ( - SpecialAbilityGroup.CombatStylesArmed, - SpecialAbilityGroup.CombatStylesUnarmed - ) - - const equalTypeStylesActive = - countActiveGroupEntries (staticData) (hero) (SAA.gr (wiki_entry)) - - return totalActive >= 3 || equalTypeStylesActive >= 2 - } - - // Otherwise, only one of each type can be active. - else { - return pipe_ (wiki_entry, SAA.gr, hasActiveGroupEntry (staticData) (hero)) - } - } - -const isAdditionDisabledSpecialAbilitySpecific = - (staticData: StaticDataRecord) => - (hero: HeroModelRecord) => - (matching_script_and_lang_related: Tuple<[boolean, List, List]>) => - (wiki_entry: Record): boolean => { - const current_id = SAA.id (wiki_entry) - - if (CheckStyleUtils.isCombatStyleSpecialAbility (wiki_entry)) { - return isAdditionDisabledForCombatStyle (staticData) (hero) (wiki_entry) - } - - if (CheckStyleUtils.isMagicalStyleSpecialAbility (wiki_entry)) { - const combination_hero_entry = lookup (SpecialAbilityId.MagicStyleCombination) - (HA.specialAbilities (hero)) - - const total_active = countActiveGroupEntries (staticData) - (hero) - (SpecialAbilityGroup.MagicalStyles) - - return total_active >= (isMaybeActive (combination_hero_entry) ? 2 : 1) - } - - if (CheckStyleUtils.isBlessedStyleSpecialAbility (wiki_entry)) { - return hasActiveGroupEntry (staticData) (hero) (SpecialAbilityGroup.BlessedStyles) - } - - if (CheckStyleUtils.isSkillStyleSpecialAbility (wiki_entry)) { - return hasActiveGroupEntry (staticData) (hero) (SpecialAbilityGroup.SkillStyles) - } - - if (current_id === SpecialAbilityId.CombatStyleCombination) { - return !hasActiveGroupEntry (staticData) - (hero) - ( - SpecialAbilityGroup.CombatStylesArmed, - SpecialAbilityGroup.CombatStylesUnarmed - ) - } - - if (current_id === SpecialAbilityId.MagicStyleCombination) { - return !hasActiveGroupEntry (staticData) (hero) (SpecialAbilityGroup.MagicalStyles) - } - - if (current_id === SpecialAbilityId.DunklesAbbildDerBuendnisgabe) { - return hasActiveGroupEntry (staticData) (hero) (SpecialAbilityGroup.Paktgeschenke) - } - - if (current_id === SpecialAbilityId.WegDerSchreiberin) { - return flength (sel2 (matching_script_and_lang_related)) >= 1 - && flength (sel3 (matching_script_and_lang_related)) >= 1 - } - - if (SAA.gr (wiki_entry) === SpecialAbilityGroup.Paktgeschenke) { - const dunkles_abbild = lookup (SpecialAbilityId.DunklesAbbildDerBuendnisgabe) - (HA.specialAbilities (hero)) - - const allPactPresents = getAllEntriesByGroup (SDA.specialAbilities (staticData)) - (HA.specialAbilities (hero)) - (SpecialAbilityGroup.Paktgeschenke) - - const countPactPresents = - foldr ((obj: Record) => { - if (isActive (obj)) { - const wikiObj = lookup (ADA.id (obj)) (SDA.specialAbilities (staticData)) - - if ( - any (pipe (SAA.prerequisites, isOrderedMap)) - (wikiObj) - && any (pipe (SAA.cost, isList)) - (wikiObj) - && isJust (bind (wikiObj) (SAA.tiers)) - ) { - return add (sum ( - bindF (AOA.tier) (listToMaybe (ADA.active (obj))) - )) - } - - return inc - } - - return ident as ident - }) - (0) - (allPactPresents) - - // isFaeriePact? - const isDisabled = all (pipe (PA.category, lte (1))) (HA.pact (hero)) - ? isMaybeActive (dunkles_abbild) - || all (pipe (PA.level, lte (countPactPresents))) (HA.pact (hero)) - - // is Lesser Pact? - : all (pipe (PA.level, lte (0))) (HA.pact (hero)) - - // Lesser Pact only provides 3 PactGifts - ? countPactPresents >= 3 - - // Normal DemonPact: KdV + 7 PactGifts - : all (pipe (PA.level, lte (countPactPresents - 7))) (HA.pact (hero)) - - return isDisabled - } - - if (current_id === SpecialAbilityId.LanguageSpecializations) { - return pipe (HA.rules, RA.enableLanguageSpecializations, not) (hero) - } - - if (elem (SAA.gr (wiki_entry)) (List (31, 32))) { - // TODO: add option to activate vampire or lycanthropy and activate this - // SAs based on that option - return true - } - - return false - } - -/** - * Checks if you can somehow add an ActiveObject to the given entry. - * @param state The present state of the current hero. - * @param instance The entry. - */ -const isAdditionDisabledEntrySpecific = - (wiki: StaticDataRecord) => - (hero: HeroModelRecord) => - (matching_script_and_lang_related: Tuple<[boolean, List, List]>) => - (wiki_entry: Activatable): boolean => - ( - SpecialAbility.is (wiki_entry) - && isAdditionDisabledSpecialAbilitySpecific (wiki) - (hero) - (matching_script_and_lang_related) - (wiki_entry) - ) - || !validatePrerequisites (wiki) - (hero) - (getFirstLevelPrerequisites (AAL.prerequisites (wiki_entry))) - (AAL.id (wiki_entry)) - -const hasGeneralRestrictionToAdd = - any (pipe (ADA.dependencies, elem (false))) - -const hasReachedMaximumEntries = - (wiki_entry: Activatable) => - (mhero_entry: Maybe>) => - any (lte (fromMaybe (0) - (fmap (pipe (ADA.active, flength)) - (mhero_entry)))) - (AAL.max (wiki_entry)) - -const hasReachedImpossibleMaximumLevel = Maybe.elem (0) - -const isInvalidExtendedSpecialAbility = - (wiki_entry: Activatable) => - (validExtendedSpecialAbilities: List) => - CheckStyleUtils.isExtendedSpecialAbility (wiki_entry) - && notElem (AAL.id (wiki_entry)) (validExtendedSpecialAbilities) - -const doesNotApplyToMagActionsThoughRequired = - (required_apply_to_mag_actions: boolean) => - (wiki_entry: Activatable): boolean => - SpecialAbility.is (wiki_entry) - ? false - : required_apply_to_mag_actions && Advantage.AL.isExclusiveToArcaneSpellworks (wiki_entry) - -/** - * Checks if the given entry can be added. - * @param obj - * @param state The current hero's state. - */ -export const isAdditionDisabled = - (wiki: StaticDataRecord) => - (hero: HeroModelRecord) => - (required_apply_to_mag_actions: boolean) => - (validExtendedSpecialAbilities: List) => - (matching_script_and_lang_related: Tuple<[boolean, List, List]>) => - (wiki_entry: Activatable) => - (mhero_entry: Maybe>) => - (max_level: Maybe): boolean => - isAdditionDisabledEntrySpecific (wiki) (hero) (matching_script_and_lang_related) (wiki_entry) - || hasGeneralRestrictionToAdd (mhero_entry) - || hasReachedMaximumEntries (wiki_entry) (mhero_entry) - || hasReachedImpossibleMaximumLevel (max_level) - || isInvalidExtendedSpecialAbility (wiki_entry) (validExtendedSpecialAbilities) - || doesNotApplyToMagActionsThoughRequired (required_apply_to_mag_actions) (wiki_entry) diff --git a/src/Increasable/ActivatableSkills.bs.js b/src/Increasable/ActivatableSkills.bs.js index 92b18250..190d7b19 100644 --- a/src/Increasable/ActivatableSkills.bs.js +++ b/src/Increasable/ActivatableSkills.bs.js @@ -53,6 +53,17 @@ function countActiveSkillEntries(domain, hero) { }), domain ? hero.liturgicalChants : hero.spells); } +function hasActiveSkillEntries(domain, hero) { + return Curry._2(Ley_IntMap$OptolithClient.IntMap.Foldable.any, (function (entry) { + var match = entry.value; + if (match) { + return true; + } else { + return false; + } + }), domain ? hero.liturgicalChants : hero.spells); +} + export { getValueDef , valueToInt , @@ -60,6 +71,7 @@ export { isActiveM , getActiveSkillEntries , countActiveSkillEntries , + hasActiveSkillEntries , } /* Ley_IntMap-OptolithClient Not a pure module */ diff --git a/src/Increasable/ActivatableSkills.re b/src/Increasable/ActivatableSkills.re index 929dd6ce..fc90264b 100644 --- a/src/Increasable/ActivatableSkills.re +++ b/src/Increasable/ActivatableSkills.re @@ -56,3 +56,20 @@ let countActiveSkillEntries = (domain, hero: Hero.t) => | Inactive => false } ); + +/** + * Has active skill(s) from the specified domain. + */ +let hasActiveSkillEntries = (domain, hero: Hero.t) => + ( + switch (domain) { + | Spells => hero.spells + | LiturgicalChants => hero.liturgicalChants + } + ) + |> IM.Foldable.any((entry: Hero.ActivatableSkill.t) => + switch (entry.value) { + | Active(_) => true + | Inactive => false + } + ); diff --git a/src/Increasable/ActivatableSkills.rei b/src/Increasable/ActivatableSkills.rei index d200f693..839e62ca 100644 --- a/src/Increasable/ActivatableSkills.rei +++ b/src/Increasable/ActivatableSkills.rei @@ -36,3 +36,8 @@ let getActiveSkillEntries: * Count all active skills from the specified domain. */ let countActiveSkillEntries: (t, Hero.t) => int; + +/** + * Has at least one active skill from the specified domain. + */ +let hasActiveSkillEntries: (t, Hero.t) => bool; diff --git a/src/Misc/Hero.bs.js b/src/Misc/Hero.bs.js index 776a973e..c5933597 100644 --- a/src/Misc/Hero.bs.js +++ b/src/Misc/Hero.bs.js @@ -108,6 +108,8 @@ var ActivatableSkill = { isUnused: isUnused$2 }; +var MagicalActions = {}; + var Item = {}; var Pact = {}; @@ -121,6 +123,7 @@ export { Attribute , Energies , ActivatableSkill , + MagicalActions , Item , Pact , TransferUnfamiliar , diff --git a/src/Misc/Hero.re b/src/Misc/Hero.re index baa5bad1..64b30065 100644 --- a/src/Misc/Hero.re +++ b/src/Misc/Hero.re @@ -135,6 +135,20 @@ module ActivatableSkill = { x.value === Inactive && Ley_List.Foldable.null(x.dependencies); }; +module MagicalActions = { + type t = { + curses: Ley_IntMap.t(ActivatableSkill.t), + elvenMagicalSongs: Ley_IntMap.t(ActivatableSkill.t), + dominationRituals: Ley_IntMap.t(ActivatableSkill.t), + magicalDances: Ley_IntMap.t(ActivatableSkill.t), + magicalMelodies: Ley_IntMap.t(ActivatableSkill.t), + rogueSpells: Ley_IntMap.t(ActivatableSkill.t), + animistForces: Ley_IntMap.t(ActivatableSkill.t), + geodeRituals: Ley_IntMap.t(ActivatableSkill.t), + zibiljaRituals: Ley_IntMap.t(ActivatableSkill.t), + }; +}; + module Item = { type mundaneItem = {structurePoints: option(OneOrMany.t(int))}; @@ -402,6 +416,7 @@ type t = { skills: Ley_IntMap.t(Skill.t), combatTechniques: Ley_IntMap.t(Skill.t), spells: Ley_IntMap.t(ActivatableSkill.t), + magicalActions: MagicalActions.t, liturgicalChants: Ley_IntMap.t(ActivatableSkill.t), cantrips: Ley_IntSet.t, blessings: Ley_IntSet.t, diff --git a/src/Misc/Id.bs.js b/src/Misc/Id.bs.js index 9738d1eb..ac6e37ad 100644 --- a/src/Misc/Id.bs.js +++ b/src/Misc/Id.bs.js @@ -1254,6 +1254,44 @@ var MagicalTradition = { }; function fromInt$13(id) { + if (id !== 1) { + if (id !== 2) { + return { + TAG: /* Error */1, + _0: id + }; + } else { + return { + TAG: /* Ok */0, + _0: /* Rituals */1 + }; + } + } else { + return { + TAG: /* Ok */0, + _0: /* Spells */0 + }; + } +} + +function toInt$13(id) { + if (id) { + return 2; + } else { + return 1; + } +} + +var Group$2 = { + fromInt: fromInt$13, + toInt: toInt$13 +}; + +var Spell = { + Group: Group$2 +}; + +function fromInt$14(id) { var switcher = id - 1 | 0; if (switcher > 11 || switcher < 0) { return /* Other */{ @@ -1264,7 +1302,7 @@ function fromInt$13(id) { } } -function toInt$13(id) { +function toInt$14(id) { if (typeof id === "number") { return id + 1 | 0; } else { @@ -1273,11 +1311,11 @@ function toInt$13(id) { } var Property = { - fromInt: fromInt$13, - toInt: toInt$13 + fromInt: fromInt$14, + toInt: toInt$14 }; -function fromInt$14(id) { +function fromInt$15(id) { if (id >= 623) { if (id >= 1069) { if (id >= 1222) { @@ -1836,7 +1874,7 @@ function fromInt$14(id) { } } -function toInt$14(id) { +function toInt$15(id) { if (typeof id !== "number") { return id._0; } @@ -2039,7 +2077,7 @@ function toInt$14(id) { } } -function fromInt$15(id) { +function fromInt$16(id) { var switcher = id - 1 | 0; if (switcher > 44 || switcher < 0) { return /* Other */{ @@ -2050,7 +2088,7 @@ function fromInt$15(id) { } } -function toInt$15(id) { +function toInt$16(id) { if (typeof id === "number") { return id + 1 | 0; } else { @@ -2058,15 +2096,15 @@ function toInt$15(id) { } } -var Group$2 = { - fromInt: fromInt$15, - toInt: toInt$15 +var Group$3 = { + fromInt: fromInt$16, + toInt: toInt$16 }; var SpecialAbility = { - fromInt: fromInt$14, - toInt: toInt$14, - Group: Group$2 + fromInt: fromInt$15, + toInt: toInt$15, + Group: Group$3 }; var SelectOption = { @@ -2098,6 +2136,7 @@ export { Skill , CombatTechnique , MagicalTradition , + Spell , Property , SpecialAbility , diff --git a/src/Misc/Id.re b/src/Misc/Id.re index 2d070380..1e48c6c4 100644 --- a/src/Misc/Id.re +++ b/src/Misc/Id.re @@ -1049,6 +1049,27 @@ module MagicalTradition = { }; }; +module Spell = { + module Group = { + type t = + | Spells + | Rituals; + + let fromInt = id => + switch (id) { + | 1 => Ok(Spells) + | 2 => Ok(Rituals) + | x => Error(x) + }; + + let toInt = id => + switch (id) { + | Spells => 1 + | Rituals => 2 + }; + }; +}; + module Property = { type t = | AntiMagic diff --git a/src/Misc/Id.rei b/src/Misc/Id.rei index 6098bda8..441a1895 100644 --- a/src/Misc/Id.rei +++ b/src/Misc/Id.rei @@ -454,6 +454,18 @@ module MagicalTradition: { let toInt: t => int; }; +module Spell: { + module Group: { + type t = + | Spells + | Rituals; + + let fromInt: int => result(t, int); + + let toInt: t => int; + }; +}; + module Property: { type t = | AntiMagic diff --git a/src/Misc/Tradition.bs.js b/src/Misc/Tradition.bs.js index 3a8bbdf1..06bacd70 100644 --- a/src/Misc/Tradition.bs.js +++ b/src/Misc/Tradition.bs.js @@ -6,6 +6,10 @@ import * as Ley_IntMap$OptolithClient from "../Data/Ley_IntMap.bs.js"; import * as Ley_Option$OptolithClient from "../Data/Ley_Option.bs.js"; import * as Activatable_Accessors$OptolithClient from "../Activatable/Activatable_Accessors.bs.js"; +function isTraditionId(staticData, id) { + return Curry._2(Ley_IntMap$OptolithClient.member, id, staticData.magicalTraditions); +} + function isActiveTradition(staticData, x) { if (Curry._2(Ley_IntMap$OptolithClient.member, x.id, staticData.magicalTraditions)) { return Activatable_Accessors$OptolithClient.isActive(x); @@ -69,6 +73,10 @@ function getPrimaryAttributeId(staticData, mp) { })); } +function isTraditionId$1(staticData, id) { + return Curry._2(Ley_IntMap$OptolithClient.member, id, staticData.blessedTraditions); +} + function getHeroEntry(staticData, mp) { return Curry._2(Ley_IntMap$OptolithClient.IntMap.Foldable.find, (function (param) { if (Curry._2(Ley_IntMap$OptolithClient.member, param.id, staticData.blessedTraditions)) { @@ -120,6 +128,7 @@ function getPrimaryAttributeId$1(staticData, mp) { } var Magical = { + isTraditionId: isTraditionId, getHeroEntries: getHeroEntries, getStaticEntries: getStaticEntries, getEntries: getEntries, @@ -129,6 +138,7 @@ var Magical = { }; var Blessed = { + isTraditionId: isTraditionId$1, getHeroEntry: getHeroEntry, getStaticEntry: getStaticEntry, getEntry: getEntry, diff --git a/src/Misc/Tradition.re b/src/Misc/Tradition.re index 003a0ca3..70413271 100644 --- a/src/Misc/Tradition.re +++ b/src/Misc/Tradition.re @@ -1,23 +1,24 @@ open Static; -open Ley_IntMap; -open Ley_Option; +module L = Ley_List; +module IM = Ley_IntMap; +module O = Ley_Option; module Magical = { let isTraditionId = (staticData, id) => - member(id, staticData.magicalTraditions); + IM.member(id, staticData.magicalTraditions); let isActiveTradition = (staticData, x: Hero.Activatable.t) => isTraditionId(staticData, x.id) && Activatable_Accessors.isActive(x); - let getHeroEntries = (staticData, mp: Ley_IntMap.t(Hero.Activatable.t)) => - mp |> Ley_IntMap.elems |> Ley_List.filter(isActiveTradition(staticData)); + let getHeroEntries = (staticData, mp: IM.t(Hero.Activatable.t)) => + mp |> IM.elems |> L.filter(isActiveTradition(staticData)); - let getStaticEntries = (staticData, mp: Ley_IntMap.t(Hero.Activatable.t)) => + let getStaticEntries = (staticData, mp: IM.t(Hero.Activatable.t)) => mp - |> elems - |> mapOption(trad => + |> IM.elems + |> O.mapOption(trad => isActiveTradition(staticData, trad) - ? Ley_IntMap.lookup(trad.id, staticData.specialAbilities) : None + ? IM.lookup(trad.id, staticData.specialAbilities) : None ); type fullTradition = ( @@ -27,43 +28,39 @@ module Magical = { ); let getEntries = - (staticData, mp: Ley_IntMap.t(Hero.Activatable.t)) - : list(fullTradition) => + (staticData, mp: IM.t(Hero.Activatable.t)): list(fullTradition) => mp - |> elems - |> mapOption(trad => + |> IM.elems + |> O.mapOption(trad => isActiveTradition(staticData, trad) - ? Ley_Option.Monad.liftM2( + ? O.Monad.liftM2( (staticEntry, traditionEntry) => (staticEntry, trad, traditionEntry), - Ley_IntMap.lookup(trad.id, staticData.specialAbilities), - Ley_IntMap.lookup(trad.id, staticData.magicalTraditions), + IM.lookup(trad.id, staticData.specialAbilities), + IM.lookup(trad.id, staticData.magicalTraditions), ) : None ); let idToNumId = (staticData, id) => - Ley_Option.Monad.( - Ley_IntMap.lookup(id, staticData.magicalTraditions) >>= (x => x.numId) - ); + O.Monad.(IM.lookup(id, staticData.magicalTraditions) >>= (x => x.numId)); let numIdToId = (staticData, id) => - Ley_Option.Functor.( - Ley_IntMap.Foldable.find( + O.Functor.( + IM.Foldable.find( (trad: MagicalTradition.t) => trad.numId === id, staticData.magicalTraditions, ) <&> (x => x.id) ); - let getPrimaryAttributeId = - (staticData, mp: Ley_IntMap.t(Hero.Activatable.t)) => - Ley_Option.Monad.( + let getPrimaryAttributeId = (staticData, mp: IM.t(Hero.Activatable.t)) => + O.Monad.( mp - |> elems - |> Ley_List.Extra.firstJust(trad => + |> IM.elems + |> L.Extra.firstJust(trad => isActiveTradition(staticData, trad) - ? Ley_IntMap.lookup(trad.id, staticData.magicalTraditions) : None + ? IM.lookup(trad.id, staticData.magicalTraditions) : None ) >>= (trad => trad.primary) ); @@ -71,19 +68,19 @@ module Magical = { module Blessed = { let isTraditionId = (staticData, id) => - Ley_IntMap.member(id, staticData.blessedTraditions); + IM.member(id, staticData.blessedTraditions); let isActiveTradition = (staticData, x: Hero.Activatable.t) => isTraditionId(staticData, x.id) && Activatable_Accessors.isActive(x); - let getHeroEntry = (staticData, mp: Ley_IntMap.t(Hero.Activatable.t)) => - Ley_IntMap.Foldable.find(isActiveTradition(staticData), mp); + let getHeroEntry = (staticData, mp: IM.t(Hero.Activatable.t)) => + IM.Foldable.find(isActiveTradition(staticData), mp); - let getStaticEntry = (staticData, mp: Ley_IntMap.t(Hero.Activatable.t)) => - Ley_Option.Monad.( + let getStaticEntry = (staticData, mp: IM.t(Hero.Activatable.t)) => + O.Monad.( mp |> getHeroEntry(staticData) - >>= (trad => Ley_IntMap.lookup(trad.id, staticData.specialAbilities)) + >>= (trad => IM.lookup(trad.id, staticData.specialAbilities)) ); type fullTradition = ( @@ -93,9 +90,8 @@ module Blessed = { ); let getEntry = - (staticData, mp: Ley_IntMap.t(Hero.Activatable.t)) - : option(fullTradition) => - Ley_Option.Monad.( + (staticData, mp: IM.t(Hero.Activatable.t)): option(fullTradition) => + O.Monad.( mp |> getHeroEntry(staticData) >>= ( @@ -103,32 +99,31 @@ module Blessed = { liftM2( (staticEntry, traditionEntry) => (staticEntry, trad, traditionEntry), - Ley_IntMap.lookup(trad.id, staticData.specialAbilities), - Ley_IntMap.lookup(trad.id, staticData.blessedTraditions), + IM.lookup(trad.id, staticData.specialAbilities), + IM.lookup(trad.id, staticData.blessedTraditions), ) ) ); let idToNumId = (staticData, id) => - Ley_Option.Functor.( - Ley_IntMap.lookup(id, staticData.blessedTraditions) <&> (x => x.numId) + O.Functor.( + IM.lookup(id, staticData.blessedTraditions) <&> (x => x.numId) ); let numIdToId = (staticData, id) => - Ley_Option.Functor.( - Ley_IntMap.Foldable.find( + O.Functor.( + IM.Foldable.find( (trad: BlessedTradition.t) => trad.numId === id, staticData.blessedTraditions, ) <&> (x => x.id) ); - let getPrimaryAttributeId = - (staticData, mp: Ley_IntMap.t(Hero.Activatable.t)) => - Ley_Option.Monad.( + let getPrimaryAttributeId = (staticData, mp: IM.t(Hero.Activatable.t)) => + O.Monad.( mp |> getHeroEntry(staticData) - >>= (trad => Ley_IntMap.lookup(trad.id, staticData.magicalTraditions)) + >>= (trad => IM.lookup(trad.id, staticData.magicalTraditions)) >>= (trad => trad.primary) ); }; diff --git a/src/Misc/Tradition.rei b/src/Misc/Tradition.rei index 0b95f5ce..5d4a29e5 100644 --- a/src/Misc/Tradition.rei +++ b/src/Misc/Tradition.rei @@ -1,4 +1,10 @@ module Magical: { + /** + * `isTraditionId staticData id` checks if `id` is the special ability id of + * a magical tradition. + */ + let isTraditionId: (Static.t, int) => bool; + /** * `getHeroEntries` returns active special ability entries for all active * magical traditions. @@ -46,6 +52,12 @@ module Magical: { (Static.t, Ley_IntMap.t(Hero.Activatable.t)) => option(int); }; module Blessed: { + /** + * `isTraditionId staticData id` checks if `id` is the special ability id of + * a blessed tradition. + */ + let isTraditionId: (Static.t, int) => bool; + /** * `getHeroEntry` returns the active special ability entry for the active * blessed traditions. diff --git a/src/Prerequisites/Dependencies.bs.js b/src/Prerequisites/Dependencies.bs.js index d24f2f80..9e43e4ee 100644 --- a/src/Prerequisites/Dependencies.bs.js +++ b/src/Prerequisites/Dependencies.bs.js @@ -15,6 +15,36 @@ import * as Ley_Function$OptolithClient from "../Data/Ley_Function.bs.js"; import * as Prerequisites$OptolithClient from "./Prerequisites.bs.js"; import * as Activatable_Convert$OptolithClient from "../Activatable/Activatable_Convert.bs.js"; +function isLevelDependencyMatched(dependency, active) { + var match = dependency.level; + var match$1 = active.level; + if (match !== undefined) { + if (match$1 !== undefined) { + return match$1 >= match === dependency.active; + } else { + return !dependency.active; + } + } else { + return true; + } +} + +function areOptionDependenciesMatched(dependency, active) { + return Ley_List$OptolithClient.Index.iall((function (i, option) { + return Ley_Option$OptolithClient.option(false, (function (activeOption) { + if (option.TAG) { + return Ley_List$OptolithClient.elem(activeOption, option._0) === dependency.active; + } else { + return Id$OptolithClient.SelectOption.$eq(activeOption, option._0) === dependency.active; + } + }), Ley_Option$OptolithClient.Monad.$great$great$eq(Ley_List$OptolithClient.Safe.atMay(active.options, i), Activatable_Convert$OptolithClient.activatableOptionToSelectOptionId)); + }), dependency.options); +} + +function isDependencyMatched(dependency, active) { + return (isLevelDependencyMatched(dependency, active) && areOptionDependenciesMatched(dependency, active)) === dependency.active; +} + function flattenSkillDependencies(getValueForTargetId, id, dependencies) { return Ley_Option$OptolithClient.mapOption((function (dep) { var targets = dep.target; @@ -59,24 +89,8 @@ function flattenActivatableDependencies(getActiveListForTargetId, id, dependenci if (!targets.TAG) { return dep; } - var isMatchedByOtherEntry = Ley_List$OptolithClient.Foldable.any((function (active) { - if (Ley_Option$OptolithClient.option(true, (function (level) { - return Ley_Option$OptolithClient.option(!dep.active, (function (activeLevel) { - return activeLevel >= level === dep.active; - }), active.level); - }), dep.level)) { - return Ley_List$OptolithClient.Index.iall((function (i, option) { - return Ley_Option$OptolithClient.option(false, (function (activeOption) { - if (option.TAG) { - return Ley_List$OptolithClient.elem(activeOption, option._0) === dep.active; - } else { - return Id$OptolithClient.SelectOption.$eq(activeOption, option._0) === dep.active; - } - }), Ley_Option$OptolithClient.Monad.$great$great$eq(Ley_List$OptolithClient.Safe.atMay(active.options, i), Activatable_Convert$OptolithClient.activatableOptionToSelectOptionId)); - }), dep.options); - } else { - return false; - } + var isMatchedByOtherEntry = Ley_List$OptolithClient.Foldable.any((function (param) { + return isDependencyMatched(dep, param); }), Ley_List$OptolithClient.Foldable.concatMap(getActiveListForTargetId, Ley_List$OptolithClient.$$delete(id, targets._0))); if (isMatchedByOtherEntry) { return ; @@ -1019,12 +1033,18 @@ var TransferredUnfamiliar = { isEntryAllowingTransferUnfamiliarRemovable: isEntryAllowingTransferUnfamiliarRemovable }; +var Activatable = { + areOptionDependenciesMatched: areOptionDependenciesMatched, + isDependencyMatched: isDependencyMatched +}; + export { Flatten , TransferredUnfamiliar , addDependencies , removeDependencies , getMaxLevel , + Activatable , } /* Tradition-OptolithClient Not a pure module */ diff --git a/src/Prerequisites/Dependencies.re b/src/Prerequisites/Dependencies.re index bdf98b87..d32c5f51 100644 --- a/src/Prerequisites/Dependencies.re +++ b/src/Prerequisites/Dependencies.re @@ -5,6 +5,63 @@ module O = Ley_Option; open O.Monad; module IM = Ley_IntMap; +module Activatable = { + let isLevelDependencyMatched = + ( + dependency: Hero.Activatable.dependency, + active: Hero.Activatable.single, + ) => + switch (dependency.level, active.level) { + // If there is no level dependency, skip + | (None, _) => true + // If there is a level dependency but no actual level, skip (this case + // should not happen at all) + | (Some(_), None) => !dependency.active + // The active level must be at least the required level, if required. If + // prohibited, the level must be lower than the level specified in the + // dependency + | (Some(dependencyLevel), Some(activeLevel)) => + activeLevel >= dependencyLevel === dependency.active + }; + + let areOptionDependenciesMatched = + ( + dependency: Hero.Activatable.dependency, + active: Hero.Activatable.single, + ) => + L.Index.iall( + (i, option) => + // Get the active option at the same position as the + // required option + L.Safe.atMay(active.options, i) + >>= Activatable_Convert.activatableOptionToSelectOptionId + |> O.option(false, activeOption => + switch (option) { + | OneOrMany.One(option) => + // If only one option, required and active must + // be equal + Id.SelectOption.(activeOption == option) === dependency.active + | OneOrMany.Many(options) => + // If multiple options are possible, one must be + // equal to the active option + L.elem(activeOption, options) === dependency.active + } + ), + dependency.options, + ); + + let isDependencyMatched = (dependency, active) => + ( + isLevelDependencyMatched(dependency, active) + && areOptionDependenciesMatched(dependency, active) + ) + // Prohibiting instead of requiring a specific entry configuration is a + // logical inversion, which means that we can compare with the value if it + // should be required (true) or prohibited (false) to implement this + // inversion + === dependency.active; +}; + module Flatten = { /** * `flattenSkillDependencies getValueForTargetId id dependencies` flattens the @@ -101,46 +158,7 @@ module Flatten = { |> L.Foldable.concatMap(getActiveListForTargetId) // Check if the dependency is met by another entry so that it can be // ignored currently - |> L.Foldable.any((active: Hero.Activatable.single) - // Check if level dependency is met - => - O.option( - // If there is no level dependency, skip - true, - level => - O.option( - // If there is a level dependency but no actual level, - // skip (this case should not happen at all) - !dep.active, - // The active level must be at least the required level - activeLevel => activeLevel >= level === dep.active, - active.level, - ), - dep.level, - ) - // Check if options dependency/-ies is/are met - && L.Index.iall( - (i, option) => - // Get the active option at the same position as the - // required option - L.Safe.atMay(active.options, i) - >>= Activatable_Convert.activatableOptionToSelectOptionId - |> O.option(false, activeOption => - switch (option) { - | OneOrMany.One(option) => - // If only one option, required and active must - // be equal - Id.SelectOption.(activeOption == option) - === dep.active - | OneOrMany.Many(options) => - // If multiple options are possible, one must be - // equal to the active option - L.elem(activeOption, options) === dep.active - } - ), - dep.options, - ) - ) + |> L.Foldable.any(Activatable.isDependencyMatched(dep)) |> ( isMatchedByOtherEntry => if (isMatchedByOtherEntry) { diff --git a/src/Prerequisites/Dependencies.rei b/src/Prerequisites/Dependencies.rei index 51cb86b5..3887652e 100644 --- a/src/Prerequisites/Dependencies.rei +++ b/src/Prerequisites/Dependencies.rei @@ -118,3 +118,22 @@ let getMaxLevel: Ley_IntMap.t(Prerequisite.t) ) => option(int); + +module Activatable: { + /** + * `areOptionDependenciesMatched dependency single` takes an activatable + * `dependency` and a `single` activatable activation and returns if that + * activation matches the options defined in the dependency. Other values from + * the dependency are not validated. + */ + let areOptionDependenciesMatched: + (Hero.Activatable.dependency, Hero.Activatable.single) => bool; + + /** + * `isDependencyMatched dependency single` takes an activatable `dependency` + * and a `single` activatable activation and returns if that activation + * matches the dependency. + */ + let isDependencyMatched: + (Hero.Activatable.dependency, Hero.Activatable.single) => bool; +};