172 lines
8.6 KiB
JavaScript
172 lines
8.6 KiB
JavaScript
import sortBy from 'lodash/sortBy';
|
|
import { characterActions } from "../actions";
|
|
import { ChoiceUtils } from "../engine/Choice";
|
|
import { DisplayConfigurationTypeEnum } from "../engine/Core";
|
|
import { DataOriginTypeEnum } from "../engine/DataOrigin";
|
|
import { DefinitionHacks } from "../engine/Definition";
|
|
import { FeatAccessors, FeatUtils } from "../engine/Feat";
|
|
import { PrerequisiteUtils, PrerequisiteValidators } from "../engine/Prerequisite";
|
|
import { RuleDataUtils } from "../engine/RuleData";
|
|
import { SourceUtils } from "../engine/Source";
|
|
import { rulesEngineSelectors } from "../selectors";
|
|
import { BaseManager } from './BaseManager';
|
|
export const featDefinitionMap = new Map();
|
|
const featMangerMap = new Map();
|
|
export const getFeatManager = (params) => {
|
|
const { feat } = params;
|
|
const featId = FeatAccessors.getId(feat);
|
|
if (featMangerMap.has(featId)) {
|
|
const FeatManager = featMangerMap.get(featId);
|
|
if (!FeatManager) {
|
|
throw new Error(`FeatManager for feat ${featId} is null`);
|
|
}
|
|
if (FeatManager.params.feat === feat) {
|
|
return FeatManager;
|
|
}
|
|
}
|
|
const newFeatManager = new FeatManager(params);
|
|
featMangerMap.set(featId, newFeatManager);
|
|
return newFeatManager;
|
|
};
|
|
export class FeatManager extends BaseManager {
|
|
constructor(params) {
|
|
super(params);
|
|
this.handleAcceptOnSuccess = (onSuccess) => {
|
|
return () => {
|
|
typeof onSuccess === 'function' && onSuccess();
|
|
};
|
|
};
|
|
this.handleRejectOnError = (onError) => {
|
|
return () => {
|
|
typeof onError === 'function' && onError();
|
|
};
|
|
};
|
|
this.handleAdd = (onSuccess, onError) => {
|
|
return this.dispatch(characterActions.adhocFeatCreate(this.getId()));
|
|
};
|
|
this.handleRemove = (onSuccess, onError) => {
|
|
featMangerMap.delete(this.getId());
|
|
return this.dispatch(characterActions.adhocFeatRemove(this.getId()));
|
|
};
|
|
//Accessors
|
|
this.getId = () => FeatAccessors.getId(this.feat);
|
|
this.getEntityTypeId = () => FeatAccessors.getEntityTypeId(this.feat);
|
|
this.getName = () => FeatAccessors.getName(this.feat);
|
|
this.getDataOrigin = () => FeatAccessors.getDataOrigin(this.feat);
|
|
this.getDataOriginType = () => FeatAccessors.getDataOriginType(this.feat);
|
|
this.getActions = () => FeatAccessors.getActions(this.feat);
|
|
this.getChoices = () => FeatAccessors.getChoices(this.feat);
|
|
this.getOptions = () => FeatAccessors.getOptions(this.feat);
|
|
this.getSpells = () => FeatAccessors.getSpells(this.feat);
|
|
this.getPrerequisites = () => FeatAccessors.getPrerequisites(this.feat);
|
|
this.getPrerequisiteDescription = () => FeatAccessors.getPrerequisiteDescription(this.feat);
|
|
this.getDescription = () => FeatAccessors.getDescription(this.feat);
|
|
this.getSources = () => FeatAccessors.getSources(this.feat);
|
|
this.getSnippet = () => FeatAccessors.getSnippet(this.feat);
|
|
this.isRepeatable = () => FeatAccessors.isRepeatable(this.feat);
|
|
this.getRepeatableParentId = () => FeatAccessors.getRepeatableParentId(this.feat);
|
|
this.isHomebrew = () => FeatAccessors.isHomebrew(this.feat);
|
|
this.getDefinition = () => FeatAccessors.getDefinition(this.feat);
|
|
// Possible TODO for the future: We may add data to Entity Tags to distinguish
|
|
// user-facing categories from technical-only tags.
|
|
this.getCategories = () => FeatAccessors.getCategories(this.feat);
|
|
//Utils
|
|
//Gets all the prereq Failures for this feat - returns an array of arrays, where each inner array is a grouping of failures
|
|
this.getPrerequisiteFailures = () => {
|
|
const prerequisiteData = rulesEngineSelectors.getPrerequisiteData(this.state);
|
|
return PrerequisiteUtils.getPrerequisiteGroupingFailures(this.getPrerequisites(), prerequisiteData);
|
|
};
|
|
// Creates a string that summarizes the prerequisite failures for this feat
|
|
this.getPrerequisiteFailuresText = () => {
|
|
//get all the prerequisite failures, sorted so that if every group failure is a missing prereq, they come first
|
|
const failures = sortBy(this.getPrerequisiteFailures(), (failureGroup) => failureGroup.every((f) => !f.shouldExclude) ? 0 : 1);
|
|
// Create an intro string for the summary
|
|
let intro = '';
|
|
// Check if all failures are either missing or prohibited
|
|
const hasOnlyMissingFailures = failures.every((failureGroup) => failureGroup.every((f) => !f.shouldExclude));
|
|
const hasOnlyProhibitedFailures = failures.every((failureGroup) => failureGroup.every((f) => f.shouldExclude));
|
|
// Determine the intro text based on the type of failures
|
|
if (hasOnlyMissingFailures) {
|
|
intro = 'Missing: ';
|
|
}
|
|
else if (hasOnlyProhibitedFailures) {
|
|
intro = 'Prohibits: ';
|
|
}
|
|
else {
|
|
intro = 'Requires: ';
|
|
}
|
|
// Each failure is sorted so that prohibited failures come last, and then combined into a single string
|
|
const combinedStrings = failures
|
|
.map((failure) => sortBy(failure, (f) => (f.shouldExclude ? 1 : 0))
|
|
.map((failureAnd) => {
|
|
if (failureAnd.shouldExclude && !hasOnlyProhibitedFailures && !hasOnlyMissingFailures) {
|
|
return `Prohibits ${failureAnd.data.requiredDescription}`;
|
|
}
|
|
return `${failureAnd.data.requiredDescription}`;
|
|
})
|
|
.reduce((acc, desc, idx) => {
|
|
let connector = '';
|
|
if (idx < failure.length - 2) {
|
|
connector = ', ';
|
|
}
|
|
else if (idx < failure.length - 1) {
|
|
connector = ' and ';
|
|
}
|
|
acc += `${desc}${connector}`;
|
|
return acc;
|
|
}, ''))
|
|
.join(' or ');
|
|
// return the intro text followed by the combined strings
|
|
return `${intro}${combinedStrings}`;
|
|
};
|
|
this.getRepeatableGroupId = () => FeatUtils.getRepeatableGroupId(this.feat);
|
|
this.isRepeatableFeatParent = () => this.isRepeatable() && this.getRepeatableParentId() === null;
|
|
this.getDefinitionKey = () => {
|
|
return DefinitionHacks.hack__generateDefinitionKey(this.getEntityTypeId(), this.getId());
|
|
};
|
|
this.getHelperText = () => {
|
|
return RuleDataUtils.getBuilderHelperTextByDefinitionKeys([this.getDefinitionKey()], rulesEngineSelectors.getRuleData(this.state), DisplayConfigurationTypeEnum.FEAT);
|
|
};
|
|
//validate that a feat meets its prereqs
|
|
this.canAdd = () => {
|
|
const prerequisiteData = rulesEngineSelectors.getPrerequisiteData(this.state);
|
|
const prereqs = this.getPrerequisites();
|
|
return PrerequisiteValidators.validatePrerequisiteGrouping(prereqs, prerequisiteData);
|
|
};
|
|
this.isHiddenFeat = () => FeatUtils.isHiddenFeat(this.feat);
|
|
this.isSimulatedRepeatableFeat = () => {
|
|
return this.isRepeatable() && this.getDataOriginType() === DataOriginTypeEnum.SIMULATED;
|
|
};
|
|
this.getPrimarySourceName = () => this.isHomebrew()
|
|
? 'Homebrew'
|
|
: SourceUtils.getSourceFullNames(this.getSources(), rulesEngineSelectors.getRuleData(this.state))[0];
|
|
this.getSourceCategory = () => {
|
|
var _a;
|
|
const ruleData = rulesEngineSelectors.getRuleData(this.state);
|
|
const sources = this.getSources();
|
|
if (sources.length === 0)
|
|
return null;
|
|
const sourceData = RuleDataUtils.getSourceDataInfo(sources[0].sourceId, ruleData);
|
|
return (_a = sourceData === null || sourceData === void 0 ? void 0 : sourceData.sourceCategory) !== null && _a !== void 0 ? _a : null;
|
|
};
|
|
this.params = params;
|
|
this.feat = params.feat;
|
|
}
|
|
static getFeatManager(id) {
|
|
const featManager = featMangerMap.get(id);
|
|
if (!featManager) {
|
|
throw new Error(`featManager for feat ${id} is null`);
|
|
}
|
|
return featManager;
|
|
}
|
|
handleChoiceChange(featId, choiceType, choiceId, optionValue) {
|
|
this.dispatch(characterActions.featChoiceSetRequest(featId, choiceType, choiceId, optionValue));
|
|
}
|
|
hasUnfinishedChoices() {
|
|
return this.getUnfinishedChoices().length > 0;
|
|
}
|
|
getUnfinishedChoices() {
|
|
return this.getChoices().filter((choice) => ChoiceUtils.isTodo(choice));
|
|
}
|
|
}
|