@casual-simulation/aux-common
Version:
Common library for AUX projects
2,895 lines • 89.8 kB
JavaScript
import { DEFAULT_WORKSPACE_SCALE, DEFAULT_WORKSPACE_HEIGHT, DEFAULT_WORKSPACE_GRID_SCALE, DEFAULT_BUILDER_USER_COLOR, DEFAULT_PLAYER_USER_COLOR, DEFAULT_BOT_SHAPE, DEFAULT_WORKSPACE_SIZE, DEFAULT_LABEL_ANCHOR, BOT_SPACE_TAG, DEFAULT_ORIENTATION_MODE, DEFAULT_ANCHOR_POINT, DEFAULT_LABEL_ALIGNMENT, DEFAULT_SCALE_MODE, DEFAULT_MEET_PORTAL_ANCHOR_POINT, DEFAULT_TAG_PORTAL_ANCHOR_POINT, TAG_MASK_SPACE_PRIORITIES, DNA_TAG_PREFIX, DEFAULT_LABEL_FONT_SIZE, DEFAULT_LABEL_WORD_WRAP_MODE, DEFAULT_MENU_BOT_FORM, DEFAULT_PORTAL_CAMERA_CONTROLS_MODE, DEFAULT_BOT_CURSOR, BOT_LINK_TAG_PREFIX, DATE_TAG_PREFIX, STRING_TAG_PREFIX, NUMBER_TAG_PREFIX, DEFAULT_BOT_PORTAL_ANCHOR_POINT, VECTOR_TAG_PREFIX, ROTATION_TAG_PREFIX, SYSTEM_PORTAL_PANE, SYSTEM_PORTAL, SYSTEM_PORTAL_SEARCH, SYSTEM_PORTAL_DIFF, SHEET_PORTAL, DEFAULT_MESH_POSITIONING_MODE, DEFAULT_MAP_PORTAL_KIND, } from './Bot';
import TWEEN from '@tweenjs/tween.js';
import { cacheFunction } from './BotCalculationContext';
import { v4 as uuid } from 'uuid';
import { union, isEqual, sortBy, cloneDeep, differenceBy, intersectionBy, unionBy, } from 'es-toolkit/compat';
import { merge, shortUuid } from '../utils';
import { DateTime, SystemZone } from 'luxon';
import { Quaternion, Rotation, Vector2, Vector3 } from '../math';
export let isFormulaObjectSymbol = Symbol('isFormulaObject');
export let ShortId_Length = 5;
/**
* The default energy for actions.
*/
export const DEFAULT_ENERGY = 100000;
export const POSSIBLE_DIMENSION_VISUALIZERS = ['*'];
/**
* Determines whether the given tag value is a valid value or if
* it represents nothing.
* @param value The value.
*/
export function hasValue(value) {
return !(value === null || typeof value === 'undefined' || value === '');
}
/**
* Converts the given value to a string.
* @param value The value that should be rendered into a string.
* @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);
}
}
/**
* Cleans the bot by removing any null or undefined properties.
* @param bot The bot to clean.
*/
export function cleanBot(bot) {
let cleaned = merge({}, bot);
// Make sure we're not modifying another bot's tags
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;
}
/**
* Determines if the given workspace is currently minimized.
* @param workspace The 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;
}
/**
* Filters the given list of bots by whether they belong to the given selection.
* @param bots The bots to filter.
* @param selectionId The selection to check.
*/
export function filterBotsBySelection(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)));
});
}
/**
* Gets whether the bot is pointable.
* @param calc The calculation context.
* @param bot The bot.
*/
export function isBotPointable(calc, bot) {
return calculateBooleanTagValue(calc, bot, 'auxPointable', true);
}
/**
* Gets whether the bot is focusable.
* @param calc The calculation context.
* @param bot The bot.
*/
export function isBotFocusable(calc, bot) {
return calculateBooleanTagValue(calc, bot, 'auxFocusable', true);
}
/**
* Gets a list of tags that the given bots contain.
*
* @param bots The array of bots that the list of tags should be retrieved
* for.
* @param currentTags The current array of tags that is being displayed.
* The new list will try to preserve the order of the tags
* in this list.
* @param extraTags The list of tags that should not be removed from the
* output list.
* @param allowedTags The list of tags that should be allowed in the output list.
*/
export function botTags(bots, currentTags, extraTags, allowedTags = null) {
const botTags = bots
.flatMap((f) => Object.keys(f.tags))
.map((t) => ({ tag: t, space: null }));
const botMasks = bots.flatMap((b) => {
if (!b.masks) {
return [];
}
let tags = [];
for (let space in b.masks) {
let spaceTags = Object.keys(b.masks[space]).map((k) => ({
tag: k,
space: space,
}));
tags.push(...spaceTags);
}
return tags;
});
const allBotTags = unionBy(botTags, botMasks, tagComparer);
const extraTagPairs = extraTags.map((t) => ({ tag: t, space: null }));
const currentTagPairs = currentTags.map((t) => ({ tag: t, space: null }));
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 = bots.flatMap((f) => Object.keys(f.tags));
const nonHiddenTags = botTags.filter((t) => includeHidden || !isHiddenTag(t));
return nonHiddenTags;
}
/**
* Find bots that match the short ids.
* @param bots The bots to search through.
* @param shortIds The short ids to search for.
* @returns bot array or null if no matches found.
*/
export function botsFromShortIds(bots, shortIds) {
let matches = [];
shortIds.forEach((shortId) => {
let bot = this.botFromShortId(bots, shortId);
if (bot)
matches.push(bot);
});
if (matches.length > 0)
return matches;
else
return null;
}
/**
* Find bot that matches the short id.
* @param bots The bots to search through.
* @param shortId The short id to search for.
* @returns bot or undefined if no match found.
*/
export function botFromShortId(bots, shortId) {
return bots.find((f) => {
return getShortId(f) === shortId;
});
}
/**
* Return the short id for the bot.
* @param bot The bot to get short id for.
*/
export function getShortId(bot) {
let id = typeof bot === 'string' ? bot : bot.id;
if (typeof id !== 'string') {
if (id !== null && typeof id !== 'undefined') {
return 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;
}
/**
* Determines if the given tag is a hidden tag.
* @param tag The tag to test.
*/
export function isHiddenTag(tag) {
return /^_/.test(tag) || /(\w+)\._/.test(tag);
}
/**
* Determines if the given bot is a runtime bot.
* @param bot The bot to check.
*/
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 && bot.precalculated === true;
}
export function isExistingBot(bot) {
return bot && bot.id != undefined;
}
/**
* Gets the space that the given bot lives in.
* @param bot The 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;
}
export function calculateRawBotValue(context, object, tag) {
const value = object.tags[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 object.tags[newTag];
}
return value;
}
/**
* Calculates the list of bot IDs that are stored in the given tag in the given bot.
* @param bot The bot.
* @param tag The 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 = [];
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);
if (typeof value === 'number') {
const rawValue = calculateRawBotValue(context, bot, tag);
if (typeof rawValue === 'string') {
return rawValue;
}
}
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;
}
/**
* Determines if the given value represents a script.
* @param value The value.
*/
export function isScript(value) {
return typeof value === 'string' && value.indexOf('@') === 0;
}
/**
* Determines if the given value represents a script.
* @param value The value.
*/
export function isModule(value) {
return typeof value === 'string' && value.indexOf('📄') === 0;
}
/**
* Determiens if the given value represents a bot link.
* @param value The value.
*/
export function isBotLink(value) {
return typeof value === 'string' && value.startsWith(BOT_LINK_TAG_PREFIX);
}
/**
* Parses the given value into a list of Bot IDs if it is a link.
* Returns null if the value is not a bot link.
* @param value The value to parse.
*/
export function parseBotLink(value) {
if (isBotLink(value)) {
const split = value.substring(BOT_LINK_TAG_PREFIX.length).split(',');
return split.filter((id) => hasValue(id));
}
return null;
}
/**
* Creates a bot link that links to the given bot IDs.
* @param botIds The IDs of the bots to link to.
*/
export function createBotLink(botIds) {
return `${BOT_LINK_TAG_PREFIX}${botIds.join(',')}`;
}
/**
* Determines if the given value represents a date time.
* @param value The value.
*/
export function isBotDate(value) {
return typeof value === 'string' && value.startsWith(DATE_TAG_PREFIX);
}
/**
* Parses the given value into a date time object.
* Returns null if the value is not a date.
* @param value The value to parse.
*/
export function parseBotDate(value) {
if (isBotDate(value)) {
const dateString = value.substring(DATE_TAG_PREFIX.length);
const [date, timezone] = dateString.split(' ');
const hasTimezone = hasValue(timezone);
const result = DateTime.fromISO(date, {
setZone: !hasTimezone,
zone: hasTimezone ? timezone : 'utc',
});
if (result.isValid) {
return result;
}
}
return null;
}
/**
* Formats the given value into a parseable string.
* @param value The date to format.
*/
export function formatBotDate(value) {
const dateString = value.toISO({
suppressMilliseconds: true,
});
const partialFormat = `${DATE_TAG_PREFIX}${dateString}`;
if (value.zone === SystemZone.instance) {
return partialFormat + ` local`;
}
else if (!value.zone.isUniversal) {
return partialFormat + ` ${value.zoneName}`;
}
else {
return partialFormat;
}
}
/**
* Parses the given value into a script.
* Returns the script if the value is a script.
* Returns null if the value is not a script.
* @param value The value to parse.
*/
export function parseScript(value) {
if (isScript(value)) {
return value.substring(1);
}
return null;
}
/**
* Parses the given value into a script.
* Returns the script if the value is a script.
* Returns the value if it is not a script.
* @param value The value to parse.
*/
export function parseScriptSafe(value) {
var _a;
return (_a = parseScript(value)) !== null && _a !== void 0 ? _a : value;
}
/**
* Parses the given value into a module script.
* Returns the module script if the value is a module.
* Returns null if the value is not a module.
* @param value The value to parse.
*/
export function parseModule(value) {
if (isModule(value)) {
return value.substring('📄'.length);
}
return null;
}
/**
* Parses the given value into a script.
* Returns the script if the value is a script.
* Returns the value if it is not a script.
* @param value The value to parse.
*/
export function parseModuleSafe(value) {
var _a;
return (_a = parseModule(value)) !== null && _a !== void 0 ? _a : value;
}
/**
* Parses the given value into a formula.
* Returns the formula if the value is a formula.
* Returns null if the value is not a formula.
* @param value The value to parse.
*/
export function parseFormula(value) {
if (isFormula(value)) {
return value.substring(DNA_TAG_PREFIX.length);
}
return null;
}
/**
* Parses the given value into a formula.
* Returns the JSON if the value is a formula.
* Returns the value if it is not a formula.
* @param value The value to parse.
*/
export function parseFormulaSafe(value) {
var _a;
return (_a = parseFormula(value)) !== null && _a !== void 0 ? _a : 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;
}
/**
* Determines if the given value is for a script entrypoint.
* @param prefix The prefix to check against.
* @param value The value to check.
*/
export function isPortalScript(prefix, value) {
return typeof value === 'string' && value.startsWith(prefix);
}
export function hasPortalScript(prefixes, value) {
return getScriptPrefix(prefixes, value) !== null;
}
/**
* Determines which of the given script prefixes the given value matches.
* @param prefixes The script prefixes to test against the value.
* @param value The value to test.
* @returns
*/
export function getScriptPrefix(prefixes, value) {
if (typeof value === 'string') {
for (let prefix of prefixes) {
if (value.startsWith(prefix)) {
return prefix;
}
}
}
return null;
}
const INFINITIES = new Set(['infinity', '-infinity']);
/**
* Determines if the given value represents a number.
*/
export function isNumber(value) {
value = parseTaggedNumber(value);
return (typeof value === 'string' &&
value.length > 0 &&
((/^-?\d*(?:\.?\d+)?(?:\d[eE]-?\d+)?$/.test(value) && value !== '-') ||
(typeof value === 'string' && INFINITIES.has(value.toLowerCase()))));
}
/**
* Determines if the given value is a string that is tagged with the 📝 emoji.
* @param value The value to check.
*/
export function isTaggedString(value) {
return typeof value === 'string' && value.startsWith(STRING_TAG_PREFIX);
}
/**
* Parses the given tagged string into a regular string value.
* @param value The value that should be parsed as a string.
*/
export function parseTaggedString(value) {
if (isTaggedString(value)) {
return value.substring(STRING_TAG_PREFIX.length);
}
return value;
}
/**
* Determines if the given value starts with the 🔢 emoji tag.
* @param value The value to test.
*/
export function isTaggedNumber(value) {
return typeof value === 'string' && value.startsWith(NUMBER_TAG_PREFIX);
}
/**
* Parses the given tagged number into a regular number value.
* @param value The value to parse.
*/
export function parseTaggedNumber(value) {
if (isTaggedNumber(value)) {
return value.substring(NUMBER_TAG_PREFIX.length);
}
return value;
}
/**
* Parses the given value into a number.
* @param value The value to parse.
*/
export function parseNumber(value) {
value = parseTaggedNumber(value);
if (isNumber(value)) {
const valueLowerCase = value.toLowerCase();
if (valueLowerCase === 'infinity') {
return Infinity;
}
else if (valueLowerCase === '-infinity') {
return -Infinity;
}
return parseFloat(value);
}
return NaN;
}
/**
* Determines if the given value is a valid real number and returns it if it is.
* If it is not, then the given default value will be returned.
* @param value The value to check for real-ness. All numerical values are considered real except NaN, and +/- Infinity.
* @param defaultIfInvalid The default value to return if the value is not real.
*/
export function realNumberOrDefault(value, defaultIfInvalid) {
if (typeof value !== 'number' ||
isNaN(value) ||
value === Infinity ||
value === -Infinity) {
return defaultIfInvalid;
}
return value;
}
/**
* Formats the given value into a parseable vector string.
* @param vector The vector to format.
*/
export function formatBotVector(vector) {
if (!('z' in vector)) {
return `➡️${vector.x},${vector.y}`;
}
else {
return `➡️${vector.x},${vector.y},${vector.z}`;
}
}
/**
* Determines if the given value represents a bot vector value.
* @param value The value to check.
*/
export function isBotVector(value) {
return typeof value === 'string' && value.startsWith(VECTOR_TAG_PREFIX);
}
/**
* Parses the given value into a Vector2 or Vector3 value.
* @param value The value to parse as a bot vector.
*/
export function parseBotVector(value) {
if (typeof value !== 'string') {
return null;
}
if (!value.startsWith('➡️')) {
return null;
}
const valueWithoutArrow = value.substring('➡️'.length);
const firstComma = valueWithoutArrow.indexOf(',');
if (firstComma < 0) {
return null;
}
const firstNumber = valueWithoutArrow.substring(0, firstComma);
const valueWithoutFirstNumber = valueWithoutArrow.substring(firstComma + 1);
const secondComma = valueWithoutFirstNumber.indexOf(',');
if (secondComma < 0) {
return new Vector2(parseNumber(firstNumber), parseNumber(valueWithoutFirstNumber));
}
const secondNumber = valueWithoutFirstNumber.substring(0, secondComma);
const thirdNumber = valueWithoutFirstNumber.substring(secondComma + 1);
return new Vector3(parseNumber(firstNumber), parseNumber(secondNumber), parseNumber(thirdNumber));
}
/**
* Formats the given value into a parseable rotation string.
* @param vector The vector to format.
*/
export function formatBotRotation(rotation) {
const q = rotation instanceof Rotation ? rotation.quaternion : rotation;
return `🔁${q.x},${q.y},${q.z},${q.w}`;
}
/**
* Determines if the given value represents a bot rotation.
* @param value The value to check.
*/
export function isBotRotation(value) {
return typeof value === 'string' && value.startsWith(ROTATION_TAG_PREFIX);
}
/**
* Parses the given value into a Rotation value.
* @param value The value to parse as a bot vector.
*/
export function parseBotRotation(value) {
if (typeof value !== 'string') {
return null;
}
if (!value.startsWith('🔁')) {
return null;
}
const valueWithoutArrow = value.substring('🔁'.length);
const firstComma = valueWithoutArrow.indexOf(',');
if (firstComma < 0) {
return null;
}
const firstNumber = valueWithoutArrow.substring(0, firstComma);
const valueWithoutFirstNumber = valueWithoutArrow.substring(firstComma + 1);
const secondComma = valueWithoutFirstNumber.indexOf(',');
if (secondComma < 0) {
return null;
}
const secondNumber = valueWithoutFirstNumber.substring(0, secondComma);
const valueWithoutSecondNumber = valueWithoutFirstNumber.substring(secondComma + 1);
const thirdComma = valueWithoutSecondNumber.indexOf(',');
if (thirdComma < 0) {
return null;
}
const thirdNumber = valueWithoutSecondNumber.substring(0, thirdComma);
const fourthNumber = valueWithoutSecondNumber.substring(thirdComma + 1);
if (fourthNumber.length <= 0) {
return null;
}
return new Rotation(new Quaternion(parseNumber(firstNumber), parseNumber(secondNumber), parseNumber(thirdNumber), parseNumber(fourthNumber)));
}
/**
* Determines if the given object is a bot.
* @param object The object to check.
*/
export function isBot(object) {
if (object) {
return !!object.id && !!object.tags;
}
return false;
}
/**
* Gets the array of objects in the given state that are currently active.
* @param state The state to get the active objects of.
*/
export function getActiveObjects(state) {
return Object.values(state);
}
/**
* Determines if the given tag is "well known".
* @param tag The tag.
*/
export function isTagWellKnown(tag) {
return isHiddenTag(tag);
}
/**
* Determines if the bots are equal disregarding well-known hidden tags
* and their IDs. Bot "appearance equality" means instead of asking "are these bots exactly the same?"
* we ask "are these bots functionally the same?". In this respect we care about things like color, label, etc.
* We also care about things like draggable but not _position, _index _selection, etc.
*
* You can determine if a tag is "well-known" by using isTagWellKnown().
* @param first The first bot.
* @param second The second bot.
*/
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(Object.keys(first.tags), Object.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;
}
/**
* Validates the given tag and returns any errors for it.
* @param tag The 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;
}
/**
* Gets a partial bot that toggles whether the given bot is apart of the given selection.
* @param bot The bot.
* @param selectionId The ID of the selection.
* @param userId The User that is adding the bot to the selection.
*/
export function toggleBotSelection(bot, selectionId, userId) {
return {
tags: {
[selectionId]: !bot.tags[selectionId],
},
};
}
/**
* Gets the color that the given user bot should appear as.
* @param calc The bot calculation context.
* @param userBot The user bot.
* @param globalsBot The globals bot.
* @param domain The 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;
}
}
/**
* Gets the menu ID that is used for the given user.
* @param userBot The bot for the user.
*/
export function getUserMenuId(calc, userBot) {
return calculateBotValue(calc, userBot, 'menuPortal');
}
/**
* Gets the list of bots that are in the user's menu.
* @param calc The bot calculation context.
* @param userBot The user bot to use.
*/
export function getBotsInMenu(calc, userBot) {
const context = getUserMenuId(calc, userBot);
return botsInDimension(calc, context);
}
/**
* Gets the list of bots that are in the given dimension.
* @param calc The bot calculation context.
* @param dimension The dimension to search for bots in.
*/
export function botsInDimension(calc, dimension) {
const bots = calc.objects.filter((f) => isBotInDimension(calc, f, dimension));
return sortBy(bots, (f) => botDimensionSortOrder(calc, f, dimension));
}
/**
* Gets a diff that adds a bot to the given dimension.
* If the bot is already in the dimension, then nothing happens.
* If other bots are already at the given position, then the bot will be placed at the topmost index.
* @param calc The bot calculation context.
* @param dimension The dimension that the bot should be added to.
* @param x The x position that the bot should be placed at.
* @param y The x position in the dimension that the bot should be placed at.
* @param index The index that the bot should be placed at.
*/
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),
};
}
/**
* Gets a diff that removes a bot from the given dimension.
* @param calc The bot calculation context.
* @param dimension The dimension that the bot should be removed from.
*/
export function removeFromDimensionDiff(calc, dimension) {
return {
[dimension]: null,
[`${dimension}X`]: null,
[`${dimension}Y`]: null,
[`${dimension}SortOrder`]: null,
};
}
/**
* Gets a diff that sets a bot's position in the given dimension.
* @param calc The bot calculation context.
* @param dimension The dimension.
* @param x The X position.
* @param y The Y position.
* @param index The 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;
}
/**
* Gets the bot update needed to add the given bot to the given user's menu.
* @param calc The calculation context.
* @param userBot The bot of the user.
* @param id The ID that should be used for the menu item. This is separate from bot ID.
* @param index The index that the bot should be added to. Positive infinity means add at the end. 0 means add at the beginning.
*/
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,
},
};
}
/**
* Gets the bot update needed to remove a bot from the given user's menu.
* @param calc The bot calculation context.
* @param userBot The bot of the user.
*/
export function removeBotFromMenu(calc, userBot) {
const dimension = getUserMenuId(calc, userBot);
return {
tags: {
[dimension]: null,
[`${dimension}Id`]: null,
[`${dimension}SortOrder`]: null,
},
};
}
/**
* Gets the list of tags that are on the given bot.
* @param bot
*/
export function tagsOnBot(bot) {
let tags = new Set(Object.keys(bot.tags));
if (bot.masks) {
for (let space in bot.masks) {
let k = Object.keys(bot.masks[space]);
for (let key of k) {
tags.add(key);
}
}
}
return [...tags.values()];
}
/**
* Gets the specified tag value from the specified bot.
* @param bot The bot that the tag should be retrieved from.
* @param tag The tag to retrieve.
*/
export function getTag(bot, tag) {
return bot.tags[tag];
}
/**
* Gets the specified tag from the specified bot.
* @param bot The bot that the tag should be retrieved from.
* @param tag The tag to retrieve.
*/
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()}`;
}
/**
* Creates a bot with a new ID and the given tags.
* @param id The ID of the bot.
* @param tags The tags to use in the bot.
* @param space The space of the bot.
*/
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,
};
}
/**
* Calculates the grid scale for the given workspace.
* @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);
}
}
/**
* Calculates the grid scale from the given constants.
* @param surfaceScale
* @param gridScale
*/
export function calculateGridScaleFromConstants(surfaceScale, gridScale) {
return surfaceScale * gridScale;
}
/**
* Calculates the difference between the two given states.
* In particular, it calculates which operations need to be performed on prev in order to get current.
* The returned object contains the bots that were added, removed, and/or updated between the two states.
* This operation runs in O(n) time where n is the number of bots.
* @param prev The previous state.
* @param current The current state.
*/
export function calculateStateDiff(prev, current) {
prev = prev || {};
current = current || {};
let diff = {
addedBots: [],
removedBots: [],
updatedBots: [],
};
const ids = union(Object.keys(prev), Object.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;
}
/**
* Trims the leading # symbol and trailing () symbols from the given tag.
* @param tag The tag.
*/
export function trimEvent(tag) {
const withoutHash = trimTag(tag);
if (withoutHash.endsWith('()')) {
return withoutHash.substring(0, withoutHash.length - 2);
}
return withoutHash;
}
/**
* Gets a list of strings from the given bot and tag.
* @param calc The bot calculation context.
* @param bot The bot.
* @param tag The tag.
*/
export function getBotStringList(calc, bot, tag) {
let value = calculateBotValue(calc, bot, tag);
if (value && !Array.isArray(value)) {
value = [value];
}
return value;
}
/**
* Gets the AUX_BOT_VERSION number that the given bot was created with.
* If not specified, then undefined is returned.
* @param calc The bot calculation context.
* @param bot THe bot.
*/
export function getBotVersion(calc, bot) {
return calculateNumericalTagValue(calc, bot, 'auxVersion', undefined);
}
/**
* Gets the index that the given bot is at in the given dimension.
* @param calc The calculation context to use.
* @param bot The bot.
* @param dimension The dimension.
*/
export function getBotIndex(calc, bot, dimension) {
return calculateNumericalTagValue(calc, bot, `${dimension}SortOrder`, 0);
}
/**
* Gets the position value from the given bot and tag.
* @param bot The bot.
* @param tag The tag.
*/
export function getTagPosition(bot, tag, defaultValue = 0) {
const vector = calculateBotVectorTagValue(null, bot, tag, null);
if (vector instanceof Vector2) {
return {
x: vector.x,
y: vector.y,
z: 0,
};
}
else if (vector instanceof Vector3) {
return {
x: vector.x,
y: vector.y,
z: vector.z,
};
}
return {
x: calculateNumericalTagValue(null, bot, `${tag}X`, defaultValue),
y: calculateNumericalTagValue(null, bot, `${tag}Y`, defaultValue),
z: calculateNumericalTagValue(null, bot, `${tag}Z`, defaultValue),
};
}
/**
* Gets the rotation value from the given bot and tag.
* @param bot The bot.
* @param tag The tag.
*/
export function getTagRotation(bot, tag) {
const rotation = calculateBotRotationTagValue(null, bot, tag, null);
if (rotation) {
return rotation;
}
const x = calculateNumericalTagValue(null, bot, `${tag}X`, 0);
const y = calculateNumericalTagValue(null, bot, `${tag}Y`, 0);
const z = calculateNumericalTagValue(null, bot, `${tag}Z`, 0);
const w = calculateNumericalTagValue(null, bot, `${tag}W`, null);
if (hasValue(w)) {
return new Rotation(new Quaternion(x, y, z, w));
}
else {
return new Rotation({
euler: {
x,
y,
z,
},
});
}
}
/**
* Gets the position that the given bot is at in the given dimension.
* @param calc The calculation context to use.
* @param bot The bot.
* @param dimension The dimension.
*/
export function getBotPosition(calc, bot, dimension) {
const vector = calculateBotVectorTagValue(calc, bot, `${dimension}Position`, null);
if (vector instanceof Vector2) {
return {
x: vector.x,
y: vector.y,
z: 0,
};
}
else if (vector instanceof Vector3) {
return {
x: vector.x,
y: vector.y,
z: vector.z,
};
}
return {
x: calculateNumericalTagValue(calc, bot, `${dimension}X`, 0),
y: calculateNumericalTagValue(calc, bot, `${dimension}Y`, 0),
z: calculateNumericalTagValue(calc, bot, `${dimension}Z`, 0),
};
}
/**
* Gets the rotation that the given bot is at in the given dimension.
* @param calc The calculation context to use.
* @param bot The bot.
* @param dimension The dimension.
*/
export function getBotRotation(calc, bot, dimension) {
const rotation = calculateBotRotationTagValue(calc, bot, `${dimension}Rotation`, null);
if (rotation) {
return rotation;
}
return new Rotation({
euler: {
x: calculateNumericalTagValue(calc, bot, `${dimension}RotationX`, 0),
y: calculateNumericalTagValue(calc, bot, `${dimension}RotationY`, 0),
z: calculateNumericalTagValue(calc, bot, `${dimension}RotationZ`, 0),
},
});
}
/**
* Calculates the auxScaleX, auxScaleY, and auxScaleZ values from the given object.
* @param context The calculation context.
* @param obj The object.
* @param multiplier The value that scale values should be multiplied by.
* @param defaultScale The default value.
* @param prefix The optional prefix for the tags. Defaults to `aux.`
*/
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);
const x = scaleX * uniformScale;
const y = scaleY * uniformScale;
const z = scaleZ * uniformScale;
return {
x: Math.max(0.000000000000000001, Math.abs(x)) *
Math.sign(x || 1),
y: Math.max(0.000000000000000001, Math.abs(y)) *
Math.sign(y || 1),
z: Math.max(0.000000000000000001, Math.abs(z)) *
Math.sign(z || 1),
};
}, obj.id, defaultScale, prefix);
}
/**
* Gets the shape of the bot.
* @param calc The calculation context to use.
* @param bot The bot.
*/
export function getBotShape(calc, bot) {
const shape = calculateBotValue(calc, bot, 'auxForm');
if (shape === 'cube' ||
shape === 'skybox' ||
shape === 'sphere' ||
shape === 'sprite' ||
shape === 'map' ||
shape === 'mesh' ||
shape === 'iframe' ||
shape === 'nothing' ||
shape === 'frustum' ||
shape === 'helix' ||
shape === 'egg' ||
shape === 'hex' ||
shape === 'cursor' ||
shape === 'portal' ||
shape === 'dimension' ||
shape === 'circle' ||
shape === 'keyboard' ||
shape === 'codeButton' ||
shape === 'codeHint' ||
shape === 'spherePortal' ||
shape === 'light') {
return shape;
}
return DEFAULT_BOT_SHAPE;
}
export function getDefaultEasing(easing) {
return hasValue(easing)
? typeof easing === 'function'
? { mode: 'inout', type: 'linear' }
: typeof easing === 'string'
? {
mode: 'inout',
type: easing,
}
: easing
: {
mode: 'inout',
type: 'linear',
};
}
export function getEasing(easing) {
if (typeof easing === 'function') {
return easing;
}
const value = getDefaultEasing(easing);
return getTweenEasing(value);
}
export function getTweenEasing(easing) {
switch (easing.type) {
case 'linear':
default:
return TWEEN.Easing.Linear.None;
case 'circular':
return resolveEaseType(easing.mode, TWEEN.Easing.Circular);
case 'cubic':
return resolveEaseType(easing.mode, TWEEN.Easing.Cubic);
case 'exponential':
return resolveEaseType(easing.mode, TWEEN.Easing.Exponential);
case 'elastic':
return resolveEaseType(easing.mode, TWEEN.Easing.Elastic);
case 'quadratic':
return resolveEaseType(easing.mode, TWEEN.Easing.Quadratic);
case 'quartic':
return resolveEaseType(easing.mode, TWEEN.Easing.Quartic);
case 'quintic':
return resolveEaseType(easing.mode, TWEEN.Easing.Quintic);
case 'sinusoidal':
return resolveEaseType(easing.mode, TWEEN.Easing.Sinusoidal);
}
}
function resolveEaseType(mode, val) {
if ('None' in val) {
return val.None;
}
else {
switch (mode) {
case 'in':
return val.In;
case 'out':
return val.Out;
case 'inout':
default:
return val.InOut;
}
}
}
/**
* Gets the form of a menu bot.
* @param calc The calculation context to use.
* @param bot The bot.
*/
export function getMenuBotForm(calc, bot) {
const shape = calculateBotValue(calc, bot, 'auxForm');
if (shape === 'button' || shape === 'input') {
return shape;
}
return DEFAULT_MENU_BOT_FORM;
}
/**
* Gets the formSubType of menu bot.
* @param calc The calculation context to use.
* @param bot The bot.
* @returns
*/
export function getMenuBotSubtype(calc, bot) {
const subShape = calculateBotValue(calc, bot, 'auxFormSubtype');
if (subShape === 'password') {
return subShape;
}
return 'input';
}
/**
* Gets the form of a menu bot.
* @param calc The calculation context to use.
* @param bot The 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';
}
}
/**
* Gets the sub-shape of the bot.
* @param calc The calculation context to use.
* @param bot The bot.
*/
export function getBotSubShape(calc, bot) {
const shape = calculateBotValue(calc, bot, 'auxFormSubtype');
if (shape === 'gltf' ||
shape === 'jsonObject' ||
shape === 'html' ||
shape === 'src' ||
shape === 'pointLight' ||
shape === 'ambientLight' ||
shape === 'directionalLight' ||
shape === 'spotLight' ||
shape === 'hemisphereLight' ||
shape === 'ldraw' ||
shape === 'ldrawText') {
return shape;
}
return null;
}
/**
* Gets the anchor position for the bot's label.
* @param calc The calculation context to use.
* @param bot The 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 === 'floatingBillboard' ||
anchor === 'front' ||
anchor === 'left' ||
anchor === 'right' ||
anchor === 'top') {
return anchor;
}
return DEFAULT_LABEL_ANCHOR;
}
/**
* Determines whether the given anchor is a floating anchor.
* @param anchor The anchor to check.
*/
export function isFloatingAnchor(anchor) {
return anchor === 'floating' || anchor === 'floatingBillboard';
}
/**
* Gets the text alignment for the bot's label.
* @param calc The calculation context.
* @param bot The 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;
}
/**
* Gets the amount of padding that should be used to auto-sized labels.
* @param calc The calculation context.
* @param bot The 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,
};
}
/**
* Determines if the given value is NaN, or +/- infinity.
* @param val
*/
function isIrrational(val) {
return isNaN(val) || val === Infinity || val === -Infinity;
}
/**
* Gets the scale mode for the bot.
* @param calc The calculation context.
* @param bot The bot.
*/
export function getBotScaleMode(calc, bot) {
const anchor = calculateBotValue(calc, bot, 'auxScaleMode');
if (anchor === 'fit' || anchor === 'absolute') {
return anchor;
}
return DEFAULT_SCALE_MODE;
}
/**
* Gets the mesh positioning mode for the bot.
* @param calc The calculation context.
* @param bot The bot.
*/
export function getBotMeshPositioningMode(calc, bot) {
const anchor = calculateBotValue(calc, bot, 'auxMeshPositioningMode');
if (anchor === 'center' || anchor === 'absolute') {
return anchor;
}
return DEFAULT_MESH_POSITIONING_MODE;
}
/**
* Gets the orientation mode for the given bot.
* @param calc The calculation context.
* @param bot The bot.
*/
export function getBotOrientationMode(calc, bot) {
const mode = (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',
]);
/**
* Gets the anchor point for the given bot.
* @param calc The calculation context.
* @param bot The bot.
*/
export function getBotAnchorPoint(calc, bot) {
const mode = calculateBotValue(calc, bot, 'auxAnchorPoint');
return calculateAnchorPoint(mode);
}
/**
* Ensures that the given bot anchor point value is valid by converting the given value to a valid anchor point value.
* @param value The value.
*/
export function calculateAnchorPoint(value) {
const vector = parseBotVector(value);
if (vector instanceof Vector3) {
return [vector.x, vector.y, vector.z];
}
else if (vector instanceof Vector2) {
return [vector.x, vector.y, 0];
}
else if (Array.isArray(value)) {
if (value.length >= 3 && value.every((v) => typeof v === 'number')) {
return value;
}
}
else if (possibleAnchorPoints.has(value)) {
return value;
}
return DEFAULT_ANCHOR_POINT;
}
export function getMapPortalKind(bot) {
var _a;
const kind = calculateStringTagValue(null, bot, 'auxMapPortalKind', null);
return (_a = calculateMapPortalKind(kind)) !== null && _a !== void 0 ? _a : DEFAULT_MAP_PORTAL_KIND;
}
export function calculateMapPortalKind(value) {
if (value === 'globe' || value === 'plane') {
return value;
}
return null;
}
/**
* Calculates the 3D offset of the anchor point.
* @param point The anchor 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,
};
}
}
/**
* Gets the anchor point offset for the bot in AUX coordinates.
* @param calc The calculation context.
* @param bot The 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',
]);
export function getPortalAnchorPoint(calc, bot, tag, defaultValue) {
const mode = calculateBotValue(calc, bot, tag);
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;
}
}
else if (possibleMeetPortalAnchorPoints.has(mode)) {
return mode;
}
return defaultValue;
}
/**
* Gets the meet portal anchor point for the given bot.
* @param calc The calculation context.
* @param bot The bot.
*/
export function getBotMeetPortalAnchorPoint(calc, bot) {
return getPortalAnchorPoint(calc, bot, 'auxMeetPortalAnchorPoint', DEFAULT_MEET_PORTAL_ANCHOR_POINT);
}
/**
* Gets the tag portal anchor point for the given bot.
* @param calc The calculation context.
* @param bot The bot.
*/
export function getBotTagPortalAnchorPoint(calc, bot) {
return getPortalAnchorPoint(calc, bot, 'auxTagPortalAnchorPoint', DEFAULT_TAG_PORTAL_ANCHOR_POINT);
}
/**
* Gets the bot portal anchor point for the given bot.
* @param calc The calculation context.
* @param bot The bot.
*/
export function getBotPortalAnchorPoint(calc, bot) {
return getPortalAnchorPoint(calc, bot, 'auxBotPortalAnchorPoint', DEFAULT_BOT_PORTAL_ANCHOR_POINT);
}
/**
* Gets the system portal pane that is currently set on the given bot.
* @param calc The calculation context.
* @param bot The bot.
*/
export function getSystemPortalPane(calc, bot) {
const pane = (calculateStringTagValue(calc, bot, SYSTEM_PORTAL_PANE, null));
if (pane === 'bots' ||
pane === 'diff' ||
pane === 'search' ||
pane === 'sheet') {
return pane;
}
return null;
}
/**
* Gets the system portal pane that is currently open.
* Returns null if the system portal is not open.
* @param calc The calculation context.
* @param bot The bot.
*/
export function getOpenSystemPortalPane(calc, bot) {
const systemPortal = calculateBotValue(calc, bot, SYSTEM_PORTAL);
const hasSystemPortal = hasValue(systemPortal);
if (!hasSystemPortal) {
return null;
}
const pane = getSystemPortalPane(calc, bot);
if (hasValue(pane)) {
if (pane === 'sheet') {
const sheetPortal = calculateBotValue(calc, bot, SHEET_PORTAL);
if (hasValue(sheetPortal)) {
return 'sheet';
}
}
else {
return pane;
}
}
const systemPortalSearch = calculateBotValue(calc, bot, SYSTEM_PORTAL_SEARCH);
if (hasValue(systemPortalSearch)) {
return 'search';
}
const diffPortal = calculateBotValue(calc, bot, SYSTEM_PORTAL_DIFF);
if (hasValue(diffPortal)) {
return 'diff';
}
const sheetPortal = calculateBotValue(calc, bot, SHEET_PORTAL);
if (hasValue(sheetPortal)) {
return 'sheet';
}
return 'bots';
}
/**
* Gets the camera type for the given portal bot.
* @param calc The calculation context.
* @param bot The bot.
*/
export function getCameraType(calc, bot) {
const mode = (calculateBotValue(calc, bot, 'auxPortalCameraType'));
if (mode === 'orthographic' || mode === 'perspective') {
return mode;
}
return null;
}
/**
* Gets the anchor point offset for the bot in AUX coordinates.
* @param calc The calculation context.
* @param bot The 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',
];
/**
* Gets the CSS that should be used for the given cursor value.
* @param cursor The 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`;
}
}
/**
* Finds and returns the bot cursor type that matches the given value.
* @param value The 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;
}
try {
// try parsing the value as a URL
const url = new URL(value);
const position = getTagPosition(bot, `${tag}Hotspot`);
return {
type: 'link',
url: value,
x: position.x,
y: position.y,
};
}
catch (_a) {
/* empty because we want to hide the error and return the default */
}
return DEFAULT_BOT_CURSOR;
}
/**
* Gets the cursor that has been configured on the given bot.
* Returns null if the bot does not have a valid string value.
* @param calc The calculation context.
* @param bot The bot.
*/
export function getBotCursor(calc, bot) {
return calculateBotCursor(calc, bot, 'auxCursor');
}
/**
* Gets the cursor that has been configured as the portal cursor for the given bot.
* Returns null if the bot does not have a valid string value.
* @param calc The calculation context.
* @param bot The bot.
*/
export function getPortalCursor(calc, bot) {
return calculateBotCursor(calc, bot, 'auxPortalCursor');
}
/**
* Gets the anchor point offset for the bot in AUX coordinates.
* @param calc The calculation context.
* @param bot The 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',
]);
/**
* Gets whether the bot has a tag to enable LODs.
* @param calc The calculation context.
* @param bot The 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;
}
/**
* Calcualtes the LOD that a bot should be in based on the virtual distance, minimum threshold, and maximum threshold.
* @param virtualDistance The percentage of the screen that the bot takes up.
* @param minThreshold The minimum LOD threshold.
* @param maxThreshold The maximum LOD threshold.
*/
export function calculateBotLOD(virtualDistance, minThreshold, maxThreshold) {
return virtualDistance < minThreshold
? 'min'
: virtualDistance > maxThreshold
? 'max'
: 'normal';
}
/**
* Calculates the portal camera controls mode that the given bot has set.
* @param calc The calculation context.
* @param bot The portal config bot.
*/
export function calculatePortalCameraControlsMode(calc, bot) {
const mode = (calculateBotValue(calc, bot, 'auxPortalCameraControls'));
if (mode === 'player' || mode === false) {
return mode;
}
return DEFAULT_PORTAL_CAMERA_CONTROLS_MODE;
}
/**
* Calculates the label font size that the given bot has set.
* @param calc The calculation context.
* @param bot The bot.
*/
export function calculateLabelFontSize(calc, bot) {
const mode = (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;
}
/**
* Calculates the label word wrapping mode that the given bot has set.
* @param calc The calculation context.
* @param bot The bot.
*/
export function calculateLabelWordWrapMode(calc, bot) {
const mode = (calculateBotValue(calc, bot, 'auxLabelWordWrapMode'));
if (mode === 'breakCharacters' ||
mode === 'breakWords' ||
mode === 'none') {
return mode;
}
return DEFAULT_LABEL_WORD_WRAP_MODE;
}
/**
* Determines if the given bot is a config bot for the given dimension.
* @param calc The calculation context.
* @param bot The bot to check.
* @param dimension The dimension to check if the bot is the config of.
*/
export function isConfigForContext(calc, bot, dimension) {
const contexts = getBotConfigDimensions(calc, bot);
return contexts.indexOf(dimension) >= 0;
}
/**
* Gets whether the dimension(s) that the given bot represents are locked.
* Uses at the auxPortalLocked tag to determine whether it is locked.
* @param calc The calculation context.
* @param bot The bot.
*/
export function isDimensionLocked(calc, bot) {
return calculateBooleanTagValue(calc, bot, 'auxPortalLocked', false);
}
/**
* Gets the list of contexts that the given bot is a config bot for.
* @param calc The calculation context.
* @param bot The bot that represents the dimension.
*/
export function getBotConfigDimensions(calc, bot) {
const result = calculateBotValue(calc, bot, 'auxDimensionConfig');
return parseBotConfigDimensions(result);
}
/**
* Parses a list of dimension names from the given value.
* @param value The value to parse.
*/
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 [];
}
/**
* Calculates the dimensions that are stored on the given bot and portal tag.
* @param bot The Bot.
* @param tag The portal tag.
* @returns
*/
export function calculateDimensions(bot, tag) {
const val = calculateBotValue(null, bot, tag);
const dimensions = parseBotConfigDimensions(val);
return dimensions;
}
/**
* Gets a value from the given dimension bot.
* @param calc The calculation context.
* @param dimensionBot The bot that represents the dimension.
* @param name The name of the value to get.
*/
export function getDimensionValue(calc, dimensionBot, name) {
return calculateBotValue(calc, dimensionBot, `auxPortal${name}`);
}
/**
* Gets the ID of the bot that the given bot should be transformed by.
* @param calc The bot calculation context.
* @param bot The bot to check.
*/
export function getBotTransformer(calc, bot) {
const ids = calculateBotIds(bot, 'transformer');
if (ids) {
return ids[0];
}
return null;
}
/**
* Gets whether the given bot is movable.
* @param calc The calculation context.
* @param bot The bot to check.
*/
export function isBotMovable(calc, bot) {
// checks if bot is movable, but we should also allow it if it is pickupable so we can drag it into miniGridPortal if movable is false
return calculateBooleanTagValue(calc, bot, 'auxDraggable', true);
}
/**
* Gets whether the given bot is listening for shouts or whispers.
* @param calc The calculation context.
* @param bot The bot to check.
*/
export function isBotListening(calc, bot) {
return calculateBooleanTagValue(calc, bot, 'auxListening', true);
}
/**
* Gets whether the given bot's dimension is movable.
* @param calc The calculation context.
* @param bot The bot to check.
*/
export function isDimensionMovable(calc, bot) {
return calculateBooleanTagValue(calc, bot, 'auxDimensionSurfaceMovable', true);
}
/**
* Gets the position that the dimension should be at using the given bot.
* @param calc The calculation context to use.
* @param bot The bot that represents the dimension.
*/
export function getDimensionPosition(calc, bot) {
return {
x: calculateNumericalTagValue(calc, bot, `auxDimensionX`, 0),
y: calculateNumericalTagValue(calc, bot, `auxDimensionY`, 0),
z: calculateNumericalTagValue(calc, bot, `auxDimensionZ`, 0),
};
}
/**
* Gets the rotation that the dimension should be at using the given bot.
* @param calc The calculation context to use.
* @param bot The bot that represents the dimension.
*/
export function getDimensionRotation(calc, bot) {
return {
x: calculateNumericalTagValue(calc, bot, `auxDimensionOrientationX`, 0),
y: calculateNumericalTagValue(calc, bot, `auxDimensionOrientationY`, 0),
z: calculateNumericalTagValue(calc, bot, `auxDimensionOrientationZ`, 0),
};
}
/**
* Gets whether the dimension is minimized.
* @param calc The calculation context to use.
* @param bot The bot that represents the dimension.
*/
export function getDimensionMinimized(calc, bot) {
return getDimensionValue(calc, bot, 'SurfaceMinimized');
}
/**
* Gets the color of the dimension.
* @param calc The calculation context to use.
* @param bot The bot that represents the dimension.
*/
export function getDimensionColor(calc, bot) {
return getDimensionValue(calc, bot, 'Color');
}
/**
* Gets the size of the dimension.
* @param calc The calculation context to use.
* @param bot The bot that represents the dimension.
*/
export function getDimensionSize(calc, bot) {
if (getDimensionVisualizeMode(calc, bot) === 'surface') {
return calculateNumericalTagValue(calc, bot, `auxDimensionSurfaceSize`, DEFAULT_WORKSPACE_SIZE);
}
return 0;
}
/**
* Gets the auxDimensionVisualize mode from the given bot.
* @param calc The calculation context.
* @param bot The 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;
}
}
/**
* Gets the grid of the dimension.
* @param calc The calculation context to use.
* @param bot The bot that represents the dimension.
*/
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;
}
/**
* Gets the height of the specified grid on the dimension.
* @param calc The calculation context to use.
* @param bot The bot that represents the dimension.
* @param key The key for the grid position to lookup in the dimension grid.
*/
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;
}
/**
* Gets the grid scale of the dimension.
* @param calc The calculation context to use.
* @param bot The bot that represents the dimension.
*/
export function getDimensionGridScale(calc, bot) {
return getDimensionValue(calc, bot, 'GridScale');
}
/**
* Gets the scale of the dimension.
* @param calc The calculation context to use.
* @param bot The bot that represents the dimension.
*/
export function getDimensionScale(calc, bot) {
return (getDimensionValue(calc, bot, 'SurfaceScale') || DEFAULT_WORKSPACE_SCALE);
}
/**
* Gets the default height of the dimension.
* @param calc The calculation context to use.
* @param bot The bot that represents the dimension.
*/
export function getDimensionDefaultHeight(calc, bot) {
return getDimensionValue(calc, bot, 'SurfaceDefaultHeight');
}
/**
* Filters the given list of objects to those matching the given workspace ID and grid position.
* The returned list is in the order of their indexes.
* @param calc The bot calculation context to use.
* @param dimension The ID of the dimension that the objects need to be on.
* @param position The position that the objects need to be at.
*/
export function objectsAtDimensionGridPosition(calc, dimension, position) {
return cacheFunction(calc, 'objectsAtDimensionGridPosition', () => {
let botsAtPosition = [];
for (let bot of calc.objects) {
if (!isBotInDimension(calc, bot, dimension)) {
continue;
}
const botPos = getBotPosition(calc, bot, dimension);
if (position.x === botPos.x && position.y === botPos.y) {
botsAtPosition.push(bot);
}
}
return sortBy(botsAtPosition, (o) => getBotIndex(calc, o, dimension), (o) => o.id);
}, dimension, position.x, position.y);
}
/**
* Calculates whether the given bot should be stacked onto another bot or if
* it should be combined with another bot.
* @param calc The bot calculation context.
* @param dimension The dimension.
* @param gridPosition The grid position that the bot is being dragged to.
* @param bot The bot that is being dragged.
*/
export function getDropBotFromGridPosition(calc, dimension, gridPosition, ...bots) {
const objs = differenceBy(objectsAtDimensionGridPosition(calc, dimension, gridPosition), bots, (f) => f.id);
return {
other: objs[0],
};
}
/**
* Filters the given list of objects to those that are assigned to the given workspace ID.
* @param objects The objects to filter.
* @param workspaceId The ID of the workspace that the objects need to be on,
*/
export function objectsAtWorkspace(objects, workspaceId) {
return objects.filter((o) => {
return o.tags._workspace === workspaceId;
});
}
/**
* Duplicates the given bot and returns a new bot with a new ID but the same tags.
* The bot will be exactly the same as the previous except for 3 things.
* First, it will have a different ID.
* Second, it will never be marked as destroyed.
* Third, it will not have any well known tags. (see isTagWellKnown())
* @param calc The bot calculation context.
* @param bot The bot to duplicate.
* @param data The optional data that should override the existing bot 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 cleanBot(newBot);
}
/**
* Filters the given list of tags by whether they are well known or used in a dimension.
* @param calc The bot calculation context.
* @param tags The list of tags to filter.
*/
export function filterWellKnownAndDimensionTags(calc, tags) {
const contextsToRemove = getDimensions(calc);
const tagsToRemove = tags.filter((t) => isWellKnownOrDimension(t, contextsToRemove));
return tagsToRemove;
}
/**
* Gets the list of contexts that the given calculation dimension contains.
* @param calc The bot calculation context.
*/
export function getDimensions(calc) {
return union(...calc.objects.map((o) => getBotConfigDimensions(calc, o)));
}
/**
* Determines if the given tag is well known or in one of the given dimensions.
* @param tag The tag to check.
* @param dimensions The dimensions to check the tag against.
*/
export function isWellKnownOrDimension(tag, dimensions) {
return isTagWellKnown(tag) || dimensions.some((c) => tag.indexOf(c) === 0);
}
/**
* Determines if the given value is some bot tags.
* @param value The value to test.
*/
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) {
var _a;
try {
let uri = new URL(id);
const channel = (_a = uri.searchParams.get('inst')) !== null && _a !== void 0 ? _a : 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,
};
}
}
/**
* Normalizes the given URL so that it will load the AUX bot instead of the web application.
* @param url The 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;
}
/**
* Calculates the value of the given tag on the given bot. If the result is not a bot, then the given default value
* is returned.
* @param context The context.
* @param bot The bot.
* @param tag The tag.
* @param defaultValue The default value to use if the tag doesn't exist or the result is not a bot.
*/
export function calculateBotValueAsBot(context, bot, tag, defaultValue) {
if (bot.tags[tag]) {
const result = calculateBotValue(context, bot, tag);
if (isBot(result)) {
return result;
}
}
return defaultValue;
}
/**
* Calculates the value of the given tag on the given bot as a list of strings.
* @param context The calculation context.
* @param bot The bot.
* @param tag The tag.
* @param defaultValue The default value.
*/
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;
}
/**
* Calculates the value of the given tag on the given bot. If the result is not a number, then the given default value
* is returned.
* @param context The calculation context.
* @param bot The bot.
* @param tag The tag.
* @param defaultValue The default value to use if the tag doesn't exist or the result is not a number.
*/
export function calculateNumericalTagValue(context, bot, tag, defaultValue) {
const result = calculateBotValue(context, bot, tag);
if (typeof result === 'number' && result !== null) {
return result;
}
return defaultValue;
}
/**
* Calculates the value of the given tag on the given bot. If the result is not a boolean, then the given default value is returned.
* @param context The context.
* @param bot The bot.
* @param tag The tag.
* @param defaultValue The default value to use.
*/
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;
}
/**
* Calculates the value of the given tag on the given bot. If the result is not a stirng, then the given default value is returned.
* @param context THe context.
* @param bot The bot.
* @param tag The tag.
* @param defaultValue The default value to use.
*/
export function calculateStringTagValue(context, bot, tag, defaultValue) {
const result = calculateBotValue(context, bot, tag);
if (typeof result === 'string' && result !== null) {
return result;
}
return defaultValue;
}
/**
* Calcualtes the given of the given tag on the given bot as a bot ID.
* @param context The context.
* @param bot The bot.
* @param tag The tag.
* @param defaultValue The default value to use.
*/
export function calculateBotIdTagValue(bot, tag, defaultValue) {
const ids = calculateBotIds(bot, tag);
if (ids) {
return ids[0];
}
return defaultValue;
}
/**
* Calculates the value of the given tag on the given bot as a vector.
* @param bot The bot.
* @param tag The tag.
* @param defaultValue The default value to use.
*/
export function calculateBotVectorTagValue(context, bot, tag, defaultValue) {
const value = calculateBotValue(context, bot, tag);
const result = parseBotVector(value);
return result ? result : defaultValue;
}
/**
* Calculates the value of the given tag on the given bot as a rotation.
* @param bot The bot.
* @param tag The tag.
* @param defaultValue The default value to use.
*/
export function calculateBotRotationTagValue(context, bot, tag, defaultValue) {
const value = calculateBotValue(context, bot, tag);
const result = parseBotRotation(value);
return result ? result : defaultValue;
}
/**
* Determines if the given bot is able to be destroyed.
* Defaults to true.
* @param calc The bot calculation context.
* @param bot The bot to check.
*/
export function isDestroyable(calc, bot) {
return calculateBooleanTagValue(calc, bot, 'auxDestroyable', true);
}
/**
* Determines if the given bot is able to be edited by the bot sheet.
* Defaults to true.
* @param calc The bot calculation context.
* @param bot The bot to check.
*/
export function isEditable(calc, bot) {
return calculateBooleanTagValue(calc, bot, 'auxEditable', true);
}
/**
* Determines if the given bot is trying to load a simulation.
* @param calc The calculation context.
* @param bot The bot to check.
*/
export function isSimulation(calc, bot) {
return !!getBotChannel(calc, bot);
}
/**
* Gets the server tag from the given bot.
* @param calc The bot calculation context to use.
* @param bot The bot.
*/
export function getBotChannel(calc, bot) {
return calculateBotValue(calc, bot, 'server');
}
/**
* Gets the first bot which is in the aux.channels dimension that has the server tag set to the given ID.
* @param calc The bot calculation context.
* @param id The ID to search for.
*/
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;
}
}
/**
* Returns wether or not the given bot resides in the given dimension id.
* @param context The bot calculation context to run formulas with.
* @param bot The bot.
* @param dimensionId The id of the dimension that we are asking if the bot is in.
*/
export function isBotInDimension(context, bot, dimensionId) {
if (!dimensionId)
return false;
let dimensionValue = calculateBooleanTagValue(context, bot, dimensionId.valueOf(), false); //calculateBotValue(context, bot, dimensionId.valueOf());
return dimensionValue;
}
/**
* Gets the tag that is used to set the dimension for the given portal type.
* @param portal The portal type.
*/
export function getPortalTag(portal) {
if (portal.endsWith('Portal')) {
return portal;
}
return `${portal}Portal`;
}
/**
* Gets the ID of the bot that should be used to configure the given portal.
* @param context The context.
* @param bot The bot that is defining the portal.
* @param portal The portal.
*/
export function getPortalConfigBotID(context, bot, portal) {
const tag = `${getPortalTag(portal)}ConfigBot`;
return calculateStringTagValue(context, bot, tag, null);
}
/**
* Gets the theme that is set on the given bot.
* @param context The context.
* @param bot The bot.
*/
export function getBotTheme(context, bot) {
const value = calculateStringTagValue(context, bot, 'theme', 'auto');
if (value === 'dark' || value === 'light' || value === 'auto') {
return value;
}
return 'auto';
}
/**
* Gets the sort order that the given bot should appear in the given dimension.
* @param context The bot calculation context.
* @param bot The bot.
* @param dimensionId The ID of the dimension that we're getting the sort order for.
*/
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);
}
/**
* Formats the given value and returns a string representing it.
* @param value The value to format.
*/
export function formatValue(value) {
if (isBotRotation(value)) {
const result = parseBotRotation(value);
if (result) {
return result.toString();
}
else {
return value;
}
}
else 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;
}
}
/**
* Calculates the value of the given formula as if it was on the given bot (object) and tag.
* @param object The bot that the formula was from.
* @param tag The tag that the formula was from.
* @param formula The formula.
*/
export function calculateValue(object, tag, formula) {
if (isNumber(formula)) {
return parseNumber(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');
/**
* Gets the original object that the given object was constructed from.
* Returns the object if there is no original object.
* @param obj The object.
*/
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);
}
/**
* Gets the list of spaces that the given tag mask exists in.
* @param bot The bot.
* @param tag The tag.
*/
export function getTagMaskSpaces(bot, tag) {
if (!bot.masks) {
return [];
}
let spaces = [];
for (let space in bot.masks) {
if (!bot.masks[space]) {
continue;
}
if (tag in bot.masks[space]) {
spaces.push(space);
}
}
return spaces;
}
/**
* Gets the list of tags that are tag masks on the given bot.
* @param bot The bot.
*/
export function tagMasksOnBot(bot) {
if (!bot.masks) {
return [];
}
let tags = new Set();
for (let space in bot.masks) {
for (let tag in bot.masks[space]) {
tags.add(tag);
}
}
return [...tags.values()];
}
/**
* Gets the value of the given tag mask in the given space.
* @param bot The bot.
* @param space The space that the tag mask is in.
* @param tag The tag.
*/
export function getTagMask(bot, space, tag) {
if (!bot.masks) {
return undefined;
}
if (!bot.masks[space]) {
return undefined;
}
return bot.masks[space][tag];
}
/**
* Determines whether the given bot has a tag or mask for the given tag.
* @param bot The bot.
* @param tag The 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;
}
/**
* Determines if the given bot has a mask for the given tag.
* @param bot The bot.
* @param tag The tag.
*/
export function hasMaskForTag(bot, tag) {
for (let space in bot.masks) {
if (hasValue(bot.masks[space][tag])) {
return true;
}
}
return false;
}
/**
* Gets the tag value for the given space.
* If the space is null, then the tag value is retrieved from the tags.
* If the space is specified, then the tag value is retrieved from the corresponding tag masks.
* @param bot The bot.
* @param tag The tag.
* @param space The space.
*/
export function getTagValueForSpace(bot, tag, space) {
var _a, _b;
if (hasValue(space)) {
return (_b = (_a = bot.masks) === null || _a === void 0 ? void 0 : _a[space]) === null || _b === void 0 ? void 0 : _b[tag];
}
else {
return bot.tags[tag];
}
}
/**
* Gets the first space that the given tag exists in.
* If the tag has a value in a tag mask, then the space that the mask exists in is returned.
* If the tag does not have a value in a tag mask, then null is returned.
* @param bot The bot.
* @param tag The tag.
*/
export function getSpaceForTag(bot, tag) {
var _a, _b;
for (let space of TAG_MASK_SPACE_PRIORITIES) {
if (hasValue((_b = (_a = bot.masks) === null || _a === void 0 ? void 0 : _a[space]) === null || _b === void 0 ? void 0 : _b[tag])) {
return space;
}
}
return null;
}
/**
* Calculates the bot update that is needed to set the given tag in the given space to the given value.
* If the given space is null, then the tag will be set in the bot'ss tags.
* If the given space has a value, then the tag will be set as a tag mask in the given space.
* @param tag The tag to change.
* @param value The value to set.
* @param space The space.
*/
export function getUpdateForTagAndSpace(tag, value, space) {
if (hasValue(space)) {
return {
masks: {
[space]: {
[tag]: value,
},
},
};
}
else {
return {
tags: {
[tag]: value,
},
};
}
}
/**
* Parses the given tag to determine if it represents a script or formula.
* @param tag The tag to parse.
*/
export function parseNewTag(tag) {
var _a;
const script = parseScript(tag);
const formula = parseFormula(tag);
const name = (_a = script !== null && script !== void 0 ? script : formula) !== null && _a !== void 0 ? _a : tag;
return {
name,
isScript: !!script,
isFormula: !!formula,
};
}
//# sourceMappingURL=BotCalculations.js.map