mirror of
https://github.com/JannisX11/blockbench.git
synced 2025-04-18 17:51:12 +08:00
1823 lines
40 KiB
JavaScript
1823 lines
40 KiB
JavaScript
Animator.MolangParser.context = {}
|
|
Animator.MolangParser.global_variables = {
|
|
true: 1,
|
|
false: 0,
|
|
get 'query.delta_time'() {
|
|
let time = (performance.now() - Timeline.last_frame_timecode) / 1000
|
|
if (time < 0) time += 1
|
|
return Math.clamp(time, 0, 0.1)
|
|
},
|
|
get 'query.anim_time'() {
|
|
return Animator.MolangParser.context.animation
|
|
? Animator.MolangParser.context.animation.time
|
|
: Timeline.time
|
|
},
|
|
get 'query.life_time'() {
|
|
return Timeline.time
|
|
},
|
|
get 'query.time_stamp'() {
|
|
return Math.floor(Timeline.time * 20) / 20
|
|
},
|
|
get 'query.all_animations_finished'() {
|
|
if (AnimationController.selected?.selected_state) {
|
|
let state = AnimationController.selected?.selected_state
|
|
let state_time = state.getStateTime()
|
|
let all_finished = state.animations.allAre((a) => {
|
|
let animation = Animation.all.find((anim) => anim.uuid == a.animation)
|
|
return !animation || state_time > animation.length
|
|
})
|
|
return all_finished ? 1 : 0
|
|
}
|
|
return 0
|
|
},
|
|
get 'query.state_time'() {
|
|
if (AnimationController.selected?.selected_state) {
|
|
AnimationController.selected.selected_state.getStateTime();
|
|
}
|
|
return Timeline.time
|
|
},
|
|
get 'query.any_animation_finished'() {
|
|
if (AnimationController.selected?.selected_state) {
|
|
let state = AnimationController.selected?.selected_state
|
|
let state_time = state.getStateTime()
|
|
let finished_anim = state.animations.find((a) => {
|
|
let animation = Animation.all.find((anim) => anim.uuid == a.animation)
|
|
return animation && state_time > animation.length
|
|
})
|
|
return finished_anim ? 1 : 0
|
|
}
|
|
return 0
|
|
},
|
|
'query.camera_rotation'(axis) {
|
|
let val = cameraTargetToRotation(
|
|
Preview.selected.camera.position.toArray(),
|
|
Preview.selected.controls.target.toArray()
|
|
)[axis ? 0 : 1]
|
|
if (axis == 0) val *= -1
|
|
return val
|
|
},
|
|
'query.rotation_to_camera'(axis) {
|
|
let val = cameraTargetToRotation([0, 0, 0], Preview.selected.camera.position.toArray())[
|
|
axis ? 0 : 1
|
|
]
|
|
if (axis == 0) val *= -1
|
|
return val
|
|
},
|
|
get 'query.distance_from_camera'() {
|
|
return Preview.selected.camera.position.length() / 16
|
|
},
|
|
'query.lod_index'(indices) {
|
|
indices.sort((a, b) => a - b)
|
|
let distance = Preview.selected.camera.position.length() / 16
|
|
let index = indices.length
|
|
indices.forEachReverse((val, i) => {
|
|
if (distance < val) index = i
|
|
})
|
|
return index
|
|
},
|
|
'query.camera_distance_range_lerp'(a, b) {
|
|
let distance = Preview.selected.camera.position.length() / 16
|
|
return Math.clamp(Math.getLerp(a, b, distance), 0, 1)
|
|
},
|
|
get 'query.is_first_person'() {
|
|
return Project.bedrock_animation_mode == 'attachable_first' ? 1 : 0
|
|
},
|
|
get 'context.is_first_person'() {
|
|
return Project.bedrock_animation_mode == 'attachable_first' ? 1 : 0
|
|
},
|
|
get time() {
|
|
return Timeline.time
|
|
},
|
|
}
|
|
Animator.MolangParser.variableHandler = function (variable, variables) {
|
|
var inputs = Interface.Panels.variable_placeholders.inside_vue.text.split('\n')
|
|
var i = 0
|
|
while (i < inputs.length) {
|
|
let key, val
|
|
;[key, val] = inputs[i].split(/=\s*(.+)/)
|
|
key = key.replace(/[\s;]/g, '')
|
|
key = key
|
|
.replace(/^v\./, 'variable.')
|
|
.replace(/^q\./, 'query.')
|
|
.replace(/^t\./, 'temp.')
|
|
.replace(/^c\./, 'context.')
|
|
|
|
if (key === variable && val !== undefined) {
|
|
val = val.trim()
|
|
|
|
if (val.match(/^(slider|toggle|impulse)\(/)) {
|
|
let [type, content] = val.substring(0, val.length - 1).split(/\(/)
|
|
let [id] = content.split(/\(|, */)
|
|
id = id.replace(/['"]/g, '')
|
|
|
|
let button = Interface.Panels.variable_placeholders.inside_vue.buttons.find(
|
|
(b) => b.id === id && b.type == type
|
|
)
|
|
return button ? parseFloat(button.value) : 0
|
|
} else {
|
|
return val[0] == `'` ? val : Animator.MolangParser.parse(val, variables)
|
|
}
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
|
|
function getAllMolangExpressions() {
|
|
let expressions = []
|
|
Animation.all.forEach((animation) => {
|
|
for (let key in Animation.properties) {
|
|
let property = Animation.properties[key]
|
|
if (
|
|
Condition(property.condition, animation) &&
|
|
property.type == 'molang' &&
|
|
animation[key] &&
|
|
isNaN(animation[key])
|
|
) {
|
|
let value = animation[key]
|
|
expressions.push({
|
|
value,
|
|
type: 'animation',
|
|
key,
|
|
animation,
|
|
})
|
|
}
|
|
}
|
|
|
|
for (let key in animation.animators) {
|
|
let animator = animation.animators[key]
|
|
for (let channel in animator.channels) {
|
|
animator[channel].forEach((kf, i) => {
|
|
kf.data_points.forEach((data_point) => {
|
|
for (let key in KeyframeDataPoint.properties) {
|
|
let property = KeyframeDataPoint.properties[key]
|
|
if (
|
|
Condition(property.condition, data_point) &&
|
|
property.type == 'molang' &&
|
|
data_point[key] &&
|
|
isNaN(data_point[key])
|
|
) {
|
|
expressions.push({
|
|
value: data_point[key],
|
|
type: 'keyframe',
|
|
key,
|
|
animation,
|
|
animator,
|
|
channel,
|
|
kf,
|
|
})
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
})
|
|
AnimationController.all.forEach((controller) => {
|
|
controller.states.forEach((state) => {
|
|
if (state.on_entry && isNaN(state.on_entry)) {
|
|
expressions.push({
|
|
value: state.on_entry,
|
|
type: 'controller',
|
|
controller,
|
|
state,
|
|
})
|
|
}
|
|
if (state.on_entry && isNaN(state.on_exit)) {
|
|
expressions.push({
|
|
value: state.on_exit,
|
|
type: 'controller',
|
|
controller,
|
|
state,
|
|
})
|
|
}
|
|
state.animations.forEach((a) => {
|
|
if (a.blend_value && isNaN(a.blend_value)) {
|
|
expressions.push({
|
|
value: a.blend_value,
|
|
type: 'controller_animation',
|
|
controller,
|
|
state,
|
|
})
|
|
}
|
|
})
|
|
state.transitions.forEach((t) => {
|
|
if (t.condition && isNaN(t.condition)) {
|
|
expressions.push({
|
|
value: t.condition,
|
|
type: 'controller_transition',
|
|
controller,
|
|
state,
|
|
})
|
|
}
|
|
})
|
|
})
|
|
})
|
|
return expressions
|
|
}
|
|
|
|
new ValidatorCheck('molang_syntax', {
|
|
condition: { features: ['animation_mode'] },
|
|
update_triggers: ['update_keyframe_selection', 'edit_animation_properties'],
|
|
run() {
|
|
let check = this
|
|
let keywords = ['return', 'continue', 'break']
|
|
let two_expression_regex =
|
|
isApp || window.chrome ? new RegExp('(?<!\\w)[0-9._]+\\(|\\)[a-z0-9._]+') : null
|
|
function validateMolang(string, message, instance) {
|
|
if (!string || typeof string !== 'string') return
|
|
let clear_string = string.replace(/'.*'/g, '0')
|
|
|
|
let issues = []
|
|
if (clear_string.match(/([-+*/]\s*[+*/])|(\+\s*-)/)) {
|
|
issues.push('Two directly adjacent operators')
|
|
}
|
|
if (clear_string.match(/^[+*/.,?=&<>|]/)) {
|
|
issues.push('Expression starts with an invalid character')
|
|
}
|
|
if (
|
|
(clear_string.match(/[\w.]\s+[\w.]/) &&
|
|
!keywords.find((k) => clear_string.includes(k))) ||
|
|
clear_string.match(/\)\(/) ||
|
|
(two_expression_regex && clear_string.match(two_expression_regex))
|
|
) {
|
|
issues.push('Two expressions with no operator in between')
|
|
}
|
|
if (clear_string.match(/(^|[^a-z0-9_])[\d.]+[a-z_]+/i)) {
|
|
issues.push(
|
|
'Invalid token ' +
|
|
clear_string
|
|
.match(/(^|[^a-z0-9_])[\d.]+[a-z_]+/i)[0]
|
|
.replace(/[^a-z0-9._]/g, '')
|
|
)
|
|
}
|
|
if (clear_string.match(/[^\w\s+\-*/().,;:[\]!?=<>&|]/)) {
|
|
issues.push(
|
|
'Invalid character: ' +
|
|
clear_string.match(/[^\s\w+\-*/().,;:[\]!?=<>&|]+/g).join(', ')
|
|
)
|
|
}
|
|
let left = string.match(/\(/g) || 0
|
|
let right = string.match(/\)/g) || 0
|
|
if (left.length !== right.length) {
|
|
issues.push('Brackets do not match')
|
|
}
|
|
|
|
if (issues.length) {
|
|
let button
|
|
if (instance instanceof Animation) {
|
|
button = {
|
|
name: 'Edit Animation',
|
|
icon: 'movie',
|
|
click() {
|
|
Dialog.open.close()
|
|
instance.propertiesDialog()
|
|
},
|
|
}
|
|
} else {
|
|
button = {
|
|
name: 'Reveal Keyframe',
|
|
icon: 'icon-keyframe',
|
|
click() {
|
|
Dialog.open.close()
|
|
instance.showInTimeline()
|
|
},
|
|
}
|
|
}
|
|
check.fail({
|
|
message: `${message} ${issues.join('; ')}. Script: \`${string}\``,
|
|
buttons: [button],
|
|
})
|
|
}
|
|
}
|
|
|
|
getAllMolangExpressions().forEach((ex) => {
|
|
if (ex.type == 'animation') {
|
|
validateMolang(
|
|
ex.value,
|
|
`Property "${ex.key}" on animation "${ex.animation.name}" contains invalid molang:`,
|
|
ex.animation
|
|
)
|
|
} else if (ex.type == 'keyframe') {
|
|
let channel_name = ex.animator.channels[ex.channel].name
|
|
validateMolang(
|
|
ex.value,
|
|
`${channel_name} keyframe at ${ex.kf.time.toFixed(2)} on "${
|
|
ex.animator.name
|
|
}" in "${ex.animation.name}" contains invalid molang:`,
|
|
ex.kf
|
|
)
|
|
}
|
|
})
|
|
},
|
|
})
|
|
|
|
/**
|
|
* Global Molang autocomplete object
|
|
*/
|
|
const MolangAutocomplete = {}
|
|
|
|
/**
|
|
* Gets all the Molang variables used in the project
|
|
* @param {string[]} excluded Variable names to exclude
|
|
* @returns {Set<string>}
|
|
*/
|
|
function getProjectMolangVariables(excluded) {
|
|
const variables = new Set()
|
|
const expressions = getAllMolangExpressions()
|
|
for (const expression of expressions) {
|
|
if (!expression.value) continue
|
|
const matches = expression.value.match(/(v|variable)\.(\w+)/gi)
|
|
if (!matches) continue
|
|
for (const match of matches) {
|
|
const name = match.split('.')[1]
|
|
if (!(excluded && excluded.includes(name))) variables.add(name)
|
|
}
|
|
}
|
|
return variables
|
|
}
|
|
|
|
/**
|
|
* Gets the temporary Molang variables in a molang expression string
|
|
* @param {string} expression
|
|
* @param {string[]} excluded Variable names to exclude
|
|
* @returns {Set<string>}
|
|
*/
|
|
function getTemporaryMolangVariables(expression, excluded) {
|
|
const variables = new Set()
|
|
const matches = expression.match(/(t|temp)\.(\w+)/gi)
|
|
if (!matches) return variables
|
|
for (const match of matches) {
|
|
const name = match.split('.')[1]
|
|
if (!(excluded && excluded.includes(name))) variables.add(name)
|
|
}
|
|
return variables
|
|
}
|
|
|
|
/**
|
|
* Sorts autocomplete results based on how well they match the incomplete string, then alphabetically
|
|
* @param {MolangAutocompleteResult[]} results
|
|
* @param {string} incomplete
|
|
* @returns {MolangAutocompleteResult[]}
|
|
*/
|
|
function sortAutocompleteResults(results, incomplete) {
|
|
return results.sort((a, b) => {
|
|
if (a.priority && b.priority) return b.priority - a.priority
|
|
else if (a.priority) return -1
|
|
else if (b.priority) return 1
|
|
if (a.text.startsWith(incomplete) && !b.text.startsWith(incomplete)) return -1
|
|
if (b.text.startsWith(incomplete) && !a.text.startsWith(incomplete)) return 1
|
|
return a.text.localeCompare(b.text)
|
|
})
|
|
}
|
|
|
|
;(function () {
|
|
/**
|
|
* @typedef MolangAutocompleteResult
|
|
* @property {string} text The text to insert
|
|
* @property {string} [label] The label to display in the autocomplete menu
|
|
* @property {number} overlap The number of characters to overlap with the incomplete string
|
|
* @property {number} [priority] The suggestion priority. A higher number means it will be suggested first
|
|
*/
|
|
|
|
/**
|
|
* @typedef RootToken
|
|
* @property {string} id The ID of the new root token
|
|
* @property {string[]} [arguments] The arguments of the root token
|
|
* @property {number} [priority] The suggestion priority of the root token. A higher number means it will be suggested first
|
|
*/
|
|
|
|
/**
|
|
* @typedef Query
|
|
* @property {string} id The ID of the new query
|
|
* @property {string[]} [arguments] The arguments of the query
|
|
* @property {number} [priority] The suggestion priority of the query. A higher number means it will be suggested first
|
|
*/
|
|
|
|
/**
|
|
* @typedef NamespaceOptions
|
|
* @property {string} id The ID of the new namespace
|
|
* @property {string} [shorthand] The shorthand of the new namespace. Eg. `q` for `query`
|
|
* @property {number} [priority] The suggestion priority of the namespace. A higher number means it will be suggested first
|
|
*/
|
|
|
|
MolangAutocomplete.Namespace = class Namespace {
|
|
/**
|
|
* @type {string} The ID of the namespace.
|
|
*/
|
|
id
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
shorthand
|
|
/**
|
|
* @type {Map<string, Query>}
|
|
*/
|
|
queries = new Map()
|
|
/**
|
|
* @type {Map<string, () => Query[]>}
|
|
*/
|
|
queryGetters = new Map()
|
|
/**
|
|
* @param {NamespaceOptions} options
|
|
*/
|
|
constructor(options) {
|
|
this.id = options.id
|
|
this.shorthand = options.shorthand
|
|
}
|
|
|
|
/**
|
|
* Adds a new query to the namespace
|
|
* @param {Query} query
|
|
* @returns {Namespace} This namespace
|
|
*/
|
|
addQuery(query) {
|
|
this.queries.set(query.id, query)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* @param {string} queryID
|
|
* @returns {boolean} True if the query exists in the namespace
|
|
*/
|
|
hasQuery(queryID) {
|
|
// This function isn't used internally, but keeps the API consistent for plugin devs.
|
|
return this.queries.has(queryID)
|
|
}
|
|
|
|
/**
|
|
* Removes a query from the namespace
|
|
* @param {string} queryID
|
|
* @returns {boolean} True if the query was removed, false if it did not exist
|
|
*/
|
|
removeQuery(queryID) {
|
|
return this.queries.delete(queryID)
|
|
}
|
|
|
|
/**
|
|
* Adds a getter function that returns dynamically generated queries.
|
|
* @param {string} id
|
|
* @param {(incomplete: string) => Query[]} getter
|
|
* @returns {Namespace} This namespace
|
|
*/
|
|
addQueryGetter(id, getter) {
|
|
this.queryGetters.set(id, getter)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Removes a query getter function
|
|
* @param {string} id
|
|
*/
|
|
removeQueryGetter(id) {
|
|
this.queryGetters.delete(id)
|
|
}
|
|
|
|
/**
|
|
* @typedef NamespaceUnionOptions
|
|
* @property {string} id The ID of the new namespace
|
|
* @property {string} [shorthand] The shorthand of the new namespace. Eg. `q` for `query`
|
|
* @property {number} [priority] The suggestion priority of the namespace. A higher number means it will be suggested first
|
|
*/
|
|
|
|
/**
|
|
* Creates a new Namespace that is a union of this namespace and another
|
|
* @param {Namespace} other
|
|
* @param {NamespaceUnionOptions} [options] Options to override the new namespace's properties. If not provided, the new namespace will inherit this namespace's properties
|
|
* @returns {Namespace} The new namespace
|
|
*/
|
|
createUnion(other, options) {
|
|
const union = new MolangAutocomplete.Namespace({
|
|
id: options?.id || this.id,
|
|
shorthand: options?.shorthand || this.shorthand,
|
|
priority: options?.priority || this.priority,
|
|
})
|
|
union.queries = new Map([...this.queries, ...other.queries])
|
|
union.queryGetters = new Map([...this.queryGetters, ...other.queryGetters])
|
|
return union
|
|
}
|
|
|
|
/**
|
|
* Returns any queries in this namespace who's ID starts with `incomplete`.
|
|
* @param {string} expression The expression the query is being used in
|
|
* @param {string} incomplete The incomplete query ID
|
|
* @param {boolean} [recursive=true] If true, will also search inherited contexts
|
|
* @returns {Query[]} The queries
|
|
*/
|
|
getPossibleQueries(expression, incomplete, recursive = true) {
|
|
const possibleQueries = []
|
|
this.queries.forEach((query) => {
|
|
if (query.id.startsWith(incomplete)) possibleQueries.push(query)
|
|
})
|
|
this.queryGetters.values().forEach((getter) => {
|
|
const queries = getter(expression, incomplete)
|
|
queries.forEach((query) => {
|
|
if (query.id.startsWith(incomplete)) possibleQueries.push(query)
|
|
})
|
|
})
|
|
if (recursive && this.inheritedContext) {
|
|
return [
|
|
...possibleQueries,
|
|
...this.inheritedContext.getPossibleQueries(expression, incomplete),
|
|
]
|
|
}
|
|
return possibleQueries
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @typedef MolangAutocompleteContextOptions
|
|
* @property {string} id
|
|
* @property {string[]} [rootTokens]
|
|
* @property {MolangAutocomplete.Context} [inheritedContext]
|
|
*/
|
|
|
|
MolangAutocomplete.Context = class Context {
|
|
/**
|
|
* @type {MolangAutocomplete.Context[]}
|
|
*/
|
|
static all = []
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
id
|
|
/**
|
|
* @type {Map<string, RootToken>}
|
|
*/
|
|
rootTokens = new Map()
|
|
/**
|
|
* @type {Map<string, Namespace>}
|
|
*/
|
|
namespaces = new Map()
|
|
/**
|
|
* @type {MolangAutocomplete.Context}
|
|
*/
|
|
inheritedContext
|
|
|
|
/**
|
|
* @param {MolangAutocompleteContextOptions} options
|
|
*/
|
|
constructor(options) {
|
|
this.id = options.id
|
|
this.inheritedContext = options.inheritedContext
|
|
MolangAutocomplete.Context.all.push(this)
|
|
}
|
|
|
|
/**
|
|
* Adds a new root token to the context
|
|
* @param {RootToken} token
|
|
* @returns {Context} This context
|
|
*/
|
|
addRootToken(token) {
|
|
this.rootTokens.set(token.id, token)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Returns the root token with the given ID
|
|
* @param {string} tokenID
|
|
* @returns {RootToken} The root token, or undefined if it does not exist
|
|
*/
|
|
getRootToken(tokenID) {
|
|
return this.rootTokens.get(tokenID)
|
|
}
|
|
|
|
/**
|
|
* Removes a root token from the context
|
|
* @param {string} tokenID
|
|
* @returns {boolean} True if the token was removed, false if it did not exist
|
|
* @returns {boolean}
|
|
*/
|
|
removeRootToken(tokenID) {
|
|
return this.rootTokens.delete(tokenID)
|
|
}
|
|
|
|
/**
|
|
* Returns true if the context has a namespace with the given ID
|
|
* @param {string} namespaceID
|
|
* @param {boolean} [recursive=true] If true, will also search inherited contexts
|
|
* @returns {boolean}
|
|
*/
|
|
hasNamespace(namespaceID, recursive = true) {
|
|
if (this.namespaces.has(namespaceID)) return true
|
|
if (recursive && this.inheritedContext)
|
|
return this.inheritedContext.hasNamespace(namespaceID)
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Adds a new namespace to the context
|
|
* @param {Namespace} namespace
|
|
* @param {boolean} [createUnion=true] If true, will create a union of the namespace with any existing namespaces with the same ID. If false, will overwrite any existing namespaces with the same ID. (Default: true)
|
|
* @returns {Context} This context
|
|
*/
|
|
addNamespace(namespace, createUnion = true) {
|
|
if (createUnion && this.namespaces.has(namespace.id)) {
|
|
this.namespaces.set(
|
|
namespace.id,
|
|
this.namespaces.get(namespace.id).createUnion(namespace)
|
|
)
|
|
} else {
|
|
this.namespaces.set(namespace.id, namespace)
|
|
}
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Returns the namespace with the given ID
|
|
*
|
|
* @param {string} namespaceID
|
|
* @param {boolean} [recursive=true] If true, will also search inherited contexts
|
|
* @returns {Namespace} The namespace, or undefined if it does not exist
|
|
*/
|
|
getNamespace(namespaceID, recursive = true) {
|
|
if (recursive && this.inheritedContext) {
|
|
const subNamespace = this.inheritedContext.getNamespace(namespaceID)
|
|
if (this.namespaces.has(namespaceID)) {
|
|
const namespace = this.namespaces.get(namespaceID)
|
|
if (subNamespace) {
|
|
return namespace.createUnion(subNamespace)
|
|
}
|
|
}
|
|
return subNamespace
|
|
}
|
|
if (this.namespaces.has(namespaceID)) return this.namespaces.get(namespaceID)
|
|
return undefined
|
|
}
|
|
|
|
/**
|
|
* Removes a namespace from the context
|
|
*
|
|
* This will not remove namespaces from inherited contexts
|
|
* @param {string} namespaceID
|
|
* @returns {boolean} True if the namespace was removed, false if it did not exist
|
|
*/
|
|
removeNamespace(namespaceID) {
|
|
return this.namespaces.delete(namespaceID)
|
|
}
|
|
|
|
/**
|
|
* Returns any namespaces in this context who's ID starts with `incomplete`.
|
|
* @param {string} incomplete
|
|
* @param {boolean} [recursive=true] If true, will also search inherited contexts
|
|
* @returns {Namespace[]} The namespaces
|
|
*/
|
|
getPossibleNamespaces(incomplete, recursive = true) {
|
|
const possibleNamespaces = new Map()
|
|
this.namespaces.forEach((namespace) => {
|
|
if (
|
|
namespace.id.startsWith(incomplete) ||
|
|
(namespace.shorthand && namespace.shorthand.startsWith(incomplete))
|
|
)
|
|
possibleNamespaces.set(namespace.id, namespace)
|
|
})
|
|
if (recursive && this.inheritedContext) {
|
|
const inheritedNamespaces = this.inheritedContext.getPossibleNamespaces(incomplete)
|
|
inheritedNamespaces.forEach((namespace) => {
|
|
if (possibleNamespaces.has(namespace.id)) {
|
|
const union = possibleNamespaces.get(namespace.id).createUnion(namespace)
|
|
possibleNamespaces.set(namespace.id, union)
|
|
} else {
|
|
possibleNamespaces.set(namespace.id, namespace)
|
|
}
|
|
})
|
|
}
|
|
return [...possibleNamespaces.values()]
|
|
}
|
|
|
|
/**
|
|
* Returns any root tokens in this context who's ID starts with `incomplete`.
|
|
* @param {string} incomplete
|
|
* @param {boolean} [recursive=true] If true, will also search inherited contexts
|
|
* @returns {RootToken[]} The root tokens
|
|
*/
|
|
getPossibleRootTokens(incomplete, recursive = true) {
|
|
const possibleRootTokens = []
|
|
this.rootTokens.forEach((token) => {
|
|
if (token.id.startsWith(incomplete)) possibleRootTokens.push(token)
|
|
})
|
|
if (recursive && this.inheritedContext) {
|
|
return [
|
|
...possibleRootTokens,
|
|
...this.inheritedContext.getPossibleRootTokens(incomplete),
|
|
]
|
|
}
|
|
return possibleRootTokens
|
|
}
|
|
|
|
/**
|
|
* Attempts to autocomplete the given text from the given position in the text
|
|
* @param {string} text The text to attempt to autocomplete
|
|
* @param {number} position The position of the cursor in the text
|
|
* @returns {MolangAutocompleteResult[]} The autocomplete results
|
|
*/
|
|
autocomplete(text, position) {
|
|
const result = []
|
|
const start = text
|
|
.substring(0, position)
|
|
.split(/[^a-zA-Z_.]\.*/g)
|
|
.last()
|
|
.toLowerCase()
|
|
if (start.length === 0) return result
|
|
const [space, dir] = start.split('.').slice(-2)
|
|
|
|
const possibleRootTokens = this.getPossibleRootTokens(start)
|
|
possibleRootTokens.forEach((token) => {
|
|
result.push({
|
|
text: token.arguments ? `${token.id}()` : token.id,
|
|
label: token.arguments
|
|
? `${token.id}( ${token.arguments.join(', ')} )`
|
|
: undefined,
|
|
overlap: start.length,
|
|
priority: token.priority,
|
|
})
|
|
})
|
|
|
|
const possibleNamespaces = this.getPossibleNamespaces(space)
|
|
switch (possibleNamespaces.length) {
|
|
default:
|
|
possibleNamespaces.forEach((ns) => {
|
|
result.push({ text: ns.id, overlap: space.length, priority: ns.priority })
|
|
if (ns.shorthand)
|
|
result.push({
|
|
text: ns.shorthand,
|
|
overlap: space.length,
|
|
priority: ns.priority,
|
|
})
|
|
})
|
|
return sortAutocompleteResults(result, start)
|
|
case 0:
|
|
return sortAutocompleteResults(result, start)
|
|
case 1: {
|
|
const namespace = possibleNamespaces[0]
|
|
if (!dir && !start.endsWith('.')) {
|
|
return sortAutocompleteResults(
|
|
[
|
|
...result,
|
|
{
|
|
text: namespace.id,
|
|
overlap: space.length,
|
|
priority: namespace.priority,
|
|
},
|
|
],
|
|
start
|
|
)
|
|
}
|
|
const possibleQueries = namespace.getPossibleQueries(text, dir)
|
|
switch (possibleQueries.length) {
|
|
default:
|
|
return sortAutocompleteResults(
|
|
[
|
|
...result,
|
|
...possibleQueries.map((q) => ({
|
|
text: q.arguments ? `${q.id}()` : q.id,
|
|
label: q.arguments
|
|
? `${q.id}( ${q.arguments.join(', ')} )`
|
|
: undefined,
|
|
overlap: dir.length,
|
|
priority: q.priority,
|
|
})),
|
|
],
|
|
dir
|
|
)
|
|
case 0:
|
|
return sortAutocompleteResults(result, start)
|
|
case 1: {
|
|
const query = possibleQueries[0]
|
|
return sortAutocompleteResults(
|
|
[
|
|
...result,
|
|
{
|
|
text: query.arguments ? `${query.id}()` : query.id,
|
|
label: query.arguments
|
|
? `${query.id}( ${query.arguments.join(', ')} )`
|
|
: undefined,
|
|
overlap: dir.length,
|
|
priority: query.priority,
|
|
},
|
|
],
|
|
dir
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes the context from the list of all contexts
|
|
*/
|
|
delete() {
|
|
MolangAutocomplete.Context.all.remove(this)
|
|
}
|
|
}
|
|
|
|
MolangAutocomplete.DefaultContext = new MolangAutocomplete.Context({
|
|
id: 'defaultContext',
|
|
})
|
|
.addRootToken({
|
|
id: 'true',
|
|
})
|
|
.addRootToken({
|
|
id: 'false',
|
|
})
|
|
.addRootToken({
|
|
id: 'this',
|
|
})
|
|
.addRootToken({
|
|
id: 'loop',
|
|
arguments: ['count', 'expression'],
|
|
})
|
|
.addRootToken({
|
|
id: 'return',
|
|
})
|
|
.addRootToken({
|
|
id: 'break',
|
|
})
|
|
.addRootToken({
|
|
id: 'continue',
|
|
})
|
|
.addNamespace(
|
|
new MolangAutocomplete.Namespace({
|
|
id: 'query',
|
|
shorthand: 'q',
|
|
})
|
|
.addQuery({
|
|
id: 'anim_time',
|
|
})
|
|
.addQuery({
|
|
id: 'life_time',
|
|
})
|
|
.addQuery({
|
|
id: 'state_time',
|
|
})
|
|
.addQuery({
|
|
id: 'yaw_speed',
|
|
})
|
|
.addQuery({
|
|
id: 'ground_speed',
|
|
})
|
|
.addQuery({
|
|
id: 'vertical_speed',
|
|
})
|
|
.addQuery({
|
|
id: 'property',
|
|
arguments: ['property'],
|
|
})
|
|
.addQuery({
|
|
id: 'has_property',
|
|
arguments: ['property'],
|
|
})
|
|
.addQuery({
|
|
id: 'variant',
|
|
})
|
|
.addQuery({
|
|
id: 'mark_variant',
|
|
})
|
|
.addQuery({
|
|
id: 'skin_id',
|
|
})
|
|
|
|
.addQuery({
|
|
id: 'above_top_solid',
|
|
})
|
|
.addQuery({
|
|
id: 'actor_count',
|
|
})
|
|
.addQuery({
|
|
id: 'all',
|
|
arguments: ['value', 'values...'],
|
|
})
|
|
.addQuery({
|
|
id: 'all_tags',
|
|
})
|
|
.addQuery({
|
|
id: 'anger_level',
|
|
})
|
|
.addQuery({
|
|
id: 'any',
|
|
arguments: ['value', 'values...'],
|
|
})
|
|
.addQuery({
|
|
id: 'any_tag',
|
|
})
|
|
.addQuery({
|
|
id: 'approx_eq',
|
|
arguments: ['value', 'values...'],
|
|
})
|
|
.addQuery({
|
|
id: 'armor_color_slot',
|
|
})
|
|
.addQuery({
|
|
id: 'armor_material_slot',
|
|
})
|
|
.addQuery({
|
|
id: 'armor_texture_slot',
|
|
})
|
|
.addQuery({
|
|
id: 'average_frame_time',
|
|
})
|
|
.addQuery({
|
|
id: 'blocking',
|
|
})
|
|
.addQuery({
|
|
id: 'body_x_rotation',
|
|
})
|
|
.addQuery({
|
|
id: 'body_y_rotation',
|
|
})
|
|
.addQuery({
|
|
id: 'bone_aabb',
|
|
})
|
|
.addQuery({
|
|
id: 'bone_origin',
|
|
})
|
|
.addQuery({
|
|
id: 'bone_rotation',
|
|
})
|
|
.addQuery({
|
|
id: 'camera_distance_range_lerp',
|
|
})
|
|
.addQuery({
|
|
id: 'camera_rotation',
|
|
arguments: ['axis'],
|
|
})
|
|
.addQuery({
|
|
id: 'can_climb',
|
|
})
|
|
.addQuery({
|
|
id: 'can_damage_nearby_mobs',
|
|
})
|
|
.addQuery({
|
|
id: 'can_dash',
|
|
})
|
|
.addQuery({
|
|
id: 'can_fly',
|
|
})
|
|
.addQuery({
|
|
id: 'can_power_jump',
|
|
})
|
|
.addQuery({
|
|
id: 'can_swim',
|
|
})
|
|
.addQuery({
|
|
id: 'can_walk',
|
|
})
|
|
.addQuery({
|
|
id: 'cape_flap_amount',
|
|
})
|
|
.addQuery({
|
|
id: 'cardinal_facing',
|
|
})
|
|
.addQuery({
|
|
id: 'cardinal_facing_2d',
|
|
})
|
|
.addQuery({
|
|
id: 'cardinal_player_facing',
|
|
})
|
|
.addQuery({
|
|
id: 'combine_entities',
|
|
arguments: ['entitiesReferences...'],
|
|
})
|
|
.addQuery({
|
|
id: 'count',
|
|
})
|
|
.addQuery({
|
|
id: 'current_squish_value',
|
|
})
|
|
.addQuery({
|
|
id: 'dash_cooldown_progress',
|
|
})
|
|
.addQuery({
|
|
id: 'day',
|
|
})
|
|
.addQuery({
|
|
id: 'death_ticks',
|
|
})
|
|
.addQuery({
|
|
id: 'debug_output',
|
|
})
|
|
.addQuery({
|
|
id: 'delta_time',
|
|
})
|
|
.addQuery({
|
|
id: 'distance_from_camera',
|
|
})
|
|
.addQuery({
|
|
id: 'effect_emitter_count',
|
|
})
|
|
.addQuery({
|
|
id: 'effect_particle_count',
|
|
})
|
|
.addQuery({
|
|
id: 'equipment_count',
|
|
})
|
|
.addQuery({
|
|
id: 'equipped_item_all_tags',
|
|
})
|
|
.addQuery({
|
|
id: 'equipped_item_any_tag',
|
|
arguments: ['tags...'],
|
|
})
|
|
.addQuery({
|
|
id: 'equipped_item_is_attachable',
|
|
})
|
|
.addQuery({
|
|
id: 'eye_target_x_rotation',
|
|
})
|
|
.addQuery({
|
|
id: 'eye_target_y_rotation',
|
|
})
|
|
.addQuery({
|
|
id: 'facing_target_to_range_attack',
|
|
})
|
|
.addQuery({
|
|
id: 'frame_alpha',
|
|
})
|
|
.addQuery({
|
|
id: 'get_actor_info_id',
|
|
})
|
|
.addQuery({
|
|
id: 'get_animation_frame',
|
|
})
|
|
.addQuery({
|
|
id: 'get_default_bone_pivot',
|
|
})
|
|
.addQuery({
|
|
id: 'get_locator_offset',
|
|
})
|
|
.addQuery({
|
|
id: 'get_root_locator_offset',
|
|
})
|
|
.addQuery({
|
|
id: 'had_component_group',
|
|
arguments: ['group'],
|
|
})
|
|
.addQuery({
|
|
id: 'has_any_family',
|
|
arguments: ['families...'],
|
|
})
|
|
.addQuery({
|
|
id: 'has_armor_slot',
|
|
})
|
|
.addQuery({
|
|
id: 'has_biome_tag',
|
|
})
|
|
.addQuery({
|
|
id: 'has_block_property',
|
|
})
|
|
.addQuery({
|
|
id: 'has_cape',
|
|
})
|
|
.addQuery({
|
|
id: 'has_collision',
|
|
})
|
|
.addQuery({
|
|
id: 'has_dash_cooldown',
|
|
})
|
|
.addQuery({
|
|
id: 'has_gravity',
|
|
})
|
|
.addQuery({
|
|
id: 'has_owner',
|
|
})
|
|
.addQuery({
|
|
id: 'has_rider',
|
|
})
|
|
.addQuery({
|
|
id: 'has_target',
|
|
})
|
|
.addQuery({
|
|
id: 'head_roll_angle',
|
|
})
|
|
.addQuery({
|
|
id: 'head_x_rotation',
|
|
})
|
|
.addQuery({
|
|
id: 'head_y_rotation',
|
|
})
|
|
.addQuery({
|
|
id: 'health',
|
|
})
|
|
.addQuery({
|
|
id: 'heartbeat_interval',
|
|
})
|
|
.addQuery({
|
|
id: 'heartbeat_phase',
|
|
})
|
|
.addQuery({
|
|
id: 'heightmap',
|
|
})
|
|
.addQuery({
|
|
id: 'hurt_direction',
|
|
})
|
|
.addQuery({
|
|
id: 'hurt_time',
|
|
})
|
|
.addQuery({
|
|
id: 'in_range',
|
|
arguments: ['value', 'min', 'max'],
|
|
})
|
|
.addQuery({
|
|
id: 'invulnerable_ticks',
|
|
})
|
|
.addQuery({
|
|
id: 'is_admiring',
|
|
})
|
|
.addQuery({
|
|
id: 'is_alive',
|
|
})
|
|
.addQuery({
|
|
id: 'is_angry',
|
|
})
|
|
.addQuery({
|
|
id: 'is_attached_to_entity',
|
|
})
|
|
.addQuery({
|
|
id: 'is_avoiding_block',
|
|
})
|
|
.addQuery({
|
|
id: 'is_avoiding_mobs',
|
|
})
|
|
.addQuery({
|
|
id: 'is_baby',
|
|
})
|
|
.addQuery({
|
|
id: 'is_breathing',
|
|
})
|
|
.addQuery({
|
|
id: 'is_bribed',
|
|
})
|
|
.addQuery({
|
|
id: 'is_carrying_block',
|
|
})
|
|
.addQuery({
|
|
id: 'is_casting',
|
|
})
|
|
.addQuery({
|
|
id: 'is_celebrating',
|
|
})
|
|
.addQuery({
|
|
id: 'is_celebrating_special',
|
|
})
|
|
.addQuery({
|
|
id: 'is_charged',
|
|
})
|
|
.addQuery({
|
|
id: 'is_charging',
|
|
})
|
|
.addQuery({
|
|
id: 'is_chested',
|
|
})
|
|
.addQuery({
|
|
id: 'is_critical',
|
|
})
|
|
.addQuery({
|
|
id: 'is_croaking',
|
|
})
|
|
.addQuery({
|
|
id: 'is_dancing',
|
|
})
|
|
.addQuery({
|
|
id: 'is_delayed_attacking',
|
|
})
|
|
.addQuery({
|
|
id: 'is_digging',
|
|
})
|
|
.addQuery({
|
|
id: 'is_eating',
|
|
})
|
|
.addQuery({
|
|
id: 'is_eating_mob',
|
|
})
|
|
.addQuery({
|
|
id: 'is_elder',
|
|
})
|
|
.addQuery({
|
|
id: 'is_emerging',
|
|
})
|
|
.addQuery({
|
|
id: 'is_emoting',
|
|
})
|
|
.addQuery({
|
|
id: 'is_enchanted',
|
|
})
|
|
.addQuery({
|
|
id: 'is_fire_immune',
|
|
})
|
|
.addQuery({
|
|
id: 'is_first_person',
|
|
})
|
|
.addQuery({
|
|
id: 'is_ghost',
|
|
})
|
|
.addQuery({
|
|
id: 'is_gliding',
|
|
})
|
|
.addQuery({
|
|
id: 'is_grazing',
|
|
})
|
|
.addQuery({
|
|
id: 'is_idling',
|
|
})
|
|
.addQuery({
|
|
id: 'is_ignited',
|
|
})
|
|
.addQuery({
|
|
id: 'is_illager_captain',
|
|
})
|
|
.addQuery({
|
|
id: 'is_in_contact_with_water',
|
|
})
|
|
.addQuery({
|
|
id: 'is_in_love',
|
|
})
|
|
.addQuery({
|
|
id: 'is_in_ui',
|
|
})
|
|
.addQuery({
|
|
id: 'is_in_water',
|
|
})
|
|
.addQuery({
|
|
id: 'is_in_water_or_rain',
|
|
})
|
|
.addQuery({
|
|
id: 'is_interested',
|
|
})
|
|
.addQuery({
|
|
id: 'is_invisible',
|
|
})
|
|
.addQuery({
|
|
id: 'is_item_equipped',
|
|
})
|
|
.addQuery({
|
|
id: 'is_item_name_any',
|
|
arguments: ['slotName', 'slot?', 'itemNames...'],
|
|
})
|
|
.addQuery({
|
|
id: 'is_jump_goal_jumping',
|
|
})
|
|
.addQuery({
|
|
id: 'is_jumping',
|
|
})
|
|
.addQuery({
|
|
id: 'is_laying_down',
|
|
})
|
|
.addQuery({
|
|
id: 'is_laying_egg',
|
|
})
|
|
.addQuery({
|
|
id: 'is_leashed',
|
|
})
|
|
.addQuery({
|
|
id: 'is_levitating',
|
|
})
|
|
.addQuery({
|
|
id: 'is_lingering',
|
|
})
|
|
.addQuery({
|
|
id: 'is_moving',
|
|
})
|
|
.addQuery({
|
|
id: 'is_name_any',
|
|
})
|
|
.addQuery({
|
|
id: 'is_on_fire',
|
|
})
|
|
.addQuery({
|
|
id: 'is_on_ground',
|
|
})
|
|
.addQuery({
|
|
id: 'is_on_screen',
|
|
})
|
|
.addQuery({
|
|
id: 'is_onfire',
|
|
})
|
|
.addQuery({
|
|
id: 'is_orphaned',
|
|
})
|
|
.addQuery({
|
|
id: 'is_owner_identifier_any',
|
|
arguments: ['identifiers...'],
|
|
})
|
|
.addQuery({
|
|
id: 'is_persona_or_premium_skin',
|
|
})
|
|
.addQuery({
|
|
id: 'is_playing_dead',
|
|
})
|
|
.addQuery({
|
|
id: 'is_powered',
|
|
})
|
|
.addQuery({
|
|
id: 'is_pregnant',
|
|
})
|
|
.addQuery({
|
|
id: 'is_ram_attacking',
|
|
})
|
|
.addQuery({
|
|
id: 'is_resting',
|
|
})
|
|
.addQuery({
|
|
id: 'is_riding',
|
|
})
|
|
.addQuery({
|
|
id: 'is_roaring',
|
|
})
|
|
.addQuery({
|
|
id: 'is_rolling',
|
|
})
|
|
.addQuery({
|
|
id: 'is_saddled',
|
|
})
|
|
.addQuery({
|
|
id: 'is_scared',
|
|
})
|
|
.addQuery({
|
|
id: 'is_selected_item',
|
|
})
|
|
.addQuery({
|
|
id: 'is_shaking',
|
|
})
|
|
.addQuery({
|
|
id: 'is_shaking_wetness',
|
|
})
|
|
.addQuery({
|
|
id: 'is_sheared',
|
|
})
|
|
.addQuery({
|
|
id: 'is_shield_powered',
|
|
})
|
|
.addQuery({
|
|
id: 'is_silent',
|
|
})
|
|
.addQuery({
|
|
id: 'is_sitting',
|
|
})
|
|
.addQuery({
|
|
id: 'is_sleeping',
|
|
})
|
|
.addQuery({
|
|
id: 'is_sneaking',
|
|
})
|
|
.addQuery({
|
|
id: 'is_sneezing',
|
|
})
|
|
.addQuery({
|
|
id: 'is_sniffing',
|
|
})
|
|
.addQuery({
|
|
id: 'is_sonic_boom',
|
|
})
|
|
.addQuery({
|
|
id: 'is_spectator',
|
|
})
|
|
.addQuery({
|
|
id: 'is_sprinting',
|
|
})
|
|
.addQuery({
|
|
id: 'is_stackable',
|
|
})
|
|
.addQuery({
|
|
id: 'is_stalking',
|
|
})
|
|
.addQuery({
|
|
id: 'is_standing',
|
|
})
|
|
.addQuery({
|
|
id: 'is_stunned',
|
|
})
|
|
.addQuery({
|
|
id: 'is_swimming',
|
|
})
|
|
.addQuery({
|
|
id: 'is_tamed',
|
|
})
|
|
.addQuery({
|
|
id: 'is_transforming',
|
|
})
|
|
.addQuery({
|
|
id: 'is_using_item',
|
|
})
|
|
.addQuery({
|
|
id: 'is_wall_climbing',
|
|
})
|
|
.addQuery({
|
|
id: 'item_in_use_duration',
|
|
})
|
|
.addQuery({
|
|
id: 'item_is_charged',
|
|
})
|
|
.addQuery({
|
|
id: 'item_max_use_duration',
|
|
})
|
|
.addQuery({
|
|
id: 'item_remaining_use_duration',
|
|
})
|
|
.addQuery({
|
|
id: 'item_slot_to_bone_name',
|
|
arguments: ['slotName'],
|
|
})
|
|
.addQuery({
|
|
id: 'key_frame_lerp_time',
|
|
})
|
|
.addQuery({
|
|
id: 'last_frame_time',
|
|
})
|
|
.addQuery({
|
|
id: 'last_hit_by_player',
|
|
})
|
|
.addQuery({
|
|
id: 'lie_amount',
|
|
})
|
|
.addQuery({
|
|
id: 'life_span',
|
|
})
|
|
.addQuery({
|
|
id: 'lod_index',
|
|
})
|
|
.addQuery({
|
|
id: 'log',
|
|
})
|
|
.addQuery({
|
|
id: 'main_hand_item_max_duration',
|
|
})
|
|
.addQuery({
|
|
id: 'main_hand_item_use_duration',
|
|
})
|
|
.addQuery({
|
|
id: 'max_durability',
|
|
})
|
|
.addQuery({
|
|
id: 'max_health',
|
|
})
|
|
.addQuery({
|
|
id: 'max_trade_tier',
|
|
})
|
|
.addQuery({
|
|
id: 'maximum_frame_time',
|
|
})
|
|
.addQuery({
|
|
id: 'minimum_frame_time',
|
|
})
|
|
.addQuery({
|
|
id: 'model_scale',
|
|
})
|
|
.addQuery({
|
|
id: 'modified_distance_moved',
|
|
})
|
|
.addQuery({
|
|
id: 'modified_move_speed',
|
|
})
|
|
.addQuery({
|
|
id: 'moon_brightness',
|
|
})
|
|
.addQuery({
|
|
id: 'moon_phase',
|
|
})
|
|
.addQuery({
|
|
id: 'movement_direction',
|
|
})
|
|
.addQuery({
|
|
id: 'noise',
|
|
})
|
|
.addQuery({
|
|
id: 'on_fire_time',
|
|
})
|
|
.addQuery({
|
|
id: 'out_of_control',
|
|
})
|
|
.addQuery({
|
|
id: 'player_level',
|
|
})
|
|
.addQuery({
|
|
id: 'position',
|
|
arguments: ['axis'],
|
|
})
|
|
.addQuery({
|
|
id: 'position_delta',
|
|
arguments: ['axis'],
|
|
})
|
|
.addQuery({
|
|
id: 'previous_squish_value',
|
|
})
|
|
.addQuery({
|
|
id: 'remaining_durability',
|
|
})
|
|
.addQuery({
|
|
id: 'roll_counter',
|
|
})
|
|
.addQuery({
|
|
id: 'rotation_to_camera',
|
|
arguments: ['axis'],
|
|
})
|
|
.addQuery({
|
|
id: 'shake_angle',
|
|
})
|
|
.addQuery({
|
|
id: 'shake_time',
|
|
})
|
|
.addQuery({
|
|
id: 'shield_blocking_bob',
|
|
})
|
|
.addQuery({
|
|
id: 'show_bottom',
|
|
})
|
|
.addQuery({
|
|
id: 'sit_amount',
|
|
})
|
|
.addQuery({
|
|
id: 'sleep_rotation',
|
|
})
|
|
.addQuery({
|
|
id: 'sneeze_counter',
|
|
})
|
|
.addQuery({
|
|
id: 'spellcolor',
|
|
})
|
|
.addQuery({
|
|
id: 'standing_scale',
|
|
})
|
|
.addQuery({
|
|
id: 'structural_integrity',
|
|
})
|
|
.addQuery({
|
|
id: 'surface_particle_color',
|
|
})
|
|
.addQuery({
|
|
id: 'surface_particle_texture_coordinate',
|
|
})
|
|
.addQuery({
|
|
id: 'surface_particle_texture_size',
|
|
})
|
|
.addQuery({
|
|
id: 'swell_amount',
|
|
})
|
|
.addQuery({
|
|
id: 'swelling_dir',
|
|
})
|
|
.addQuery({
|
|
id: 'swim_amount',
|
|
})
|
|
.addQuery({
|
|
id: 'tail_angle',
|
|
})
|
|
.addQuery({
|
|
id: 'target_x_rotation',
|
|
})
|
|
.addQuery({
|
|
id: 'target_y_rotation',
|
|
})
|
|
.addQuery({
|
|
id: 'texture_frame_index',
|
|
})
|
|
.addQuery({
|
|
id: 'time_of_day',
|
|
})
|
|
.addQuery({
|
|
id: 'time_since_last_vibration_detection',
|
|
})
|
|
.addQuery({
|
|
id: 'time_stamp',
|
|
})
|
|
.addQuery({
|
|
id: 'total_emitter_count',
|
|
})
|
|
.addQuery({
|
|
id: 'total_particle_count',
|
|
})
|
|
.addQuery({
|
|
id: 'trade_tier',
|
|
})
|
|
.addQuery({
|
|
id: 'unhappy_counter',
|
|
})
|
|
.addQuery({
|
|
id: 'walk_distance',
|
|
})
|
|
.addQuery({
|
|
id: 'wing_flap_position',
|
|
})
|
|
.addQuery({
|
|
id: 'wing_flap_speed',
|
|
})
|
|
)
|
|
.addNamespace(
|
|
new MolangAutocomplete.Namespace({
|
|
id: 'math',
|
|
shorthand: 'm',
|
|
})
|
|
.addQuery({
|
|
id: 'sin',
|
|
arguments: ['value'],
|
|
})
|
|
.addQuery({
|
|
id: 'cos',
|
|
arguments: ['value'],
|
|
})
|
|
.addQuery({
|
|
id: 'abs',
|
|
arguments: ['value'],
|
|
})
|
|
.addQuery({
|
|
id: 'clamp',
|
|
arguments: ['value', 'min', 'max'],
|
|
})
|
|
.addQuery({
|
|
id: 'pow',
|
|
arguments: ['base', 'exponent'],
|
|
})
|
|
.addQuery({
|
|
id: 'sqrt',
|
|
arguments: ['value'],
|
|
})
|
|
.addQuery({
|
|
id: 'random',
|
|
arguments: ['low', 'high'],
|
|
})
|
|
.addQuery({
|
|
id: 'random_integer',
|
|
arguments: ['low', 'high'],
|
|
})
|
|
.addQuery({
|
|
id: 'ceil',
|
|
arguments: ['value'],
|
|
})
|
|
.addQuery({
|
|
id: 'round',
|
|
arguments: ['value'],
|
|
})
|
|
.addQuery({
|
|
id: 'trunc',
|
|
arguments: ['value'],
|
|
})
|
|
.addQuery({
|
|
id: 'floor',
|
|
arguments: ['value'],
|
|
})
|
|
.addQuery({
|
|
id: 'mod',
|
|
arguments: ['value', 'denominator'],
|
|
})
|
|
.addQuery({
|
|
id: 'min',
|
|
arguments: ['a', 'b'],
|
|
})
|
|
.addQuery({
|
|
id: 'max',
|
|
arguments: ['a', 'b'],
|
|
})
|
|
.addQuery({
|
|
id: 'exp',
|
|
arguments: ['value'],
|
|
})
|
|
.addQuery({
|
|
id: 'ln',
|
|
arguments: ['value'],
|
|
})
|
|
.addQuery({
|
|
id: 'lerp',
|
|
arguments: ['start', 'end', '0_to_1'],
|
|
})
|
|
.addQuery({
|
|
id: 'lerprotate',
|
|
arguments: ['start', 'end', '0_to_1'],
|
|
})
|
|
.addQuery({
|
|
id: 'pi',
|
|
})
|
|
.addQuery({
|
|
id: 'asin',
|
|
arguments: ['value'],
|
|
})
|
|
.addQuery({
|
|
id: 'acos',
|
|
arguments: ['value'],
|
|
})
|
|
.addQuery({
|
|
id: 'atan',
|
|
arguments: ['value'],
|
|
})
|
|
.addQuery({
|
|
id: 'atan2',
|
|
arguments: ['y', 'x'],
|
|
})
|
|
.addQuery({
|
|
id: 'die_roll',
|
|
arguments: ['num', 'low', 'high'],
|
|
})
|
|
.addQuery({
|
|
id: 'die_roll_integer',
|
|
arguments: ['num', 'low', 'high'],
|
|
})
|
|
.addQuery({
|
|
id: 'hermite_blend',
|
|
arguments: ['0_to_1'],
|
|
})
|
|
)
|
|
.addNamespace(
|
|
new MolangAutocomplete.Namespace({
|
|
id: 'context',
|
|
shorthand: 'c',
|
|
})
|
|
.addQuery({
|
|
id: 'item_slot',
|
|
})
|
|
.addQuery({
|
|
id: 'block_face',
|
|
})
|
|
.addQuery({
|
|
id: 'cardinal_block_face_placed_on',
|
|
})
|
|
.addQuery({
|
|
id: 'is_first_person',
|
|
})
|
|
.addQuery({
|
|
id: 'owning_entity',
|
|
})
|
|
.addQuery({
|
|
id: 'player_offhand_arm_height',
|
|
})
|
|
.addQuery({
|
|
id: 'other',
|
|
})
|
|
.addQuery({
|
|
id: 'count',
|
|
})
|
|
)
|
|
.addNamespace(
|
|
new MolangAutocomplete.Namespace({
|
|
id: 'variable',
|
|
shorthand: 'v',
|
|
})
|
|
.addQuery({
|
|
id: 'attack_time',
|
|
})
|
|
.addQuery({
|
|
id: 'is_first_person',
|
|
})
|
|
.addQueryGetter('variables', (_, incomplete) => {
|
|
const variables = getProjectMolangVariables([incomplete])
|
|
return [...variables].map((v) => ({ id: v }))
|
|
})
|
|
)
|
|
.addNamespace(
|
|
new MolangAutocomplete.Namespace({
|
|
id: 'temp',
|
|
shorthand: 't',
|
|
}).addQueryGetter('temporary_variables', (expression, incomplete) => {
|
|
const variables = getTemporaryMolangVariables(expression, [incomplete])
|
|
return [...variables].map((v) => ({ id: v }))
|
|
})
|
|
)
|
|
|
|
MolangAutocomplete.KeyframeContext = new MolangAutocomplete.Context({
|
|
id: 'keyframeContext',
|
|
inheritedContext: MolangAutocomplete.DefaultContext,
|
|
})
|
|
|
|
MolangAutocomplete.AnimationControllerContext = new MolangAutocomplete.Context({
|
|
id: 'animationControllerContext',
|
|
inheritedContext: MolangAutocomplete.DefaultContext,
|
|
}).addNamespace(
|
|
new MolangAutocomplete.Namespace({
|
|
id: 'query',
|
|
shorthand: 'q',
|
|
})
|
|
.addQuery({
|
|
id: 'all_animations_finished',
|
|
priority: 100,
|
|
})
|
|
.addQuery({
|
|
id: 'any_animation_finished',
|
|
priority: 100,
|
|
})
|
|
)
|
|
|
|
MolangAutocomplete.AnimationContext = new MolangAutocomplete.Context({
|
|
id: 'animationContext',
|
|
inheritedContext: MolangAutocomplete.DefaultContext,
|
|
})
|
|
|
|
MolangAutocomplete.VariablePlaceholdersContext = new MolangAutocomplete.Context({
|
|
id: 'variablePlaceholdersContext',
|
|
inheritedContext: MolangAutocomplete.DefaultContext,
|
|
})
|
|
.addRootToken({
|
|
id: 'toggle',
|
|
arguments: ['name'],
|
|
})
|
|
.addRootToken({
|
|
id: 'slider',
|
|
arguments: ['name', 'step?', 'min?', 'max?'],
|
|
})
|
|
.addRootToken({
|
|
id: 'impulse',
|
|
arguments: ['name', 'duration'],
|
|
})
|
|
|
|
MolangAutocomplete.BedrockBindingContext = new MolangAutocomplete.Context({
|
|
id: 'bedrockBindingContext',
|
|
inheritedContext: MolangAutocomplete.DefaultContext,
|
|
})
|
|
})()
|