import { Bot , Workspace , DEFAULT_WORKSPACE_SCALE , DEFAULT_WORKSPACE_HEIGHT , DEFAULT_WORKSPACE_GRID_SCALE , DEFAULT_BUILDER_USER_COLOR , DEFAULT_PLAYER_USER_COLOR , AuxDomain , BotShape , DEFAULT_BOT_SHAPE , BotTags , DEFAULT_WORKSPACE_SIZE , BotLabelAnchor , DEFAULT_LABEL_ANCHOR , BotDragMode , DimensionVisualizeMode , PrecalculatedBot , PrecalculatedTags , BotsState , DEFAULT_USER_INACTIVE_TIME , DEFAULT_USER_DELETION_TIME , BotPositioningMode , BotSpace , BOT_SPACE_TAG , PortalType , BotSubShape , BotOrientationMode , DEFAULT_ORIENTATION_MODE , BotAnchorPoint , DEFAULT_ANCHOR_POINT , BotLOD , BotLabelAlignment , DEFAULT_LABEL_ALIGNMENT , BotScaleMode , DEFAULT_SCALE_MODE , MeetPortalAnchorPoint , DEFAULT_MEET_PORTAL_ANCHOR_POINT , BotSignatures , DEFAULT_TAG_PORTAL_ANCHOR_POINT , TAG_MASK_SPACE_PRIORITIES , RuntimeBot , DNA_TAG_PREFIX , BotLabelFontSize , DEFAULT_LABEL_FONT_SIZE , BotLabelWordWrap , DEFAULT_LABEL_WORD_WRAP_MODE , MenuBotForm , DEFAULT_MENU_BOT_FORM , PortalCameraControlsMode , DEFAULT_PORTAL_CAMERA_CONTROLS_MODE , MenuBotHoverStyle , MenuBotResolvedHoverStyle , DEFAULT_MENU_BOT_HOVER_STYLE , PortalCameraType , BotCursorType , DEFAULT_BOT_CURSOR , BotLabelPadding , } from './Bot' ; import { BotCalculationContext , cacheFunction } from './BotCalculationContext' ; import { v4 as uuid } from 'uuid' ; import { flatMap , union , keys , intersection , some , assign , find , values , isEqual , sortBy , cloneDeep , difference , mapValues , differenceBy , maxBy , intersectionBy , unionBy , } from 'lodash' ; import { PartialBot } from '../bots' ; import { merge , shortUuid } from '../utils' ; import { BotObjectsContext } from './BotObjectsContext' ; export var isFormulaObjectSymbol = Symbol ( 'isFormulaObject' ) ; export var ShortId_Length = 5 ; /**
 * The default energy for actions.
 */ export const DEFAULT_ENERGY = 100000 ; export type SimulationIdParseResult = SimulationIdParseFailure | SimulationIdParseSuccess ; export interface SimulationIdParseFailure { success : false ; } export interface SimulationIdParseSuccess { success : true ; channel ? : string ; host ? : string ; } export const POSSIBLE_DIMENSION_VISUALIZERS = [ '*' ] as const ; /**
 * Defines an interface that represents the difference between
 * to BotsState objects.
 */ export interface BotsStateDiff { addedBots : Bot [ ] ; removedBots : string [ ] ; updatedBots : Bot [ ] ; } param value export function hasValue ( value ) { return ! ( value === null || typeof value === 'undefined' || value === '' ) ; } param value returns export function convertToString ( value ) { if ( ! hasValue ( value ) ) { return '' ; } if ( typeof value === 'string' ) { return value ; } else if ( typeof value !== 'object' && value !== undefined ) { return value . toString ( ) ; } else if ( value instanceof Date ) { return value . toISOString ( ) ; } else { return JSON . stringify ( value ) ; } } param bot export function cleanBot ( bot ) { let cleaned = merge ( { } , bot ) ; let newTags = merge ( { } , cleaned . tags ) ; cleaned . tags = newTags ; for ( let property in cleaned . tags ) { let value = cleaned . tags [ property ] ; if ( ! hasValue ( value ) ) { delete cleaned . tags [ property ] ; } } return cleaned ; } param workspace export function isMinimized ( calc , workspace ) { return getDimensionMinimized ( calc , workspace ) ; } /**
 * Determines if the given bot contains data for a dimension.
 */ export function isDimension ( calc , dimensionBot ) { return getBotConfigDimensions ( calc , dimensionBot ) . length > 0 ; } /**
 * Determines if the given dimension bot is being visualized in the viewport.
 */ export function isVisibleDimension ( calc , dimensionBot ) { const result = calculateBotValue ( calc , dimensionBot , 'auxDimensionVisualize' ) ; if ( typeof result === 'string' && hasValue ( result ) ) { return true ; } else if ( Array . isArray ( result ) ) { return true ; } return false ; } param bots param selectionId export function filterBotsBySelection < TBot extends Bot > ( bots , selectionId ) { return bots . filter ( ( f ) => { if ( f . id === selectionId ) { return true ; } if ( selectionId === 'id' || selectionId === 'space' ) { return true ; } return ( hasValue ( f . tags [ selectionId ] ) || hasValue ( calculateBotValue ( null , f , selectionId ) ) ) ; } ) ; } param data export function getUploadState ( data ) { if ( 'version' in data ) { return data . state ; } return data ; } param calc param bot export function isBotPointable ( calc , bot ) { return calculateBooleanTagValue ( calc , bot , 'auxPointable' , true ) ; } param calc param bot export function isBotFocusable ( calc , bot ) { return calculateBooleanTagValue ( calc , bot , 'auxFocusable' , true ) ; } param bots param currentTags param extraTags param allowedTags export function botTags ( bots , currentTags , extraTags , allowedTags = null ) { const botTags = flatMap ( bots , ( f ) => keys ( f . tags ) ) . map ( ( t ) => ( { tag : t , space : null as string } as const ) ) ; const botMasks = flatMap ( bots , ( b ) => { if ( ! b . masks ) { return [ ] ; } let tags = [ ] as { tag : string ; space : string ; } [ ] ; for ( let space in b . masks ) { let spaceTags = keys ( b . masks [ space ] ) . map ( ( k ) => ( { tag : k , space : space , } as const ) ) ; tags . push ( ... spaceTags ) ; } return tags ; } ) ; const allBotTags = unionBy ( botTags , botMasks , tagComparer ) ; const extraTagPairs = extraTags . map ( ( t ) => ( { tag : t , space : null as string } as const ) ) ; const currentTagPairs = currentTags . map ( ( t ) => ( { tag : t , space : null as string } as const ) ) ; const tagsToKeep = unionBy ( allBotTags , extraTagPairs , tagComparer ) ; const allTags = unionBy ( currentTagPairs , tagsToKeep , tagComparer ) ; const onlyTagsToKeep = intersectionBy ( allTags , tagsToKeep , tagComparer ) ; if ( allowedTags ) { const allowedTagsSet = new Set ( allowedTags ) ; return onlyTagsToKeep . filter ( ( t ) => allowedTagsSet . has ( t . tag ) ) ; } return onlyTagsToKeep ; function tagComparer ( tagPair ) { return `${ tagPair . tag }.${ ! tagPair . space ? 'null' : tagPair . space }` ; } } export function getAllBotTags ( bots , includeHidden ) { const botTags = flatMap ( bots , ( f ) => keys ( f . tags ) ) ; const nonHiddenTags = botTags . filter ( ( t ) => includeHidden || ! isHiddenTag ( t ) ) ; return nonHiddenTags ; } param bots param shortIds returns export function botsFromShortIds ( bots , shortIds ) { var matches = [ ] ; shortIds . forEach ( ( shortId ) => { var bot = this . botFromShortId ( bots , shortId ) ; if ( bot ) matches . push ( bot ) ; } ) ; if ( matches . length > 0 ) return matches ; else return null ; } param bots param shortId returns export function botFromShortId ( bots , shortId ) { return find ( bots , ( f ) => { return getShortId ( f ) === shortId ; } ) ; } param bot export function getShortId ( bot ) { let id = typeof bot === 'string' ? bot : bot . id ; if ( typeof id !== 'string' ) { if ( id !== null && typeof id !== 'undefined' ) { return ( < any > id ) . toString ( ) ; } else { return null ; } } let str = id . substr ( 0 , ShortId_Length ) ; if ( id . startsWith ( 'mod-' ) ) { str = 'mod' ; } if ( id . startsWith ( 'config' ) ) { str = 'config' ; } return str ; } param tag export function isHiddenTag ( tag ) { return /^_/ . test ( tag ) || /(\w+)\._/ . test ( tag ) ; } param bot export function isRuntimeBot ( bot ) { if ( ! ! bot && typeof bot === 'object' ) { return ( ! ! bot . id && typeof bot . tags === 'object' && typeof bot . raw === 'object' && typeof bot . masks === 'object' && typeof bot . tags . toJSON === 'function' && typeof bot . listeners === 'object' && typeof bot . changes === 'object' && typeof bot . maskChanges === 'object' ) ; } return false ; } export function isPrecalculated ( bot ) { return bot && ( < PrecalculatedBot > bot ) . precalculated === true ; } export function isExistingBot ( bot ) { return bot && ( < Bot > bot ) . id != undefined ; } param bot export function getBotSpace ( bot ) { const type = bot . space ; if ( ! hasValue ( type ) ) { return 'shared' ; } return type ; } export function calculateBotValue ( context , object , tag ) { const value = calculateBotTagValue ( object , tag ) ; if ( typeof value === 'undefined' && typeof tag === 'string' && tag . startsWith ( 'aux' ) && tag . length >= 4 ) { const firstChar = tag . substring ( 3 , 4 ) ; const rest = tag . substring ( 4 ) ; const newTag = firstChar . toLowerCase ( ) + rest ; return calculateBotTagValue ( object , newTag ) ; } return value ; } param bot param tag export function calculateBotIds ( object , tag ) { const value = calculateBotValue ( null , object , tag ) ; if ( isBotLink ( value ) ) { return parseBotLink ( value ) ; } else if ( typeof value === 'string' ) { return [ value ] ; } else if ( isBot ( value ) ) { return [ value . id ] ; } else if ( Array . isArray ( value ) ) { let ids = [ ] as string [ ] ; for ( let item of value ) { if ( isBot ( item ) ) { ids . push ( item . id ) ; } else if ( typeof item === 'string' ) { ids . push ( item ) ; } } return ids ; } return null ; } function calculateBotTagValue ( object , tag ) { if ( tag === 'id' ) { return object . id ; } else if ( tag === BOT_SPACE_TAG ) { return getBotSpace ( object ) ; } else if ( isPrecalculated ( object ) ) { return object . values [ tag ] ; } else if ( isRuntimeBot ( object ) ) { return object . tags [ tag ] ; } else { return calculateValue ( object , tag , object . tags [ tag ] ) ; } } export function calculateFormattedBotValue ( context , bot , tag ) { const value = calculateBotValue ( context , bot , tag ) ; return formatValue ( value ) ; } /**
 * Determines if the given value represents a formula.
 */ export function isFormula ( value ) { return typeof value === 'string' && value . indexOf ( DNA_TAG_PREFIX ) === 0 ; } param value export function isScript ( value ) { return typeof value === 'string' && value . indexOf ( '@' ) === 0 ; } param value export function isBotLink ( value ) { return typeof value === 'string' && value . startsWith ( '🔗' ) ; } param value export function parseBotLink ( value ) { if ( isBotLink ( value ) ) { const split = value . substring ( '🔗' . length ) . split ( ',' ) ; return split . filter ( ( id ) => hasValue ( id ) ) ; } return null ; } param botIds export function createBotLink ( botIds ) { return `🔗${ botIds . join ( ',' ) }` ; } param value export function parseScript ( value ) { if ( isScript ( value ) ) { return value . substring ( 1 ) ; } return null ; } param value export function parseScriptSafe ( value ) { return parseScript ( value ) ?? value ; } param value export function parseFormula ( value ) { if ( isFormula ( value ) ) { return value . substring ( DNA_TAG_PREFIX . length ) ; } return null ; } param value export function parseFormulaSafe ( value ) { return parseFormula ( value ) ?? value ; } /**
 * Trims the leading script symbol off the given tag.
 */ export function trimPortalScript ( scriptPrefixes , tag ) { const prefix = getScriptPrefix ( scriptPrefixes , tag ) ; if ( prefix ) { return tag . substring ( prefix . length ) ; } return tag ; } /**
 * Trims the leading script symbol off the given tag.
 */ export function trimPrefixedScript ( prefix , tag ) { if ( tag . startsWith ( prefix ) ) { return tag . substring ( prefix . length ) ; } return tag ; } param prefix param value export function isPortalScript ( prefix , value ) { return typeof value === 'string' && value . startsWith ( prefix ) ; } export function hasPortalScript ( prefixes , value ) { return getScriptPrefix ( prefixes , value ) !== null ; } export function getScriptPrefix ( prefixes , value ) { if ( typeof value === 'string' ) { for ( let prefix of prefixes ) { if ( value . startsWith ( prefix ) ) { return prefix ; } } } return null ; } /**
 * Determines if the given value represents a number.
 */ export function isNumber ( value ) { return ( typeof value === 'string' && value . length > 0 && ( ( /^-?\d*(?:\.?\d+)?$/ . test ( value ) && value !== '-' ) || ( typeof value === 'string' && 'infinity' === value . toLowerCase ( ) ) ) ) ; } param object export function isBot ( object ) { if ( object ) { return ! ! object . id && ! ! object . tags ; } return false ; } param state export function getActiveObjects ( state ) { return < Bot [ ] > values ( state ) ; } param tag export function isTagWellKnown ( tag ) { return isHiddenTag ( tag ) ; } param first param second export function doBotsAppearEqual ( first , second , options = { } ) { if ( first === second ) { return true ; } else if ( ! first || ! second ) { return false ; } options = merge ( { ignoreSelectionTags : true , ignoreId : false , } , options ) ; if ( ! options . ignoreId && first . id === second . id ) { return true ; } const tags = union ( keys ( first . tags ) , keys ( second . tags ) ) ; const usableTags = tags . filter ( ( t ) => ! isTagWellKnown ( t ) ) ; let allEqual = true ; for ( let t of usableTags ) { if ( ! isEqual ( first . tags [ t ] , second . tags [ t ] ) ) { allEqual = false ; break ; } } return allEqual ; } export interface BotAppearanceEqualityOptions { ignoreId ? : boolean ; } /**
 * Defines an interface that represents the result of validating a tag.
 */ export interface TagValidation { valid : boolean ; 'tag.required' ? : TagRequired ; 'tag.invalidChar' ? : TagInvalidChar ; } export interface TagRequired { } export interface TagInvalidChar { char : string ; } param tag export function validateTag ( tag ) { let errors = { valid : true , } ; if ( ! tag || ! tag . trim ( ) ) { errors . valid = false ; errors [ 'tag.required' ] = { } ; } else { if ( tag . indexOf ( '#' ) >= 0 ) { errors . valid = false ; errors [ 'tag.invalidChar' ] = { char : '#' } ; } } return errors ; } param bot param selectionId param userId export function toggleBotSelection ( bot , selectionId , userId ) { return { tags : { [ selectionId ] : ! bot . tags [ selectionId ] , } , } ; } param calc param userBot param globalsBot param domain export function getUserBotColor ( calc , userBot , domain ) { if ( userBot . tags [ 'auxColor' ] ) { return calculateBotValue ( calc , userBot , 'auxColor' ) ; } if ( domain === 'builder' ) { return DEFAULT_BUILDER_USER_COLOR ; } else { return DEFAULT_PLAYER_USER_COLOR ; } } param userBot export function getUserMenuId ( calc , userBot ) { return calculateBotValue ( calc , userBot , 'menuPortal' ) ; } param calc param userBot export function getBotsInMenu ( calc , userBot ) { const context = getUserMenuId ( calc , userBot ) ; return botsInDimension ( calc , context ) ; } param calc param dimension export function botsInDimension ( calc , dimension ) { const bots = calc . objects . filter ( ( f ) => isBotInDimension ( calc , f , dimension ) ) ; return sortBy ( bots , ( f ) => botDimensionSortOrder ( calc , f , dimension ) ) ; } param calc param dimension param x param y param index export function addToDimensionDiff ( calc , dimension , x = 0 , y = 0 , index ? ) { const bots = objectsAtDimensionGridPosition ( calc , dimension , { x , y } ) ; return { [ dimension ] : true , ... setPositionDiff ( calc , dimension , x , y , typeof index === 'undefined' ? bots . length : index ) , } ; } param calc param dimension export function removeFromDimensionDiff ( calc , dimension ) { return { [ dimension ] : null , [ `${ dimension }X` ] : null , [ `${ dimension }Y` ] : null , [ `${ dimension }SortOrder` ] : null , } ; } param calc param dimension param x param y param index export function setPositionDiff ( calc , dimension , x ? , y ? , index ? ) { let tags = { } ; if ( typeof x === 'number' ) { tags [ `${ dimension }X` ] = x ; } if ( typeof y === 'number' ) { tags [ `${ dimension }Y` ] = y ; } if ( typeof index === 'number' ) { tags [ `${ dimension }SortOrder` ] = index ; } return tags ; } param calc param userBot param id param index export function addBotToMenu ( calc , userBot , id , index = Infinity ) { const dimension = getUserMenuId ( calc , userBot ) ; const bots = getBotsInMenu ( calc , userBot ) ; const idx = isFinite ( index ) ? index : bots . length ; return { tags : { [ `${ dimension }Id` ] : id , [ `${ dimension }SortOrder` ] : idx , [ dimension ] : true , } , } ; } param calc param userBot export function removeBotFromMenu ( calc , userBot ) { const dimension = getUserMenuId ( calc , userBot ) ; return { tags : { [ dimension ] : null , [ `${ dimension }Id` ] : null , [ `${ dimension }SortOrder` ] : null , } , } ; } param bot export function tagsOnBot ( bot ) { let tags = new Set ( keys ( bot . tags ) ) ; if ( bot . masks ) { for ( let space in bot . masks ) { let k = keys ( bot . masks [ space ] ) ; for ( let key of k ) { tags . add ( key ) ; } } } return [ ... tags . values ( ) ] ; } param bot param tag export function getTag ( bot , tag ) { return bot . tags [ tag ] ; } param bot param tag export function getBotTag ( bot , tag ) { if ( tag === 'id' ) { return bot . id ; } else if ( tag === BOT_SPACE_TAG ) { return getBotSpace ( bot ) ; } return getTag ( bot , tag ) ; } /**
 * Creates a new codimensionntext ID.
 */ export function createDimensionId ( ) { return `${ shortUuid ( ) }` ; } param id param tags param space export function createBot ( id = uuid ( ) , tags = { } , space ? ) { if ( hasValue ( space ) ) { return { id , tags , space , } ; } return { id , tags } ; } export function createPrecalculatedBot ( id = uuid ( ) , values = { } , tags ? , space ? ) { if ( hasValue ( space ) ) { return { id , space , precalculated : true , tags : tags || values , values , } ; } return { id , precalculated : true , tags : tags || values , values , } ; } param workspace export function calculateGridScale ( calc , workspace , defaultSurfaceScale = DEFAULT_WORKSPACE_SCALE , defaultGridScale = DEFAULT_WORKSPACE_GRID_SCALE ) { if ( workspace ) { const scale = calculateNumericalTagValue ( calc , workspace , `auxPortalSurfaceScale` , defaultSurfaceScale ) ; const gridScale = calculateNumericalTagValue ( calc , workspace , `auxPortalGridScale` , defaultGridScale ) ; return calculateGridScaleFromConstants ( scale , gridScale ) ; } else { return calculateGridScaleFromConstants ( defaultSurfaceScale , defaultGridScale ) ; } } param surfaceScale param gridScale export function calculateGridScaleFromConstants ( surfaceScale , gridScale ) { return surfaceScale * gridScale ; } param prev param current export function calculateStateDiff ( prev , current ) { prev = prev || { } ; current = current || { } ; let diff = { addedBots : [ ] , removedBots : [ ] , updatedBots : [ ] , } ; const ids = union ( keys ( prev ) , keys ( current ) ) ; ids . forEach ( ( id ) => { const prevVal = prev [ id ] ; const currVal = current [ id ] ; if ( prevVal && ! currVal ) { diff . removedBots . push ( prevVal . id ) ; } else if ( ! prevVal && currVal ) { diff . addedBots . push ( currVal ) ; } else if ( ! isEqual ( prevVal , currVal ) ) { diff . updatedBots . push ( currVal ) ; } } ) ; return diff ; } /**
 * Trims the leading # symbol off the given tag.
 */ export function trimTag ( tag ) { if ( tag . startsWith ( '#' ) || tag . startsWith ( '@' ) ) { return tag . substring ( 1 ) ; } return tag ; } param tag export function trimEvent ( tag ) { const withoutHash = trimTag ( tag ) ; if ( withoutHash . endsWith ( '()' ) ) { return withoutHash . substring ( 0 , withoutHash . length - 2 ) ; } return withoutHash ; } param calc param bot param tag export function getBotStringList ( calc , bot , tag ) { let value = calculateBotValue ( calc , bot , tag ) ; if ( value && ! Array . isArray ( value ) ) { value = [ value ] ; } return value ; } param calc param bot export function getBotVersion ( calc , bot ) { return calculateNumericalTagValue ( calc , bot , 'auxVersion' , undefined ) ; } param calc param bot param dimension export function getBotIndex ( calc , bot , dimension ) { return calculateNumericalTagValue ( calc , bot , `${ dimension }SortOrder` , 0 ) ; } param calc param bot param dimension export function getBotPosition ( calc , bot , dimension ) { return { x : calculateNumericalTagValue ( calc , bot , `${ dimension }X` , 0 ) , y : calculateNumericalTagValue ( calc , bot , `${ dimension }Y` , 0 ) , z : calculateNumericalTagValue ( calc , bot , `${ dimension }Z` , 0 ) , } ; } param calc param bot param dimension export function getBotRotation ( calc , bot , dimension ) { return { x : calculateNumericalTagValue ( calc , bot , `${ dimension }RotationX` , 0 ) , y : calculateNumericalTagValue ( calc , bot , `${ dimension }RotationY` , 0 ) , z : calculateNumericalTagValue ( calc , bot , `${ dimension }RotationZ` , 0 ) , } ; } param context param obj param multiplier param defaultScale param prefix export function getBotScale ( context , obj , defaultScale = 1 , prefix = 'aux' ) { return cacheFunction ( context , 'getBotScale' , ( ) => { const scaleX = calculateNumericalTagValue ( context , obj , `${ prefix }ScaleX` , defaultScale ) ; const scaleY = calculateNumericalTagValue ( context , obj , `${ prefix }ScaleY` , defaultScale ) ; const scaleZ = calculateNumericalTagValue ( context , obj , `${ prefix }ScaleZ` , defaultScale ) ; const uniformScale = calculateNumericalTagValue ( context , obj , `${ prefix }Scale` , 1 ) ; return { x : scaleX * uniformScale , z : scaleZ * uniformScale , y : scaleY * uniformScale , } ; } , obj . id , defaultScale , prefix ) ; } param calc param bot export function getBotShape ( calc , bot ) { const shape = calculateBotValue ( calc , bot , 'auxForm' ) ; if ( shape === 'cube' || shape === 'sphere' || shape === 'sprite' || shape === 'mesh' || shape === 'iframe' || shape === 'nothing' || shape === 'frustum' || shape === 'helix' || shape === 'egg' || shape === 'hex' || shape === 'cursor' || shape === 'portal' || shape === 'dimension' ) { return shape ; } return DEFAULT_BOT_SHAPE ; } param calc param bot export function getMenuBotForm ( calc , bot ) { const shape = calculateBotValue ( calc , bot , 'auxForm' ) ; if ( shape === 'button' || shape === 'input' ) { return shape ; } return DEFAULT_MENU_BOT_FORM ; } param calc param bot export function getMenuBotHoverStyle ( calc , bot ) { let shape = calculateBotValue ( calc , bot , 'auxMenuItemHoverMode' ) ; if ( shape === 'hover' || shape === 'none' ) { return shape ; } else { const onClick = calculateBotValue ( calc , bot , 'onClick' ) ; return hasValue ( onClick ) ? 'hover' : 'none' ; } } param calc param bot export function getBotSubShape ( calc , bot ) { const shape = calculateBotValue ( calc , bot , 'auxFormSubtype' ) ; if ( shape === 'gltf' || shape === 'html' || shape === 'src' ) { return shape ; } return null ; } param calc param bot export function getBotLabelAnchor ( calc , bot ) { return getBotTagAnchor ( calc , bot , 'auxLabelPosition' ) ; } export function getBotTagAnchor ( calc , bot , tag ) { const anchor = calculateBotValue ( calc , bot , tag ) ; if ( anchor === 'back' || anchor === 'floating' || anchor === 'front' || anchor === 'left' || anchor === 'right' || anchor === 'top' ) { return anchor ; } return DEFAULT_LABEL_ANCHOR ; } param calc param bot export function getBotLabelAlignment ( calc , bot ) { const anchor = calculateBotValue ( calc , bot , 'auxLabelAlignment' ) ; if ( anchor === 'center' || anchor === 'left' || anchor === 'right' ) { return anchor ; } return DEFAULT_LABEL_ALIGNMENT ; } param calc param bot export function getBotLabelPadding ( calc , bot ) { const padding = calculateNumericalTagValue ( calc , bot , 'auxLabelPadding' , 0 ) ; const x = calculateNumericalTagValue ( calc , bot , 'auxLabelPaddingX' , 0 ) ; const y = calculateNumericalTagValue ( calc , bot , 'auxLabelPaddingY' , 0 ) ; const horizontal = padding + x ; const vertical = padding + y ; return { horizontal : isIrrational ( horizontal ) ? 0 : horizontal , vertical : isIrrational ( vertical ) ? 0 : vertical , } ; } param val function isIrrational ( val ) { return isNaN ( val ) || val === Infinity || val === - Infinity ; } param calc param bot export function getBotScaleMode ( calc , bot ) { const anchor = calculateBotValue ( calc , bot , 'auxScaleMode' ) ; if ( anchor === 'fit' || anchor === 'absolute' ) { return anchor ; } return DEFAULT_SCALE_MODE ; } param calc param bot export function getBotOrientationMode ( calc , bot ) { const mode = < BotOrientationMode > ( calculateStringTagValue ( calc , bot , 'auxOrientationMode' , DEFAULT_ORIENTATION_MODE ) ) ; if ( mode === 'absolute' || mode === 'billboard' || mode === 'billboardTop' || mode === 'billboardFront' ) { return mode ; } return DEFAULT_ORIENTATION_MODE ; } const possibleAnchorPoints = new Set ( [ 'center' , 'front' , 'back' , 'bottom' , 'top' , 'left' , 'right' , ] as const ) ; param calc param bot export function getBotAnchorPoint ( calc , bot ) { const mode = < BotAnchorPoint > calculateBotValue ( calc , bot , 'auxAnchorPoint' ) ; return calculateAnchorPoint ( mode ) ; } param value export function calculateAnchorPoint ( value ) { if ( Array . isArray ( value ) ) { if ( value . length >= 3 && value . every ( ( v ) => typeof v === 'number' ) ) { return value ; } } else if ( possibleAnchorPoints . has ( value as any ) ) { return value ; } return DEFAULT_ANCHOR_POINT ; } param point export function calculateAnchorPointOffset ( point ) { if ( typeof point === 'string' ) { let offset = { x : 0 , y : 0 , z : 0 , } ; if ( point === 'center' ) { offset . z = 0 ; } else if ( point === 'top' ) { offset . z = - 0.5 ; } else if ( point === 'bottom' ) { offset . z = 0.5 ; } else if ( point === 'front' ) { offset . y = - 0.5 ; } else if ( point === 'back' ) { offset . y = 0.5 ; } else if ( point === 'left' ) { offset . x = 0.5 ; } else if ( point === 'right' ) { offset . x = - 0.5 ; } else { offset . z = 0.5 ; } return offset ; } else { const [ x , y , z ] = point ; return { x : - x , y : - y , z : - z , } ; } } param calc param bot export function getAnchorPointOffset ( calc , bot ) { const point = getBotAnchorPoint ( calc , bot ) ; return calculateAnchorPointOffset ( point ) ; } const possibleMeetPortalAnchorPoints = new Set ( [ 'fullscreen' , 'top' , 'topRight' , 'topLeft' , 'bottom' , 'bottomRight' , 'bottomLeft' , 'left' , 'right' , ] as const ) ; param calc param bot export function getBotMeetPortalAnchorPoint ( calc , bot ) { const mode = < MeetPortalAnchorPoint > ( calculateBotValue ( calc , bot , 'auxMeetPortalAnchorPoint' ) ) ; if ( Array . isArray ( mode ) ) { if ( mode . every ( ( v ) => [ 'string' , 'number' ] . indexOf ( typeof v ) >= 0 ) ) { let result = mode . slice ( 0 , 4 ) ; while ( result . length < 4 ) { result . push ( 0 ) ; } return result as MeetPortalAnchorPoint ; } } else if ( possibleMeetPortalAnchorPoints . has ( mode ) ) { return mode ; } return DEFAULT_MEET_PORTAL_ANCHOR_POINT ; } param calc param bot export function getBotTagPortalAnchorPoint ( calc , bot ) { const mode = < MeetPortalAnchorPoint > ( calculateBotValue ( calc , bot , 'auxTagPortalAnchorPoint' ) ) ; if ( Array . isArray ( mode ) ) { if ( mode . every ( ( v ) => [ 'string' , 'number' ] . indexOf ( typeof v ) >= 0 ) ) { let result = mode . slice ( 0 , 4 ) ; while ( result . length < 4 ) { result . push ( 0 ) ; } return result as MeetPortalAnchorPoint ; } } else if ( possibleMeetPortalAnchorPoints . has ( mode ) ) { return mode ; } return DEFAULT_TAG_PORTAL_ANCHOR_POINT ; } param calc param bot export function getCameraType ( calc , bot ) { const mode = < PortalCameraType > ( calculateBotValue ( calc , bot , 'auxPortalCameraType' ) ) ; if ( mode === 'orthographic' || mode === 'perspective' ) { return mode ; } return null ; } param calc param bot export function getBotMeetPortalAnchorPointOffset ( calc , bot ) { const point = getBotMeetPortalAnchorPoint ( calc , bot ) ; return calculateMeetPortalAnchorPointOffset ( point ) ; } const botCursors = [ 'auto' , 'default' , 'none' , 'context-menu' , 'help' , 'pointer' , 'progress' , 'wait' , 'cell' , 'crosshair' , 'text' , 'vertical-text' , 'alias' , 'copy' , 'move' , 'no-drop' , 'not-allowed' , 'grab' , 'grabbing' , 'all-scroll' , 'col-resize' , 'row-resize' , 'n-resize' , 'e-resize' , 's-resize' , 'w-resize' , 'ne-resize' , 'nw-resize' , 'se-resize' , 'sw-resize' , 'ew-resize' , 'ns-resize' , 'nesw-resize' , 'nwse-resize' , 'zoom-in' , 'zoom-out' , ] ; param cursor export function getCursorCSS ( cursor ) { if ( ! hasValue ( cursor ) ) { return null ; } if ( typeof cursor === 'string' ) { return cursor ; } else if ( cursor . type === 'link' ) { return `url("${ cursor . url }") ${ cursor . x } ${ cursor . y }, auto` ; } } param value function calculateBotCursor ( calc , bot , tag ) { const value = calculateStringTagValue ( calc , bot , tag , null ) ; if ( ! hasValue ( value ) ) { return null ; } if ( botCursors . indexOf ( value ) >= 0 ) { return value as BotCursorType ; } try { const url = new URL ( value ) ; return { type : 'link' , url : value , x : calculateNumericalTagValue ( calc , bot , tag + 'HotspotX' , 0 ) , y : calculateNumericalTagValue ( calc , bot , tag + 'HotspotY' , 0 ) , } ; } catch { } return DEFAULT_BOT_CURSOR ; } param calc param bot export function getBotCursor ( calc , bot ) { return calculateBotCursor ( calc , bot , 'auxCursor' ) ; } param calc param bot export function getPortalCursor ( calc , bot ) { return calculateBotCursor ( calc , bot , 'auxPortalCursor' ) ; } param calc param bot export function getBotTagPortalAnchorPointOffset ( calc , bot ) { const point = getBotTagPortalAnchorPoint ( calc , bot ) ; return calculateMeetPortalAnchorPointOffset ( point ) ; } /**
 * Calculates the CSS style for the given meet portal anchor point.
 */ export function calculateMeetPortalAnchorPointOffset ( anchorPoint ) { if ( typeof anchorPoint === 'string' ) { if ( anchorPoint === 'top' ) { return { top : '0px' , height : '50%' , 'min-height' : '250px' , left : '0px' , right : '0px' , } ; } else if ( anchorPoint === 'topRight' ) { return { top : '25px' , height : '25%' , 'min-height' : '250px' , width : '25%' , 'min-width' : '250px' , right : '25px' , } ; } else if ( anchorPoint === 'topLeft' ) { return { top : '25px' , height : '25%' , 'min-height' : '250px' , width : '25%' , 'min-width' : '250px' , left : '25px' , } ; } else if ( anchorPoint === 'bottom' ) { return { bottom : '0px' , height : '50%' , 'min-height' : '250px' , left : '0px' , right : '0px' , } ; } else if ( anchorPoint === 'bottomRight' ) { return { bottom : '25px' , height : '25%' , 'min-height' : '250px' , width : '25%' , 'min-width' : '250px' , right : '25px' , } ; } else if ( anchorPoint === 'bottomLeft' ) { return { bottom : '25px' , height : '25%' , 'min-height' : '250px' , width : '25%' , 'min-width' : '250px' , left : '25px' , } ; } else if ( anchorPoint === 'left' ) { return { bottom : '0px' , height : '100%' , 'min-height' : '250px' , width : '50%' , 'min-width' : '250px' , left : '0px' , } ; } else if ( anchorPoint === 'right' ) { return { bottom : '0px' , height : '100%' , 'min-height' : '250px' , width : '50%' , 'min-width' : '250px' , right : '0px' , } ; } else { return { top : '0px' , right : '0px' , bottom : '0px' , left : '0px' , } ; } } else { const [ top , right , bottom , left ] = anchorPoint ; return { top : stringOrPx ( top ) , right : stringOrPx ( right ) , bottom : stringOrPx ( bottom ) , left : stringOrPx ( left ) , } ; } } function stringOrPx ( value ) { if ( typeof value === 'string' ) { return value ; } return `${ value }px` ; } const lodTags = new Set ( [ 'onMaxLODEnter' , 'onMaxLODExit' , 'onMinLODEnter' , 'onMinLODExit' , 'auxMaxLODThreshold' , 'auxMinLODThreshold' , ] as const ) ; param calc param bot export function botHasLOD ( calc , bot ) { for ( let tag of lodTags . values ( ) ) { const val = calculateBotValue ( calc , bot , tag ) ; if ( isScript ( val ) ) { return true ; } } return false ; } param virtualDistance param minThreshold param maxThreshold export function calculateBotLOD ( virtualDistance , minThreshold , maxThreshold ) { return virtualDistance < minThreshold ? 'min' : virtualDistance > maxThreshold ? 'max' : 'normal' ; } param calc param bot export function calculatePortalCameraControlsMode ( calc , bot ) { const mode = < PortalCameraControlsMode > ( calculateBotValue ( calc , bot , 'auxPortalCameraControls' ) ) ; if ( mode === 'player' || mode === false ) { return mode ; } return DEFAULT_PORTAL_CAMERA_CONTROLS_MODE ; } param calc param bot export function calculateLabelFontSize ( calc , bot ) { const mode = < BotLabelFontSize > ( calculateBotValue ( calc , bot , 'auxLabelFontSize' ) ) ; if ( mode === 'auto' ) { return mode ; } else if ( typeof mode === 'number' ) { if ( mode < 0.001 ) { return 0.001 ; } return mode ; } return DEFAULT_LABEL_FONT_SIZE ; } param calc param bot export function calculateLabelWordWrapMode ( calc , bot ) { const mode = < BotLabelWordWrap > ( calculateBotValue ( calc , bot , 'auxLabelWordWrapMode' ) ) ; if ( mode === 'breakCharacters' || mode === 'breakWords' || mode === 'none' ) { return mode ; } return DEFAULT_LABEL_WORD_WRAP_MODE ; } param calc param bot param dimension export function isConfigForContext ( calc , bot , dimension ) { const contexts = getBotConfigDimensions ( calc , bot ) ; return contexts . indexOf ( dimension ) >= 0 ; } param calc param bot export function isDimensionLocked ( calc , bot ) { return calculateBooleanTagValue ( calc , bot , 'auxPortalLocked' , false ) ; } param calc param bot export function getBotConfigDimensions ( calc , bot ) { const result = calculateBotValue ( calc , bot , 'auxDimensionConfig' ) ; return parseBotConfigDimensions ( result ) ; } param value export function parseBotConfigDimensions ( value ) { if ( typeof value === 'string' && hasValue ( value ) ) { return [ value ] ; } else if ( typeof value === 'number' && hasValue ( value ) ) { return [ value . toString ( ) ] ; } else if ( typeof value === 'boolean' && hasValue ( value ) ) { return [ value . toString ( ) ] ; } else if ( Array . isArray ( value ) ) { return value ; } return [ ] ; } param calc param dimensionBot param name export function getDimensionValue ( calc , dimensionBot , name ) { return calculateBotValue ( calc , dimensionBot , `auxPortal${ name }` ) ; } param calc param bot export function getBotTransformer ( calc , bot ) { const ids = calculateBotIds ( bot , 'transformer' ) ; if ( ids ) { return ids [ 0 ] ; } return null ; } param calc param bot export function isBotMovable ( calc , bot ) { return calculateBooleanTagValue ( calc , bot , 'auxDraggable' , true ) ; } param calc param bot export function isBotListening ( calc , bot ) { return calculateBooleanTagValue ( calc , bot , 'auxListening' , true ) ; } param calc param bot export function isDimensionMovable ( calc , bot ) { return calculateBooleanTagValue ( calc , bot , 'auxDimensionSurfaceMovable' , true ) ; } param calc param bot export function getDimensionPosition ( calc , bot ) { return { x : calculateNumericalTagValue ( calc , bot , `auxDimensionX` , 0 ) , y : calculateNumericalTagValue ( calc , bot , `auxDimensionY` , 0 ) , z : calculateNumericalTagValue ( calc , bot , `auxDimensionZ` , 0 ) , } ; } param calc param bot export function getDimensionRotation ( calc , bot ) { return { x : calculateNumericalTagValue ( calc , bot , `auxDimensionOrientationX` , 0 ) , y : calculateNumericalTagValue ( calc , bot , `auxDimensionOrientationY` , 0 ) , z : calculateNumericalTagValue ( calc , bot , `auxDimensionOrientationZ` , 0 ) , } ; } param calc param bot export function getDimensionMinimized ( calc , bot ) { return getDimensionValue ( calc , bot , 'SurfaceMinimized' ) ; } param calc param bot export function getDimensionColor ( calc , bot ) { return getDimensionValue ( calc , bot , 'Color' ) ; } param calc param bot export function getDimensionSize ( calc , bot ) { if ( getDimensionVisualizeMode ( calc , bot ) === 'surface' ) { return calculateNumericalTagValue ( calc , bot , `auxDimensionSurfaceSize` , DEFAULT_WORKSPACE_SIZE ) ; } return 0 ; } param calc param bot export function getDimensionVisualizeMode ( calc , bot ) { const val = calculateBotValue ( calc , bot , 'auxDimensionVisualize' ) ; if ( typeof val === 'boolean' ) { return val ; } if ( val === 'surface' ) { return val ; } else { return false ; } } param calc param bot export function getBuilderDimensionGrid ( calc , bot ) { const tags = tagsOnBot ( bot ) ; const gridTags = tags . filter ( ( t ) => t . indexOf ( 'auxDimensionConfig.surface.grid.' ) === 0 && t . indexOf ( ':' ) > 0 ) ; let val = { } ; for ( let tag of gridTags ) { val [ tag . substr ( 'auxDimensionConfig.surface.grid.' . length ) ] = calculateNumericalTagValue ( calc , bot , tag , undefined ) ; } return val ; } param calc param bot param key export function getDimensionGridHeight ( calc , bot , key ) { let contextGrid = getBuilderDimensionGrid ( calc , bot ) ; if ( contextGrid && contextGrid [ key ] ) { if ( contextGrid [ key ] ) { return contextGrid [ key ] ; } } return DEFAULT_WORKSPACE_HEIGHT ; } param calc param bot export function getDimensionGridScale ( calc , bot ) { return getDimensionValue ( calc , bot , 'GridScale' ) ; } param calc param bot export function getDimensionScale ( calc , bot ) { return ( getDimensionValue ( calc , bot , 'SurfaceScale' ) || DEFAULT_WORKSPACE_SCALE ) ; } param calc param bot export function getDimensionDefaultHeight ( calc , bot ) { return getDimensionValue ( calc , bot , 'SurfaceDefaultHeight' ) ; } param calc param dimension param position export function objectsAtDimensionGridPosition ( calc , dimension , position ) { return cacheFunction ( calc , 'objectsAtDimensionGridPosition' , ( ) => { const botsAtPosition = calc . lookup . query ( calc , [ dimension , `${ dimension }X` , `${ dimension }Y` ] , [ true , position . x , position . y ] , [ undefined , 0 , 0 ] ) ; return < Bot [ ] > sortBy ( botsAtPosition , ( o ) => getBotIndex ( calc , o , dimension ) , ( o ) => o . id ) ; } , dimension , position . x , position . y ) ; } param calc param dimension param gridPosition param bot export function getDropBotFromGridPosition ( calc , dimension , gridPosition , ... bots ) { const objs = differenceBy ( objectsAtDimensionGridPosition ( calc , dimension , gridPosition ) , bots , ( f ) => f . id ) ; return { other : objs [ 0 ] , } ; } param objects param workspaceId export function objectsAtWorkspace ( objects , workspaceId ) { return objects . filter ( ( o ) => { return o . tags . _workspace === workspaceId ; } ) ; } param calc param bot param data export function duplicateBot ( calc , bot , data ? ) { let copy = cloneDeep ( bot ) ; const tags = tagsOnBot ( copy ) ; const tagsToRemove = filterWellKnownAndDimensionTags ( calc , tags ) ; tagsToRemove . forEach ( ( t ) => { delete copy . tags [ t ] ; } ) ; let newBot = merge ( copy , data || { } ) ; newBot . id = uuid ( ) ; return < Bot > cleanBot ( newBot ) ; } param calc param tags export function filterWellKnownAndDimensionTags ( calc , tags ) { const contextsToRemove = getDimensions ( calc ) ; const tagsToRemove = tags . filter ( ( t ) => isWellKnownOrDimension ( t , contextsToRemove ) ) ; return tagsToRemove ; } param calc export function getDimensions ( calc ) { return union ( ... calc . objects . map ( ( o ) => getBotConfigDimensions ( calc , o ) ) ) ; } param tag param dimensions export function isWellKnownOrDimension ( tag , dimensions ) { return isTagWellKnown ( tag ) || dimensions . some ( ( c ) => tag . indexOf ( c ) === 0 ) ; } param value export function isBotTags ( value ) { return ! isBot ( value ) ; } export function simulationIdToString ( id ) { if ( id . host ) { let str = id . host ; if ( id . channel ) { str += `?inst=${ encodeURIComponent ( id . channel ) }` ; } return str ; } return id . channel ; } export function parseSimulationId ( id ) { try { let uri = new URL ( id ) ; const channel = uri . searchParams . get ( 'inst' ) ?? uri . searchParams . get ( 'server' ) ; if ( channel ) { return { success : true , host : `${ uri . protocol }//${ uri . host }` , channel , } ; } else { return { success : true , host : `${ uri . protocol }//${ uri . host }` , } ; } } catch ( ex ) { return { success : true , channel : id , } ; } } param url export function normalizeAUXBotURL ( url ) { const parsed = new URL ( url ) ; if ( parsed . pathname . indexOf ( '.aux' ) !== parsed . pathname . length - '.aux' . length ) { parsed . pathname = `${ parsed . pathname }.aux` ; return parsed . href ; } return url ; } param context param bot param tag param defaultValue export function calculateBotValueAsBot ( context , bot , tag , defaultValue ) { if ( bot . tags [ tag ] ) { const result = calculateBotValue ( context , bot , tag ) ; if ( isBot ( result ) ) { return result ; } } return defaultValue ; } param context param bot param tag param defaultValue export function calculateStringListTagValue ( context , bot , tag , defaultValue ) { let value = calculateBotValue ( context , bot , tag ) ; if ( typeof value === 'undefined' || value === null || value === '' ) { return defaultValue ; } else if ( ! Array . isArray ( value ) ) { value = [ value ] ; } if ( value ) { for ( let i = 0 ; i < value . length ; i ++ ) { let v = value [ i ] ; if ( typeof v !== 'undefined' && v !== null ) { value [ i ] = v . toString ( ) ; } } } return value ; } param context param bot param tag param defaultValue export function calculateNumericalTagValue ( context , bot , tag , defaultValue ) { const result = calculateBotValue ( context , bot , tag ) ; if ( typeof result === 'number' && result !== null ) { return result ; } return defaultValue ; } param context param bot param tag param defaultValue export function calculateBooleanTagValue ( context , bot , tag , defaultValue ) { const result = calculateBotValue ( context , bot , tag ) ; if ( typeof result === 'boolean' && result !== null ) { return result ; } else if ( typeof result === 'object' && result instanceof Boolean ) { return result . valueOf ( ) ; } return defaultValue ; } param context param bot param tag param defaultValue export function calculateStringTagValue ( context , bot , tag , defaultValue ) { const result = calculateBotValue ( context , bot , tag ) ; if ( typeof result === 'string' && result !== null ) { return result ; } return defaultValue ; } param context param bot param tag param defaultValue export function calculateBotIdTagValue ( bot , tag , defaultValue ) { const ids = calculateBotIds ( bot , tag ) ; if ( ids ) { return ids [ 0 ] ; } return defaultValue ; } param calc param bot export function isDestroyable ( calc , bot ) { return calculateBooleanTagValue ( calc , bot , 'auxDestroyable' , true ) ; } param calc param bot export function isEditable ( calc , bot ) { return calculateBooleanTagValue ( calc , bot , 'auxEditable' , true ) ; } param calc param bot export function isSimulation ( calc , bot ) { return ! ! getBotChannel ( calc , bot ) ; } param calc param bot export function getBotChannel ( calc , bot ) { return calculateBotValue ( calc , bot , 'server' ) ; } param calc param id export function getChannelBotById ( calc , id ) { const bots = calc . objects . filter ( ( o ) => { return ( isBotInDimension ( calc , o , 'aux.channels' ) && calculateBotValue ( calc , o , 'server' ) === id ) ; } ) ; if ( bots . length > 0 ) { return bots [ 0 ] ; } else { return null ; } } param context param bot param dimensionId export function isBotInDimension ( context , bot , dimensionId ) { if ( ! dimensionId ) return false ; let dimensionValue = calculateBooleanTagValue ( context , bot , dimensionId . valueOf ( ) , false ) ; return dimensionValue ; } param portal export function getPortalTag ( portal ) { if ( portal . endsWith ( 'Portal' ) ) { return portal ; } return `${ portal }Portal` ; } param context param bot param portal export function getPortalConfigBotID ( context , bot , portal ) { const tag = `${ getPortalTag ( portal ) }ConfigBot` ; return calculateStringTagValue ( context , bot , tag , null ) ; } param context param bot param dimensionId export function botDimensionSortOrder ( context , bot , dimensionId ) { if ( ! dimensionId ) return NaN ; const dimensionValue = calculateBotValue ( context , bot , `${ dimensionId }SortOrder` ) ; if ( typeof dimensionValue === 'string' ) { return dimensionValue ; } else if ( typeof dimensionValue === 'number' ) { return dimensionValue ; } else { return 0 ; } } export function isUserActive ( calc , bot ) { return calculateBooleanTagValue ( calc , bot , `auxPlayerActive` , false ) ; } param value export function formatValue ( value ) { if ( typeof value === 'object' ) { if ( ! value ) { return null ; } else if ( Array . isArray ( value ) ) { return `[${ value . map ( ( v ) => formatValue ( v ) ) . join ( ',' ) }]` ; } else if ( value instanceof Error ) { return value . toString ( ) ; } else if ( isBot ( value ) ) { return getShortId ( value ) ; } else if ( value instanceof Date ) { return value . toISOString ( ) ; } else { return JSON . stringify ( value ) ; } } else if ( typeof value !== 'undefined' && value !== null ) { return value . toString ( ) ; } else { return value ; } } param object param tag param formula export function calculateValue ( object , tag , formula ) { if ( isNumber ( formula ) ) { return parseFloat ( formula ) ; } else if ( formula === 'true' ) { return true ; } else if ( formula === 'false' ) { return false ; } else { return formula ; } } /**
 * Defines a symbol for a property that contains the original object that
 * a value was transformed from.
 */ export const ORIGINAL_OBJECT = Symbol ( 'ORIGINAL_OBJECT' ) ; param obj export function getOriginalObject ( obj ) { if ( typeof obj === 'object' && ! ! obj && ORIGINAL_OBJECT in obj ) { return obj [ ORIGINAL_OBJECT ] ; } return obj ; } export function getMaskSpaces ( bot ) { if ( ! bot . masks ) { return [ ] ; } return Object . keys ( bot . masks ) ; } param bot param tag export function getTagMaskSpaces ( bot , tag ) { if ( ! bot . masks ) { return [ ] ; } let spaces = [ ] as string [ ] ; for ( let space in bot . masks ) { if ( ! bot . masks [ space ] ) { continue ; } if ( tag in bot . masks [ space ] ) { spaces . push ( space ) ; } } return spaces ; } param bot export function tagMasksOnBot ( bot ) { if ( ! bot . masks ) { return [ ] ; } let tags = new Set < string > ( ) ; for ( let space in bot . masks ) { for ( let tag in bot . masks [ space ] ) { tags . add ( tag ) ; } } return [ ... tags . values ( ) ] ; } param bot param space param tag export function getTagMask ( bot , space , tag ) { if ( ! bot . masks ) { return undefined ; } if ( ! bot . masks [ space ] ) { return undefined ; } return bot . masks [ space ] [ tag ] ; } param bot param tag export function hasTagOrMask ( bot , tag ) { let hasTag = hasValue ( bot . tags [ tag ] ) ; if ( hasTag ) { return true ; } if ( bot . masks ) { for ( let space in bot . masks ) { if ( hasValue ( bot . masks [ space ] [ tag ] ) ) { return true ; } } } return false ; } param bot param tag export function hasMaskForTag ( bot , tag ) { for ( let space in bot . masks ) { if ( hasValue ( bot . masks [ space ] [ tag ] ) ) { return true ; } } return false ; } param bot param tag param space export function getTagValueForSpace ( bot , tag , space ) { if ( hasValue ( space ) ) { return bot . masks ?. [ space ] ?. [ tag ] ; } else { return bot . tags [ tag ] ; } } param bot param tag export function getSpaceForTag ( bot , tag ) { for ( let space of TAG_MASK_SPACE_PRIORITIES ) { if ( hasValue ( bot . masks ?. [ space ] ?. [ tag ] ) ) { return space ; } } return null ; } param tag param value param space export function getUpdateForTagAndSpace ( tag , value , space ) { if ( hasValue ( space ) ) { return { masks : { [ space ] : { [ tag ] : value , } , } , } ; } else { return { tags : { [ tag ] : value , } , } ; } } param tag export function parseNewTag ( tag ) { const script = parseScript ( tag ) ; const formula = parseFormula ( tag ) ; const name = script ?? formula ?? tag ; return { name , isScript : ! ! script , isFormula : ! ! formula , } ; }