Minecraft-Protocol-1.19.2-C.../MessageBuilder.js
2024-06-08 03:03:00 -04:00

257 lines
8.4 KiB
JavaScript

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/<namespace>/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<MessageBuilder | string>} ...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<MessageBuilder | string>} ...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