/*
* This file is part of Spoutcraft.
*
* Copyright (c) 2011 SpoutcraftDev <http://spoutcraft.org/>
* Spoutcraft is licensed under the GNU Lesser General Public License.
*
* Spoutcraft is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Spoutcraft 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.spoutcraft.client.gui.minimap;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import javax.imageio.ImageIO;
import gnu.trove.map.hash.TIntObjectHashMap;
import org.lwjgl.opengl.GL11;
import net.minecraft.src.Minecraft;
import net.minecraft.src.GuiScreen;
import org.spoutcraft.api.Spoutcraft;
import org.spoutcraft.api.animation.PropertyAnimation;
import org.spoutcraft.api.gui.GenericScrollable;
import org.spoutcraft.api.gui.MinecraftTessellator;
import org.spoutcraft.api.gui.Orientation;
import org.spoutcraft.api.gui.Point;
import org.spoutcraft.api.gui.RenderUtil;
import org.spoutcraft.api.gui.ScrollBarPolicy;
import org.spoutcraft.api.gui.WidgetType;
import org.spoutcraft.api.property.Property;
import org.spoutcraft.api.util.map.TIntPairObjectHashMap;
import org.spoutcraft.client.SpoutClient;
import org.spoutcraft.client.chunkcache.HeightMap;
public class MapWidget extends GenericScrollable {
static TIntObjectHashMap<TIntPairObjectHashMap<Map>> chunks = new TIntObjectHashMap<TIntPairObjectHashMap<Map>>(250);
static int levelOfDetail = 1;
static final int MIN_LOD = 1;
static HeightMap heightMap;
static Map blankMap = new Map(1); // Singleton instance used to indicate no pixels to draw in a chunk
double scale = 1f;
boolean dirty = true;
GuiScreen parent = null;
BufferedImage imageBuffer = null;
private static MapWidgetRenderer renderer = null;
private Point lastPlayerPos = new Point((int) Minecraft.getMinecraft().thePlayer.posX, (int) Minecraft.getMinecraft().thePlayer.posZ);
private static Random random = new Random();
public MapWidget(GuiScreen parent) {
if (renderer == null) {
renderer = new MapWidgetRenderer();
renderer.start();
}
levelOfDetail = 1;
this.parent = parent;
HeightMap newheightMap = HeightMap.getHeightMap(MinimapUtils.getWorldName());
if (newheightMap != heightMap) {
chunks.clear();
heightMap = newheightMap;
}
addProperty("scale", new Property() {
@Override
public void set(Object value) {
setScale((double)(Double) value);
}
@Override
public Object get() {
return getScale();
}
});
addProperty("scrollpos", new Property() {
@Override
public void set(Object value) {
Point p = (Point) value;
scrollTo(p, false, 0);
}
@Override
public Object get() {
return mapOutsideToCoords(new Point(0,0));
}
});
setScrollBarPolicy(Orientation.HORIZONTAL, ScrollBarPolicy.SHOW_NEVER);
setScrollBarPolicy(Orientation.VERTICAL, ScrollBarPolicy.SHOW_NEVER);
}
private void updateLOD() {
int newlod = levelOfDetail;
newlod = (int) (1 / scale);
if (newlod < MIN_LOD) {
newlod = MIN_LOD;
}
if (newlod != levelOfDetail) {
renderer.renderQueue.clear();
}
levelOfDetail = newlod;
}
@Override
public int getInnerSize(Orientation axis) {
if (axis == Orientation.HORIZONTAL) {
return (int) ((double) (heightMap.getMaxX() - heightMap.getMinX() + 1) * scale * 16d);
} else {
return (int) ((double) (heightMap.getMaxZ() - heightMap.getMinZ() + 1) * scale * 16d) + 30;
}
}
public static Map drawChunk(int x, int z, boolean force) {
synchronized(chunks) {
Map map = chunks.get(levelOfDetail).get(x, z);
if (map == null || (force && map == blankMap)) {
map = new Map(16);
map.originOffsetX = 0;
map.originOffsetY = 0;
map.renderSize = 16;
} else if (!force) {
return map;
}
boolean pixelSet = false;
try {
for (int cx = 0; cx < 16; cx++) {
for (int cz = 0; cz < 16; cz++) {
int aX = x * 16 + cx * levelOfDetail;
int aZ = z * 16 + cz * levelOfDetail;
short height = heightMap.getHeight(aX, aZ);
int id = heightMap.getBlockId(aX, aZ);
byte data = heightMap.getData(aX, aZ);
if (id < 0) {
id = 256 + id;
}
if (id == -1 || height == -1) {
continue;
} else {
pixelSet = true;
}
if (levelOfDetail <= 2) {
short reference = heightMap.getHeight(aX + levelOfDetail, aZ + levelOfDetail);
int color = MapCalculator.getHeightColor(height, reference);
map.heightimg.setARGB(cx, cz, color);
}
map.setColorPixel(cz, cx, BlockColor.getBlockColor(id, data).color | 0xff000000);
}
}
}
catch (Exception e) {
pixelSet = false;
}
if (pixelSet) {
getChunkMap(levelOfDetail).put(x, z, map);
} else {
getChunkMap(levelOfDetail).put(x, z, blankMap);
}
return map;
}
}
public static TIntPairObjectHashMap<Map> getChunkMap(int levelOfDetail) {
TIntPairObjectHashMap<Map> chunkmap = chunks.get(levelOfDetail);
if (chunkmap == null) {
chunkmap = new TIntPairObjectHashMap<Map>(500);
chunks.put(levelOfDetail, chunkmap);
}
return chunkmap;
}
public Point mapOutsideToCoords(Point outside) {
int x = outside.getX() + scrollX;
int y = outside.getY() + scrollY;
x /= scale;
y /= scale;
x += heightMap.getMinX() * 16;
y += heightMap.getMinZ() * 16;
return new Point(x,y);
}
public Point mapCoordsToOutside(Point coords) {
int x = coords.getX();
int y = coords.getY();
x -= heightMap.getMinX() * 16;
y -= heightMap.getMinZ() * 16;
x *= scale;
y *= scale;
return new Point(x, y);
}
public void reset() {
setScale(1f, true, 500);
showPlayer(500);
}
public void showPlayer(int duration) {
scrollTo(getPlayerPosition(), duration != 0, duration);
}
public void scrollTo(Point p, boolean animated, int duration) {
scrollTo(p.getX(), p.getY(), animated, duration);
}
public void scrollTo(Point p) {
scrollTo(p, false, 0);
}
public void scrollTo(int x, int z) {
scrollTo(x,z, false, 0);
}
public void scrollTo(int x, int z, boolean animated, int duration) {
if (!animated) {
Point p = mapCoordsToOutside(new Point(x,z));
int scrollX = p.getX(), scrollZ = p.getY();
setScrollPosition(Orientation.HORIZONTAL, scrollX - (int) (getWidth() / 2));
setScrollPosition(Orientation.VERTICAL, scrollZ - (int) (getHeight() / 2));
} else {
Point start = getCenterCoord();
Point end = new Point(x, z);
PropertyAnimation ani = new PropertyAnimation(this, "scrollpos");
ani.setStartValue(start);
ani.setEndValue(end);
ani.setDuration(duration);
ani.start();
}
}
public Point getCenterCoord() {
return mapOutsideToCoords(new Point((int) (getWidth() / 2), (int) (getHeight() / 2)));
}
public BufferedImage renderFullImage() {
int scrollX = (int) (getScrollPosition(Orientation.HORIZONTAL) / scale);
int scrollY = (int) (getScrollPosition(Orientation.VERTICAL) / scale);
int minChunkX = heightMap.getMinX() + scrollX / 16,
minChunkZ = heightMap.getMinZ() + scrollY / 16,
maxChunkX = 0,
maxChunkZ = 0;
int horiz = (int) (getWidth() / 16 / scale) + 1;
int vert = (int) (getHeight() / 16 / scale) + 1;
maxChunkX = minChunkX + horiz;
maxChunkZ = minChunkZ + vert;
minChunkX++;
minChunkZ++;
BufferedImage fullImage = new BufferedImage((maxChunkX - minChunkX) * 16 + 32, (maxChunkZ - minChunkZ) * 16 + 32, BufferedImage.TYPE_INT_ARGB);
for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) {
for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) {
Map map = drawChunk(chunkX, chunkZ, dirty);
if (map != null && map != blankMap) {
Raster raster = map.getColorRaster();
int startX = (chunkX - minChunkX) * 16;
int startZ = (chunkZ - minChunkZ) * 16;
java.awt.image.DataBufferInt buf = (java.awt.image.DataBufferInt)raster.getDataBuffer();
int[] srcbuf = buf.getData();
fullImage.setRGB(startX, startZ, 16, 16, srcbuf, 0, 16);
}
}
}
return fullImage;
}
public boolean saveToDesktop() {
try {
BufferedImage fullImage = renderFullImage();
// Creates a file named 'minimap 3-29-2012.png' in the desktop, if possible
// Otherwise saves to screenshots. Appends "(1)", etc as needed to avoid overwriting existing files
DateFormat df = new SimpleDateFormat("dd-MM-yyyy");
String fileName = "minimap " + df.format(new Date());
File desktop = new File(System.getProperty("user.home"), "Desktop");
if (!desktop.exists()) {
desktop = new File(Minecraft.getMinecraft().mcDataDir, "screenshots");
}
String fullFileName = fileName;
int duplicate = 0;
while (true) {
if (!fileExists(desktop, fullFileName, ".png")) {
break;
}
duplicate++;
fullFileName = fileName + " (" + duplicate + ")";
}
ImageIO.write(fullImage, "png", new File(desktop, fullFileName + ".png"));
return true;
}
catch (Exception e) {
e.printStackTrace();
}
return false;
}
private boolean fileExists(File dir, String name, String ext) {
name += ext;
for (File f : dir.listFiles()) {
if (f.getName().equalsIgnoreCase(name)) {
return true;
}
}
return false;
}
@Override
public void renderContents() {
GL11.glDisable(2929);
GL11.glEnable(3042);
GL11.glDepthMask(false);
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
int scrollX = (int) (getScrollPosition(Orientation.HORIZONTAL) / scale);
int scrollY = (int) (getScrollPosition(Orientation.VERTICAL) / scale);
GL11.glScaled(scale, scale, scale);
GL11.glTranslatef(-heightMap.getMinX() * 16, -heightMap.getMinZ() * 16, 0);
int minChunkX = heightMap.getMinX() + scrollX / 16,
minChunkZ = heightMap.getMinZ() + scrollY / 16,
maxChunkX = 0,
maxChunkZ = 0;
int horiz = (int) (getWidth() / 16 / scale) + 1;
int vert = (int) (getHeight() / 16 / scale) + 1;
maxChunkX = minChunkX + horiz;
maxChunkZ = minChunkZ + vert;
minChunkX = Math.max(minChunkX, heightMap.getMinX());
minChunkZ = Math.max(minChunkZ, heightMap.getMinZ());
maxChunkX = Math.min(maxChunkX, heightMap.getMaxX());
maxChunkZ = Math.min(maxChunkZ, heightMap.getMaxZ());
GL11.glPushMatrix();
synchronized(chunks) {
for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX+=levelOfDetail) {
for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ+=levelOfDetail) {
Map map = getChunkMap(levelOfDetail).get(chunkX, chunkZ);
if (dirty || map == null || random .nextInt(10000) == 0) {
renderer.renderQueue.add(new Point(chunkX, chunkZ));
}
if (map != null && map != blankMap) {
GL11.glPushMatrix();
int x = chunkX * 16;
int y = chunkZ * 16;
int width = x + 16 * levelOfDetail;
int height = y + 16 * levelOfDetail;
map.loadColorImage();
MinecraftTessellator tessellator = Spoutcraft.getTessellator();
tessellator.startDrawingQuads();
tessellator.addVertexWithUV((double) width, (double) height, -90, 1, 1);
tessellator.addVertexWithUV((double) width, (double) y, -90, 1, 0);
tessellator.addVertexWithUV((double) x, (double) y, -90, 0, 0);
tessellator.addVertexWithUV((double) x, (double) height, -90, 0, 1);
tessellator.draw();
// GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
// RenderUtil.drawRectangle(x, y, width, height, 0x88ffffff);
if (MinimapConfig.getInstance().isHeightmap()) {
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_DST_COLOR);
map.loadHeightImage();
tessellator.startDrawingQuads();
tessellator.addVertexWithUV((double) width, (double) height, -90, 1, 1);
tessellator.addVertexWithUV((double) width, (double) y, -90, 1, 0);
tessellator.addVertexWithUV((double) x, (double) y, -90, 0, 0);
tessellator.addVertexWithUV((double) x, (double) height, -90, 0, 1);
tessellator.draw();
}
GL11.glPopMatrix();
}
}
}
}
int x = (int) SpoutClient.getHandle().thePlayer.posX;
int z = (int) SpoutClient.getHandle().thePlayer.posZ;
drawPOI("You", x, z, 0xffff0000);
for (Waypoint waypoint : MinimapConfig.getInstance().getAllWaypoints(MinimapUtils.getWorldName())) {
if (!waypoint.deathpoint || MinimapConfig.getInstance().isDeathpoints()) {
drawPOI(waypoint.name, waypoint.x, waypoint.z, 0xff00ff00);
}
}
if (MinimapConfig.getInstance().getFocussedWaypoint() != null) {
Waypoint pos = MinimapConfig.getInstance().getFocussedWaypoint();
drawPOI("Marker", pos.x, pos.z, 0xff00ffff);
}
GL11.glPopMatrix();
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glEnable(2929);
GL11.glDisable(3042);
dirty = false;
Point newpos = getPlayerPosition();
if (lastPlayerPos.getX() != newpos.getX() || lastPlayerPos.getY() != newpos.getY()) {
showPlayer(0);
lastPlayerPos = newpos;
}
}
private void drawPOI(String name, int x, int z, int color) {
int mouseX = (int) ((getScreen().getMouseX() - getX() + scrollX) / scale + heightMap.getMinX() * 16);
int mouseY = (int) ((getScreen().getMouseY() - getY() + scrollY) / scale + heightMap.getMinZ() * 16);
int radius = (int) (2f / scale);
if (radius <= 0) {
radius = 2;
}
int mouseRadius = radius * 2;
if (parent.isInBoundingRect(x - mouseRadius, z - mouseRadius, mouseRadius * 2, mouseRadius * 2, mouseX, mouseY)) {
color = 0xff0000ff;
parent.drawTooltip(name, x, z);
}
RenderUtil.drawRectangle(x - radius, z - radius, x + radius, z + radius, color);
}
@Override
public WidgetType getType() {
return WidgetType.ScrollArea;
}
public double getScale() {
return scale;
}
public void setScale(double value) {
setScale(value, false, 0);
}
public void setScale(double newscale, boolean animated, int duration) {
if (!animated) {
Point center = getCenterCoord();
this.scale = newscale;
scrollTo(center);
updateLOD();
} else {
PropertyAnimation ani = new PropertyAnimation(this, "scale");
ani.setStartNumber(this.scale);
ani.setEndNumber(newscale);
ani.setDuration(duration);
ani.start();
}
}
public void zoomBy(double d) {
double newscale = scale * d;
if (newscale <= 0) {
newscale = 0.0000001;
}
setScale(newscale, true, 100);
}
public Point getPlayerPosition() {
int x = (int) SpoutClient.getHandle().thePlayer.posX;
int z = (int) SpoutClient.getHandle().thePlayer.posZ;
return new Point(x, z);
}
}