diff --git a/src/library/CharacterManifestData.js b/src/library/CharacterManifestData.js index ecef2a53..dd216d55 100644 --- a/src/library/CharacterManifestData.js +++ b/src/library/CharacterManifestData.js @@ -1,4 +1,9 @@ import { getAsArray } from "./utils"; +import { ManifestRestrictions } from "./manifestRestrictions"; + +/** + * @typedef {import('./manifestRestrictions').TraitRestriction} TraitRestriction + */ /** * @typedef {Object} TextureCollectionItem @@ -33,7 +38,7 @@ export class CharacterManifestData{ colliderTraits, lipSyncTraits, blinkerTraits, - typeRestrictions, + traitRestrictions, defaultCullingLayer, defaultCullingDistance, offset, @@ -59,7 +64,7 @@ export class CharacterManifestData{ this.colliderTraits = getAsArray(colliderTraits); this.lipSyncTraits = getAsArray(lipSyncTraits); this.blinkerTraits = getAsArray(blinkerTraits); - this.typeRestrictions = typeRestrictions; + this.traitRestrictions = traitRestrictions; this.defaultCullingLayer = defaultCullingLayer this.defaultCullingDistance = defaultCullingDistance this.offset = offset; @@ -77,23 +82,6 @@ export class CharacterManifestData{ getAllTraitsGroupID(); - const populateTypeRestrictions = () =>{ - if (this.typeRestrictions){ - for (const prop in this.typeRestrictions){ - const typeRestrictionValues = getAsArray(this.typeRestrictions[prop]); - typeRestrictionValues.forEach(tr => { - if (this.typeRestrictions[tr] == null){ - this.typeRestrictions[tr] = []; - } - if (this.typeRestrictions[tr].indexOf(prop) == -1){ - this.typeRestrictions[tr].push(prop); - } - }); - } - } - } - populateTypeRestrictions(); - const defaultOptions = () =>{ // Support Old configuration downloadOptions.vrmMeta = downloadOptions.vrmMeta || vrmMeta; @@ -117,6 +105,7 @@ export class CharacterManifestData{ } defaultOptions(); + this.manifestRestrictions = new ManifestRestrictions(this); // create texture and color traits first this.textureTraits = []; @@ -135,6 +124,8 @@ export class CharacterManifestData{ this.modelTraits = []; this.modelTraitsMap = null; this.createModelTraits(traits); + + this.manifestRestrictions._init() } appendManifestData(manifestData, replaceExisting){ manifestData.textureTraits.forEach(newTextureTraitGroup => { @@ -196,6 +187,28 @@ export class CharacterManifestData{ getAllTraits(){ return this.getRandomTraits(this.allTraits); } + + /** + * Assumes the trait options have unique IDs; + * @param {string} optionID + */ + getTraitOptionById(optionID){ + return this.getAllTraitOptions().find((option)=>option.id == optionID); + } + /** + * Get trait options by type; + * @param {string} type + */ + getTraitOptionsByType(type){ + return this.getAllTraitOptions().filter((option)=>option.type == type); + } + /** + * Returns all ModelTrait items in an array. + */ + getAllTraitOptions(){ + return this.modelTraits.map((trait)=>trait?.getCollection()).flat(); + } + getAllBlendShapeTraits(){ return this.modelTraits.map(traitGroup => traitGroup.getCollection()).flat().map((c)=>c.blendshapeTraits).flat().map((c)=>c?.collection).flat().filter((c)=>!!c); } @@ -424,13 +437,7 @@ export class CharacterManifestData{ // Updates all restricted traits for each group models this.modelTraits.forEach(modelTrait => { - modelTrait.restrictedTraits.forEach(groupTraitID => { - const groupModel = this.getModelGroup(groupTraitID); - console.log(groupModel); - if (groupModel){ - groupModel.addTraitRestriction(modelTrait.trait); - } - }); + this.manifestRestrictions.createTraitRestriction(modelTrait); }); } @@ -481,6 +488,11 @@ export class TraitModelsGroup{ */ manifestData + /** + * @type {TraitRestriction|undefined} + */ + restrictions + constructor(manifestData, options){ const { trait, @@ -490,8 +502,6 @@ export class TraitModelsGroup{ cullingDistance, // can be undefined; if undefined, will use default from manifestData cullingLayer, // can be undefined; if undefined, will use default from manifestData collection, - restrictedTraits = [], - restrictedTypes = [] } = options; this.manifestData = manifestData; @@ -501,9 +511,6 @@ export class TraitModelsGroup{ this.iconSvg = iconSvg; this.fullIconSvg = manifestData.getTraitIconsDirectorySvg() + iconSvg; - this.restrictedTraits = restrictedTraits; - this.restrictedTypes = restrictedTypes; - this.cameraTarget = cameraTarget; this.cullingDistance = cullingDistance; this.cullingLayer = cullingLayer; @@ -535,11 +542,6 @@ export class TraitModelsGroup{ } }); } - addTraitRestriction(traitID){ - if (this.restrictedTraits.indexOf(traitID) == -1){ - this.restrictedTraits.push(traitID) - } - } createCollection(itemCollection, replaceExisting = false){ if (replaceExisting) this.collection = []; @@ -799,6 +801,11 @@ class TraitColorsGroup{ } } export class ModelTrait{ + /** + * @type {string} + */ + type + blendshapeTraits = []; /** * @type {string[]} @@ -813,15 +820,19 @@ export class ModelTrait{ */ traitGroup blendshapeTraitsMap = new Map(); + /** + * @type {string[]} + */ + _restrictedItems = [] constructor(traitGroup, options){ const { id, + type = '', directory, name, thumbnail, cullingDistance, cullingLayer, - type = [], textureCollection, blendshapeTraits, colorCollection, @@ -829,6 +840,7 @@ export class ModelTrait{ decalMeshNameTargets, fullDirectory, fullThumbnail, + restrictedItems }= options; this.manifestData = traitGroup.manifestData; this.traitGroup = traitGroup; @@ -837,7 +849,7 @@ export class ModelTrait{ this.id = id; this.directory = directory; - + this._restrictedItems = restrictedItems||[]; if (fullDirectory){ this.fullDirectory = fullDirectory } @@ -882,31 +894,13 @@ export class ModelTrait{ isRestricted(targetModelTrait){ if (targetModelTrait == null) return false; - - const groupTraitID = targetModelTrait.traitGroup.trait; - if (this.traitGroup.restrictedTraits.indexOf(groupTraitID) != -1) - return true; - - if (this.type.length > 0 && this.manifestData.restrictedTypes > 0){ - - const haveCommonValue = (arr1, arr2) => { - if (arr1 == null || arr2 == null) - return false; - for (let i = 0; i < arr1.length; i++) { - if (arr2.includes(arr1[i])) { - return true; // Found a common value - } - } - return false; // No common value found - } - - const restrictedTypes = this.manifestData.restrictedTypes; - const traitTypes = getAsArray(this.type); - traitTypes.forEach(type => { - return haveCommonValue(restrictedTypes[type], traitTypes) - }); + if(this.traitGroup.restrictions?.isTraitAllowed(targetModelTrait.traitGroup.trait)){ + return false; + } + if(this.traitGroup.restrictions?.isTypeAllowed(targetModelTrait.type)){ + return false; } - return false; + return true } getGroupBlendShapeTraits(){ return this.blendshapeTraits; @@ -1147,138 +1141,4 @@ class SelectedOption{ } } - - - const getRestrictions = () => { - - const traitRestrictions = templateInfo.traitRestrictions // can be null - const typeRestrictions = {}; - - for (const prop in traitRestrictions){ - - // create the counter restrcitions traits - getAsArray(traitRestrictions[prop].restrictedTraits).map((traitName)=>{ - - // check if the trait restrictions exists for the other trait, if not add it - if (traitRestrictions[traitName] == null) traitRestrictions[traitName] = {} - // make sure to have an array setup, if there is none, create a new empty one - if (traitRestrictions[traitName].restrictedTraits == null) traitRestrictions[traitName].restrictedTraits = [] - - // finally merge existing and new restrictions - traitRestrictions[traitName].restrictedTraits = [...new Set([ - ...traitRestrictions[traitName].restrictedTraits , - ...[prop]])] // make sure to add prop as restriction - }) - - // do the same for the types - getAsArray(traitRestrictions[prop].restrictedTypes).map((typeName)=>{ - //notice were adding the new data to typeRestrictions and not trait - if (typeRestrictions[typeName] == null) typeRestrictions[typeName] = {} - //create the restricted trait in this type - if (typeRestrictions[typeName].restrictedTraits == null) typeRestrictions[typeName].restrictedTraits = [] - - typeRestrictions[typeName].restrictedTraits = [...new Set([ - ...typeRestrictions[typeName].restrictedTraits , - ...[prop]])] // make sure to add prop as restriction - }) - } - - // now merge defined type to type restrictions - for (const prop in templateInfo.typeRestrictions){ - // check if it already exsits - if (typeRestrictions[prop] == null) typeRestrictions[prop] = {} - if (typeRestrictions[prop].restrictedTypes == null) typeRestrictions[prop].restrictedTypes = [] - typeRestrictions[prop].restrictedTypes = [...new Set([ - ...typeRestrictions[prop].restrictedTypes , - ...getAsArray(templateInfo.typeRestrictions[prop])])] - - // now that we have setup the type restrictions, lets counter create for the other traits - getAsArray(templateInfo.typeRestrictions[prop]).map((typeName)=>{ - // prop = boots - // typeName = pants - if (typeRestrictions[typeName] == null) typeRestrictions[typeName] = {} - if (typeRestrictions[typeName].restrictedTypes == null) typeRestrictions[typeName].restrictedTypes =[] - typeRestrictions[typeName].restrictedTypes = [...new Set([ - ...typeRestrictions[typeName].restrictedTypes , - ...[prop]])] // make sure to add prop as restriction - }) - } - } - - // _filterRestrictedOptions(options){ - // let removeTraits = []; - // for (let i =0; i < options.length;i++){ - // const option = options[i]; - - // //if this option is not already in the remove traits list then: - // if (!removeTraits.includes(option.trait.name)){ - // const typeRestrictions = restrictions?.typeRestrictions; - // // type restrictions = what `type` cannot go wit this trait or this type - // if (typeRestrictions){ - // getAsArray(option.item?.type).map((t)=>{ - // //combine to array - // removeTraits = [...new Set([ - // ...removeTraits , // get previous remove traits - // ...findTraitsWithTypes(getAsArray(typeRestrictions[t]?.restrictedTypes)), //get by restricted traits by types coincidence - // ...getAsArray(typeRestrictions[t]?.restrictedTraits)])] // get by restricted trait setup - - // }) - // } - - // // trait restrictions = what `trait` cannot go wit this trait or this type - // const traitRestrictions = restrictions?.traitRestrictions; - // if (traitRestrictions){ - // removeTraits = [...new Set([ - // ...removeTraits, - // ...findTraitsWithTypes(getAsArray(traitRestrictions[option.trait.name]?.restrictedTypes)), - // ...getAsArray(traitRestrictions[option.trait.name]?.restrictedTraits), - - // ])] - // } - // } - // } - - // // now update uptions - // removeTraits.forEach(trait => { - // let removed = false; - // updateCurrentTraitMap(trait, null); - - // for (let i =0; i < options.length;i++){ - // // find an option with the trait name - // if (options[i].trait?.name === trait){ - // options[i] = { - // item:null, - // trait:templateInfo.traits.find((t) => t.name === trait) - // } - // removed = true; - // break; - // } - // } - // // if no option setup was found, add a null option to remove in case user had it added before - // if (!removed){ - // options.push({ - // item:null, - // trait:templateInfo.traits.find((t) => t.name === trait) - // }) - // } - // }); - - // return options; - // } - - // const findTraitsWithTypes = (types) => { - // const typeTraits = []; - // for (const prop in avatar){ - // for (let i = 0; i < types.length; i++){ - // const t = types[i] - - // if (avatar[prop].traitInfo?.type?.includes(t)){ - // typeTraits.push(prop); - // break; - // } - // } - // } - // return typeTraits; - // } - \ No newline at end of file diff --git a/src/library/characterManager.js b/src/library/characterManager.js index 84a64ace..d2e9ad12 100644 --- a/src/library/characterManager.js +++ b/src/library/characterManager.js @@ -69,8 +69,6 @@ export class CharacterManager { helperRoot.renderOrder = 10000; this.rootModel.add(helperRoot) this.vrmHelperRoot = helperRoot; - - } /** @@ -593,6 +591,73 @@ export class CharacterManager { console.warn(`No trait with name: ${ groupTraitID } was found.`) } } + + + /** + * @private + * Can be used to check if a trait is restricted by another trait + * @param {string} traitGroupID + * @param {string} traitID + * @typedef {Object} RuleResult + * @property {boolean} allowed - Whether the trait is allowed. + * @property {Object} blocking - The blocking trait information. + * @returns {RuleResult[]} + */ + _getTraitAllowedRules(traitGroupID,traitID){ + const isAllowAggregated = [] + for( const trait in this.avatar){ + const object = this.avatar[trait]; + const isAllowed = object.traitInfo.traitGroup.restrictions?.isReverseAllowed(object.traitInfo.type,traitGroupID,object.traitInfo.id,traitID) + if(isAllowed && !isAllowed?.allowed){ + isAllowAggregated.push(isAllowed) + } + } + return isAllowAggregated.length? isAllowAggregated:[{allowed:true,blocking:{}}] + } + + /** + * INTERNAL: Checks and Remove blocking traits; Used when loading a new trait + * @param {string} groupTraitID + * @param {string} traitID + */ + _checkRestrictionsBeforeLoad(groupTraitID,traitID){ + const isAllowed = this._getTraitAllowedRules(groupTraitID,traitID) + + if(isAllowed[0].allowed){ + return + } + for(const rule of isAllowed){ + if(rule.blocking.blockingTrait){ + /** + * We have a trait blocking, remove it; + */ + this.removeTrait(rule.blocking.blockingTrait); + } + if(rule.blocking.blockingItemId){ + /** + * We have a specific item ID blocking, remove it; + */ + const trait = this.manifestData.getTraitOptionById(rule.blocking.blockingItemId); + if(trait){ + this.removeTrait(trait.traitGroup.trait); + } + } + if (rule.blocking.blockingType){ + /* + * We have a specific type blocking, remove it; + */ + const traits = this.manifestData.getTraitOptionsByType(rule.blocking.blockingType); + if(traits.length){ + for(const prop in this.avatar){ + if(this.avatar[prop].traitInfo.type == rule.blocking.blockingType){ + this.removeTrait(prop); + } + } + } + } + } + } + /** * Loads a specific trait based on group and trait IDs. * @@ -609,7 +674,7 @@ export class CharacterManager { try { // Retrieve the selected trait using manifest data const selectedTrait = this.manifestData.getTraitOption(groupTraitID, traitID); - + this._checkRestrictionsBeforeLoad(groupTraitID,traitID) // If the trait is found, load it into the avatar using the _loadTraits method if (selectedTrait) { await this._loadTraits(getAsArray(selectedTrait),soloView); diff --git a/src/library/manifestRestrictions.js b/src/library/manifestRestrictions.js new file mode 100644 index 00000000..f9cc7bdc --- /dev/null +++ b/src/library/manifestRestrictions.js @@ -0,0 +1,380 @@ +import { getAsArray } from "./utils"; + +/** + * @typedef {import('./CharacterManifestData').CharacterManifestData} CharacterManifestData + * @typedef {import('./CharacterManifestData').ModelTrait} ModelTrait + * @typedef {import('./CharacterManifestData').TraitModelsGroup} TraitModelsGroup + * */ + +export class ManifestRestrictions { + + /** + * @type {Record;} + */ + traitRestrictions + + /** + * @type {Record} + */ + restrictionMaps = {}; + + /** + * restrictions for specific trait items + * example: { 'hat-blue-02': ['pants-blue-01', 'hat-blue-03'] } + * @type {Map>} + */ + itemRestrictions = new Map() + + /** + * @type {CharacterManifestData} + */ + manifestData + + constructor( manifestData) { + this.manifestData = manifestData; + this.traitRestrictions = manifestData.traitRestrictions || {} + this._validateTraitRestrictions(); + + } + + _init(){ + this._setupSpecificItemRestrictions() + this.logRules() + } + + + logRules = () => { + const log = [] + for(const r in this.restrictionMaps){ + const restriction = this.restrictionMaps[r] + restriction.restrictedTypes.size && log.push(`Trait: ${restriction.group.trait} is restrciting traits ${Array.from(restriction.restrictedTraits.values()).join(', ')}`) + restriction.restrictedTypes.size && log.push(`Trait: ${restriction.group.trait} also restricts types ${Array.from(restriction.restrictedTypes.values()).join(', ')}`) + } + this.itemRestrictions.forEach((v,k)=>{ + log.push(`Item ${k} is restricting item ${Array.from(v.values()).join(', ')}`) + }) + console.log(log.join('\n')) + } + + /** + * @private + * Setup specific item restrictions + */ + _setupSpecificItemRestrictions = () => { + const all = this.manifestData.getAllTraitOptions() + all.forEach((c)=>{ + this.itemRestrictions.set(c.id, new Set()) + }) + + for(const modelTrait of all){ + if(!modelTrait._restrictedItems || modelTrait._restrictedItems.length == 0) { + continue; + } + const restrictedSpecificIds = new Set() + + + /** + * If item A has [B,C], then add B,C + */ + for(const itemId of modelTrait._restrictedItems) { + const itemData = all.find((d) => d.id == itemId) + if(!itemData) { + console.warn(`[${modelTrait.traitGroup.trait}] Restricted item ${itemId} not found`) + continue; + } + restrictedSpecificIds.add(itemId) + } + this.itemRestrictions.set(modelTrait.id, restrictedSpecificIds) + + + /** + * Go over itemRestrictions and add the other item that is restricting the current trait + * if D has [A,B], then add D to the restrictedTraits list of A,B + */ + if(restrictedSpecificIds.size > 0) { + + for(const item of restrictedSpecificIds){ + const r = this.itemRestrictions.get(item) + if(!r) continue; + if(!r.has(modelTrait.id)){ + r.add(modelTrait.id) + } + } + } + } + + this.itemRestrictions.forEach((v,k)=>{ + if(v.size == 0) { + this.itemRestrictions.delete(k) + } + }) + + } + + /** + * + * @param {TraitModelsGroup} group + */ + createTraitRestriction = (group) => { + if (this.restrictionMaps[group.trait]) { + return this.restrictionMaps[group.trait]; + } + const restriction = new TraitRestriction(this, group); + group.restrictions = restriction; + + this.restrictionMaps[group.trait] = restriction; + + return restriction + } + + /** + * Given a list of traits, get the traits that are forbidden given the restrictions + * @param {string[]} traitGroups + */ + getForbiddenTraits = (traitGroups) => { + const disallowedTraits =new Set() + for(const traitId in this.restrictionMaps) { + if(!traitGroups.includes(traitId)) { + continue + } + + const restriction = this.restrictionMaps[traitId]; + for(const trait of restriction.restrictedTraits) { + disallowedTraits.add(trait) + } + } + return Array.from(disallowedTraits.values()) + } + + /** + * Given a list of traits, get the types that are forbidden given the restrictions + * @param {string[]} traitGroups + */ + getForbiddenTypes = (traitGroups) => { + const disallowedTypes =new Set() + for(const traitId in this.restrictionMaps) { + if(!traitGroups.includes(traitId)) { + continue + } + + const restriction = this.restrictionMaps[traitId]; + for(const trait of restriction.restrictedTraits) { + disallowedTypes.add(trait) + } + } + return Array.from(disallowedTypes.values()) + } + /** + * @private + * Validate trait restrictions + */ + _validateTraitRestrictions = () => { + /** + * @type {Record} + */ + const traitRes = {} + if (this.traitRestrictions) { + for (const prop in this.traitRestrictions) { + if (traitRes[prop] == null) { + traitRes[prop] = { restrictedTraits: [], restrictedTypes: [] } + } + traitRes[prop].restrictedTraits = getAsArray(this.traitRestrictions[prop].restrictedTraits).filter((t) => !!t); + traitRes[prop].restrictedTypes = getAsArray(this.traitRestrictions[prop].restrictedTypes).filter((t) => !!t); + } + } + this.traitRestrictions = traitRes + } +} + + +/** + * @typedef {Object} TraitRestrictionResult + * @property {boolean} allowed + * @property {string} blockingTrait + */ +/** + * @typedef {Object} TypeRestrictionResult + * @property {boolean} allowed + * @property {string} blockingType + */ +/** + * @typedef {Object} blockingObject + * @property {string} blockingTrait + * @property {string} blockingType + * @property {string} blockingItemId + */ +/** + * @typedef {Object} ItemRestrictionResult + * @property {boolean} allowed + * @property {blockingObject} blocking + */ + +export class TraitRestriction { + /** + * @type {TraitModelsGroup} + */ + group; + /** + * @type {Set} + */ + restrictedTraits + /** + * @type {Set} + */ + restrictedTypes + + /** + * + * @param {ManifestRestrictions} manifestRestrictions + * @param {TraitModelsGroup} group + */ + constructor(manifestRestrictions, group) { + this.manifestRestrictions = manifestRestrictions; + this.group = group; + + this.restrictedTraits = new Set(this.manifestRestrictions.traitRestrictions[group.trait]?.restrictedTraits || []); + this.restrictedTypes = new Set(this.manifestRestrictions.traitRestrictions[group.trait]?.restrictedTypes || []); + + /** + * Check if the current trait is restricting another trait, if so add the current trait to the restrictedTraits list of the other trait + * Note that this works only because we are iterating over the traitRestrictions objects one at a time. + */ + for(const traitKey in this.manifestRestrictions.traitRestrictions) { + if(traitKey == group.trait) { + continue; + } + const otherTraitRestriction = this.manifestRestrictions.traitRestrictions[traitKey] + /** + * If the current trait is restricting another trait, then add current trait to the restrictedTraits list of other key + */ + for(const traitId of this.restrictedTraits) { + const objectFromMap =this.manifestRestrictions.restrictionMaps[traitId] + if(objectFromMap){ + if(!objectFromMap.restrictedTraits.has(group.trait)) { + objectFromMap.restrictedTraits.add(traitKey); + } + } + } + /** + * If the current trait is restricted by another trait, then add the current trait to the restrictedTraits list + */ + if(otherTraitRestriction.restrictedTraits.includes(group.trait) && !this.restrictedTraits.has(traitKey)) { + this.restrictedTraits.add(traitKey); + } + } + } + + get manifestData(){ + return this.manifestRestrictions.manifestData; + } + + get traitId() { + return this.group.trait; + } + /** + * Check whether the trait ID is permitted for this trait restriction + * true if the trait is not in the restrictedTraits list + * @type {string} traitId + * @returns {boolean} + */ + isTraitAllowed = (traitId) => { + return !this.restrictedTraits.has(traitId); + } + /** + * Check whether the type is permitted for this trait restriction + * true if the type is not in the restrictedTypes list + * @type {string} typeName + * @returns {boolean} + */ + isTypeAllowed = (typeName) => { + return !this.restrictedTypes.has(typeName); + } + /** + * Check whether this trait restriction is allowed by target trait + * @param {string} targetTrait + * @returns {TraitRestrictionResult} + */ + isReverseTraitAllowed = (targetTrait) => { + const restriction = this.manifestRestrictions.restrictionMaps[targetTrait]; + if (restriction) { + const isAllowed = restriction.isTraitAllowed(this.traitId) + return {allowed:isAllowed, blockingTrait: isAllowed?undefined:this.traitId}; + } + + return {allowed:true, blockingTrait: undefined}; + } + /** + * Check whether the type from this restriction is allowed by target trait + * @param {string} sourceType + * @param {string} targetTrait + * @returns {TypeRestrictionResult} + */ + isReverseTypeAllowed = (sourceType,targetTrait) => { + if(!sourceType) return {allowed:true}; + const restriction = this.manifestRestrictions.restrictionMaps[targetTrait]; + if (restriction) { + const isAllowed = restriction.isTypeAllowed(sourceType) + return {allowed:isAllowed, blockingType: isAllowed?undefined:this.traitId}; + } + + return {allowed:true} + } + /** + * Check whether the soruceItem allows the targetItem + * @param {string} sourceItemId + * @param {string} targetItemId + * @returns {boolean} + */ + isItemAllowed = (sourceItemId, targetItemId) => { + if(!sourceItemId) return true; + const list = this.manifestRestrictions.itemRestrictions.get(sourceItemId) + if (list) { + return !list.has(targetItemId) + } + + return true + } + + /** + * + * @param {string} sourceItemId + * @param {string} targetItemId + * @returns {ItemRestrictionResult} + */ + isReverseItemAllowed = (sourceItemId, targetItemId) => { + if(!sourceItemId) return {allowed:true, blockingItemId:undefined}; + const list = this.manifestRestrictions.itemRestrictions.get(targetItemId) + if (list) { + const isAllowed = !list.has(sourceItemId) + return {allowed:isAllowed, blockingItemId: isAllowed?undefined:sourceItemId} + } + + return {allowed:true, blockingItemId:undefined} + } + /** + * + * @param {string} sourceType + * @param {string} targetTrait + * @param {string} sourceItemId + * @param {string} targetItemId + * @returns {ItemRestrictionResult} + */ + isReverseAllowed = (sourceType,targetTrait,sourceItemId,targetItemId) => { + + const isReverseTraitAllowed = this.isReverseTraitAllowed(targetTrait) + const isReverseTypeAllowed = this.isReverseTypeAllowed(sourceType,targetTrait) + const isReverseItemAllowed = this.isReverseItemAllowed(sourceItemId,targetItemId) + + return {allowed:isReverseTraitAllowed.allowed && isReverseTypeAllowed.allowed && isReverseItemAllowed.allowed, blocking: { + blockingTrait:isReverseTraitAllowed.blockingTrait, + blockingType:isReverseTypeAllowed.blockingType, + blockingItemId:isReverseItemAllowed.blockingItemId + }} + } +} \ No newline at end of file