mirror of
https://github.com/danbulant/flying-squid
synced 2026-06-24 17:21:43 +00:00
Merge pull request #161 from mhsjlw/portal
[WIP] implement portal detection and use it when flint and steel is used
This commit is contained in:
commit
22b8b2f91d
5 changed files with 415 additions and 3 deletions
|
|
@ -24,6 +24,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-runtime": "^5.4.4",
|
"babel-runtime": "^5.4.4",
|
||||||
"emit-then": "^1.0.2",
|
"emit-then": "^1.0.2",
|
||||||
|
"event-promise": "0.0.1",
|
||||||
|
"flatmap": "0.0.3",
|
||||||
"minecraft-data": "0.7.0",
|
"minecraft-data": "0.7.0",
|
||||||
"minecraft-protocol": "0.16.3",
|
"minecraft-protocol": "0.16.3",
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "0.5.1",
|
||||||
|
|
@ -38,11 +40,11 @@
|
||||||
"prismarine-world": "0.3.3",
|
"prismarine-world": "0.3.3",
|
||||||
"prismarine-world-sync": "0.1.0",
|
"prismarine-world-sync": "0.1.0",
|
||||||
"random-seed": "^0.2.0",
|
"random-seed": "^0.2.0",
|
||||||
|
"range": "0.0.3",
|
||||||
"request-promise": "^0.4.3",
|
"request-promise": "^0.4.3",
|
||||||
"requireindex": "~1.0.0",
|
"requireindex": "~1.0.0",
|
||||||
"spiralloop": "1.0.2",
|
"spiralloop": "1.0.2",
|
||||||
"vec3": "0.1.3",
|
"vec3": "0.1.3"
|
||||||
"event-promise": "0.0.1"
|
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ module.exports = {
|
||||||
version:require("./lib/version"),
|
version:require("./lib/version"),
|
||||||
generations:require("./lib/generations"),
|
generations:require("./lib/generations"),
|
||||||
experience:require("./lib/experience"),
|
experience:require("./lib/experience"),
|
||||||
UserError:require("./lib/user_error")
|
UserError:require("./lib/user_error"),
|
||||||
|
portal_detector:require('./lib/portal_detector')
|
||||||
};
|
};
|
||||||
|
|
||||||
function createMCServer(options) {
|
function createMCServer(options) {
|
||||||
|
|
|
||||||
30
src/lib/plugins/useItem.js
Normal file
30
src/lib/plugins/useItem.js
Normal file
|
|
@ -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)];
|
||||||
108
src/lib/portal_detector.js
Normal file
108
src/lib/portal_detector.js
Normal file
|
|
@ -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)));
|
||||||
|
}
|
||||||
271
test/portal_detector.js
Normal file
271
test/portal_detector.js
Normal file
|
|
@ -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,[])
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue