Finished active validation

This commit is contained in:
Lukas Obermann
2020-07-14 01:39:50 +02:00
parent 4492304173
commit 3348c08808
39 changed files with 1311 additions and 1950 deletions
+50
View File
@@ -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,
// };
};
+26
View File
@@ -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)
// ) =>
@@ -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));
})));
}));
}));
@@ -394,7 +394,7 @@ let getEntrySpecificCost =
>>= (
fst =>
fst
|> singleToSingleWithId(heroEntry)
|> singleToSingleWithId(heroEntry, 0)
|> getCostFromHeroEntry
),
);
@@ -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 */
+410 -571
View File
@@ -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<number>) => (max_level: Maybe<number>): boolean =>
// isJust (max_level) && isJust (min_level)
//
// const isRequiredByOthers =
// (current_active: Record<ActiveObjectWithId>) =>
// (state_entry: Record<ActivatableDependent>): boolean =>
// pipe (
// ADA.dependencies,
// any (
// ifElse<ActivatableDependency, boolean>
// (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<number>, List<number>]>) =>
// (wiki_entry: Activatable) =>
// (hero_entry: Record<ActivatableDependent>) =>
//
// // tslint:disable-next-line: cyclomatic-complexity
// (active: Record<ActiveObjectWithId>): 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<ActivatableSkillDependent>) =>
// 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<string | number> (aspc_id) (all_aspcs)
//
// return OrderedMap.any ((chant: Record<ActivatableSkillDependent>) =>
// 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<Record<ActivatableSkillDependent>> (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<ActivatableDependent>) =>
// (active: Record<ActiveObjectWithId>) =>
//
// // 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<EntryWithCategory, Activatable>
// (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<string>))),
// 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<number> =
//
// // 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<ActiveObjectWithId>): Maybe<number> => {
// 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<number> => {
// 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<number>
//
// const adjustMinimumLevelByDependencies =
// (entry: Record<ActiveObjectWithId>) =>
// flip (foldl ((min_level: Maybe<number>): (dep: ActivatableDependency) => Maybe<number> =>
// 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)
// <MinLevelDepSid>
// (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<ActiveObjectWithId>) =>
// (entry_dependencies: List<ActivatableDependency>): Maybe<number> =>
// adjustMinimumLevelByDependencies (entry)
// (entry_dependencies)
// (getEntrySpecificMinimumLevel (wiki)
// (hero)
// (entry))
//
// const minMaybe: (mx: Maybe<number>) => (my: Maybe<number>) => Maybe<number> =
// 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<ActivatableDependency>) =>
// (entry_id: string): Maybe<number> => {
// 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<number>, List<number>]>) =>
// (entry: Record<ActiveObjectWithId>): Maybe<Record<ActivatableActivationValidation>> =>
// pipe (
// getWikiEntry (wiki),
// bindF<EntryWithCategory, Activatable> (ensure (isActivatableWikiEntry)),
// bindF (
// wiki_entry =>
// pipe_ (
// entry,
// AOWIA.id,
// getHeroStateItem (hero),
// bindF<Dependent, Record<ActivatableDependent>>
// (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,
};
};
@@ -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;
@@ -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,
+14 -9
View File
@@ -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 */
+13 -12
View File
@@ -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.
+4 -1
View File
@@ -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;
+4 -4
View File
@@ -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) =>
+5 -5
View File
@@ -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;
@@ -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
@@ -20,7 +20,7 @@ let getActivePactGiftsCount:
*/
let isAdditionValid:
(
Activatable_Inactive_Cache.t,
Activatable_Cache.t,
Static.t,
Hero.t,
option(int),
@@ -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 ,
+14 -7
View File
@@ -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.
@@ -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.
*/
@@ -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<string | number>> (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<string | number>> (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<string | number | List<number>>> (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<Record<ActivatableDependent>>) => {
const isNoActiveSelection = isNotActive (mhero_entry)
const isNoRequiredSelection = isNotRequired (mhero_entry)
return (e: Record<SelectOption>) =>
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<LiturgicalChant>) =>
flip (foldr (flip <number, number, (d: IntMap.t<number>) => IntMap.t<number>>
(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<number, number, List<number>> (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<number, List<number>> (
(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<string | number, List<string | number>>) =>
(curr_select_id: string | number) =>
pipe_ (
curr_select_id,
ensure (isString),
bindF (lookupF (HA.skills (hero))),
liftM2 ((apps: List<string | number>) =>
(skill: Record<SkillDependent>) =>
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<Record<ActivatableDependent>>) =>
(wiki_entry: Activatable) =>
(mhero_entry: Maybe<Record<ActivatableDependent>>): ident<Maybe<List<Record<SelectOption>>>> => {
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<number> ())
(pipe (
getActiveSelections,
filter (isNumber),
nub
))
(mhero_entry)
const isInfiniteActive: (id: number) => (x: Record<SelectOption>) => boolean =
id => x => SOA.id (x) === id && elem (id) (unique_selections)
const isPrejudiceAndActive = isInfiniteActive (7)
const isUnworldlyAndActive = isInfiniteActive (8)
const isNotActiveAndMaxNotReached: (x: Record<SelectOption>) => 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<number | string>
(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<string | number> (getPropsWith3Gte10 (staticData) (hero))))
return fmap (filterMapListT (composeT (
isNoRequiredOrActiveSelection,
isValidProperty
)))
}
case SpecialAbilityId.propertyFocus: {
const isActivePropertyKnowledge =
filterT (notP (pipe_ (
hero,
HA.specialAbilities,
lookup<string> (SpecialAbilityId.propertyKnowledge),
isNotActive
)))
return fmap (filterMapListT (composeT (
isNoRequiredOrActiveSelection,
isActivePropertyKnowledge
)))
}
case SpecialAbilityId.aspectKnowledge: {
const valid_aspects = getAspectsWith3Gte10 (staticData) (hero)
const isAspectValid = pipe (SOA.id, elemF<string | number> (valid_aspects))
return maybe (ident as ident<Maybe<List<Record<SelectOption>>>>)
((blessed_trad: Record<ActivatableDependent>) =>
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<string>) => Maybe<Record<Spell> | Record<LiturgicalChant>>) =
current_id === prefixSA (SpecialAbilityId.spellEnhancement)
? bindF (lookupF (SDA.spells (staticData)))
: bindF (lookupF (SDA.liturgicalChants (staticData)))
const isNotUnfamiliar =
(x: Record<Spell> | Record<LiturgicalChant>) =>
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<Record<SelectOption>>>
}))
(List ()))
}
case SpecialAbilityId.languageSpecializations: {
return pipe_ (
staticData,
SDA.specialAbilities,
lookup<string> (SpecialAbilityId.language),
bindF (AAL.select),
maybe (cnst (Nothing) as ident<Maybe<List<Record<SelectOption>>>>)
(current_select => {
const available_langs =
// Pair: fst = sid, snd = current_level
maybe (List<Pair<number, number>> ())
(pipe (
ADA.active,
foldr ((obj: Record<ActiveObject>) =>
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<Pair<number, number>>>
)
))
(List ())
))
(pipe_ (
hero,
HA.specialAbilities,
lookup<string> (SpecialAbilityId.language)
))
const filterLanguages =
foldr (isNoRequiredOrActiveSelection
(e => {
const lang =
find ((l: Pair<number, number>) =>
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<Record<SelectOption>>>
}))
(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<Record<InactiveActivatable>>
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<AdventurePointsCategories>) =>
(wiki_entry: Activatable) =>
(mhero_entry: Maybe<Record<ActivatableDependent>>): Maybe<OtherOptionsModifier> => {
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<SkillDependent>) =>
(metalworking: Record<SkillDependent>) =>
SkDA.value (woodworking) + SkDA.value (metalworking) >= 12
? Just (ident)
: Nothing
)
(pipe (HA.skills, lookup<string> (SkillId.woodworking)) (hero))
(pipe (HA.skills, lookup<string> (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<number | List<number>, List<number>> (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<string> (SpecialAbilityId.wissensdurst),
fmap (ADA.active),
bindF (listToMaybe),
bindF (AOA.sid),
misStringM,
bindF (lookupF (SDA.skills (wiki))),
bindF (skill => pipe (
bindF<number | List<number>, List<number>> (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<string>) =>
(required_apply_to_mag_actions: boolean) =>
(matching_script_and_lang_related: Tuple<[boolean, List<number>, List<number>]>) =>
(adventure_points: Record<AdventurePointsCategories>) =>
(validExtendedSpecialAbilities: List<string>) =>
(hero_magical_traditions: List<Record<ActivatableDependent>>) =>
(wiki_entry: Activatable) =>
(mhero_entry: Maybe<Record<ActivatableDependent>>): Maybe<Record<InactiveActivatable>> => {
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<ActivatableDependent["dependencies"]> (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<RecordI<Activatable>>,
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
}
@@ -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<SpecialAbility>): boolean => {
const combination_hero_entry = lookup<string> (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<number>, List<number>]>) =>
(wiki_entry: Record<SpecialAbility>): 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<string> (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<string> (SpecialAbilityId.DunklesAbbildDerBuendnisgabe)
(HA.specialAbilities (hero))
const allPactPresents = getAllEntriesByGroup (SDA.specialAbilities (staticData))
(HA.specialAbilities (hero))
(SpecialAbilityGroup.Paktgeschenke)
const countPactPresents =
foldr ((obj: Record<ActivatableDependent>) => {
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<number>
})
(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<number>, List<number>]>) =>
(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<ActivatableDependency> (false)))
const hasReachedMaximumEntries =
(wiki_entry: Activatable) =>
(mhero_entry: Maybe<Record<ActivatableDependent>>) =>
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<string>) =>
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<string>) =>
(matching_script_and_lang_related: Tuple<[boolean, List<number>, List<number>]>) =>
(wiki_entry: Activatable) =>
(mhero_entry: Maybe<Record<ActivatableDependent>>) =>
(max_level: Maybe<number>): 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)
+12
View File
@@ -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 */
+17
View File
@@ -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
}
);
+5
View File
@@ -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;
+3
View File
@@ -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 ,
+15
View File
@@ -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,
+52 -13
View File
@@ -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 ,
+21
View File
@@ -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
+12
View File
@@ -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
+10
View File
@@ -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,
+41 -46
View File
@@ -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)
);
};
+12
View File
@@ -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.
+38 -18
View File
@@ -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 */
+58 -40
View File
@@ -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) {
+19
View File
@@ -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;
};