flying-squid
Version:
A minecraft server written in node.js
361 lines (321 loc) • 11.8 kB
JavaScript
const UserError = require('flying-squid').UserError
const colors = require('colors')
module.exports.player = function (player, serv, { version }) {
player.handleCommand = async (str) => {
try {
const res = await serv.commands.use(str, { player }, player.op)
if (res) player.chat(serv.color.red + res)
} catch (err) {
if (err.userError) player.chat(serv.color.red + 'Error: ' + err.message)
else setTimeout(() => { throw err }, 0)
}
}
}
module.exports.entity = function (entity, serv) {
entity.selectorString = (str) => serv.selectorString(str, entity.position, entity.world)
}
module.exports.server = function (serv, { version }) {
serv.handleCommand = async (str) => {
try {
const res = await serv.commands.use(str)
if (res) serv.info(res)
} catch (err) {
if (err.userError) serv.err(err.message)
else setTimeout(() => { throw err }, 0)
}
}
serv.commands.add({
base: 'ping',
info: 'to pong!',
usage: '/ping [number]',
action (params, ctx) {
const num = params[0] * 1 + 1
let str = 'pong'
if (!isNaN(num)) str += ' [' + num + ']'
if (ctx.player) ctx.player.chat(str + '!')
else serv.info(str + '!')
}
})
serv.commands.add({
base: 'modpe',
info: 'for modpe commands',
usage: '/modpe <params>',
onlyPlayer: true,
parse (str) { return str || false },
action (str, ctx) {
ctx.player.emit('modpe', str)
}
})
serv.commands.add({
base: 'version',
info: 'to get version of the server',
usage: '/version',
action () {
return 'This server is running flying-squid version ' + version
}
})
serv.commands.add({
base: 'bug',
info: 'to bug report',
usage: '/bug',
action () {
return 'Report bugs or issues here: https://github.com/PrismarineJS/flying-squid/issues'
}
})
serv.commands.add({
base: 'selector',
info: 'Get entities id from selector like @a',
usage: '/selector <selector>',
op: true,
parse (str) {
return str || false
},
action (sel, ctx) {
const arr = ctx.player ? serv.selectorString(sel, ctx.player.position, ctx.player.world) : serv.selectorString(sel)
if (ctx.player) ctx.player.chat(JSON.stringify(arr.map(a => a.id)))
else serv.info(JSON.stringify(arr.map(a => a.id)))
}
})
serv.commands.add({
base: 'help',
aliases: ['?'],
info: 'to show all commands',
usage: '/help [command]',
tab: ['command'],
parse (str) {
const params = str.split(' ')
const page = parseInt(params[params.length - 1])
if (page) {
params.pop()
}
const search = params.join(' ')
return { search, page: (page && page - 1) || 0 }
},
action ({ search, page }, ctx) {
if (page < 0) return 'Page # must be >= 1'
const hash = serv.commands.uniqueHash
const PAGE_LENGTH = 7
let found = Object.keys(hash).filter(h => (h + ' ').indexOf((search && search + ' ') || '') === 0)
if (found.length === 0) { // None found
return 'Could not find any matches'
} else if (found.length === 1) { // Single command found, giev info on command
const cmd = hash[found[0]]
const usage = (cmd.params && cmd.params.usage) || cmd.base
const info = (cmd.params && cmd.params.info) || 'No info'
if (ctx.player) ctx.player.chat(usage + ': ' + info)
else serv.info(usage + ': ' + info)
} else { // Multiple commands found, give list with pages
const totalPages = Math.ceil((found.length - 1) / PAGE_LENGTH)
if (page >= totalPages) return 'There are only ' + totalPages + ' help pages'
found = found.sort()
if (found.indexOf('search') !== -1) {
const baseCmd = hash[search]
if (ctx.player) ctx.player.chat(baseCmd.base + ' -' + ((baseCmd.params && baseCmd.params.info && ' ' + baseCmd.params.info) || '=-=-=-=-=-=-=-=-'))
else serv.info(baseCmd.base + ' -' + ((baseCmd.params && baseCmd.params.info && ' ' + baseCmd.params.info) || '=-=-=-=-=-=-=-=-'))
} else {
if (ctx.player) ctx.player.chat('&2--=[ &fHelp&2, page &f' + (page + 1) + ' &2of &f' + totalPages + ' &2]=--')
else serv.info(colors.green('--=[ ') + colors.white('Help') + colors.green(', page ') + colors.white(page + 1) + colors.green(' of ') + colors.white(totalPages) + colors.green(' ]=--'))
}
for (let i = PAGE_LENGTH * page; i < Math.min(PAGE_LENGTH * (page + 1), found.length); i++) {
if (found[i] === search) continue
const cmd = hash[found[i]]
const usage = (cmd.params && cmd.params.usage) || cmd.base
const info = (cmd.params && cmd.params.info) || 'No info'
if (ctx.player) ctx.player.chat(usage + ': ' + info + ' ' + (cmd.params.onlyPlayer ? ('| &aPlayer only') : (cmd.params.onlyConsole ? ('| &cConsole only') : '')))
else serv.info(colors.yellow(usage) + ': ' + info + ' ' + (cmd.params.onlyPlayer ? (colors.bgRed(colors.black('Player only'))) : (cmd.params.onlyConsole ? colors.bgGreen(colors.black('Console only')) : '')))
}
}
}
})
serv.commands.add({
base: 'stop',
info: 'Stop the server',
usage: '/stop',
op: true,
async action () {
await serv.quit('Server closed')
process.exit(0)
}
})
serv.commands.add({
base: 'say',
info: 'Broadcast a message',
usage: '/say <message>',
op: true,
parse (params) {
return params || false
},
action (params, ctx) {
const who = ctx.player ? ctx.player.username : 'Server'
serv.broadcast(`[${who}] ` + params)
serv.info(`[CHAT]: [${who}] ` + params)
}
})
serv.commands.add({
base: 'me',
info: 'Displays a message about yourself',
usage: '/me <message>',
op: false,
parse (params) {
return params || false
},
action (params, ctx) {
const who = ctx.player ? ctx.player.username : 'Server'
serv.broadcast(`* ${who} ` + params)
serv.info(`* ${who} ` + params)
}
})
function shuffleArray (array) {
let currentIndex = array.length
let temporaryValue
let randomIndex
// While there remain elements to shuffle...
while (currentIndex !== 0) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex)
currentIndex -= 1
// And swap it with the current element.
temporaryValue = array[currentIndex]
array[currentIndex] = array[randomIndex]
array[randomIndex] = temporaryValue
}
return array
}
const notudf = i => typeof i !== 'undefined'
serv.selector = (type, opt, selfEntityId) => {
if (['all', 'random', 'self', 'near', 'entity'].indexOf(type) === -1) { throw new UserError('serv.selector(): type must be either [all, random, self, near, or entity]') }
const count = opt.count !== undefined
? opt.count
: (type === 'all' || type === 'entity' ? serv.entities.length : 1)
const pos = opt.pos
let sample
if (type === 'all') sample = serv.players
else if (type === 'self') sample = serv.players.filter(p => p.id === selfEntityId)
else if (type === 'random' || type === 'near') sample = serv.players.filter(p => p.health !== 0)
else if (type === 'entity') sample = Object.keys(serv.entities).map(k => serv.entities[k])
const checkOption = (val, compare) => {
if (!val) return true
const not = val[0] === '!'
let v = val
if (not) v = v.slice(1, v.length)
if (not && compare === v) return false
if (!not && compare !== v) return false
return true
}
const scores = {
max: [],
min: []
}
Object.keys(opt).forEach(o => {
if (o.indexOf('score_') !== 0) return
const score = o.replace('score_', '')
if (score.indexOf('_min') === score.length - 1) {
scores.min.push({
score: score.replace('_min', ''),
val: opt[o]
})
} else {
scores.max.push({
score,
val: opt[o]
})
}
})
sample = sample.filter(s => {
if ((notudf(opt.radius) && s.position.distanceTo(pos) > opt.radius) ||
(notudf(opt.minRadius) && s.position.distanceTo(pos) < opt.minRadius) ||
(notudf(opt.gameMode) && s.gameMode !== opt.gameMode) ||
(notudf(opt.level) && s.level > opt.level) ||
(notudf(opt.minLevel) && s.level < opt.minLevel) ||
(notudf(opt.yaw) && s.yaw > opt.yaw) ||
(notudf(opt.minYaw) && s.yaw < opt.minYaw) ||
(notudf(opt.pitch) && s.pitch > opt.pitch) ||
(notudf(opt.minPitch) && s.pitch < opt.minPitch)) { return false }
if (!checkOption(opt.team, s.team)) return false
if (!checkOption(opt.name, s.username)) return false
if (!checkOption(opt.type, s.name)) return false
let fail = false
scores.max.forEach(m => {
if (fail) return
if (!notudf(s.scores[m.score])) fail = true
else if (s.scores[m] > m.val) fail = true
})
if (fail) return false
scores.min.forEach(m => {
if (fail) return
if (!notudf(s.scores[m.score])) fail = true
else if (s.scores[m] < m.val) fail = true
})
return !fail
})
if (type === 'near') sample.sort((a, b) => a.position.distanceTo(opt.pos) > b.position.distanceTo(opt.pos))
else if (type === 'random') sample = shuffleArray(sample)
else sample = sample.reverse() // Front = newest
if (count > 0) return sample.slice(0, count)
else return sample.slice(count) // Negative, returns from end
}
serv.selectorString = (str, pos, world, allowUser = true, ctxEntityId) => {
if (pos) pos = pos.clone()
const player = serv.getPlayer(str)
if (!player && str[0] !== '@') return []
else if (player) return allowUser ? [player] : []
const match = str.match(/^@([arspe])(?:\[([^\]]+)\])?$/)
if (match[1] === 'r' && !pos) throw new UserError('Can\'t find nearest players')
if (match === null) throw new UserError('Invalid selector format')
const typeConversion = {
a: 'all',
r: 'random',
s: 'self',
p: 'near',
e: 'entity'
}
const type = typeConversion[match[1]]
const opt = match[2] ? match[2].split(',') : []
const optPair = []
let err
opt.forEach(o => {
const match = o.match(/^([^=]+)=([^=]+)$/)
if (match === null) err = new UserError('Invalid selector option format: "' + o + '"')
else optPair.push({ key: match[1], val: match[2] })
})
if (err) throw err
const optConversion = {
type: 'type',
r: 'radius',
rm: 'minRadius',
m: 'gameMode',
c: 'count',
l: 'level',
lm: 'minLevel',
team: 'team',
name: 'name',
rx: 'yaw',
rxm: 'minYaw',
ry: 'pitch',
rym: 'minPitch'
}
const convertInt = ['r', 'rm', 'm', 'c', 'l', 'lm', 'rx', 'rxm', 'ry', 'rym']
const data = {
pos: pos || '',
world,
scores: [],
minScores: []
}
optPair.forEach(({ key, val }) => {
if (['x', 'y', 'z'].indexOf(key) !== -1) pos[key] = val
else if (!optConversion[key]) {
data[key] = val
} else {
if (convertInt.indexOf(key) !== -1) val = parseInt(val)
data[optConversion[key]] = val
}
})
return serv.selector(type, data, ctxEntityId)
}
serv.posFromString = (str, pos) => {
if (str.indexOf('~') === -1) return parseFloat(str)
if (str.match(/~-?\d+/)) return parseFloat(str.slice(1)) + pos
else if (str === '~') return pos
else throw new UserError('Invalid position')
}
}