package haven.pathfinder;
import static haven.MCache.cmaps;
import static haven.MCache.tilesz;
import java.util.Random;
import haven.Coord;
import haven.Gob;
import haven.MapView;
import haven.Resource;
import haven.Resource.Tile;
public class Map
{
public int w = VRANGE*2;
public int h = VRANGE*2;
public int minWeight = Integer.MAX_VALUE;
public Node nodes[][];
public int playerSize; //boat 26, player 4
public int playerBounds;
public static final int VRANGE = 50*tilesz.x;
private static Random rng = new Random();
public enum SceneType {
ONFOOT, BOAT, GENERIC
}
public Map(int playerSize) {
super();
this.playerSize = playerSize;
this.playerBounds = playerSize/2; // not precise...
nodes = new Node[w][h];
for(int i=0;i<h;i++){
for(int j=0;j<w;j++){
nodes[j][i] = new Node(j, i);
}
}
}
private void setNode(int x, int y, Node.Type type, boolean isTile) {
nodes[x][y].type = type;
nodes[x][y].isTile = isTile;
if (type.getValue() < minWeight)
minWeight = type.getValue();
}
// each pixel considered as a single node
public void createNodesFromHitbox(int x, int y, int width, int height, Node.Type type, boolean isTile) {
for (int i = x; i < x+width; i++) {
for (int j = y; j < y+height; j++) {
if (nodes[i][j].type != Node.Type.BLOCK && nodes[i][j].type != Node.Type.BLOCK_DYNAMIC)
setNode(i, j, type, isTile);
}
}
}
public void initScene(MapView mv, Gob player, Coord dst, Gob[] gobs, SceneType sceneType) {
Coord playerCoord = player.getc().sub(mv.mc).add(VRANGE, VRANGE);
Node src = nodes[playerCoord.x][playerCoord.y];
Node.srcNode = src;
initTiles(mv, sceneType);
initGobes(mv, gobs, player, dst);
initClearances();
}
private void initTiles(MapView mv, SceneType sceneType) {
Coord requl = mv.mc.sub(VRANGE, VRANGE).div(tilesz).div(cmaps);
Coord reqbr = mv.mc.add(VRANGE, VRANGE).div(tilesz).div(cmaps);
Coord cgc = new Coord(0, 0);
for(cgc.y = requl.y; cgc.y <= reqbr.y; cgc.y++) {
for(cgc.x = requl.x; cgc.x <= reqbr.x; cgc.x++) {
if(mv.map.grids.get(cgc) == null)
mv.map.request(new Coord(cgc));
}
}
Coord center = new Coord(Node.srcNode.x, Node.srcNode.y);
Coord tc = mv.mc.div(tilesz);
for(int y = -VRANGE/tilesz.y; y < VRANGE/tilesz.y; y++) {
for(int x = -VRANGE/tilesz.x; x < VRANGE/tilesz.x; x++) {
Coord ctc = tc.add(new Coord(x, y));
Coord sc = ctc.mul(tilesz).sub(mv.mc).add(VRANGE, VRANGE);
if (sc.x+tilesz.x >= w || sc.y+tilesz.y >= h || sc.x < 0 || sc.y < 0 )
continue;
if ((new Coord(sc.x, sc.y)).dist(center) >= VRANGE) {
createNodesFromHitbox(sc.x, sc.y, tilesz.x, tilesz.y, Node.Type.BLOCK, true);
continue;
}
Tile groundTile = mv.map.getground(ctc);
Node.Type tileType = groundTile.resolveTileType();
if (groundTile != null) {
if (tileType == Node.Type.NOT_IMPLEMENTED) {
System.out.format("TILE: %s %s XxY:%sx%s ctc:%s sc:%s\n", groundTile.getOuter().name, tileType, x, y, ctc, sc);
}
if (tileType != Node.Type.IGNORE) {
int width = tilesz.x;
int height = tilesz.x;
if (sceneType == SceneType.ONFOOT) {
if (tileType == Node.Type.WATER_DEEP) {
tileType = Node.Type.BLOCK;
}
} else if (sceneType == SceneType.BOAT) {
if (tileType != Node.Type.WATER_DEEP && tileType != Node.Type.WATER_SHALLOW) {
tileType = Node.Type.BLOCK;
}
//FIXME: probably can be removed now...
width += sc.x+width+2 >= w ? 0 : 2;
height += sc.y+height+2 >= h ? 0 : 2;
}
createNodesFromHitbox(sc.x, sc.y, width, height, tileType, true);
}
}
}
}
}
private void initGobes(MapView mv, Gob[] gobs, Gob player, Coord dst) {
Coord playerCoord = player.getc().sub(mv.mc).add(VRANGE, VRANGE);
for (Gob g : gobs) {
Node.Type t = g.resolveObType(mv);
Resource.Neg neg = g.getneg();
if (neg == null)
continue;
// NOTE: for objects with hitbox - bs.x and bs.y > 0 but not for all. e.g. straw bed.
Coord a = g.getc().add(neg.bc).sub(mv.mc).add(VRANGE, VRANGE);
Coord c = g.getc().add(neg.bc).add(neg.bs).sub(mv.mc).add(VRANGE, VRANGE);
if (t == Node.Type.NOT_IMPLEMENTED) {
System.out.format("[NEG] cc:%s bs:%s bc:%s sz:%s\n", neg.cc.toString(), neg.bs.toString(), neg.bc.toString(), neg.sz.toString());
System.out.format("[GOB] getc():%s sc:%s %s\n", g.getc(), g.sc != null ? g.sc.toString() : "null", g.getres() != null? g.getres().name : "res is null");
System.out.format("[HB] a:%s c:%s\n", a, c);
}
// so we don't block ourself with gear gobs, boats, etc.
if (player.getc().equals(g.getc()) ||
playerCoord.x <= c.x && playerCoord.x >= a.x &&
playerCoord.y <= c.y && playerCoord.y >= a.y) // sometimes obj pos lags behind so it's not exactly same coord
continue;
// make sure the destination is not blocked by gob hitbox
if (dst != null && dst.equals(g.getc())) {
continue;
}
if (t != Node.Type.IGNORE &&
a.x + (c.x-a.x) < w && a.y + (c.y-a.y) < h &&
a.x >= 0 && a.y >= 0 && c.x >= 0 && c.y >= 0) {
// NOTE: since we calculate clearances by expanding East-South
// we need to account for upper and left player hitbox bounds
int width = c.x+playerBounds+1 >= w ? w-a.x : c.x-a.x+playerBounds+1;
int height = c.y+playerBounds+1 >= h ? h-a.y : c.y-a.y+playerBounds+1;
createNodesFromHitbox(a.x, a.y, width, height, t, false);
}
}
}
// Initialize clearance values for an object with bound playerBounds (player = 4/2+1, boat = 26/2+1).
// Final node clearance will be either NO_CLEARANCE or 'playerBounds'.
//
// NOTE: Nodes with clearance=NO_CLEARANCE could be actually traversed by objects of size < 'playerBounds'
// hence, the clearance values need to be recomputed each time for objects of different size.
private void initClearances() {
for (int x = w - 1; x >= 0; x--) {
for (int y = h - 1; y >= 0; y--) {
if (x+playerBounds >= w || y+playerBounds >= h) {
nodes[x][y].clearance = 1;
} else {
boolean allClear = true;
int i;
for (i = 0; i < playerBounds; i++) {
if (nodes[x+i][y].type == Node.Type.BLOCK || nodes[x+i][y].type == Node.Type.BLOCK_DYNAMIC ||
nodes[x][y+i].type == Node.Type.BLOCK || nodes[x][y+i].type == Node.Type.BLOCK_DYNAMIC ||
nodes[x+i][y+i].type == Node.Type.BLOCK || nodes[x+i][y+i].type == Node.Type.BLOCK_DYNAMIC) {
allClear = false;
break;
}
}
nodes[x][y].clearance = allClear ? playerBounds : i-1;
}
}
}
}
public Node[] getAdjacent4(Node n){
Node next[] = new Node[4];
// top
if(n.y > 0 && !nodes[n.x][n.y-1].pathTraversed) {
next[0] = nodes[n.x][n.y-1];
next[0].parent = n;
}
// right
if(n.x+1 < w && !nodes[n.x+1][n.y].pathTraversed) {
next[1] = nodes[n.x+1][n.y];
next[1].parent = n;
}
// bottom
if(n.y+1 < h && !nodes[n.x][n.y+1].pathTraversed) {
next[2] = nodes[n.x][n.y+1];
next[2].parent = n;
}
// left
if(n.x > 0 && !nodes[n.x-1][n.y].pathTraversed) {
next[3] = nodes[n.x-1][n.y];
next[3].parent = n;
}
return next;
}
public Node[] getAdjacent8(Node n){
Node next[] = new Node[8];
// top
if(n.y > 0 && !nodes[n.x][n.y-1].pathTraversed) {
next[0] = nodes[n.x][n.y-1];
next[0].parent = n;
}
// right
if(n.x+1 < w && !nodes[n.x+1][n.y].pathTraversed) {
next[1] = nodes[n.x+1][n.y];
next[1].parent = n;
}
// bottom
if(n.y+1 < h && !nodes[n.x][n.y+1].pathTraversed) {
next[2] = nodes[n.x][n.y+1];
next[2].parent = n;
}
// left
if(n.x > 0 && !nodes[n.x-1][n.y].pathTraversed) {
next[3] = nodes[n.x-1][n.y];
next[3].parent = n;
}
// WN
if(n.y > 0 && n.x > 0 && !nodes[n.x-1][n.y-1].pathTraversed) {
next[4] = nodes[n.x-1][n.y-1];
next[4].parent = n;
}
// NE
if(n.y > 0 && n.x+1 < w && !nodes[n.x+1][n.y-1].pathTraversed) {
next[5] = nodes[n.x+1][n.y-1];
next[5].parent = n;
}
// ES
if(n.y+1 < h && n.x+1 < w && !nodes[n.x+1][n.y+1].pathTraversed) {
next[6] = nodes[n.x+1][n.y+1];
next[6].parent = n;
}
// SW
if(n.y+1 < h && n.x > 0 && !nodes[n.x-1][n.y+1].pathTraversed){
next[7] = nodes[n.x-1][n.y+1];
next[7].parent = n;
}
return next;
}
public Coord findLandMark(Coord currentPos, int distanceToIgnore) {
int X = w-1;
int Y = h-1;
int x=currentPos.x, y=currentPos.y, dx = 0, dy = -1;
int t = Math.max(X,Y);
int maxI = t*t;
for (int i=0; i < maxI; i++){
if ((-X/2 <= x) && (x <= X/2) && (-Y/2 <= y) && (y <= Y/2)) {
System.out.println(x+","+y);
if (x+tilesz.x < w &&
nodes[x][y].type == Node.Type.PAVEMENT &&
nodes[x+tilesz.x][y].type == Node.Type.PAVEMENT) {
return new Coord(x, y);
}
}
if( (x == y) || ((x < 0) && (x == -y)) || ((x > 0) && (x == 1-y))) {
t=dx; dx=-dy; dy=t;
}
x+=dx; y+=dy;
}
return null;
}
public boolean isEnoughSpace(int xp, int yp, int objSize) {
for (int x = xp - objSize/2; x < xp+objSize/2; x++) {
for (int y = yp - objSize/2; y < yp+objSize/2; y++) {
if (x >= w || y >= h || x < 0 || y < 0 ||
nodes[x][y].type == Node.Type.WATER_DEEP ||
nodes[x][y].type == Node.Type.WATER_SHALLOW ||
nodes[x][y].type == Node.Type.BLOCK ||
nodes[x][y].type == Node.Type.BLOCK_DYNAMIC) {
return false;
}
}
}
return true;
}
//FIXME not efficient!
public Coord findEmptyGroundTile(Coord currentPos, int objSize) {
Coord closest = new Coord(0,0);
for (int x = currentPos.x+playerSize+tilesz.x; x < currentPos.x+playerSize+50*tilesz.x; x+=tilesz.x) {
for (int y = currentPos.y+playerSize+tilesz.y; y < currentPos.y+playerSize+50*tilesz.y; y+=tilesz.y) {
if (x+1 < w && y+1 < h &&
nodes[x][y].type != Node.Type.WATER_DEEP &&
nodes[x][y].type != Node.Type.WATER_SHALLOW &&
nodes[x][y].type != Node.Type.BLOCK &&
nodes[x][y].type != Node.Type.BLOCK_DYNAMIC) {
if (isEnoughSpace(x, y, objSize) && new Coord(x, y).dist(currentPos) < closest.dist(currentPos))
closest = new Coord(x, y);
}
}
}
for (int x = currentPos.x+playerSize+tilesz.x; x < currentPos.x+playerSize+50*tilesz.x; x+=tilesz.x) {
for (int y = currentPos.y-playerSize-+tilesz.y; y > currentPos.y-playerSize+50*tilesz.y; y-=tilesz.y) {
if (x+1 < w && y >= 0 &&
nodes[x][y].type != Node.Type.WATER_DEEP &&
nodes[x][y].type != Node.Type.WATER_SHALLOW &&
nodes[x][y].type != Node.Type.BLOCK &&
nodes[x][y].type != Node.Type.BLOCK_DYNAMIC) {
if (isEnoughSpace(x, y, objSize) && new Coord(x, y).dist(currentPos) < closest.dist(currentPos))
closest = new Coord(x, y);
}
}
}
for (int x = currentPos.x-playerSize-tilesz.x; x > currentPos.x-playerSize-50*tilesz.x; x-=tilesz.x) {
for (int y = currentPos.y+playerSize+tilesz.y; y < currentPos.y+playerSize+50*tilesz.y; y+=tilesz.y) {
if (x >= 0 && y+1 < h &&
nodes[x][y].type != Node.Type.WATER_DEEP &&
nodes[x][y].type != Node.Type.WATER_SHALLOW &&
nodes[x][y].type != Node.Type.BLOCK &&
nodes[x][y].type != Node.Type.BLOCK_DYNAMIC) {
if (isEnoughSpace(x, y, objSize) && new Coord(x, y).dist(currentPos) < closest.dist(currentPos))
closest = new Coord(x, y);
}
}
}
for (int x = currentPos.x-playerSize-tilesz.x; x > currentPos.x-playerSize-50*tilesz.x; x-=tilesz.x) {
for (int y = currentPos.y-playerSize-tilesz.y; y > currentPos.y-playerSize-50*tilesz.x; y-=tilesz.y) {
if (x >= 0 && y >= 0 &&
nodes[x][y].type != Node.Type.WATER_DEEP &&
nodes[x][y].type != Node.Type.WATER_SHALLOW &&
nodes[x][y].type != Node.Type.BLOCK &&
nodes[x][y].type != Node.Type.BLOCK_DYNAMIC) {
if (isEnoughSpace(x, y, objSize) && new Coord(x, y).dist(currentPos) < closest.dist(currentPos))
closest = new Coord(x, y);
}
}
}
return closest;
}
// find random water tile N tiles away from the given position
public Coord findRandomWaterTile(Coord currentPos) {
int DIST_FROM_CUR = 33*tilesz.x;
int BORDER_LIMIT = 3*tilesz.x;
for (int i = 0; i < 5000; i++) {
int xlow, xhigh;
if (rng.nextBoolean()) {
xlow = BORDER_LIMIT;
xhigh = currentPos.x-DIST_FROM_CUR;
} else {
xlow = currentPos.x+DIST_FROM_CUR;
xhigh = w - BORDER_LIMIT;
}
if (xhigh-xlow < 1)
continue;
int xr = rng.nextInt(xhigh-xlow)+xlow;
int ylow, yhigh;
if (rng.nextBoolean()) {
ylow = currentPos.y+DIST_FROM_CUR;
yhigh = h - BORDER_LIMIT;
} else {
ylow = BORDER_LIMIT;
yhigh = currentPos.y-DIST_FROM_CUR;
}
if (yhigh-ylow < 1)
continue;
int yr = rng.nextInt(yhigh-ylow)+ylow;
if (nodes[xr][yr].type == Node.Type.WATER_DEEP) {
return new Coord(xr, yr);
}
}
return null;
}
// find a shallow tile at the defined DISTANCE.
public Coord findNextShallowTile(Coord currentPos, Coord prevPos) {
int DISTANCE = 10*tilesz.x;
// ugly fix for avoiding getting stuck at small islands inside a lake
int range = (3 - 1) + 1; // [1,3]
DISTANCE += ((int)(Math.random() * range) + 1)*tilesz.x;
int cornerX = currentPos.x-DISTANCE;
int cornerY = currentPos.y-DISTANCE;
for (int i = 0; i <= DISTANCE*2; i+=tilesz.x) {
// x top
Coord tilePos = nextShallowTileCheck(DISTANCE, currentPos, prevPos, cornerX+i, currentPos.y-DISTANCE);
if (tilePos != null)
return tilePos;
// x bottom
tilePos = nextShallowTileCheck(DISTANCE, currentPos, prevPos, cornerX+i, currentPos.y+DISTANCE);
if (tilePos != null)
return tilePos;
// y left
tilePos = nextShallowTileCheck(DISTANCE, currentPos, prevPos, currentPos.x-DISTANCE, cornerY+i);
if (tilePos != null)
return tilePos;
// y right
tilePos = nextShallowTileCheck(DISTANCE, currentPos, prevPos, currentPos.x+DISTANCE, cornerY+i);
if (tilePos != null)
return tilePos;
}
return null;
}
public Coord findClosesWaterFromShore(Coord currentPos) {
for (int DISTANCE = tilesz.x; DISTANCE < tilesz.x*10; DISTANCE+=tilesz.x) {
int cornerX = currentPos.x-DISTANCE;
int cornerY = currentPos.y-DISTANCE;
for (int i = tilesz.x/2; i <= DISTANCE*2; i+=tilesz.x) {
// x top
if (isWaterTileCloseToShore(cornerX+i, currentPos.y-DISTANCE))
return new Coord(cornerX+i, currentPos.y-DISTANCE);
// x bottom
if (isWaterTileCloseToShore(cornerX+i, currentPos.y+DISTANCE))
return new Coord(cornerX+i, currentPos.y+DISTANCE);
// y left
if (isWaterTileCloseToShore(currentPos.x-DISTANCE, cornerY+i))
return new Coord(currentPos.x-DISTANCE, cornerY+i);
// y right
if (isWaterTileCloseToShore(currentPos.x+DISTANCE, cornerY+i))
return new Coord(currentPos.x+DISTANCE, cornerY+i);
}
}
return null;
}
private boolean isWaterTileCloseToShore(int x, int y) {
if (x-tilesz.x > 0 && y-tilesz.x > 0 && x+tilesz.x < w && y+tilesz.x < h &&
nodes[x][y].type == Node.Type.WATER_SHALLOW) {
// follow only outer shallow waters
if (nodes[x+tilesz.x][y].type != Node.Type.WATER_DEEP && nodes[x+tilesz.x][y].type != Node.Type.WATER_SHALLOW ||
nodes[x][y+tilesz.x].type != Node.Type.WATER_DEEP && nodes[x][y+tilesz.x].type != Node.Type.WATER_SHALLOW ||
nodes[x-tilesz.x][y].type != Node.Type.WATER_DEEP && nodes[x-tilesz.x][y].type != Node.Type.WATER_SHALLOW ||
nodes[x][y-tilesz.x].type != Node.Type.WATER_DEEP && nodes[x][y-tilesz.x].type != Node.Type.WATER_SHALLOW) {
return true;
}
}
return false;
}
private Coord nextShallowTileCheck(int distance, Coord currentPos, Coord prevPos, int x, int y) {
Coord newPos = new Coord(x, y);
if (x-tilesz.x > 0 && y-tilesz.x > 0 && x+tilesz.x < w && y+tilesz.x < h &&
nodes[x][y].type == Node.Type.WATER_SHALLOW &&
newPos.dist(prevPos) >= newPos.dist(currentPos)) {
// follow only outer shallow waters
if (nodes[x+tilesz.x][y].type == Node.Type.WATER_DEEP ||
nodes[x][y+tilesz.x].type == Node.Type.WATER_DEEP ||
nodes[x-tilesz.x][y].type == Node.Type.WATER_DEEP ||
nodes[x][y-tilesz.x].type == Node.Type.WATER_DEEP) {
return newPos;
}
}
return null;
}
public Coord findClosestShoreTile(Coord currentPos) {
Coord closest = new Coord(0,0);
for (int x = currentPos.x+playerSize; x < currentPos.x+playerSize+50*tilesz.x; x+=tilesz.x) {
for (int y = currentPos.y+playerSize; y < currentPos.y+playerSize+50*tilesz.y; y+=tilesz.y) {
if (x+1 < w && y+1 < h &&
nodes[x][y].type != Node.Type.WATER_DEEP &&
nodes[x][y].type != Node.Type.WATER_SHALLOW &&
nodes[x][y].type != Node.Type.BLOCK &&
nodes[x][y].type != Node.Type.BLOCK_DYNAMIC) {
if (new Coord(x, y).dist(currentPos) < closest.dist(currentPos))
closest = new Coord(x, y);
}
}
}
for (int x = currentPos.x+playerSize; x < currentPos.x+playerSize+50*tilesz.x; x+=tilesz.x) {
for (int y = currentPos.y-playerSize; y > currentPos.y-playerSize+50*tilesz.y; y-=tilesz.y) {
if (x+1 < w && y >= 0 &&
nodes[x][y].type != Node.Type.WATER_DEEP &&
nodes[x][y].type != Node.Type.WATER_SHALLOW &&
nodes[x][y].type != Node.Type.BLOCK &&
nodes[x][y].type != Node.Type.BLOCK_DYNAMIC) {
if (new Coord(x, y).dist(currentPos) < closest.dist(currentPos))
closest = new Coord(x, y);
}
}
}
for (int x = currentPos.x-playerSize; x > currentPos.x-playerSize-50*tilesz.x; x-=tilesz.x) {
for (int y = currentPos.y+playerSize; y < currentPos.y+playerSize+50*tilesz.y; y+=tilesz.y) {
if (x >= 0 && y+1 < h &&
nodes[x][y].type != Node.Type.WATER_DEEP &&
nodes[x][y].type != Node.Type.WATER_SHALLOW &&
nodes[x][y].type != Node.Type.BLOCK &&
nodes[x][y].type != Node.Type.BLOCK_DYNAMIC) {
if (new Coord(x, y).dist(currentPos) < closest.dist(currentPos))
closest = new Coord(x, y);
}
}
}
for (int x = currentPos.x-playerSize; x > currentPos.x-playerSize-50*tilesz.x; x-=tilesz.x) {
for (int y = currentPos.y-playerSize; y > currentPos.y-playerSize-50*tilesz.x; y-=tilesz.y) {
if (x >= 0 && y >= 0 &&
nodes[x][y].type != Node.Type.WATER_DEEP &&
nodes[x][y].type != Node.Type.WATER_SHALLOW &&
nodes[x][y].type != Node.Type.BLOCK &&
nodes[x][y].type != Node.Type.BLOCK_DYNAMIC) {
if (new Coord(x, y).dist(currentPos) < closest.dist(currentPos))
closest = new Coord(x, y);
}
}
}
return closest;
}
}