Finished active validation
This commit is contained in:
@@ -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,
|
||||
// };
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
// ) =>
|
||||
|
||||
+1
-1
@@ -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));
|
||||
})));
|
||||
}));
|
||||
}));
|
||||
+1
-1
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
+3
-1
@@ -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,
|
||||
@@ -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 */
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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 ,
|
||||
|
||||
@@ -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)
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ,
|
||||
|
||||
@@ -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
@@ -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 ,
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user