flying-squid/src/lib/plugins/commands.js

304 lines
No EOL
9.2 KiB
JavaScript

const UserError = require('flying-squid').UserError;
module.exports.player=function(player, serv) {
player.commands.add({
base: 'help',
info: 'to show all commands',
usage: '/help [command]',
parse(str) {
const params = str.split(' ');
const page = parseInt(params[params.length-1]);
if (page) {
params.pop();
}
const search = params.join(' ');
return { search: search, page: (page && page - 1) || 0 };
},
action({search, page}) {
if (page < 0) return 'Page # must be >= 1';
const hash = player.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';
player.chat(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];
player.chat(baseCmd.base + ' -' + ((baseCmd.params && baseCmd.params.info && ' ' + baseCmd.params.info) || '=-=-=-=-=-=-=-=-'));
} else {
player.chat('Help -=-=-=-=-=-=-=-=-');
}
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';
player.chat(usage + ': ' + info);
}
player.chat('--=[Page ' + (page + 1) + ' of ' + totalPages + ']=--')
}
}
});
player.commands.add({
base: 'ping',
info: 'to pong!',
usage: '/ping [number]',
action(params) {
const num = params[0] * 1 + 1;
let str = 'pong';
if(!isNaN(num)) str += ' [' + num + ']';
player.chat(str + '!');
}
});
player.commands.add({
base: 'modpe',
info: 'for modpe commands',
usage: '/modpe <params>',
parse(str) { return str ? str : false },
action(str) {
player.emit("modpe", str);
}
});
player.commands.add({
base: 'version',
info: 'to get version of the server',
usage: '/version',
action() {
return 'This server is running flying-squid version 0.1.0';
}
});
player.commands.add({
base: 'bug',
info: 'to bug report',
usage: '/bug',
action() {
return 'Report bugs / issues here: https://github.com/mhsjlw/flying-squid/issues';
}
});
player.commands.add({
base: 'selector',
info: 'Get array from selector',
usage: '/selector <selector>',
op: true,
parse(str) {
return str || false;
},
action(sel) {
const arr = serv.selectorString(sel, player.position.scaled(1/32), player.world);
player.chat(JSON.stringify(arr.map(a => a.id)));
}
});
player.handleCommand = async (str) => {
try {
const res = await player.commands.use(str, player.op);
if (res) player.chat(serv.color.red + res);
}
catch(err) {
if (err instanceof 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.scaled(1/32), entity.world);
}
module.exports.server = function(serv) {
function shuffleArray(array) {
let currentIndex = array.length, temporaryValue, randomIndex ;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// 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) => {
if (['all', 'random', 'near', 'entity'].indexOf(type) == -1)
throw new UserError('serv.selector(): type must be either [all, random, near, or entity]');
const count = typeof opt.count != 'undefined' ?
count :
(type == 'all' || type == 'entity' ?serv.entities.length : 1);
const pos = opt.pos;
let sample;
if (type == 'all') sample = serv.players;
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: score,
val: opt[o]
});
}
});
sample = sample.filter(s => {
if ((notudf(opt.radius) && s.position.scaled(1/32).distanceTo(pos) > opt.radius) ||
(notudf(opt.minRadius) && s.position.scaled(1/32).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])) faii = true;
else if (s.scores[m] < m.val) fail = true;
});
if (fail) return false;
return true;
});
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) => {
pos = pos.clone();
const player = serv.getPlayer(str);
if (!player && str[0] != '@') return [];
else if (player) return allowUser ? [player] : [];
const match = str.match(/^@([a,r,p,e])(?:\[([^\]]+)\])?$/);
if (match == null) throw new UserError('Invalid selector format');
const typeConversion = {
a: 'all',
r: 'random',
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: 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);
};
serv.posFromString = (str, pos) => {
if (parseInt(str)) return parseInt(str);
if (str.match(/~-?\d+/)) return parseInt(str.slice(1)) + pos;
else if (str == '~') return pos;
else throw new UserError('Invalid position');
};
};