From 4492304173b24f1d7fff3f8b05d7f4883ae24061 Mon Sep 17 00:00:00 2001 From: Lukas Obermann Date: Sun, 12 Jul 2020 20:22:53 +0200 Subject: [PATCH] Finished single inactive entry generation and validation --- src/Activatable/Activatable_Accessors.bs.js | 24 ++ src/Activatable/Activatable_Accessors.re | 14 + src/Activatable/Activatable_Accessors.rei | 4 + src/Activatable/Activatable_Inactive.bs.js | 370 ++++++++++++++++++ src/Activatable/Activatable_Inactive.re | 221 +++++------ src/Activatable/Activatable_Inactive.rei | 34 ++ .../Activatable_Inactive_Cache.bs.js | 2 + src/Activatable/Activatable_Inactive_Cache.re | 34 ++ .../Activatable_Inactive_Validation.bs.js | 89 +++-- .../Activatable_Inactive_Validation.re | 145 +++---- .../Activatable_Inactive_Validation.rei | 46 +-- src/Data/Ley_Option.bs.js | 2 +- src/Data/Ley_Option.re | 2 +- src/Data/Ley_Option.rei | 2 +- src/Prerequisites/Dependencies.bs.js | 24 ++ src/Prerequisites/Dependencies.re | 34 ++ src/Prerequisites/Dependencies.rei | 21 + src/Prerequisites/Prerequisites.bs.js | 44 ++- src/Prerequisites/Prerequisites.re | 115 ++---- src/Prerequisites/Prerequisites.rei | 20 + 20 files changed, 890 insertions(+), 357 deletions(-) create mode 100644 src/Activatable/Activatable_Inactive_Cache.bs.js create mode 100644 src/Activatable/Activatable_Inactive_Cache.re diff --git a/src/Activatable/Activatable_Accessors.bs.js b/src/Activatable/Activatable_Accessors.bs.js index b410a8bd..d3ab8226 100644 --- a/src/Activatable/Activatable_Accessors.bs.js +++ b/src/Activatable/Activatable_Accessors.bs.js @@ -32,6 +32,10 @@ function id(x) { } } +function id$prime(x) { + return x._0.id; +} + function name(x) { return x._0.name; } @@ -69,6 +73,24 @@ function apValue(x) { } } +function apValue$prime(x) { + var match = apValue(x); + if (match !== undefined) { + if (match.TAG) { + return { + TAG: /* Many */1, + _0: match._0 + }; + } else { + return { + TAG: /* One */0, + _0: match._0 + }; + } + } + +} + function max(x) { switch (x.TAG | 0) { case /* Advantage */0 : @@ -84,10 +106,12 @@ export { isActive , isActiveM , id , + id$prime , name , selectOptions , input , apValue , + apValue$prime , max , } diff --git a/src/Activatable/Activatable_Accessors.re b/src/Activatable/Activatable_Accessors.re index f2c88e69..ca600698 100644 --- a/src/Activatable/Activatable_Accessors.re +++ b/src/Activatable/Activatable_Accessors.re @@ -11,6 +11,13 @@ let id = x => | SpecialAbility(y) => `SpecialAbility(y.id) }; +let id' = x => + switch (x) { + | Advantage(y) => y.id + | Disadvantage(y) => y.id + | SpecialAbility(y) => y.id + }; + let name = x => switch (x) { | Advantage(y) => y.name @@ -39,6 +46,13 @@ let apValue = x => | SpecialAbility(y) => y.apValue }; +let apValue' = x => + switch (apValue(x)) { + | Some(Flat(y)) => Some(OneOrMany.One(y)) + | Some(PerLevel(ys)) => Some(Many(ys)) + | None => None + }; + let max = x => switch (x) { | Advantage(y) => y.max diff --git a/src/Activatable/Activatable_Accessors.rei b/src/Activatable/Activatable_Accessors.rei index e307e007..26f8c1ab 100644 --- a/src/Activatable/Activatable_Accessors.rei +++ b/src/Activatable/Activatable_Accessors.rei @@ -20,6 +20,8 @@ let isActiveM: option(Hero.Activatable.t) => bool; let id: Static.activatable => Id.Activatable.t; +let id': Static.activatable => int; + let name: Static.activatable => string; let selectOptions: Static.activatable => SelectOption.map; @@ -28,4 +30,6 @@ let input: Static.activatable => option(string); let apValue: Static.activatable => option(Advantage.cost); +let apValue': Static.activatable => option(OneOrMany.t(int)); + let max: Static.activatable => option(int); diff --git a/src/Activatable/Activatable_Inactive.bs.js b/src/Activatable/Activatable_Inactive.bs.js index e3c269ee..80cc80a6 100644 --- a/src/Activatable/Activatable_Inactive.bs.js +++ b/src/Activatable/Activatable_Inactive.bs.js @@ -1,12 +1,382 @@ // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE +import * as Curry from "bs-platform/lib/es6/curry.js"; +import * as IC$OptolithClient from "../Increasable/IC.bs.js"; +import * as Id$OptolithClient from "../Misc/Id.bs.js"; import * as Skills$OptolithClient from "../Increasable/Skills.bs.js"; +import * as Ley_List$OptolithClient from "../Data/Ley_List.bs.js"; import * as Ley_IntMap$OptolithClient from "../Data/Ley_IntMap.bs.js"; +import * as Ley_Option$OptolithClient from "../Data/Ley_Option.bs.js"; import * as EntryGroups$OptolithClient from "../Misc/EntryGroups.bs.js"; import * as ActivatableSkills$OptolithClient from "../Increasable/ActivatableSkills.bs.js"; +import * as Activatable_Modifier$OptolithClient from "./Activatable_Modifier.bs.js"; +import * as Activatable_Accessors$OptolithClient from "./Activatable_Accessors.bs.js"; +import * as Activatable_Active_Validation$OptolithClient from "./Activatable_Active_Validation.bs.js"; +import * as Activatable_Inactive_Validation$OptolithClient from "./Activatable_Inactive_Validation.bs.js"; +import * as Activatable_Inactive_SelectOptions$OptolithClient from "./Activatable_Inactive_SelectOptions.bs.js"; +function getSermonOrVisionCountMax(hero, advantageId, disadvantageId) { + return Activatable_Modifier$OptolithClient.modifyByLevel(3, Curry._2(Ley_IntMap$OptolithClient.lookup, Id$OptolithClient.Advantage.toInt(advantageId), hero.advantages), Curry._2(Ley_IntMap$OptolithClient.lookup, Id$OptolithClient.Disadvantage.toInt(disadvantageId), hero.disadvantages)); +} + +function getInactive(cache, staticData, hero, staticEntry, maybeHeroEntry) { + var maxLevel = Activatable_Inactive_Validation$OptolithClient.getMaxLevel(staticData, hero, staticEntry, maybeHeroEntry); + var isAdditionValid = Activatable_Inactive_Validation$OptolithClient.isAdditionValid(cache, staticData, hero, maxLevel, staticEntry, maybeHeroEntry); + if (isAdditionValid) { + return Ley_Option$OptolithClient.option({ + TAG: /* Invalid */1, + _0: staticEntry + }, (function (base) { + return { + TAG: /* Valid */0, + _0: base + }; + }), Ley_Option$OptolithClient.Monad.$great$great$eq(Ley_Option$OptolithClient.Monad.$less$amp$great(Activatable_Inactive_SelectOptions$OptolithClient.getAvailableSelectOptions(staticData, hero, cache.magicalTraditions, staticEntry, maybeHeroEntry), (function (selectOptions) { + var tmp; + switch (staticEntry.TAG | 0) { + case /* Advantage */0 : + tmp = Ley_List$OptolithClient.elem(staticEntry._0.id, cache.automaticAdvantages); + break; + case /* Disadvantage */1 : + case /* SpecialAbility */2 : + tmp = false; + break; + + } + return { + id: Activatable_Accessors$OptolithClient.id$prime(staticEntry), + name: Activatable_Accessors$OptolithClient.name(staticEntry), + apValue: Activatable_Accessors$OptolithClient.apValue$prime(staticEntry), + minLevel: undefined, + maxLevel: maxLevel, + selectOptions: selectOptions, + heroEntry: maybeHeroEntry, + staticEntry: staticEntry, + customCostDisabled: true, + isAutomatic: tmp + }; + })), (function (base) { + switch (staticEntry.TAG | 0) { + case /* Advantage */0 : + return base; + case /* Disadvantage */1 : + var match = Id$OptolithClient.Disadvantage.fromInt(staticEntry._0.id); + if (typeof match === "number") { + if (match !== 22) { + if (match >= 23) { + return { + id: base.id, + name: base.name, + apValue: base.apValue, + minLevel: base.minLevel, + maxLevel: Ley_Option$OptolithClient.ensure((function (param) { + return 0 < param; + }), Activatable_Active_Validation$OptolithClient.getMaxLevelForDecreaseEntry(3, EntryGroups$OptolithClient.SpecialAbility.countActiveFromGroup(typeof match === "number" && match === 23 ? /* Predigten */23 : /* Visionen */26, cache.specialAbilityPairs))), + selectOptions: base.selectOptions, + heroEntry: base.heroEntry, + staticEntry: base.staticEntry, + customCostDisabled: base.customCostDisabled, + isAutomatic: base.isAutomatic + }; + } else { + return base; + } + } else { + return { + id: base.id, + name: base.name, + apValue: base.apValue, + minLevel: base.minLevel, + maxLevel: Ley_Option$OptolithClient.ensure((function (param) { + return 0 < param; + }), Activatable_Active_Validation$OptolithClient.getMaxLevelForDecreaseEntry(3, ActivatableSkills$OptolithClient.countActiveSkillEntries(/* Spells */0, hero))), + selectOptions: base.selectOptions, + heroEntry: base.heroEntry, + staticEntry: base.staticEntry, + customCostDisabled: base.customCostDisabled, + isAutomatic: base.isAutomatic + }; + } + } else { + return base; + } + case /* SpecialAbility */2 : + var staticSpecialAbility = staticEntry._0; + var match$1 = Id$OptolithClient.SpecialAbility.fromInt(staticSpecialAbility.id); + var exit = 0; + if (typeof match$1 !== "number") { + return base; + } + if (match$1 >= 54) { + if (match$1 >= 71) { + switch (match$1 - 71 | 0) { + case /* PropertyFocus */11 : + exit = 3; + break; + case /* Harmoniezauberei */21 : + exit = 6; + break; + case /* SkillSpecialization */0 : + case /* Hunter */3 : + case /* AreaKnowledge */4 : + case /* Literacy */5 : + case /* Language */6 : + case /* CombatReflexes */7 : + case /* ImprovedDodge */8 : + case /* TraditionGuildMages */9 : + case /* PropertyKnowledge */10 : + case /* AspectKnowledge */12 : + case /* TraditionChurchOfPraios */13 : + case /* Feuerschlucker */14 : + case /* CombatStyleCombination */15 : + case /* AdaptionZauber */16 : + case /* Exorzist */17 : + case /* FavoriteSpellwork */18 : + case /* TraditionWitches */19 : + case /* MagicStyleCombination */20 : + case /* TraditionElves */23 : + case /* TraditionDruids */24 : + return base; + case /* TerrainKnowledge */1 : + case /* CraftInstruments */2 : + case /* Matrixzauberei */22 : + case /* SpellEnhancement */25 : + exit = 1; + break; + + } + } else { + exit = 3; + } + } else { + switch (match$1) { + case /* CraftInstruments */2 : + var getSkillValue = function (skill) { + return Skills$OptolithClient.getValueDef(Curry._2(Ley_IntMap$OptolithClient.lookup, Id$OptolithClient.Skill.toInt(skill), hero.skills)); + }; + if ((getSkillValue(/* Woodworking */50) + getSkillValue(/* Metalworking */54) | 0) >= 12) { + return base; + } else { + return ; + } + case /* Hunter */3 : + if (Curry._2(Ley_IntMap$OptolithClient.IntMap.Foldable.any, (function (param) { + var heroEntry = param[1]; + return heroEntry !== undefined ? heroEntry.value >= 10 : false; + }), EntryGroups$OptolithClient.CombatTechnique.getFromGroup(/* Ranged */1, cache.combatTechniquePairs))) { + return base; + } else { + return ; + } + case /* PropertyKnowledge */10 : + case /* AspectKnowledge */12 : + exit = 2; + break; + case /* TraditionChurchOfPraios */13 : + exit = 3; + break; + case /* Recherchegespuer */29 : + return Ley_Option$OptolithClient.Monad.$great$great$eq(Ley_Option$OptolithClient.Monad.$great$great$eq(Ley_Option$OptolithClient.Monad.$great$great$eq(Ley_Option$OptolithClient.Monad.$great$great$eq(Ley_Option$OptolithClient.Monad.$less$amp$great(Curry._2(Ley_IntMap$OptolithClient.lookup, Id$OptolithClient.SpecialAbility.toInt(/* Wissensdurst */28), hero.specialAbilities), (function (param) { + return param.active; + })), Ley_Option$OptolithClient.listToOption), (function (param) { + return Ley_Option$OptolithClient.listToOption(param.options); + })), (function (param) { + if (typeof param === "number" || param.HASH !== 290194801) { + return ; + } else { + return Curry._2(Ley_IntMap$OptolithClient.lookup, param.VAL, staticData.skills); + } + })), (function (param) { + var match = staticSpecialAbility.apValue; + if (match === undefined) { + return ; + } + if (!match.TAG) { + return ; + } + var partial_arg = IC$OptolithClient.getAPForActivatation(param.ic); + var sumValues = Ley_List$OptolithClient.map((function (param) { + return partial_arg + param | 0; + }), match._0); + return Ley_Option$OptolithClient.Monad.$$return({ + id: base.id, + name: base.name, + apValue: { + TAG: /* Many */1, + _0: sumValues + }, + minLevel: base.minLevel, + maxLevel: base.maxLevel, + selectOptions: base.selectOptions, + heroEntry: base.heroEntry, + staticEntry: base.staticEntry, + customCostDisabled: base.customCostDisabled, + isAutomatic: base.isAutomatic + }); + })); + case /* PredigtDerGemeinschaft */30 : + case /* PredigtDerZuversicht */31 : + case /* PredigtDesGottvertrauens */32 : + case /* PredigtDesWohlgefallens */33 : + case /* PredigtWiderMissgeschicke */34 : + exit = 4; + break; + case /* VisionDerBestimmung */35 : + case /* VisionDerEntrueckung */36 : + case /* VisionDerGottheit */37 : + case /* VisionDesSchicksals */38 : + case /* VisionDesWahrenGlaubens */39 : + exit = 5; + break; + case /* SkillSpecialization */0 : + case /* TerrainKnowledge */1 : + case /* AreaKnowledge */4 : + case /* Literacy */5 : + case /* Language */6 : + case /* CombatReflexes */7 : + case /* ImprovedDodge */8 : + case /* PropertyFocus */11 : + case /* Feuerschlucker */14 : + case /* CombatStyleCombination */15 : + case /* AdaptionZauber */16 : + case /* Exorzist */17 : + case /* FavoriteSpellwork */18 : + case /* MagicStyleCombination */20 : + case /* Harmoniezauberei */21 : + case /* Matrixzauberei */22 : + case /* SpellEnhancement */25 : + case /* Forschungsgebiet */26 : + case /* Expertenwissen */27 : + case /* Wissensdurst */28 : + case /* HoheWeihe */40 : + case /* Lieblingsliturgie */41 : + case /* Zugvoegel */42 : + case /* JaegerinnenDerWeissenMaid */43 : + case /* AnhaengerDesGueldenen */44 : + case /* GebieterDesAspekts */45 : + case /* ChantEnhancement */46 : + return base; + case /* DunklesAbbildDerBuendnisgabe */47 : + return Ley_Option$OptolithClient.Monad.$great$great$eq(hero.pact, (function (param) { + var level = param.level; + if (level > 0) { + return { + id: base.id, + name: base.name, + apValue: base.apValue, + minLevel: base.minLevel, + maxLevel: level, + selectOptions: base.selectOptions, + heroEntry: base.heroEntry, + staticEntry: base.staticEntry, + customCostDisabled: base.customCostDisabled, + isAutomatic: base.isAutomatic + }; + } + + })); + case /* TraditionArcaneBard */49 : + case /* TraditionArcaneDancer */50 : + case /* TraditionIntuitiveMage */51 : + case /* TraditionSavant */52 : + exit = 6; + break; + case /* TraditionGuildMages */9 : + case /* TraditionWitches */19 : + case /* TraditionElves */23 : + case /* TraditionDruids */24 : + case /* TraditionIllusionist */48 : + case /* TraditionQabalyaMage */53 : + exit = 1; + break; + + } + } + switch (exit) { + case 1 : + if (Ley_List$OptolithClient.Foldable.$$null(cache.magicalTraditions)) { + return base; + } else { + return ; + } + case 2 : + var match$2 = staticSpecialAbility.apValue; + if (match$2 === undefined) { + return ; + } + if (!match$2.TAG) { + return ; + } + var index = Ley_Option$OptolithClient.option(0, (function (param) { + return Ley_List$OptolithClient.Foldable.length(param.active); + }), maybeHeroEntry); + return Ley_Option$OptolithClient.Functor.$less$amp$great(Ley_List$OptolithClient.Safe.atMay(match$2._0, index), (function (apValue) { + return { + id: base.id, + name: base.name, + apValue: { + TAG: /* One */0, + _0: apValue + }, + minLevel: base.minLevel, + maxLevel: base.maxLevel, + selectOptions: base.selectOptions, + heroEntry: base.heroEntry, + staticEntry: base.staticEntry, + customCostDisabled: base.customCostDisabled, + isAutomatic: base.isAutomatic + }; + })); + case 3 : + if (Ley_Option$OptolithClient.isNone(cache.blessedTradition)) { + return base; + } else { + return ; + } + case 4 : + var isAdvActive = function (id) { + return Activatable_Accessors$OptolithClient.isActiveM(Curry._2(Ley_IntMap$OptolithClient.lookup, Id$OptolithClient.Advantage.toInt(id), hero.advantages)); + }; + var max = getSermonOrVisionCountMax(hero, /* ZahlreichePredigten */24, /* WenigePredigten */23); + var isLessThanMax = EntryGroups$OptolithClient.SpecialAbility.countActiveFromGroup(/* Predigten */23, cache.specialAbilityPairs) < max; + if (isAdvActive(/* Prediger */22) && isLessThanMax || isAdvActive(/* Blessed */2)) { + return base; + } else { + return ; + } + case 5 : + var isAdvActive$1 = function (id) { + return Activatable_Accessors$OptolithClient.isActiveM(Curry._2(Ley_IntMap$OptolithClient.lookup, Id$OptolithClient.Advantage.toInt(id), hero.advantages)); + }; + var max$1 = getSermonOrVisionCountMax(hero, /* ZahlreicheVisionen */25, /* WenigeVisionen */24); + var isLessThanMax$1 = EntryGroups$OptolithClient.SpecialAbility.countActiveFromGroup(/* Visionen */26, cache.specialAbilityPairs) < max$1; + if (isAdvActive$1(/* Visionaer */23) && isLessThanMax$1 || isAdvActive$1(/* Blessed */2)) { + return base; + } else { + return ; + } + case 6 : + if (cache.adventurePoints.spentOnMagicalAdvantages <= 25 && cache.adventurePoints.spentOnMagicalDisadvantages <= 25 && Ley_List$OptolithClient.Foldable.$$null(cache.magicalTraditions)) { + return base; + } else { + return ; + } + + } + break; + + } + }))); + } else { + return { + TAG: /* Invalid */1, + _0: staticEntry + }; + } +} export { + getInactive , } /* Skills-OptolithClient Not a pure module */ diff --git a/src/Activatable/Activatable_Inactive.re b/src/Activatable/Activatable_Inactive.re index b7dda189..57e03767 100644 --- a/src/Activatable/Activatable_Inactive.re +++ b/src/Activatable/Activatable_Inactive.re @@ -2,19 +2,25 @@ module IM = Ley_IntMap; module L = Ley_List; module O = Ley_Option; -type t = { - id: string, +open Activatable_Inactive_Cache; + +type valid = { + id: int, name: string, - cost: option(OneOrMany.t(int)), + apValue: option(OneOrMany.t(int)), minLevel: option(int), maxLevel: option(int), - selectOptions: option(list(SelectOption.t)), + selectOptions: list(SelectOption.t), heroEntry: option(Hero.Activatable.t), staticEntry: Static.activatable, customCostDisabled: bool, isAutomatic: bool, }; +type t = + | Valid(valid) + | Invalid(Static.activatable); + let getSermonOrVisionCountMax = (hero: Hero.t, advantageId, disadvantageId) => Activatable_Modifier.modifyByLevel( 3, @@ -23,18 +29,7 @@ let getSermonOrVisionCountMax = (hero: Hero.t, advantageId, disadvantageId) => ); let modifyOtherOptions = - ( - ~staticData: Static.t, - ~hero, - ~specialAbilityPairs, - ~combatTechniquePairs, - ~adventurePoints: AdventurePoints.categories, - ~magicalTraditions, - ~blessedTradition, - ~staticEntry, - ~maybeHeroEntry, - ~base, - ) => + (cache, staticData: Static.t, hero, staticEntry, maybeHeroEntry, base) => switch (staticEntry) { | Static.Advantage(_) => Some(base) | Disadvantage(staticDisadvantage) => @@ -46,26 +41,24 @@ let modifyOtherOptions = Some({ ...base, maxLevel: - Some( - EntryGroups.SpecialAbility.countActiveFromGroup( - switch (id) { - | WenigePredigten => Predigten - | _ => Visionen - }, - specialAbilityPairs, - ) - |> Activatable_Active_Validation.getMaxLevelForDecreaseEntry(3), - ), + EntryGroups.SpecialAbility.countActiveFromGroup( + switch (id) { + | WenigePredigten => Predigten + | _ => Visionen + }, + cache.specialAbilityPairs, + ) + |> Activatable_Active_Validation.getMaxLevelForDecreaseEntry(3) + |> O.ensure((<)(0)), }) | SmallSpellSelection => Some({ ...base, maxLevel: - Some( - ActivatableSkills.countActiveSkillEntries(Spells, hero) - |> Activatable_Active_Validation.getMaxLevelForDecreaseEntry(3), - ), + ActivatableSkills.countActiveSkillEntries(Spells, hero) + |> Activatable_Active_Validation.getMaxLevelForDecreaseEntry(3) + |> O.ensure((<)(0)), }) | _ => Some(base) @@ -86,7 +79,10 @@ let modifyOtherOptions = ? Some(base) : None; | Hunter => - EntryGroups.CombatTechnique.getFromGroup(Ranged, combatTechniquePairs) + EntryGroups.CombatTechnique.getFromGroup( + Ranged, + cache.combatTechniquePairs, + ) |> IM.Foldable.any( fun // At least one ranged combat technique must be on CtR 10 or higher @@ -106,7 +102,7 @@ let modifyOtherOptions = | TraditionSchelme | TraditionBrobimGeoden => // Only one tradition allowed - L.Foldable.null(magicalTraditions) ? Some(base) : None + L.Foldable.null(cache.magicalTraditions) ? Some(base) : None | PropertyKnowledge | AspectKnowledge => @@ -122,7 +118,7 @@ let modifyOtherOptions = O.Functor.( L.Safe.atMay(values, index) - <&> (apValue => {...base, cost: Some(One(apValue))}) + <&> (apValue => {...base, apValue: Some(One(apValue))}) ); | Some(Flat(_)) | None => None @@ -148,7 +144,7 @@ let modifyOtherOptions = | TraditionChurchOfSwafnir | TraditionCultOfNuminoru => // Only one tradition allowed - O.isNone(blessedTradition) ? Some(base) : None + O.isNone(cache.blessedTradition) ? Some(base) : None | Recherchegespuer => O.Monad.( @@ -170,7 +166,7 @@ let modifyOtherOptions = |> L.map((+)(IC.getAPForActivatation(ic))) |> ( sumValues => - {...base, cost: Some(Many(sumValues))} |> return + {...base, apValue: Some(Many(sumValues))} |> return ) | Some(Flat(_)) | None => None @@ -197,7 +193,7 @@ let modifyOtherOptions = let isLessThanMax = EntryGroups.SpecialAbility.countActiveFromGroup( Predigten, - specialAbilityPairs, + cache.specialAbilityPairs, ) < max; @@ -219,7 +215,7 @@ let modifyOtherOptions = let isLessThanMax = EntryGroups.SpecialAbility.countActiveFromGroup( Visionen, - specialAbilityPairs, + cache.specialAbilityPairs, ) < max; @@ -227,8 +223,12 @@ let modifyOtherOptions = ? Some(base) : None; | DunklesAbbildDerBuendnisgabe => - O.Functor.( - hero.pact <&> (({level, _}) => {...base, maxLevel: Some(level)}) + O.Monad.( + hero.pact + >>= ( + ({level, _}) => + level > 0 ? Some({...base, maxLevel: Some(level)}) : None + ) ) | TraditionArcaneBard @@ -236,84 +236,77 @@ let modifyOtherOptions = | TraditionIntuitiveMage | TraditionSavant | TraditionAnimisten => - adventurePoints.spentOnMagicalAdvantages <= 25 - && adventurePoints.spentOnMagicalDisadvantages <= 25 - && L.Foldable.null(magicalTraditions) + cache.adventurePoints.spentOnMagicalAdvantages <= 25 + && cache.adventurePoints.spentOnMagicalDisadvantages <= 25 + && L.Foldable.null(cache.magicalTraditions) ? Some(base) : None | _ => Some(base) } ) }; -// -// /** -// * 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 -/* */ + +let getInactive = (cache, staticData, hero, staticEntry, maybeHeroEntry) => { + let maxLevel = + Activatable_Inactive_Validation.getMaxLevel( + staticData, + hero, + staticEntry, + maybeHeroEntry, + ); + + let isAdditionValid = + Activatable_Inactive_Validation.isAdditionValid( + cache, + staticData, + hero, + maxLevel, + staticEntry, + maybeHeroEntry, + ); + + if (isAdditionValid) { + O.Monad.( + Activatable_Inactive_SelectOptions.getAvailableSelectOptions( + staticData, + hero, + cache.magicalTraditions, + staticEntry, + maybeHeroEntry, + ) + <&> ( + selectOptions => { + id: Activatable_Accessors.id'(staticEntry), + name: Activatable_Accessors.name(staticEntry), + apValue: Activatable_Accessors.apValue'(staticEntry), + minLevel: None, + maxLevel, + selectOptions, + heroEntry: maybeHeroEntry, + staticEntry, + customCostDisabled: true, + isAutomatic: + switch (staticEntry) { + | Advantage({id, _}) => L.elem(id, cache.automaticAdvantages) + | Disadvantage(_) + | SpecialAbility(_) => false + }, + } + ) + >>= ( + base => + modifyOtherOptions( + cache, + staticData, + hero, + staticEntry, + maybeHeroEntry, + base, + ) + ) + |> O.option(Invalid(staticEntry), base => Valid(base)) + ); + } else { + Invalid(staticEntry); + }; +}; diff --git a/src/Activatable/Activatable_Inactive.rei b/src/Activatable/Activatable_Inactive.rei index e69de29b..71cf0b78 100644 --- a/src/Activatable/Activatable_Inactive.rei +++ b/src/Activatable/Activatable_Inactive.rei @@ -0,0 +1,34 @@ +type valid = { + id: int, + name: string, + apValue: option(OneOrMany.t(int)), + minLevel: option(int), + maxLevel: option(int), + selectOptions: list(SelectOption.t), + heroEntry: option(Hero.Activatable.t), + staticEntry: Static.activatable, + customCostDisabled: bool, + isAutomatic: bool, +}; + +type t = + | Valid(valid) + | Invalid(Static.activatable); + +/** + * `getInactive 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 getInactive: + ( + Activatable_Inactive_Cache.t, + Static.t, + Hero.t, + Static.activatable, + option(Hero.Activatable.t) + ) => + t; diff --git a/src/Activatable/Activatable_Inactive_Cache.bs.js b/src/Activatable/Activatable_Inactive_Cache.bs.js new file mode 100644 index 00000000..40fed0f7 --- /dev/null +++ b/src/Activatable/Activatable_Inactive_Cache.bs.js @@ -0,0 +1,2 @@ +// Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE +/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/src/Activatable/Activatable_Inactive_Cache.re b/src/Activatable/Activatable_Inactive_Cache.re new file mode 100644 index 00000000..222c3642 --- /dev/null +++ b/src/Activatable/Activatable_Inactive_Cache.re @@ -0,0 +1,34 @@ +/** + * A set of values to validate `Weg der Schreiberin`. + */ +type matchingLanguagesScripts = { + isEntryActiveRequiringMatch: bool, + languagesWithMatchingScripts: list(int), + scriptsWithMatchingLanguages: list(int), +}; + +/** + * The cache of computed values to optimize performance of `isAdditionValid`. + */ +type t = { + combatStyleCombination: option(Hero.Activatable.t), + armedCombatStylesCount: int, + unarmedCombatStylesCount: int, + magicalStylesCount: int, + isBlessedStyleActive: bool, + isSkillStyleActive: bool, + magicalStyleCombination: option(Hero.Activatable.t), + dunklesAbbild: option(Hero.Activatable.t), + activePactGiftsCount: int, + matchingLanguagesScripts, + validExtendedSpecialAbilities: list(int), + requiredApplyToMagicalActions: bool, + specialAbilityPairs: + Ley_IntMap.t((SpecialAbility.t, option(Hero.Activatable.t))), + combatTechniquePairs: + Ley_IntMap.t((CombatTechnique.t, option(Hero.Skill.t))), + adventurePoints: AdventurePoints.categories, + magicalTraditions: list(Tradition.Magical.fullTradition), + blessedTradition: option(Tradition.Blessed.fullTradition), + automaticAdvantages: list(int), +}; diff --git a/src/Activatable/Activatable_Inactive_Validation.bs.js b/src/Activatable/Activatable_Inactive_Validation.bs.js index 81749377..1e9fd9bd 100644 --- a/src/Activatable/Activatable_Inactive_Validation.bs.js +++ b/src/Activatable/Activatable_Inactive_Validation.bs.js @@ -2,9 +2,12 @@ import * as Curry from "bs-platform/lib/es6/curry.js"; import * as Id$OptolithClient from "../Misc/Id.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 Ley_IntMap$OptolithClient from "../Data/Ley_IntMap.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 Ley_Function$OptolithClient from "../Data/Ley_Function.bs.js"; import * as Prerequisites$OptolithClient from "../Prerequisites/Prerequisites.bs.js"; import * as Activatable_Accessors$OptolithClient from "./Activatable_Accessors.bs.js"; @@ -40,7 +43,9 @@ function getActivePactGiftsCount(specialAbilityPairs) { function isSpecialAbilitySpecificAdditionEnabled(rules, maybePact, param, specialAbility) { var matchingLanguagesScripts = param.matchingLanguagesScripts; var activePactGiftsCount = param.activePactGiftsCount; - var specialAbilityPairs = param.specialAbilityPairs; + var magicalStylesCount = param.magicalStylesCount; + var unarmedCombatStylesCount = param.unarmedCombatStylesCount; + var armedCombatStylesCount = param.armedCombatStylesCount; var match = Id$OptolithClient.SpecialAbility.fromInt(specialAbility.id); var match$1 = Curry._1(Id$OptolithClient.SpecialAbility.Group.fromInt, specialAbility.gr); var exit = 0; @@ -49,34 +54,31 @@ function isSpecialAbilitySpecificAdditionEnabled(rules, maybePact, param, specia if (typeof match$1 === "number") { if (match$1 >= 13) { if (match$1 === 24) { - return !EntryGroups$OptolithClient.SpecialAbility.hasActiveFromGroup(match$1, specialAbilityPairs); + return !param.isBlessedStyleActive; } if (match$1 === 32) { - return !EntryGroups$OptolithClient.SpecialAbility.hasActiveFromGroup(match$1, specialAbilityPairs); + return !param.isSkillStyleActive; } exit$2 = 4; } else if (match$1 >= 10) { if (match$1 >= 12) { - var totalActive = EntryGroups$OptolithClient.SpecialAbility.countActiveFromGroup(/* MagicalStyles */12, specialAbilityPairs); - return totalActive < ( + return magicalStylesCount < ( Activatable_Accessors$OptolithClient.isActiveM(param.magicalStyleCombination) ? 2 : 1 ); } exit$2 = 4; } else { if (match$1 >= 8) { + var equalTypeStylesActive = typeof match$1 === "number" ? ( + match$1 !== 8 ? ( + match$1 !== 9 ? 0 : unarmedCombatStylesCount + ) : armedCombatStylesCount + ) : 0; if (!Activatable_Accessors$OptolithClient.isActiveM(param.combatStyleCombination)) { - return !EntryGroups$OptolithClient.SpecialAbility.hasActiveFromGroup(Curry._1(Id$OptolithClient.SpecialAbility.Group.fromInt, specialAbility.gr), specialAbilityPairs); + return equalTypeStylesActive === 0; } - var totalActive$1 = EntryGroups$OptolithClient.SpecialAbility.countActiveFromGroups({ - hd: /* CombatStylesArmed */8, - tl: { - hd: /* CombatStylesUnarmed */9, - tl: /* [] */0 - } - }, specialAbilityPairs); - var equalTypeStylesActive = EntryGroups$OptolithClient.SpecialAbility.countActiveFromGroup(Curry._1(Id$OptolithClient.SpecialAbility.Group.fromInt, specialAbility.gr), specialAbilityPairs); - if (totalActive$1 < 3) { + var totalActive = armedCombatStylesCount + unarmedCombatStylesCount | 0; + if (totalActive < 3) { return true; } else { return equalTypeStylesActive < 2; @@ -91,7 +93,7 @@ function isSpecialAbilitySpecificAdditionEnabled(rules, maybePact, param, specia if (typeof match === "number") { if (match >= 21) { if (match === 47) { - return !EntryGroups$OptolithClient.SpecialAbility.hasActiveFromGroup(/* Paktgeschenke */29, specialAbilityPairs); + return activePactGiftsCount === 0; } if (match === 84) { if (Ley_List$OptolithClient.Foldable.length(matchingLanguagesScripts.languagesWithMatchingScripts) >= 1) { @@ -103,16 +105,10 @@ function isSpecialAbilitySpecificAdditionEnabled(rules, maybePact, param, specia exit$1 = 3; } else { if (match === 15) { - return !EntryGroups$OptolithClient.SpecialAbility.hasActiveFromGroups({ - hd: /* CombatStylesArmed */8, - tl: { - hd: /* CombatStylesUnarmed */9, - tl: /* [] */0 - } - }, specialAbilityPairs); + return (armedCombatStylesCount + unarmedCombatStylesCount | 0) > 0; } if (match >= 20) { - return !EntryGroups$OptolithClient.SpecialAbility.hasActiveFromGroup(/* MagicalStyles */12, specialAbilityPairs); + return magicalStylesCount > 0; } exit$1 = 3; } @@ -154,17 +150,6 @@ function isSpecialAbilitySpecificAdditionEnabled(rules, maybePact, param, specia } } -function getFlatFirstPrerequisites(staticAdvantage) { - switch (staticAdvantage.TAG | 0) { - case /* Advantage */0 : - case /* Disadvantage */1 : - return Prerequisites$OptolithClient.Flatten.getFirstDisAdvLevelPrerequisites(staticAdvantage._0.prerequisites); - case /* SpecialAbility */2 : - return Prerequisites$OptolithClient.Flatten.getFirstLevelPrerequisites(staticAdvantage._0.prerequisites); - - } -} - function isEntrySpecificAdditionEnabled(cache, staticData, hero, staticEntry) { var tmp; switch (staticEntry.TAG | 0) { @@ -178,7 +163,7 @@ function isEntrySpecificAdditionEnabled(cache, staticData, hero, staticEntry) { } if (tmp) { - return Prerequisites$OptolithClient.Validation.arePrerequisitesMet(staticData, hero, Id$OptolithClient.Activatable.generalize(Activatable_Accessors$OptolithClient.id(staticEntry)), getFlatFirstPrerequisites(staticEntry)); + return Prerequisites$OptolithClient.Validation.arePrerequisitesMet(staticData, hero, Id$OptolithClient.Activatable.generalize(Activatable_Accessors$OptolithClient.id(staticEntry)), Prerequisites$OptolithClient.Activatable.getFlatFirstPrerequisites(staticEntry)); } else { return false; } @@ -198,12 +183,11 @@ function hasNoGenerallyRestrictingDependency(param) { } } -function hasNotReachedMaximumEntries(staticEntry, maybeHeroEntry) { - var match = Activatable_Accessors$OptolithClient.max(staticEntry); - if (match !== undefined) { - if (match !== 0) { +function hasNotReachedMaximumEntries(maxLevel, maybeHeroEntry) { + if (maxLevel !== undefined) { + if (maxLevel !== 0) { if (maybeHeroEntry !== undefined) { - return match > Ley_List$OptolithClient.Foldable.length(maybeHeroEntry.active); + return maxLevel > Ley_List$OptolithClient.Foldable.length(maybeHeroEntry.active); } else { return true; } @@ -243,8 +227,8 @@ function isValidExtendedSpecialAbility(param, staticEntry) { } } -function isAdditionValid(cache, staticData, hero, staticEntry, maybeHeroEntry) { - if (isEntrySpecificAdditionEnabled(cache, staticData, hero, staticEntry) && hasNoGenerallyRestrictingDependency(maybeHeroEntry) && hasNotReachedMaximumEntries(staticEntry, maybeHeroEntry) && isValidExtendedSpecialAbility(cache, staticEntry)) { +function isAdditionValid(cache, staticData, hero, maxLevel, staticEntry, maybeHeroEntry) { + if (isEntrySpecificAdditionEnabled(cache, staticData, hero, staticEntry) && hasNoGenerallyRestrictingDependency(maybeHeroEntry) && hasNotReachedMaximumEntries(maxLevel, maybeHeroEntry) && isValidExtendedSpecialAbility(cache, staticEntry)) { switch (staticEntry.TAG | 0) { case /* Advantage */0 : case /* Disadvantage */1 : @@ -263,9 +247,28 @@ function isAdditionValid(cache, staticData, hero, staticEntry, maybeHeroEntry) { } } +function getMaxLevel(staticData, hero, staticEntry, maybeHeroEntry) { + var computedMax = Dependencies$OptolithClient.getMaxLevel(staticData, hero, Id$OptolithClient.Activatable.generalize(Activatable_Accessors$OptolithClient.id(staticEntry)), Ley_Option$OptolithClient.option(/* [] */0, (function (param) { + return param.dependencies; + }), maybeHeroEntry), Prerequisites$OptolithClient.Activatable.getLevelPrerequisites(staticEntry)); + var match = Activatable_Accessors$OptolithClient.max(staticEntry); + if (computedMax !== undefined) { + if (match !== undefined) { + return Ley_Int$OptolithClient.min(computedMax, match); + } else { + return computedMax; + } + } else if (match !== undefined) { + return match; + } else { + return ; + } +} + export { getActivePactGiftsCount , isAdditionValid , + getMaxLevel , } /* Ley_IntMap-OptolithClient Not a pure module */ diff --git a/src/Activatable/Activatable_Inactive_Validation.re b/src/Activatable/Activatable_Inactive_Validation.re index 3f571539..5138e7be 100644 --- a/src/Activatable/Activatable_Inactive_Validation.re +++ b/src/Activatable/Activatable_Inactive_Validation.re @@ -1,26 +1,10 @@ module F = Ley_Function; +module I = Ley_Int; module IM = Ley_IntMap; module L = Ley_List; +module O = Ley_Option; -type matchingLanguagesScripts = { - // `true` if an entry that requires to have matching languages and scripts - // is active - isEntryActiveRequiringMatch: bool, - languagesWithMatchingScripts: list(int), - scriptsWithMatchingLanguages: list(int), -}; - -type inactiveValidationCache = { - specialAbilityPairs: - Ley_IntMap.t((SpecialAbility.t, option(Hero.Activatable.t))), - combatStyleCombination: option(Hero.Activatable.t), - magicalStyleCombination: option(Hero.Activatable.t), - dunklesAbbild: option(Hero.Activatable.t), - activePactGiftsCount: int, - matchingLanguagesScripts, - validExtendedSpecialAbilities: list(int), - requiredApplyToMagicalActions: bool, -}; +open Activatable_Inactive_Cache; let getActivePactGiftsCount = specialAbilityPairs => specialAbilityPairs @@ -64,9 +48,13 @@ let isSpecialAbilitySpecificAdditionEnabled = ~rules: Hero.Rules.t, ~maybePact, { - specialAbilityPairs, combatStyleCombination, + armedCombatStylesCount, + unarmedCombatStylesCount, magicalStyleCombination, + magicalStylesCount, + isBlessedStyleActive, + isSkillStyleActive, dunklesAbbild, activePactGiftsCount, matchingLanguagesScripts, @@ -77,69 +65,39 @@ let isSpecialAbilitySpecificAdditionEnabled = [@warning "-4"] Id.SpecialAbility.( switch (fromInt(specialAbility.id), Group.fromInt(specialAbility.gr)) { - | (_, CombatStylesArmed | CombatStylesUnarmed) => + | (_, CombatStylesArmed as gr | CombatStylesUnarmed as gr) => + let equalTypeStylesActive = + switch (gr) { + | CombatStylesArmed => armedCombatStylesCount + | CombatStylesUnarmed => unarmedCombatStylesCount + | _ => 0 + }; + // Combination-SA is active, which allows 3 styles to be active, but only a // maximum of 2 from one type (armed/unarmed). if (Activatable_Accessors.isActiveM(combatStyleCombination)) { - let totalActive = - EntryGroups.SpecialAbility.countActiveFromGroups( - [CombatStylesArmed, CombatStylesUnarmed], - specialAbilityPairs, - ); - - let equalTypeStylesActive = - EntryGroups.SpecialAbility.countActiveFromGroup( - Id.SpecialAbility.Group.fromInt(specialAbility.gr), - specialAbilityPairs, - ); + let totalActive = armedCombatStylesCount + unarmedCombatStylesCount; totalActive < 3 || equalTypeStylesActive < 2; } else { - ! - EntryGroups.SpecialAbility.hasActiveFromGroup( - Id.SpecialAbility.Group.fromInt(specialAbility.gr), - specialAbilityPairs, - ); - // Otherwise, only one of each type can be active. - } + // Otherwise, only one of each type can be active. + equalTypeStylesActive === 0; + }; | (_, MagicalStyles) => - let totalActive = - EntryGroups.SpecialAbility.countActiveFromGroup( - MagicalStyles, - specialAbilityPairs, - ); + magicalStylesCount + < (Activatable_Accessors.isActiveM(magicalStyleCombination) ? 2 : 1) - totalActive - < (Activatable_Accessors.isActiveM(magicalStyleCombination) ? 2 : 1); + | (_, BlessedStyles) => !isBlessedStyleActive - | (_, BlessedStyles as group | SkillStyles as group) => - ! - EntryGroups.SpecialAbility.hasActiveFromGroup( - group, - specialAbilityPairs, - ) + | (_, SkillStyles) => !isSkillStyleActive | (CombatStyleCombination, _) => - ! - EntryGroups.SpecialAbility.hasActiveFromGroups( - [CombatStylesArmed, CombatStylesUnarmed], - specialAbilityPairs, - ) + armedCombatStylesCount + unarmedCombatStylesCount > 0 - | (MagicStyleCombination, _) => - ! - EntryGroups.SpecialAbility.hasActiveFromGroup( - MagicalStyles, - specialAbilityPairs, - ) + | (MagicStyleCombination, _) => magicalStylesCount > 0 - | (DunklesAbbildDerBuendnisgabe, _) => - ! - EntryGroups.SpecialAbility.hasActiveFromGroup( - Paktgeschenke, - specialAbilityPairs, - ) + | (DunklesAbbildDerBuendnisgabe, _) => activePactGiftsCount === 0 | (WegDerSchreiberin, _) => L.Foldable.length(matchingLanguagesScripts.languagesWithMatchingScripts) @@ -183,21 +141,6 @@ let isSpecialAbilitySpecificAdditionEnabled = } ); -let getFlatFirstPrerequisites = - fun - | Static.Advantage(staticAdvantage) => - Prerequisites.Flatten.getFirstDisAdvLevelPrerequisites( - staticAdvantage.prerequisites, - ) - | Disadvantage(staticDisadvantage) => - Prerequisites.Flatten.getFirstDisAdvLevelPrerequisites( - staticDisadvantage.prerequisites, - ) - | SpecialAbility(staticSpecialAbility) => - Prerequisites.Flatten.getFirstLevelPrerequisites( - staticSpecialAbility.prerequisites, - ); - let isEntrySpecificAdditionEnabled = (cache, staticData, hero: Hero.t, staticEntry) => ( @@ -217,7 +160,7 @@ let isEntrySpecificAdditionEnabled = staticData, hero, Id.Activatable.generalize(Activatable_Accessors.id(staticEntry)), - getFlatFirstPrerequisites(staticEntry), + Prerequisites.Activatable.getFlatFirstPrerequisites(staticEntry), ); let hasNoGenerallyRestrictingDependency = @@ -241,8 +184,8 @@ let hasNoGenerallyRestrictingDependency = ) | _ => true; -let hasNotReachedMaximumEntries = (staticEntry, maybeHeroEntry) => - switch (Activatable_Accessors.max(staticEntry), maybeHeroEntry) { +let hasNotReachedMaximumEntries = (maxLevel, maybeHeroEntry) => + switch (maxLevel, maybeHeroEntry) { // Impossible maximum level, thus no addition possible | (Some(0), _) => false | (Some(max), Some(({active, _}: Hero.Activatable.t))) => @@ -282,9 +225,33 @@ let appliesToMagicalActionsIfRequired = | SpecialAbility(_) => true }; -let isAdditionValid = (cache, staticData, hero, staticEntry, maybeHeroEntry) => +let isAdditionValid = + (cache, staticData, hero, maxLevel, staticEntry, maybeHeroEntry) => isEntrySpecificAdditionEnabled(cache, staticData, hero, staticEntry) && hasNoGenerallyRestrictingDependency(maybeHeroEntry) - && hasNotReachedMaximumEntries(staticEntry, maybeHeroEntry) + && hasNotReachedMaximumEntries(maxLevel, maybeHeroEntry) && isValidExtendedSpecialAbility(cache, staticEntry) && appliesToMagicalActionsIfRequired(cache, staticEntry); + +let getMaxLevel = (staticData, hero, staticEntry, maybeHeroEntry) => + staticEntry + |> Prerequisites.Activatable.getLevelPrerequisites + |> Dependencies.getMaxLevel( + staticData, + hero, + Id.Activatable.generalize(Activatable_Accessors.id(staticEntry)), + O.option( + [], + ({Hero.Activatable.dependencies, _}) => dependencies, + maybeHeroEntry, + ), + ) + |> ( + computedMax => + switch (computedMax, Activatable_Accessors.max(staticEntry)) { + | (Some(max1), Some(max2)) => Some(I.min(max1, max2)) + | (Some(max), None) + | (None, Some(max)) => Some(max) + | (None, None) => None + } + ); diff --git a/src/Activatable/Activatable_Inactive_Validation.rei b/src/Activatable/Activatable_Inactive_Validation.rei index 79949d55..021c1b17 100644 --- a/src/Activatable/Activatable_Inactive_Validation.rei +++ b/src/Activatable/Activatable_Inactive_Validation.rei @@ -5,30 +5,6 @@ * to be bought/added. */ -/** - * A set of values to validate `Weg der Schreiberin`. - */ -type matchingLanguagesScripts = { - isEntryActiveRequiringMatch: bool, - languagesWithMatchingScripts: list(int), - scriptsWithMatchingLanguages: list(int), -}; - -/** - * The cache of computed values to optimize performance of `isAdditionValid`. - */ -type inactiveValidationCache = { - specialAbilityPairs: - Ley_IntMap.t((SpecialAbility.t, option(Hero.Activatable.t))), - combatStyleCombination: option(Hero.Activatable.t), - magicalStyleCombination: option(Hero.Activatable.t), - dunklesAbbild: option(Hero.Activatable.t), - activePactGiftsCount: int, - matchingLanguagesScripts, - validExtendedSpecialAbilities: list(int), - requiredApplyToMagicalActions: bool, -}; - /** * `getActivePactGiftsCount pairs` returns the amount of active pact gifts based * on the given set of static/hero special ability entry pairs. @@ -37,17 +13,29 @@ let getActivePactGiftsCount: Ley_IntMap.t((SpecialAbility.t, option(Hero.Activatable.t))) => int; /** - * `isAdditionValid cache staticData hero staticEntry maybeHeroEntry` checks - * whether an activatable entry can be added/bought. The `cache` contains - * computed values that are used for multiple entries so they can be reused and - * thus improve performance for long lists of entries. + * `isAdditionValid cache staticData hero maybeMaxLevel staticEntry + * maybeHeroEntry` checks whether an activatable entry can be added/bought. The + * `cache` contains computed values that are used for multiple entries so they + * can be reused and thus improve performance for long lists of entries. */ let isAdditionValid: ( - inactiveValidationCache, + Activatable_Inactive_Cache.t, Static.t, Hero.t, + option(int), Static.activatable, option(Hero.Activatable.t) ) => bool; + +/** + * `getMaxLevel staticData staticEntry maybeHeroEntry` returns the maximum level based on the level prerequisites (excluding first level prerequisites) where the key is the level and the value the prerequisites that need to be met for the respective level as well as based on the registered `dependencies` of the entry. + * + * The return value is based on prerequisites *and* entry dependencies. To get + * the max level only based on prerequisites, use + * `Prerequisites.Validation.getMaxLevel`. + */ +let getMaxLevel: + (Static.t, Hero.t, Static.activatable, option(Hero.Activatable.t)) => + option(int); diff --git a/src/Data/Ley_Option.bs.js b/src/Data/Ley_Option.bs.js index 21b44f66..597274e9 100644 --- a/src/Data/Ley_Option.bs.js +++ b/src/Data/Ley_Option.bs.js @@ -17,7 +17,7 @@ function $less$amp$great(mx, f) { return $less$$great(f, mx); } -function $less$star$great(mf, mx) { +function $less$star$great(mx, mf) { if (mf !== undefined) { return $less$$great(mf, mx); } diff --git a/src/Data/Ley_Option.re b/src/Data/Ley_Option.re index 0f5f44b4..3e1d3c41 100644 --- a/src/Data/Ley_Option.re +++ b/src/Data/Ley_Option.re @@ -17,7 +17,7 @@ module Functor = { module Applicative = { open Functor; - let (<*>) = (mf, mx) => + let (<*>) = (mx, mf) => switch (mf) { | Some(f) => f <$> mx | None => None diff --git a/src/Data/Ley_Option.rei b/src/Data/Ley_Option.rei index f699b742..013faff4 100644 --- a/src/Data/Ley_Option.rei +++ b/src/Data/Ley_Option.rei @@ -13,7 +13,7 @@ module Applicative: { /** * Apply a wrapped function to a wrapped value. */ - let (<*>): (t('a => 'b), t('a)) => t('b); + let (<*>): (t('a), t('a => 'b)) => t('b); }; module Alternative: { diff --git a/src/Prerequisites/Dependencies.bs.js b/src/Prerequisites/Dependencies.bs.js index 2d340c6d..d24f2f80 100644 --- a/src/Prerequisites/Dependencies.bs.js +++ b/src/Prerequisites/Dependencies.bs.js @@ -5,12 +5,14 @@ import * as Caml_obj from "bs-platform/lib/es6/caml_obj.js"; import * as Pervasives from "bs-platform/lib/es6/pervasives.js"; import * as Id$OptolithClient from "../Misc/Id.bs.js"; import * as Hero$OptolithClient from "../Misc/Hero.bs.js"; +import * as Ley_Int$OptolithClient from "../Data/Ley_Int.bs.js"; import * as Ley_Bool$OptolithClient from "../Data/Ley_Bool.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_Option$OptolithClient from "../Data/Ley_Option.bs.js"; 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 flattenSkillDependencies(getValueForTargetId, id, dependencies) { @@ -990,6 +992,27 @@ function removeDependencies(param, param$1, param$2, param$3) { return modifyDependencies(/* Remove */1, param, param$1, param$2, param$3); } +function getMaxLevel(staticData, hero, sourceId, dependencies, prerequisites) { + return Ley_Function$OptolithClient.flip((function (param, param$1) { + return Ley_List$OptolithClient.Foldable.foldl((function (prevMax, param) { + var level = param.level; + if (param.active) { + return prevMax; + } else if (prevMax !== undefined) { + if (level !== undefined) { + return Ley_Int$OptolithClient.min(prevMax, level - 1 | 0); + } else { + return prevMax; + } + } else if (level !== undefined) { + return level - 1 | 0; + } else { + return ; + } + }), param, param$1); + }), dependencies, Prerequisites$OptolithClient.Validation.getMaxLevel(staticData, hero, sourceId, prerequisites)); +} + var TransferredUnfamiliar = { isUnfamiliarSpell: isUnfamiliarSpell, addTransferUnfamiliarDependencies: addTransferUnfamiliarDependencies, @@ -1001,6 +1024,7 @@ export { TransferredUnfamiliar , addDependencies , removeDependencies , + getMaxLevel , } /* Tradition-OptolithClient Not a pure module */ diff --git a/src/Prerequisites/Dependencies.re b/src/Prerequisites/Dependencies.re index 4bbf0660..bdf98b87 100644 --- a/src/Prerequisites/Dependencies.re +++ b/src/Prerequisites/Dependencies.re @@ -1,3 +1,5 @@ +module F = Ley_Function; +module I = Ley_Int; module L = Ley_List; module O = Ley_Option; open O.Monad; @@ -1142,3 +1144,35 @@ let addDependencies = modifyDependencies(Add); * Removes dependencies from all required entries to ensure rule validity. */ let removeDependencies = modifyDependencies(Remove); + +/** + * Return the max level based on prerequisites and dependencies. + */ +let getMaxLevel = (staticData, hero, sourceId, dependencies, prerequisites) => + Prerequisites.Validation.getMaxLevel( + staticData, + hero, + sourceId, + prerequisites, + ) + |> F.flip( + L.Foldable.foldl((prevMax, {Hero.Activatable.active, level, _}) => + switch (active, prevMax, level) { + // active must be always false because it needs to *prohibit* a certain + // level + // + // also, if there is both a previous max and an prohibited level in the + // dependency, take the minimum value + // + // the prohibited level is reduced by 1 since this level should not be + // reached, thus the maximum level must be lower + | (false, Some(prev), Some(notAllowed)) => + Some(I.min(prev, notAllowed - 1)) + | (false, Some(prev), None) => Some(prev) + | (false, None, Some(notAllowed)) => Some(notAllowed - 1) + | (false, None, None) => None + | (true, _, _) => prevMax + } + ), + dependencies, + ); diff --git a/src/Prerequisites/Dependencies.rei b/src/Prerequisites/Dependencies.rei index 38a31157..51cb86b5 100644 --- a/src/Prerequisites/Dependencies.rei +++ b/src/Prerequisites/Dependencies.rei @@ -97,3 +97,24 @@ let removeDependencies: Hero.t ) => Hero.t; + +/** + * `getMaxLevel staticData hero sourceId dependencies levelPrerequisites` + * returns the maximum level based on the map of prerequisites + * `levelPrerequisites` where the key is the level and the value the + * prerequisites that need to be met for the respective level as well as based + * on the registered `dependencies` of the entry. + * + * The return value is based on prerequisites *and* entry dependencies. To get + * the max level only based on prerequisites, use + * `Prerequisites.Validation.getMaxLevel`. + */ +let getMaxLevel: + ( + Static.t, + Hero.t, + Id.t, + list(Hero.Activatable.dependency), + Ley_IntMap.t(Prerequisite.t) + ) => + option(int); diff --git a/src/Prerequisites/Prerequisites.bs.js b/src/Prerequisites/Prerequisites.bs.js index 980d082e..51f35e11 100644 --- a/src/Prerequisites/Prerequisites.bs.js +++ b/src/Prerequisites/Prerequisites.bs.js @@ -912,6 +912,46 @@ function arePrerequisitesMet(staticData, hero, sourceId, prerequisites) { }), prerequisites); } +function getMaxLevel(staticData, hero, sourceId, prerequisites) { + return Curry._3(Ley_IntMap$OptolithClient.foldlWithKey, (function (max, level, prerequisites) { + if (max !== undefined && max <= level) { + return max; + } + if (arePrerequisitesMet(staticData, hero, sourceId, flattenPrerequisites(prerequisites, /* [] */0))) { + return max; + } else { + return level; + } + }), undefined, prerequisites); +} + +function getFlatFirstPrerequisites(staticAdvantage) { + switch (staticAdvantage.TAG | 0) { + case /* Advantage */0 : + case /* Disadvantage */1 : + return getFirstDisAdvLevelPrerequisites(staticAdvantage._0.prerequisites); + case /* SpecialAbility */2 : + return getFirstLevelPrerequisites(staticAdvantage._0.prerequisites); + + } +} + +function getLevelPrerequisites(staticAdvantage) { + switch (staticAdvantage.TAG | 0) { + case /* Advantage */0 : + case /* Disadvantage */1 : + return staticAdvantage._0.prerequisites.levels; + case /* SpecialAbility */2 : + return staticAdvantage._0.prerequisites.levels; + + } +} + +var Activatable = { + getFlatFirstPrerequisites: getFlatFirstPrerequisites, + getLevelPrerequisites: getLevelPrerequisites +}; + var Flatten = { flattenPrerequisites: flattenPrerequisites, getFirstLevelPrerequisites: getFirstLevelPrerequisites, @@ -925,13 +965,15 @@ var Dynamic = { var Validation = { isPrerequisiteMet: isPrerequisiteMet, - arePrerequisitesMet: arePrerequisitesMet + arePrerequisitesMet: arePrerequisitesMet, + getMaxLevel: getMaxLevel }; export { Flatten , Dynamic , Validation , + Activatable , } /* Tradition-OptolithClient Not a pure module */ diff --git a/src/Prerequisites/Prerequisites.re b/src/Prerequisites/Prerequisites.re index 38e7b76e..80711a05 100644 --- a/src/Prerequisites/Prerequisites.re +++ b/src/Prerequisites/Prerequisites.re @@ -1,5 +1,6 @@ open Prerequisite; +module IM = Ley_IntMap; module O = Ley_Option; type prerequisite = @@ -769,6 +770,20 @@ module Validation = { isPrerequisiteMet(staticData, hero, sourceId), prerequisites, ); + + let getMaxLevel = (staticData, hero, sourceId, prerequisites) => + IM.foldlWithKey( + (max, level, prerequisites) => + switch (max) { + | Some(max) when max <= level => Some(max) + | max => + Flatten.flattenPrerequisites(prerequisites, []) + |> arePrerequisitesMet(staticData, hero, sourceId) + ? max : Some(level) + }, + None, + prerequisites, + ); }; // @@ -786,84 +801,6 @@ module Validation = { // ? set (ri_id) (id) (req) // : req // -// -// /** -// * Returns if the current index can be skipped because there is already a lower -// * level which prerequisites are not met. -// * -// * This is for performance reasons to not check the prerequisites of higher levels. -// */ -// const skipLevelCheck = -// (current_req: Pair>) => -// (mmax: Maybe) => -// isJust (mmax) && pipe_ (current_req, fst, gt (fromJust (mmax))) -// -// /** -// * Get maximum valid level. -// * @param state The current hero data. -// * @param requirements A Map of tier prereqisite arrays. -// * @param sourceId The id of the entry the requirement objects belong to. -// */ -// export const validateLevel = -// (wiki: StaticDataRecord) => -// (state: HeroModelRecord) => -// (requirements: OrderedMap>) => -// (dependencies: List) => -// (sourceId: string): Maybe => -// pipe_ ( -// requirements, -// toList, -// sortBy (on (compare) (fst)), -// -// // first check the prerequisites: -// foldl ((max: Maybe) => (entry: Pair>) => -// -// // if `max` is lower than the current level (from `entry`), just -// // skip the prerequisite validation -// !skipLevelCheck (entry) (max) -// -// // otherwise, validate them -// && !validatePrerequisites (wiki) (state) (snd (entry)) (sourceId) -// -// // if *not* valid, set the max to be lower than the actual -// // current level (because it must not be reached) -// ? Just (fst (entry) - 1) -// -// // otherwise, just pass the previous max value -// : max) -// (Nothing), -// -// // then, check the dependencies -// flip (foldl ((max: Maybe) => (dep: ActivatableDependency) => -// -// // If `dep` prohibits higher level: -// // - it can only be contained in a record -// Record.isRecord (dep) -// -// // - and it must be *prohibited*, so `active` must be `false` -// && Maybe.elem (false) (DOA.active (dep)) -// ? pipe_ ( -// dep, -// -// // get the current prohibited level -// DOA.tier, -// -// // - if its a Nothing, do nothing -// maybe (max) -// -// // - otherwise decrease the level by one (the -// // actual level must not be reached) and then -// // take the lower one, if both the current and -// // the previous are Justs, otherwise the -// // current. -// (pipe (dec, level => Just (maybe (level) (min (level)) (max)))) -// ) -// -// // otherwise, dont do anything -// : max)) -// (dependencies) -// ) -// // /** // * Checks if all profession prerequisites are fulfilled. // * @param prerequisites An array of prerequisite objects. @@ -882,3 +819,25 @@ module Validation = { // ? isCultureValid (current_culture_id) (req) // : false) // (prerequisites) + +module Activatable = { + let getFlatFirstPrerequisites = + fun + | Static.Advantage(staticAdvantage) => + Flatten.getFirstDisAdvLevelPrerequisites(staticAdvantage.prerequisites) + | Disadvantage(staticDisadvantage) => + Flatten.getFirstDisAdvLevelPrerequisites( + staticDisadvantage.prerequisites, + ) + | SpecialAbility(staticSpecialAbility) => + Flatten.getFirstLevelPrerequisites(staticSpecialAbility.prerequisites); + + let getLevelPrerequisites = + fun + | Static.Advantage(staticAdvantage) => + staticAdvantage.prerequisites.levels + | Disadvantage(staticDisadvantage) => + staticDisadvantage.prerequisites.levels + | SpecialAbility(staticSpecialAbility) => + staticSpecialAbility.prerequisites.levels; +}; diff --git a/src/Prerequisites/Prerequisites.rei b/src/Prerequisites/Prerequisites.rei index 698f4281..bbaf6bd5 100644 --- a/src/Prerequisites/Prerequisites.rei +++ b/src/Prerequisites/Prerequisites.rei @@ -80,4 +80,24 @@ module Validation: { */ let arePrerequisitesMet: (Static.t, Hero.t, Id.t, list(prerequisite)) => bool; + + /** + * `getMaxLevel staticData hero sourceId levelPrerequisites` returns the + * maximum level based on the map of prerequisites `levelPrerequisites` where + * the key is the level and the value the prerequisites that need to be met + * for the respective level. + * + * The return value is purely based on prerequisites, entry dependencies are + * not taken into account. To get the max level based on prerequisites *and* + * dependencies, use `Dependencies.getMaxLevel`. + */ + let getMaxLevel: + (Static.t, Hero.t, Id.t, Ley_IntMap.t(Prerequisite.t)) => option(int); +}; + +module Activatable: { + let getFlatFirstPrerequisites: Static.activatable => list(prerequisite); + + let getLevelPrerequisites: + Static.activatable => Ley_IntMap.t(Prerequisite.t); };