diff --git a/package.json b/package.json index 72f0d9b..a48689e 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "dependencies": { "babel-runtime": "^5.4.4", "emit-then": "^1.0.2", + "event-promise": "0.0.1", + "flatmap": "0.0.3", "minecraft-data": "0.7.0", "minecraft-protocol": "0.16.3", "mkdirp": "0.5.1", @@ -38,11 +40,11 @@ "prismarine-world": "0.3.3", "prismarine-world-sync": "0.1.0", "random-seed": "^0.2.0", + "range": "0.0.3", "request-promise": "^0.4.3", "requireindex": "~1.0.0", "spiralloop": "1.0.2", - "vec3": "0.1.3", - "event-promise": "0.0.1" + "vec3": "0.1.3" }, "license": "MIT", "repository": { diff --git a/src/index.js b/src/index.js index 87a4874..65e1a30 100644 --- a/src/index.js +++ b/src/index.js @@ -14,7 +14,8 @@ module.exports = { version:require("./lib/version"), generations:require("./lib/generations"), experience:require("./lib/experience"), - UserError:require("./lib/user_error") + UserError:require("./lib/user_error"), + portal_detector:require('./lib/portal_detector') }; function createMCServer(options) { diff --git a/src/lib/plugins/useItem.js b/src/lib/plugins/useItem.js new file mode 100644 index 0000000..28be69d --- /dev/null +++ b/src/lib/plugins/useItem.js @@ -0,0 +1,30 @@ +var items=require("minecraft-data")(require("flying-squid").version).items; +var Vec3 = require("vec3").Vec3; +var {detectFrame,getAir}=require("flying-squid").portal_detector; + +module.exports.player=function(player,serv) +{ + player._client.on("block_place",({direction,heldItem,location} = {}) => { + if (direction == -1 || heldItem.blockId == -1 || !items[heldItem.blockId]) return; + var referencePosition = new Vec3(location.x, location.y, location.z); + var directionVector = directionToVector[direction]; + var position = referencePosition.plus(directionVector); + + var item= items[heldItem.blockId]; + if(item.name=="flint_and_steel") + player.use_flint_and_steel(referencePosition,directionVector); + }); + + player.use_flint_and_steel=async (referencePosition,direction) => { + let block=await player.world.getBlock(referencePosition); + if(block.name=="obsidian") + { + var frames=await detectFrame(player.world,referencePosition,direction); + if(frames.length==0) + return; + var air=getAir(frames[0]); + air.forEach(pos => player.setBlock(pos,90,(frames[0].bottom[0].x-frames[0].bottom[1].x)!=0 ? 1 : 2)); + } + }; +}; +var directionToVector=[new Vec3(0,-1,0),new Vec3(0,1,0),new Vec3(0,0,-1),new Vec3(0,0,1),new Vec3(-1,0,0),new Vec3(1,0,0)]; \ No newline at end of file diff --git a/src/lib/portal_detector.js b/src/lib/portal_detector.js new file mode 100644 index 0000000..b4535ea --- /dev/null +++ b/src/lib/portal_detector.js @@ -0,0 +1,108 @@ +var Vec3 = require("vec3").Vec3; +var assert = require('assert'); +var flatMap = require('flatmap'); +var range = require('range').range; + +module.exports={detectFrame,findPotentialLines,findBorder,getAir}; + +async function findLineInDirection(world,startingPoint,type,direction,directionV) +{ + var line=[]; + var point=startingPoint; + while((await world.getBlock(point)).name==type && (await world.getBlockType(point.plus(directionV)))==0) + { + line.push(point); + point=point.plus(direction); + } + return line; +} + +async function findLine(world,startingPoint,type,direction,directionV) +{ + var firstSegment=(await findLineInDirection(world,startingPoint.plus(direction.scaled(-1)),type,direction.scaled(-1),directionV)).reverse(); + var secondSegment=await findLineInDirection(world,startingPoint,type,direction,directionV); + return firstSegment.concat(secondSegment); +} + + +async function findPotentialLines(world,startingPoint,directionV) +{ + var firstLineDirection=directionV.y!=0 ? [new Vec3(1,0,0),new Vec3(0,0,1)] : + [new Vec3(0,1,0)]; + return (await Promise.all(firstLineDirection + .map(async d => ({direction:d,line:(await findLine(world,startingPoint,'obsidian',d,directionV))})))) + .filter(line => (line.line.length>=3 && line.direction.y!=0) || + (line.line.length>=2 && line.direction.y==0)); +} + +function positiveOrder(line,direction) +{ + if(direction.x==-1 || direction.y==-1 || direction.z==-1) + return line.reverse(); + return line; +} + +async function findBorder(world,{line,direction},directionV) +{ + var bottom=line; + if(bottom.length==0) + return []; + var left=await findLineInDirection(world,bottom[0].plus(direction.scaled(-1).plus(directionV)),'obsidian',directionV,direction); + var right=await findLineInDirection(world,bottom[line.length-1].plus(direction).plus(directionV),'obsidian', + directionV,direction.scaled(-1)); + if(left.length==0 || left.length!=right.length) + return null; + var top=await findLineInDirection(world,left[left.length-1].plus(direction).plus(directionV),'obsidian', + direction,directionV.scaled(-1)); + if(bottom.length!=top.length) + return null; + + left=positiveOrder(left,directionV); + right=positiveOrder(right,directionV); + top=positiveOrder(top,direction); + + + if(direction.y!=0) + [bottom,left,right,top]=[left,bottom,top,right]; + + [bottom,top]=directionV.y<0 ? [top,bottom] : [bottom,top]; + var horDir=direction.x!=0 || directionV.x!=0 ? 'x' :'z'; + [left,right]=direction[horDir]<0 || directionV[horDir]<0 ? [right,left] : [left,right]; + + if(bottom.length<2 || top.length<2 || left.length<3 || right.length<3) + return null; + + return {bottom,left,right,top}; +} + +async function detectFrame(world,startingPoint,directionV) +{ + let potentialLines=await findPotentialLines(world,startingPoint,directionV); + + return asyncFilter((await Promise.all(potentialLines + .map(line => findBorder(world,line,directionV)))) + .filter(border => border!=null) + .map(({bottom,left,right,top}) => ({bottom,left,right,top,air:getAir({bottom,left,right,top})})), + async ({air}) => await isAllAir(world,air)); +} + +async function asyncEvery(array,pred) { + return Promise.all(array.map(x => pred(x).then(y => y ? true : Promise.reject(false)))) + .then(results => true) + .catch(x => false); +} + +function asyncFilter(array,pred) { + return Promise.all(array.map(e => pred(e).then(a => a ? e : null))).then(r => r.filter(a => a!=null)); +} + +async function isAllAir(world,blocks) +{ + return asyncEvery(blocks,async block => (await world.getBlockType(block))==0); +} + +function getAir(border) +{ + var {bottom,top}=border; + return flatMap(bottom,pos => range(1,top[0].y-bottom[0].y).map(i => pos.offset(0,i,0))); +} \ No newline at end of file diff --git a/test/portal_detector.js b/test/portal_detector.js new file mode 100644 index 0000000..d22ade2 --- /dev/null +++ b/test/portal_detector.js @@ -0,0 +1,271 @@ +var {detectFrame,findPotentialLines,findBorder,getAir}=require("flying-squid").portal_detector; +var World = require('prismarine-world'); +var Chunk = require('prismarine-chunk')(require("flying-squid").version); +var Vec3 = require("vec3").Vec3; +var assert = require('chai').assert; +var range = require('range').range; +var flatMap = require('flatmap'); + +function generateLine(startingPoint,direction,length) { + return range(0,length).map(i => startingPoint.plus(direction.scaled(i))); +} + +function generatePortal(bottomLeft,direction,width,height){ + var directionV=new Vec3(0,1,0); + return { + bottom:generateLine(bottomLeft.plus(direction),direction,width-2), + left:generateLine(bottomLeft.plus(directionV),directionV,height-2), + right:generateLine(bottomLeft.plus(direction.scaled(width-1)).plus(directionV),directionV,height-2), + top:generateLine(bottomLeft.plus(directionV.scaled(height-1).plus(direction)),direction,width-2), + air:flatMap(generateLine(bottomLeft.plus(direction).plus(directionV),direction,width-2), + p => generateLine(p,directionV,height-2)) + } +} + + +async function makeWorldWithPortal(portal,additionalAir,additionalObsidian) +{ + var {bottom,left,right,top,air}=portal; + var world=new World(); + var chunk=new Chunk(); + + [bottom,left,right,top].forEach(border => border.forEach(pos => chunk.setBlockType(pos,49))); + air.forEach(pos => chunk.setBlockType(pos,0)); + + additionalAir.forEach(pos => chunk.setBlockType(pos,0)); + additionalObsidian.forEach(pos => chunk.setBlockType(pos,49)); + + + await world.setColumn(0,0,chunk); + return world; +} + +describe("Generate portal",function(){ + it("generate a line",() => { + assert.deepEqual(generateLine(new Vec3(3,1,1),new Vec3(1,0,0),2),[new Vec3(3, 1, 1), new Vec3(4, 1, 1)]) + }); + it("generate a portal", () => { + assert.deepEqual(generatePortal(new Vec3(2,1,1),new Vec3(1,0,0),4,5),{ + bottom:generateLine(new Vec3(3,1,1),new Vec3(1,0,0),2), + left:generateLine(new Vec3(2,2,1),new Vec3(0,1,0),3), + right:generateLine(new Vec3(5,2,1),new Vec3(0,1,0),3), + top:generateLine(new Vec3(3,5,1),new Vec3(1,0,0),2), + air:generateLine(new Vec3(3,2,1),new Vec3(0,1,0),3).concat(generateLine(new Vec3(4,2,1),new Vec3(0,1,0),3)) + }) + }); +}); + +describe("Detect portal", function() { + var portalData=[]; + portalData.push({ + name:"simple portal frame x", + bottomLeft:new Vec3(2,1,1), + direction:new Vec3(1,0,0), + width:4, + height:5, + additionalAir:[], + additionalObsidian:[] + }); + portalData.push({ + name:"simple portal frame z", + bottomLeft:new Vec3(2,1,1), + direction:new Vec3(0,0,1), + width:4, + height:5, + additionalAir:[], + additionalObsidian:[] + }); + portalData.push({ + name:"big simple portal frame x", + bottomLeft:new Vec3(2,1,1), + direction:new Vec3(1,0,0), + width:10, + height:10, + additionalAir:[], + additionalObsidian:[] + }); + portalData.push({ + name:"simple portal frame x with borders", + bottomLeft:new Vec3(2,1,1), + direction:new Vec3(1,0,0), + width:4, + height:5, + additionalAir:[], + additionalObsidian:[new Vec3(2,1,1),new Vec3(5,1,1),new Vec3(2,6,1),new Vec3(5,6,1)] + }); + var {bottom,left,right,top,air}=generatePortal(new Vec3(2,1,2),new Vec3(1,0,0),4,5); + + portalData.push({ + name:"2 portals", + bottomLeft:new Vec3(2,1,1), + direction:new Vec3(1,0,0), + width:4, + height:5, + additionalAir:air, + additionalObsidian:[].concat.apply([], [bottom, left, right,top]) + }); + + + portalData.forEach(({name,bottomLeft,direction,width,height,additionalAir,additionalObsidian}) => { + var portal=generatePortal(bottomLeft,direction,width,height); + var {bottom,left,right,top,air}=portal; + describe("Detect "+name,() => { + var expectedBorder={bottom,left,right,top}; + + var world; + before(async function(){ + world=await makeWorldWithPortal(portal,additionalAir,additionalObsidian); + }); + + + describe("detect potential first lines",function(){ + it("detect potential first lines from bottom left", async function() { + let potentialLines=await findPotentialLines(world,bottom[0],new Vec3(0,1,0)); + assert.include(potentialLines,{ + "direction": direction, + "line": bottom + }); + }); + + it("detect potential first lines from bottom right", async function() { + let potentialLines=await findPotentialLines(world,bottom[bottom.length-1],new Vec3(0,1,0)); + assert.include(potentialLines,{ + "direction": direction, + "line": bottom + }); + }); + + + it("detect potential first lines from top left", async function() { + let potentialLines=await findPotentialLines(world,top[0],new Vec3(0,-1,0)); + assert.include(potentialLines,{ + "direction": direction, + "line": top + }); + }); + + it("detect potential first lines from top right", async function() { + let potentialLines=await findPotentialLines(world,top[top.length-1],new Vec3(0,-1,0)); + assert.include(potentialLines,{ + "direction": direction, + "line": top + }); + }); + + it("detect potential first lines from left top", async function() { + let potentialLines=await findPotentialLines(world,left[left.length-1],direction); + assert.include(potentialLines,{ + "direction": new Vec3(0,1,0), + "line": left + }); + }); + + it("detect potential first lines from right bottom", async function() { + let potentialLines=await findPotentialLines(world,right[0],direction.scaled(-1)); + assert.include(potentialLines,{ + "direction": new Vec3(0,1,0), + "line": right + }); + }); + }); + + + describe("find borders",function() { + it("find borders from bottom", async function () { + var border = await findBorder(world, { + "direction": direction, + "line": bottom + }, new Vec3(0, 1, 0)); + assert.deepEqual(border, expectedBorder) + }); + + it("find borders from top", async function () { + var border = await findBorder(world, { + "direction": direction, + "line": top + }, new Vec3(0, -1, 0)); + assert.deepEqual(border, expectedBorder) + }); + + it("find borders from left", async function () { + var border = await findBorder(world, { + "direction": new Vec3(0, 1, 0), + "line": left + },direction); + assert.deepEqual(border, expectedBorder) + }); + it("find borders from right", async function () { + var border = await findBorder(world, { + "direction": new Vec3(0, 1, 0), + "line": right + }, direction.scaled(-1)); + assert.deepEqual(border, expectedBorder) + }); + }); + + describe("detect portals",function(){ + it("detect portals from bottom left",async function() { + var portals=await detectFrame(world,bottom[0],new Vec3(0,1,0)); + assert.deepEqual(portals,[portal]) + }); + it("detect portals from top left",async function() { + var portals=await detectFrame(world,top[0],new Vec3(0,-1,0)); + assert.deepEqual(portals,[portal]) + }); + it("detect portals from right top",async function() { + var portals=await detectFrame(world,right[right.length-1],direction.scaled(-1)); + assert.deepEqual(portals,[portal]) + }) + }); + + it("get air",function(){ + var foundAir=getAir(expectedBorder); + assert.deepEqual(foundAir,air); + }); + }); + }); + + +}); + + +describe("Doesn't detect non-portal",function() { + var portalData=[]; + + portalData.push({ + name:"simple portal frame x with one obsidian in the middle", + bottomLeft:new Vec3(2,1,1), + direction:new Vec3(1,0,0), + width:5, + height:5, + additionalAir:[], + additionalObsidian:[new Vec3(4,3,1)] + }); + + portalData.forEach(({name,bottomLeft,direction,width,height,additionalAir,additionalObsidian}) => { + var portal = generatePortal(bottomLeft, direction, width, height); + var {bottom,left,right,top,air}=portal; + describe("Doesn't detect detect " + name, () => { + var world; + before(async function () { + world=await makeWorldWithPortal(portal, additionalAir, additionalObsidian); + }); + + describe("doesn't detect portals",function(){ + it("doesn't detect portals from bottom left",async function() { + var portals=await detectFrame(world,bottom[0],new Vec3(0,1,0)); + assert.deepEqual(portals,[]) + }); + it("doesn't detect portals from top left",async function() { + var portals=await detectFrame(world,top[0],new Vec3(0,-1,0)); + assert.deepEqual(portals,[]) + }); + it("doesn't detect portals from right top",async function() { + var portals=await detectFrame(world,right[right.length-1],direction.scaled(-1)); + assert.deepEqual(portals,[]) + }) + }); + + }); + }); +});