const mojangson = require('mojangson') const nbt = require('prismarine-nbt') const supportedColors = { 0: 'black', 1: 'dark_blue', 2: 'dark_green', 3: 'dark_aqua', 4: 'dark_red', 5: 'dark_purple', 6: 'gold', 7: 'gray', 8: 'dark_gray', 9: 'blue', a: 'green', b: 'aqua', c: 'red', d: 'light_purple', e: 'yellow', f: 'white', k: 'obfuscated', l: 'bold', m: 'strikethrough', n: 'underlined', o: 'italic', r: 'reset' } function loader (registry) { class MessageBuilder { constructor () { this.with = [] this.extra = [] } /** @param {boolean} val */ setBold (val) { this.bold = val; return this } /** @param {boolean} val */ setItalic (val) { this.italic = val; return this } /** @param {boolean} val */ setUnderlined (val) { this.underlined = val; return this } /** @param {boolean} val */ setStrikethrough (val) { this.strikethrough = val; return this } /** @param {boolean} val */ setObfuscated (val) { this.obfuscated = val; return this } /** @param {string} val */ setColor (val) { this.color = val; return this } /** @param {string} val */ setText (val) { this.text = val; return this } /** * The resource location of the font for this component in the resource pack within `assets//font`. * @param {string} val Defaults to `minecraft:default` * @returns */ setFont (val) { this.font = val; return this } /** * When used, it's expected that all slots for text will be filled using .addWith() * @param {string} val * @returns */ setTranslate (val) { this.translate = val; return this } /** * text shown when shift clicked on message * @param {string} val */ setInsertion (val) { this.insertion = val; return this } /** * Overrode by .setText() * @param {string} val * @example * builder.setKeybind('key.inventory') */ setKeybind (val) { this.keybind = val; return this } /** * Displays a score holder's current score in an objective. * Displays nothing if the given score holder or the given objective do not exist, or if the score holder is not tracked in the objective. * @param {string} name if '*', show reader their own score. Otherwise, this is the player's score shown. Can be a selector string, but must never select more than one entity. * @param {string} objective The internal name of the objective to display the player's score in. */ setScore (name, objective) { this.score = { name, objective }; return this } /** * @param {'open_url'|'run_command'|'suggest_command'|'change_page'|'copy_to_clipboard'} action * @param {string|number} value * @example * builder.setClickEvent('open_url', 'https://google.com') * builder.setClickEvent('run_command', '/say Hi!') // for signs, the slash doesn't need to be there * builder.setClickEvent('suggest_command', '/say ') * builder.setClickEvent('change_page', '/say Hi!') // Can only be used in written books * builder.setClickEvent('copy_to_clipboard', 'welcome to your clipboard') */ setClickEvent (action, value) { this.clickEvent = { action, value }; return this } /** * @param {'show_text'|'show_entity'|'show_item'} action * @param {import('prismarine-item').Item|import('prismarine-entity').Entity|MessageBuilder} data * @param {'contents'|'value'} type [type='contents'] */ setHoverEvent (action, data, type = 'contents') { const hoverEvent = { action } if (type === 'contents') { switch (action) { case 'show_item': hoverEvent.contents = { id: `minecraft:${data.name}`, count: data.count, tag: data.nbt ? mojangson.stringify(data.nbt) : {} } break case 'show_entity': hoverEvent.contents = { name: data.displayName, type: `minecraft:${data.name}`, id: data.uuid } break case 'show_text': hoverEvent.contents = data break default: throw Error('Not implemented') } } else if (type === 'value') { switch (action) { case 'show_item': // works for 1.12.2 & 1.17 hoverEvent.value = mojangson.stringify(nbt.comp({ id: nbt.string(`minecraft:${data.name}`), Count: nbt.byte(data.count), tag: data.nbt || nbt.comp({}), Damage: nbt.short(0) })) break case 'show_text': hoverEvent.value = data.toString() break case 'show_entity': default: throw Error('Not implemented') } } this.hoverEvent = hoverEvent return this } /** * appended to the end of this message object with the existing formatting. * formatting can be overrode in child messagebuilder * @param {Array} ...args * @returns */ addExtra (...args) { for (const v of args) { const value = typeof v === 'string' ? v : v.toJSON() this.extra.push(value) } return this } /** * requires .translate to be set for this to be used * @param {Array} ...args * @returns */ addWith (...args) { for (const v of args) { const value = typeof v === 'string' ? v : v.toJSON() this.with.push(value) } return this } resetFormatting () { this.setBold(false) this.setItalic(false) this.setUnderlined(false) this.setStrikethrough(false) this.setObfuscated(false) this.setColor('reset') } toJSON () { const isDef = x => x !== undefined const obj = {} if (isDef(this.strikethrough)) obj.strikethrough = this.strikethrough if (isDef(this.obfuscated)) obj.obfuscated = this.obfuscated if (isDef(this.underlined)) obj.underlined = this.underlined if (isDef(this.clickEvent)) obj.clickEvent = this.clickEvent if (isDef(this.hoverEvent)) obj.hoverEvent = this.hoverEvent if (isDef(this.translate)) obj.translate = this.translate if (isDef(this.insertion)) obj.insertion = this.insertion if (isDef(this.italic)) obj.italic = this.italic if (isDef(this.color)) obj.color = this.color if (isDef(this.bold)) obj.bold = this.bold if (isDef(this.font)) obj.font = this.font if (isDef(this.text)) { // text > keybind > score obj.text = this.text } else if (isDef(this.keybind)) { obj.keybind = this.keybind } else if (isDef(this.score)) { obj.score = this.score } if (isDef(this.translate) && this.with.length > 0) { obj.with = this.with } if (this.extra.length > 0) { obj.extra = this.extra } return obj } toString () { return JSON.stringify(this) } static fromString (str, { colorSeparator = '&' } = {}) { let lastObj = null let currString = '' for (let i = str.length - 1; i > -1; i--) { const char = str.substring(i, i + 1) if (char !== colorSeparator) currString += char else { const text = currString.split('').reverse() const color = supportedColors[text.shift()] const newObj = new MessageBuilder() if (color === 'obfuscated') { newObj.setObfuscated(true) } else if (color === 'bold') { newObj.setBold(true) } else if (color === 'strikethrough') { newObj.setStrikethrough(true) } else if (color === 'underlined') { newObj.setUnderlined(true) } else if (color === 'italic') { newObj.setItalic(true) } else if (color === 'reset') { newObj.resetFormatting() } else { newObj.setColor(color) } newObj.setText(text.join('')) if (lastObj === null) lastObj = newObj else lastObj = newObj.addExtra(lastObj) currString = '' } } if (currString !== '') { const txt = currString.split('').reverse().join('') if (lastObj !== null) lastObj = new MessageBuilder().setText(txt).addExtra(lastObj) else lastObj = new MessageBuilder().setText(txt) } return lastObj } } return { MessageBuilder } } module.exports = loader