From 843fa1ffa23e421b4894d6f88163b0182eecd194 Mon Sep 17 00:00:00 2001 From: DemiPixel Date: Mon, 7 Dec 2015 01:21:45 -0800 Subject: [PATCH 1/3] Starting selectors (incomplete) --- src/lib/plugins/commands.js | 95 +++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/src/lib/plugins/commands.js b/src/lib/plugins/commands.js index 6f20440..8867c45 100644 --- a/src/lib/plugins/commands.js +++ b/src/lib/plugins/commands.js @@ -84,3 +84,98 @@ module.exports.player=function(player) { } } }; + +module.exports.server = function(serv) { + serv.selector = (type, opt) => { + if (['all', 'random', 'near', 'entity'].indexOf(type) == -1) + return new Error('serv.selector(): type must be either [all, random, near, or entity]'); + + var count = typeof opt.count != 'undefined' ? + count : + (type == 'all' || type == 'entity' ?serv.entities.length : 1); + var pos = opt.pos; + var sample; + if (type == 'all') sample = serv.players; + else if (type == 'random' || type == 'near') sample.players.filter(p => p.health != 0); + else if (type == 'entity') sample = Object.keys(serv.entities).map(k => serv.entities[k]); + + var checkOption = (val, compare) => { + var not = val[0] == '!'; + var v = val; + if (not) v = v.slice(1, v.length); + if (not && compare == v) return false; + if (!not && compare != v) return false; + return true; + } + + sample.filter(s => { + if ((opt.radius && s.position.distanceTo(pos) > opt.radius) || + (opt.minRadius && s.position.distanceTo(pos) < opt.minRadius) || + (opt.gameMode && s.gameMode != opt.gameMode) || + (opt.level && s.level > opt.level) || + (opt.minLevel && s.level < opt.minLevel) || + (opt.yaw && s.yaw > opt.yaw) || + (opt.minYaw && s.yaw < opt.minYaw) || + (opt.pitch && s.pitch > opt.pitch) || + (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.type)) return false; // "type" option of selector needs fixing + }); + } + + serv.selectorString = (str, pos) => { + pos = pos.clone(); + var player = serv.getPlayer(str); + if (!player && str[0] != '@') return null; + var match = str.match(/^@([a,r,p,e])(\[[^\]]+\])?$/); + if (match == null) return new Error('Invalid selector format'); + var typeConversion = { + a: 'all', + r: 'random', + p: 'near', + e: 'entity' + }; + var type = typeConversion[match[1]]; + var opt = match[2].split(','); + var optPair = []; + var err; + opt.forEach(o => { + var match = o.match(/^([^=]+)=([^=]+)$/); + if (match == null) err = new Error('Invalid selector option format: "' + o + '"'); + else optPair.push({key: match[1], val: match[2]}); + }); + if (err) return err; + + var 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' + }; + + var data = { + pos: pos + }; + + optPair.forEach(({key,val}) => { + if (['x', 'y', 'z'].indexOf(key) != -1) pos[key] = val; + else { + data[optConversion[key]] = val; + } + }); + + return serv.selector(data); + } +} \ No newline at end of file From e151d6958eb118beac39f08baf79b157637ef977 Mon Sep 17 00:00:00 2001 From: DemiPixel Date: Mon, 7 Dec 2015 15:59:56 -0800 Subject: [PATCH 2/3] Finish basic implementation of selectors --- src/lib/plugins/commands.js | 127 +++++++++++++++++++++++++++++++----- src/lib/plugins/spawn.js | 1 + 2 files changed, 111 insertions(+), 17 deletions(-) diff --git a/src/lib/plugins/commands.js b/src/lib/plugins/commands.js index 8867c45..e5dbdf1 100644 --- a/src/lib/plugins/commands.js +++ b/src/lib/plugins/commands.js @@ -1,4 +1,6 @@ -module.exports.player=function(player) { +var Vec3 = require("vec3").Vec3; + +module.exports.player=function(player, serv) { player.commands.add({ base: 'help', @@ -73,6 +75,22 @@ module.exports.player=function(player) { } }); + player.commands.add({ + base: 'selector', + info: 'Get array from selector', + usage: '/selector ', + op: true, + parse(str) { + return str || false; + }, + action(sel) { + var arr = serv.selectorString(sel, player.position.scaled(1/32), player.world); + if (arr instanceof Error) return arr.toString(); + else if (arr == null) return 'Could not find player'; + else player.chat(JSON.stringify(arr.map(a => a.id))); + } + }) + player.handleCommand = async (str) => { try { @@ -86,6 +104,28 @@ module.exports.player=function(player) { }; module.exports.server = function(serv) { + + function shuffleArray(array) { + var 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; + } + + var notudf = i => typeof i != 'undefined'; + serv.selector = (type, opt) => { if (['all', 'random', 'near', 'entity'].indexOf(type) == -1) return new Error('serv.selector(): type must be either [all, random, near, or entity]'); @@ -96,10 +136,11 @@ module.exports.server = function(serv) { var pos = opt.pos; var sample; if (type == 'all') sample = serv.players; - else if (type == 'random' || type == 'near') sample.players.filter(p => p.health != 0); + 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]); var checkOption = (val, compare) => { + if (!val) return true; var not = val[0] == '!'; var v = val; if (not) v = v.slice(1, v.length); @@ -108,29 +149,73 @@ module.exports.server = function(serv) { return true; } - sample.filter(s => { - if ((opt.radius && s.position.distanceTo(pos) > opt.radius) || - (opt.minRadius && s.position.distanceTo(pos) < opt.minRadius) || - (opt.gameMode && s.gameMode != opt.gameMode) || - (opt.level && s.level > opt.level) || - (opt.minLevel && s.level < opt.minLevel) || - (opt.yaw && s.yaw > opt.yaw) || - (opt.minYaw && s.yaw < opt.minYaw) || - (opt.pitch && s.pitch > opt.pitch) || - (opt.minPitch && s.pitch < opt.minPitch)) + var scores = { + max: [], + min: [] + }; + + Object.keys(opt).forEach(o => { + if (o.indexOf('score_') != 0) return; + var 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.type)) return false; // "type" option of selector needs fixing + + var 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) => { + serv.selectorString = (str, pos, world) => { pos = pos.clone(); var player = serv.getPlayer(str); if (!player && str[0] != '@') return null; - var match = str.match(/^@([a,r,p,e])(\[[^\]]+\])?$/); + var match = str.match(/^@([a,r,p,e])(?:\[([^\]]+)\])?$/); if (match == null) return new Error('Invalid selector format'); var typeConversion = { a: 'all', @@ -139,7 +224,7 @@ module.exports.server = function(serv) { e: 'entity' }; var type = typeConversion[match[1]]; - var opt = match[2].split(','); + var opt = match[2] ? match[2].split(',') : []; var optPair = []; var err; opt.forEach(o => { @@ -164,18 +249,26 @@ module.exports.server = function(serv) { ry: 'pitch', rym: 'minPitch' }; + var convertInt = ['r', 'rm', 'm', 'c', 'l', 'lm', 'rx', 'rxm', 'ry', 'rym']; var data = { - pos: pos + 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(data); + return serv.selector(type, data); } } \ No newline at end of file diff --git a/src/lib/plugins/spawn.js b/src/lib/plugins/spawn.js index 39eb868..4f9782d 100644 --- a/src/lib/plugins/spawn.js +++ b/src/lib/plugins/spawn.js @@ -182,6 +182,7 @@ module.exports.entity=function(entity,serv) { entity.lastPositionPlayersUpdated = entity.position.clone(); entity.nearbyEntities = []; entity.viewDistance = 150; + entity.score = {}; entity.bornTime = Date.now(); serv.entities[entity.id] = entity; From 5d0d9ceb115ec24929bc03b70ec7ffd63e1e6bec Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Wed, 9 Dec 2015 01:03:32 +0100 Subject: [PATCH 3/3] fix @e[type=Zombie] selector --- doc/API.md | 5 +++++ src/lib/plugins/commands.js | 2 +- src/lib/plugins/spawn.js | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/API.md b/doc/API.md index 60d3a9e..7ad598e 100644 --- a/doc/API.md +++ b/doc/API.md @@ -60,6 +60,7 @@ - [entity.world](#entityworld) - [entity.type](#entitytype) - [entity.entityType](#entityentitytype) + - [entity.name](#entityname) - [entity.nearbyEntities](#entitynearbyentities) - [entity.viewDistance](#entityviewdistance) - [entity.health](#entityhealth) @@ -424,6 +425,10 @@ Either "player", "mob", or "object" (currently) #### entity.entityType +Numerical type of the entity. + +#### entity.name + Sub-category of entity. For mobs, this is which mob (Zombie/Skeleton, etc). For objects, this is which object (Arrow/Dropped item, etc) #### entity.nearbyEntities diff --git a/src/lib/plugins/commands.js b/src/lib/plugins/commands.js index e5dbdf1..2363104 100644 --- a/src/lib/plugins/commands.js +++ b/src/lib/plugins/commands.js @@ -184,7 +184,7 @@ module.exports.server = function(serv) { if (!checkOption(opt.team, s.team)) return false; if (!checkOption(opt.name, s.username)) return false; - if (!checkOption(opt.type, s.type)) return false; // "type" option of selector needs fixing + if (!checkOption(opt.type, s.name)) return false; var fail = false; scores.max.forEach(m => { diff --git a/src/lib/plugins/spawn.js b/src/lib/plugins/spawn.js index 4f9782d..d3dce1e 100644 --- a/src/lib/plugins/spawn.js +++ b/src/lib/plugins/spawn.js @@ -1,5 +1,6 @@ var version = require("../version"); var entitiesByName=require("minecraft-data")(version).entitiesByName; +var entitiesById=require("minecraft-data")(version).entities; var Entity = require("prismarine-entity"); var path = require('path'); var requireIndex = require('requireindex'); @@ -44,6 +45,7 @@ module.exports.server=function(serv,options) { serv.spawnMob = (type, world, position, {pitch=0,yaw=0,headPitch=0,velocity=new Vec3(0,0,0),metadata=[]}={}) => { var mob = serv.initEntity('mob', type, world, position.scaled(32).floored()); + mob.name=entitiesById[type].name; mob.velocity = velocity.scaled(32).floored(); mob.pitch = pitch; mob.headPitch = headPitch;