/*
* Aphelion
* Copyright (c) 2013 Joris van der Wel & Joshua Edwards
*
* This file is part of Aphelion
*
* Aphelion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* Aphelion is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Aphelion. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, the following supplemental terms apply, based on section 7 of
* the GNU Affero General Public License (version 3):
* a) Preservation of all legal notices and author attributions
* b) Prohibition of misrepresentation of the origin of this material, and
* modified versions are required to be marked in reasonable ways as
* different from the original version (for example by appending a copyright notice).
*
* Linking this library statically or dynamically with other modules is making a
* combined work based on this library. Thus, the terms and conditions of the
* GNU Affero General Public License cover the whole combination.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent modules,
* and to copy and distribute the resulting executable under terms of your
* choice, provided that you also meet, for each linked independent module,
* the terms and conditions of the license of that module. An independent
* module is a module which is not derived from or based on this library.
*/
package aphelion.shared.map;
import aphelion.shared.resource.ResourceDB;
import aphelion.shared.event.WorkerTask;
import aphelion.shared.event.promise.PromiseException;
import aphelion.shared.map.tile.classic.TileClassicFactory;
import aphelion.shared.map.tile.TileFactory;
import aphelion.shared.map.tile.TileType;
import aphelion.shared.physics.PhysicsMap;
import aphelion.shared.swissarmyknife.Colori;
import aphelion.shared.swissarmyknife.SWASlickImageBuffer;
import aphelion.shared.swissarmyknife.SwissArmyKnife;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import org.newdawn.slick.Color;
import org.newdawn.slick.Image;
import org.newdawn.slick.geom.Vector2f;
public class MapClassic implements PhysicsMap
{
private static final Logger log = Logger.getLogger("aphelion.config");
private ResourceDB resourceDB;
private TileType[][] tiles; // 8 * 1024 * 1024 + 16 * 1024 bytes
private TileType mapEdge;
private SWASlickImageBuffer tilesetBuffer;
private Image tileSet;
private SWASlickImageBuffer radarBuffer;
private Image radar;
private TileFactory tileFactory;
private long levelSize = 1;
private ArrayList<Color> tilesetColors = new ArrayList<Color>();
private ArrayList<Color> tileColors = new ArrayList<Color>();
public MapClassic(ResourceDB resourceDB, boolean loadGraphics)
{
this.resourceDB = resourceDB;
tiles = new TileType[1024][1024];
tileFactory = new TileClassicFactory(resourceDB, loadGraphics);
mapEdge = tileFactory.getTile((short) 20);
assert mapEdge.physicsIsSolid();
}
public void read(byte[] mapFile, boolean tileset) throws IOException
{
this.levelSize = mapFile.length;
if (tileset)
{
// dimensions of the tileset should be 304, 160
BufferedImage tilesetImage = ImageIO.read(new ByteArrayInputStream(mapFile));
if (tilesetImage == null)
{
// If a map does not include a tileset, a default tileset should be used
throw new IOException("Unable to parse the map tileset. (maps without a tileset are not currently supported)");
}
parseTileSet(tilesetImage);
}
ByteBuffer buf = ByteBuffer.wrap(mapFile);
buf.order(ByteOrder.LITTLE_ENDIAN);
// skip first 2 bytes
if (buf.get() != 0x42 || buf.get() != 0x4D) // BM
{
throw new IOException("Invalid map file");
}
// After that we get a 4 byte value giving us the offset to the actual tile data.
// This way we ignore ASSS' elvl stuff.
int tileDataOffset = buf.getInt();
log.log(Level.INFO, "Tile data offset = {0}", tileDataOffset);
buf.position(tileDataOffset);
TileType tileFiller = tileFactory.getTile((short) -1);
while (buf.remaining() >= 4)
{
int i = buf.getInt();
short tile = (short) (i >> 24 & 0x00ff);
int y = (i >> 12) & 0x03FF;
int x = i & 0x03FF;
if (x > -1 && y > -1)
{
TileType tileType;
tileType = tileFactory.getTile(tile);
assert tileType != null;
tiles[x][y] = tileType;
int size = tileType.getSize();
if (size > 1 && tileType.physicsIsSolid())
{
for (int xFiller = x; xFiller < x + size; ++xFiller)
{
for (int yFiller = y; yFiller < y + size; ++yFiller)
{
if (xFiller == x && yFiller == y)
{
continue;
}
tiles[xFiller][yFiller] = tileFiller;
}
}
}
}
}
}
public Image getTileset()
{
if (tileSet == null)
{
tileSet = tilesetBuffer.getImage(Image.FILTER_NEAREST);
}
return this.tileSet;
}
public Image getRadarImage()
{
if (this.radar == null)
{
if (this.radarBuffer == null)
{
this.radar = resourceDB.getTextureLoader().getTexture("gui.loading.map").getCachedImage();
}
else
{
this.radar = this.radarBuffer.getImage();
}
}
return this.radar;
}
public static long chunkXY2Int(int x, int y)
{
long l = x;
long l1 = y;
return l & 0xffffffffL | (l1 & 0xffffffffL) << 32;
}
@Override
public boolean physicsIsSolid(int tileX, int tileY)
{
TileType tile;
if (tileX < 0 || tileY < 0
|| tileX > 1023 || tileY > 1023)
{
return true;
}
tile = getTile(tileX, tileY);
if (tile == null)
{
return false;
}
return tile.physicsIsSolid();
}
public TileType getTile(int tileX, int tileY)
{
if (tileX < 0 || tileY < 0 || tileX > 1023 || tileY > 1023)
{
return null;
}
return tiles[tileX][tileY];
}
private void parseTileSet(BufferedImage src)
{
tilesetBuffer = new SWASlickImageBuffer(src.getWidth(), src.getHeight());
for (int y = 0; y < src.getHeight(); y++)
{
for (int x = 0; x < src.getWidth(); x++)
{
int pixelCol = src.getRGB(x, y);
int a = (pixelCol >>> 24) & 0xff;
int r = (pixelCol >>> 16) & 0xff;
int g = (pixelCol >>> 8) & 0xff;
int b = pixelCol & 0xff;
if (r == 0 && g == 0 && b == 0)
{
a = 0;
}
tilesetBuffer.setRGBA(x, y, r, g, b, a);
}
}
for (int y = 0; y < 10; y++)
{
for (int x = 0; x < 19; x++)
{
ArrayList<Float> colorR = new ArrayList<Float>();
ArrayList<Float> colorG = new ArrayList<Float>();
ArrayList<Float> colorB = new ArrayList<Float>();
for (int y2 = 0; y2 < 16; y2++)
{
for (int x2 = 0; x2 < 16; x2++)
{
Color color;
int getX = x * 16 + x2;
int getY = y * 16 + y2;
float r = tilesetBuffer.getRed (getX, getY) / 255f;
float g = tilesetBuffer.getGreen(getX, getY) / 255f;
float b = tilesetBuffer.getBlue (getX, getY) / 255f;
if (r == 0 && g == 0 && b == 0)
{
continue;
}
colorR.add(r);
colorG.add(g);
colorB.add(b);
}
}
Float averageR = 0F;
int rCount = 0;
Float averageG = 0F;
int gCount = 0;
Float averageB = 0F;
int bCount = 0;
for (Float floatObject : colorR)
{
averageR += floatObject;
rCount++;
}
for (Float floatObject : colorG)
{
averageG += floatObject;
gCount++;
}
for (Float floatObject : colorB)
{
averageB += floatObject;
bCount++;
}
averageR /= rCount;
averageG /= gCount;
averageB /= bCount;
Color compositeColor = new Color(averageR, averageG, averageB);
tilesetColors.add(compositeColor);
}
}
}
public void renderRadar(boolean renderLight)
{
radarBuffer = new SWASlickImageBuffer(1024, 1024);
Random random = SwissArmyKnife.random;
Colori black = new Colori(0, 0, 0);
Colori background = new Colori(0, 0, 0); // TODO configurable
Colori colorSafe = new Colori(0x18, 0x52, 0x18); // TODO configurable
Colori colorGoal = new Colori(0xFF, 0x39, 0x08); // TODO configurable
Colori colorWall = new Colori(0x5a, 0x5A, 0x5A);
Colori colorWormholeInner = new Colori(0, 0, 0);
Colori colorWormholeOuter = new Colori(0, 0, 0);
for (int y = 0; y < 1024; y++)
{
for (int x = 0; x < 1024; x++)
{
TileType tile = tiles[x][y];
short tileID = tile == null ? 0 : tile.tileID;
if (tileID < 1 || (tileID >= 242 && tileID <= 255))
{
/* TODO: make this configurable?
// it has no color yet
if (radarBuffer.getAlpha(x, y) == 0)
{
int randomInt = random.nextInt(4) - 2;
// random background noise
radarBuffer.setRGBA(
x, y,
SwissArmyKnife.clip(background.r + randomInt, 0, 255),
SwissArmyKnife.clip(background.g + randomInt, 0, 255),
SwissArmyKnife.clip(background.b + randomInt, 0, 255),
background.a);
}*/
}
else
{
if (tileID == 171) // Safe Tile
{
colorSafe.setRGBA(radarBuffer, x, y);
}
else if (tileID == 172) // Goal Tile
{
colorGoal.setRGBA(radarBuffer, x, y);
}
else if (tileID == 217)
{
colorWall.setRGBA(radarBuffer, x + 0, y + 0);
colorWall.setRGBA(radarBuffer, x + 0, y + 1);
colorWall.setRGBA(radarBuffer, x + 1, y + 0);
colorWall.setRGBA(radarBuffer, x + 1, y + 1);
}
else if (tileID == 219)
{
for (int y2 = 0; y2 < 5; y2++)
{
for (int x2 = 0; x2 < 5; x2++)
{
colorWall.setRGBA(radarBuffer, x + x2, y + y2);
}
}
}
else if (tileID == 220)
{
colorWormholeOuter.setRGBA(radarBuffer, x, y);
colorWormholeOuter.setRGBA(radarBuffer, x + 0, y + 0);
colorWormholeOuter.setRGBA(radarBuffer, x + 1, y + 0);
colorWormholeOuter.setRGBA(radarBuffer, x + 2, y + 0);
colorWormholeOuter.setRGBA(radarBuffer, x + 3, y + 0);
colorWormholeOuter.setRGBA(radarBuffer, x + 4, y + 0);
colorWormholeOuter.setRGBA(radarBuffer, x + 0, y + 1);
colorWormholeInner.setRGBA(radarBuffer, x + 1, y + 1);
colorWormholeInner.setRGBA(radarBuffer, x + 2, y + 1);
colorWormholeInner.setRGBA(radarBuffer, x + 3, y + 1);
colorWormholeOuter.setRGBA(radarBuffer, x + 4, y + 1);
colorWormholeOuter.setRGBA(radarBuffer, x + 0, y + 2);
colorWormholeInner.setRGBA(radarBuffer, x + 1, y + 2);
black.setRGBA(radarBuffer, x + 2, y + 2);
colorWormholeInner.setRGBA(radarBuffer, x + 3, y + 2);
colorWormholeOuter.setRGBA(radarBuffer, x + 4, y + 2);
colorWormholeOuter.setRGBA(radarBuffer, x + 0, y + 3);
colorWormholeInner.setRGBA(radarBuffer, x + 1, y + 3);
colorWormholeInner.setRGBA(radarBuffer, x + 2, y + 3);
colorWormholeInner.setRGBA(radarBuffer, x + 3, y + 3);
colorWormholeOuter.setRGBA(radarBuffer, x + 4, y + 3);
colorWormholeOuter.setRGBA(radarBuffer, x + 0, y + 4);
colorWormholeOuter.setRGBA(radarBuffer, x + 1, y + 4);
colorWormholeOuter.setRGBA(radarBuffer, x + 2, y + 4);
colorWormholeOuter.setRGBA(radarBuffer, x + 3, y + 4);
colorWormholeOuter.setRGBA(radarBuffer, x + 4, y + 4);
}
else if (tileID <= 190)
{
radarBuffer.setRGBA(
x,
y,
(int) (tilesetColors.get(tileID - 1).r * 255),
(int) (tilesetColors.get(tileID - 1).g * 255),
(int) (tilesetColors.get(tileID - 1).b * 255),
255);
}
else
{
colorWall.setRGBA(radarBuffer, x, y);
}
}
}
}
if (renderLight)
{
for (int y = 0; y < 1024; y++)
{
for (int x = 0; x < 1024; x++)
{
TileType tile = tiles[x][y];
short tileID = tile == null ? 0 : tile.tileID;
if (tileID == 171)
{
lightUpColorFromEmitters(radarBuffer, x, y, 4, true, 0);
}
else if (tileID == 172)
{
lightUpColorFromEmitters(radarBuffer, x, y, 4, true, 0);
}
else if (tileID == 217)
{
for (int y2 = 0; y2 < 2; y2++)
{
for (int x2 = 0; x2 < 2; x2++)
{
lightUpColorFromEmitters(radarBuffer, x + x2, y + y2, 3, false, 0);
}
}
}
else if (tileID == 220)
{
for (int y2 = 0; y2 < 6; y2++)
{
for (int x2 = 0; x2 < 6; x2++)
{
lightUpColorFromEmitters(radarBuffer, x + x2, y + y2, 3, false, 0);
}
}
}
else if (tileID != 0)
{
lightUpColorFromEmitters(radarBuffer, x, y, 3, false, 2);
}
}
}
}
}
private void lightUpColorFromEmitters(SWASlickImageBuffer stencilWalls, int pointX, int pointY, int light_radius, boolean addOrSub, int offset)
{
Color[][] lightColors = new Color[light_radius * 2][light_radius * 2];
Vector2f vectorSource = new Vector2f(pointX, pointY);
Colori color = new Colori(0, 0, 0, 255);
for (int y = -light_radius; y < light_radius; y++)
{
for (int x = -light_radius; x < light_radius; x++)
{
if (pointX + x < 0 || pointX + x > 1023 || pointY + y < 0 || pointY + y > 1023)
{
continue;
}
lightColors[x + light_radius][y + light_radius] = new Color(0, 0, 0);
if (x == 0 && y == 0)
{
continue;
}
TileType tile = tiles[pointX + x][pointY + y];
short tileID = tile == null ? 0 : tile.tileID;
if (tileID == 172)
{
continue;
}
color.r = (short) stencilWalls.getRed(x, y);
color.g = (short) stencilWalls.getGreen(x, y);
color.b = (short) stencilWalls.getBlue(x, y);
color.a = (short) stencilWalls.getAlpha(x, y);
float x2 = x;
if (x2 == 0)
{
x2 = 1F;
}
float y2 = y;
if (y2 == 0)
{
y2 = 1F;
}
Vector2f vectorResult = new Vector2f(x2, y2);
if (addOrSub)
{
lightColors[x + light_radius][y + light_radius].r += ((vectorSource.x - vectorResult.x) / vectorSource.distance(vectorResult)) * .001;
lightColors[x + light_radius][y + light_radius].r += ((vectorSource.y - vectorResult.y) / vectorSource.distance(vectorResult)) * .001;
lightColors[x + light_radius][y + light_radius].g += ((vectorSource.x - vectorResult.x) / vectorSource.distance(vectorResult)) * .001;
lightColors[x + light_radius][y + light_radius].g += ((vectorSource.y - vectorResult.y) / vectorSource.distance(vectorResult)) * .001;
lightColors[x + light_radius][y + light_radius].b += ((vectorSource.x - vectorResult.x) / vectorSource.distance(vectorResult)) * .001;
lightColors[x + light_radius][y + light_radius].b += ((vectorSource.y - vectorResult.y) / vectorSource.distance(vectorResult)) * .001;
}
else
{
lightColors[x + light_radius][y + light_radius].r -= ((vectorSource.x - vectorResult.x) / vectorSource.distance(vectorResult)) * .001;
lightColors[x + light_radius][y + light_radius].r -= ((vectorSource.y - vectorResult.y) / vectorSource.distance(vectorResult)) * .001;
lightColors[x + light_radius][y + light_radius].g -= ((vectorSource.x - vectorResult.x) / vectorSource.distance(vectorResult)) * .001;
lightColors[x + light_radius][y + light_radius].g -= ((vectorSource.y - vectorResult.y) / vectorSource.distance(vectorResult)) * .001;
lightColors[x + light_radius][y + light_radius].b -= ((vectorSource.x - vectorResult.x) / vectorSource.distance(vectorResult)) * .001;
lightColors[x + light_radius][y + light_radius].b -= ((vectorSource.y - vectorResult.y) / vectorSource.distance(vectorResult)) * .001;
}
}
}
for (int y = -light_radius; y < light_radius; y++)
{
for (int x = -light_radius; x < light_radius; x++)
{
try
{
if (pointX + (x + offset) < 0 || pointX + (x + offset) > 1023 || pointY + (y + offset) < 0 || pointY + (y + offset) > 1023)
{
continue;
}
stencilWalls.setRGBA(
x, y,
SwissArmyKnife.clip(stencilWalls.getRed(pointX + (x + offset), pointY + (y + offset)) + (int) (lightColors[x + light_radius][y + light_radius].r * 255), 0, 255),
SwissArmyKnife.clip(stencilWalls.getGreen(pointX + (x + offset), pointY + (y + offset)) + (int) (lightColors[x + light_radius][y + light_radius].g * 255), 0, 255),
SwissArmyKnife.clip(stencilWalls.getBlue(pointX + (x + offset), pointY + (y + offset)) + (int) (lightColors[x + light_radius][y + light_radius].b * 255), 0, 255),
stencilWalls.getAlpha(x, y));
}
catch (NullPointerException e)
{
continue;
}
}
}
}
/**
* @return the mapEdge
*/
public TileType getMapEdge()
{
return mapEdge;
}
/**
* @return the levelSize
*/
public long getLevelSize()
{
return levelSize;
}
@Override
public int physicsGetMapLimitMinimum()
{
return 0;
}
@Override
public int physicsGetMapLimitMaximum()
{
return 1024;
}
/** Read and parse a lvl file. The parameter given should be the resource key.
*/
public static class LoadMapTask extends WorkerTask<String, MapClassic>
{
private final ResourceDB db;
private final boolean graphics;
public LoadMapTask(ResourceDB db, boolean graphics)
{
this.db = db;
this.graphics = graphics;
}
@Override
public MapClassic work(String argument) throws PromiseException
{
MapClassic map = new MapClassic(db, graphics);
try
{
byte[] bytes = db.getBytesSync(argument);
if (bytes == null)
{
throw new PromiseException("Resource does not exist (map)");
}
map.read(bytes, graphics);
if (graphics)
{
map.renderRadar(false);
}
}
catch (IOException ex)
{
throw new PromiseException(ex);
}
return map;
}
}
}