/*
* 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.util.Random;
import net.minecraft.src.Minecraft;
import net.minecraft.src.AxisAlignedBB;
import net.minecraft.src.Chunk;
import net.minecraft.src.EntityLiving;
import net.minecraft.src.Material;
import net.minecraft.src.World;
import org.spoutcraft.api.Spoutcraft;
import org.spoutcraft.client.SpoutClient;
import org.spoutcraft.client.chunkcache.HeightMap;
public class MapCalculator implements Runnable {
/**
* Multiply two colors by each other. Treats 0xff as 1.0.
*
* Yourself came up with the algorithm, I'm sure it makes sense to someone
*
* @param x
* Color to multiply
* @param y
* Other color to multiply
* @return multiplied color
*/
public int colorMult(int x, int y) {
int res = 0;
for (int octet = 0; octet < 3; ++octet) {
res |= (((x & 0xff) * (y & 0xff)) / 0xff) << (octet << 3);
x >>= 8;
y >>= 8;
}
return res;
}
private final float distance(int x1, int y1, int x2, int y2) {
return (float) Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
private final boolean blockIsSolid(Chunk chunk, int x, int y, int z) {
if (y > 255)
return false;
if (y < 0)
return true;
try {
int id = chunk.getBlockID(x, y, z);
int meta = chunk.getBlockMetadata(x, y, z);
return BlockColor.getBlockColor(id, meta).alpha > 0;
} catch (Exception e) {
return false;
}
}
private final int getBlockHeight(World world, int x, int z) {
if (MinimapConfig.getInstance().isCavemap()) {
Chunk chunk = world.getChunkFromBlockCoords(x, z);
cmdist.setSeed((x & 0xffff) | ((z & 0xffff) << 16));
float dist = distance((int) Minecraft.getMinecraft().thePlayer.posX, (int) Minecraft.getMinecraft().thePlayer.posZ, x, z);
int y = (int) Minecraft.getMinecraft().thePlayer.posY;
if (dist > 5)
y -= (cmdist.nextInt((int) (dist)) - ((int) dist / 2));
x &= 0xf;
z &= 0xf;
if (y < 0) {
y = 0;
} else if (y > 255) {
y = 255;
}
if (blockIsSolid(chunk, x, y, z)) {
int itery = y;
while (true) {
itery++;
if (itery > y + 10)
return y + 10;
if (!blockIsSolid(chunk, x, itery, z)) {
return itery - 1;
}
}
}
while (y > -1) {
y--;
if (blockIsSolid(chunk, x, y, z)) {
return y;
}
}
return -1;
}
return world.getHeightValue(x, z) - 1;
// return world.b(x, z).b(x & 0xf, z & 0xf);
/*
* li chunk = world.b(x, z); int y = (int)(game.h.aM); // starty; x &=
* 0xf; z &= 0xf;
*
* //while (y > 0) // {
*
*
* if (getBlockColor(id, meta).alpha == 0) return -1; // y--; else return
* y + 1; // what //}
*/
// return -1;
}
private int getBlockColor(World world, int x, int y, int z) {
int color24 = 0;
try {
if (MinimapConfig.getInstance().isColor() && !MinimapConfig.getInstance().isCavemap()) {
if (x == (int) map.getPlayerX() && z == (int) map.getPlayerZ())
return 0xff0000;
if ((world.getBlockMaterial(x, y + 1, z) == Material.ice) || (world.getBlockMaterial(x, y + 1, z) == Material.snow)) {
color24 = 0xffffff;
} else {
BlockColor col = BlockColor.getBlockColor(world.getBlockId(x, y, z), world.getBlockMetadata(x, y, z));
color24 = col.color;
}
}
} catch (Exception e) {
return 0;
}
return color24;
}
private int getBlockHeightMap(World world, int x, int y, int z) {
int height = y - 128;
double sc = Math.log10(Math.abs(height) / 8.0D + 1.0D) / 1.3D;
int result = 0x80;
if (height >= 0) {
result = (int) (sc * (0xff - result)) + result;
} else {
height = Math.abs(height);
result = result - (int) (sc * result);
}
return result;
}
private int getBlockLight(World world, int x, int y, int z) {
int light = world.getBlockLightValue_do(x, y + 1, z, false) * 17;
int min = 32;
if (light < min) {
light = min;
}
if (MinimapConfig.getInstance().isLightmap()) {
light *= 1.3f;
} else if (MinimapConfig.getInstance().isCavemap()) {
light *= 0.5;
light += 64;
}
if (light > 255) {
light = 255;
} else if (light < 0) {
light = 0;
}
return light;
}
private void mapCalc() {
if (Minecraft.getMinecraft().thePlayer == null || Minecraft.getMinecraft().theWorld == null)
return;
try {
final boolean square = MinimapConfig.getInstance().isSquare();
final World data = Minecraft.getMinecraft().theWorld;
int renderSize;
int startX;
int startZ;
synchronized (map) {
if (map.zoom != MinimapConfig.getInstance().getZoom()) {
map.zoom = MinimapConfig.getInstance().getZoom();
switch (map.zoom) {
case 0:
map.renderSize = Map.ZOOM_0;
break;
case 1:
map.renderSize = Map.ZOOM_1;
break;
case 2:
map.renderSize = Map.ZOOM_2;
break;
default:
map.renderSize = Map.ZOOM_2;
break;
}
map.renderOff = map.renderSize / 2;
map.clear();
}
map.square = square;
map.update(Minecraft.getMinecraft().thePlayer.posX, Minecraft.getMinecraft().thePlayer.posZ);
renderSize = map.renderSize;
startX = (int) (map.getPlayerX() - map.renderOff);
startZ = (int) (map.getPlayerZ() - map.renderOff);
}
for (int worldX = startX; worldX < startX + renderSize; worldX++) {
for (int worldZ = startZ; worldZ < startZ + renderSize; worldZ++) {
int worldY = getBlockHeight(data, worldX, worldZ);
int pixelX = worldX - startX;
if (pixelX >= renderSize) {
pixelX -= renderSize;
}
int pixelZ = worldZ - startZ;
pixelZ = renderSize - pixelZ;
if (pixelZ >= renderSize) {
pixelZ -= renderSize;
}
if (square || MinimapUtils.insideCircle(startX + renderSize / 2, startZ + renderSize / 2, renderSize / 2, worldX, worldZ)) {
int color = getBlockColor(data, worldX, worldY, worldZ);
if (color == 0) {
map.clearColorPixel(pixelX, pixelZ);
} else {
map.setColorPixel(pixelX, pixelZ, color);
}
short height = (short) data.getHeightValue(worldX, worldZ);
short reference = (short) data.getHeightValue(worldX + 1, worldZ + 1);
map.heightimg.setARGB(pixelZ, pixelX, getHeightColor(height, reference));
map.setLightPixel(pixelX, pixelZ, getBlockLight(data, worldX, worldY, worldZ));
} else {
map.clearColorPixel(pixelX, pixelZ);
map.heightimg.setARGB(pixelX, pixelZ, 0);
map.setLightPixel(pixelX, pixelZ, 255);
}
}
}
synchronized (map) {
for (Waypoint pt : MinimapConfig.getInstance().getWaypoints(MinimapUtils.getWorldName())) {
if (pt.enabled) {
boolean render = false;
if (square) {
render = Math.abs(map.getPlayerX() - pt.x) < map.renderSize && Math.abs(map.getPlayerZ() - pt.z) < map.renderSize;
} else {
render = MinimapUtils.insideCircle(startX + map.renderSize / 2, startZ + map.renderSize / 2, map.renderSize / 2, pt.x, pt.z);
}
if (pt.deathpoint && !MinimapConfig.getInstance().isDeathpoints()) {
render = false;
}
if (render) {
int pixelX = pt.x - startX;
int pixelZ = pt.z - startZ;
pixelZ = map.renderSize - pixelZ;
int scale = map.zoom + 2;
if (map.zoom > 2) {
scale += 2;
}
if (map.zoom > 1) {
scale += 1;
}
int color = 0xEE2C2C;
if (pt == MinimapConfig.getInstance().getFocussedWaypoint()) {
color = 0xff00ffff;
}
drawCircle(pixelX, pixelZ, scale + map.zoom + 1, pt.deathpoint ? color : 0);
drawCircle(pixelX, pixelZ, scale, pt.deathpoint ? 0 : color);
}
}
}
for (Waypoint pt : MinimapConfig.getInstance().getServerWaypoints()) {
boolean render = false;
if (square) {
render = Math.abs(map.getPlayerX() - pt.x) < map.renderSize && Math.abs(map.getPlayerZ() - pt.z) < map.renderSize;
} else {
render = MinimapUtils.insideCircle(startX + map.renderSize / 2, startZ + map.renderSize / 2, map.renderSize / 2, pt.x, pt.z);
}
if (render) {
int pixelX = pt.x - startX;
int pixelZ = pt.z - startZ;
pixelZ = map.renderSize - pixelZ;
int scale = map.zoom + 2;
if (map.zoom > 2) {
scale += 2;
}
if (map.zoom > 1) {
scale += 1;
}
int color = 0x3366CC;
if (pt == MinimapConfig.getInstance().getFocussedWaypoint()) {
color = 0xff00ffff;
}
drawCircle(pixelX, pixelZ, scale + map.zoom + 1, 0);
drawCircle(pixelX, pixelZ, scale, color);
}
}
}
} catch (Throwable whatever) { }
}
private void drawCircle(int x, int y, int radius, int color) {
try {
for (int dx = -radius; dx <= radius; dx++) {
for (int dy = -radius; dy <= radius; dy++) {
if (x + dx < map.renderSize && x + dx > 0 && y + dy < map.renderSize && y + dy > 0) {
if (MinimapUtils.insideCircle(x, y, radius, x + dx, y + dy)) {
map.setColorPixel(x + dx, y + dy, color);
}
}
}
}
} catch (ArrayIndexOutOfBoundsException ignore) {
} // happens with fast movement
}
/**
* Check if a render is necessary, and if so, do one.
*/
private void tryARender() {
if (Minecraft.getMinecraft().thePlayer == null || Minecraft.getMinecraft().theWorld == null)
return;
try {
double x = Minecraft.getMinecraft().thePlayer.posX;
double z = Minecraft.getMinecraft().thePlayer.posZ;
if (MinimapConfig.getInstance().isEnabled() && map.isDirty(x, z)) {
// long start = System.currentTimeMillis();
if (MinimapConfig.getInstance().getScanRadius() > 0) {
int radius = MinimapConfig.getInstance().getScanRadius() << 4;
for (int cx = (int) (x - radius); cx <= (int) (x + radius); cx += 16) {
for (int cz = (int) (z - radius); cz <= (int) (z + radius); cz += 16) {
Chunk chunk = Minecraft.getMinecraft().theWorld.getChunkFromBlockCoords((int) cx, (int) cz);
org.spoutcraft.client.chunkcache.HeightMapAgent.scanChunk(chunk);
}
}
}
//long dur = System.currentTimeMillis() - start;
//int chunks = (int) Math.pow(MinimapConfig.getInstance().getScanRadius() * 2, 2);
//System.out.println("Took " + dur + "ms to scan " + chunks + " chunks.\nThat is " + (float) (dur/(float)chunks) + " per chunk!");
mapCalc();
entityCalc();
map.timer = 1;
}
} catch (RuntimeException e) {
throw e;
} finally {
map.timer++;
}
}
private void entityCalc() {
synchronized(map.watchedEntities) {
map.watchedEntities.clear();
if (!Spoutcraft.hasPermission("spout.plugin.minimap.showentities")) {
return;
}
if (!MinimapConfig.getInstance().isShowingEntities()) {
return;
}
int radius = map.renderSize / 2;
double playerX = map.getPlayerX();
double playerZ = map.getPlayerZ();
for (Object ob:SpoutClient.getHandle().theWorld.getEntitiesWithinAABB(EntityLiving.class, AxisAlignedBB.getBoundingBox(playerX - radius, 0, playerZ - radius, playerX + radius, 256, playerZ + radius))) {
net.minecraft.src.Entity e = (net.minecraft.src.Entity) ob;
if (!MinimapConfig.getInstance().isEntityVisible(e.getClass())) {
continue;
}
WatchedEntity w = new WatchedEntity(e);
if (w.getTexture() != null) {
map.watchedEntities.add(w);
}
}
}
}
/**
* the run() to implement runnable - the main function of the other thread.
* this simply idles and the actual work is done in onRenderTick().
*/
public void run() {
}
/**
* Called each tick of the render.
*/
void onRenderTick() {
if (Minecraft.getMinecraft() != null && Minecraft.getMinecraft().theWorld != null) {
tryARender();
}
}
/**
* Random used to distort cave map
*/
public Random cmdist = new Random();
private Map map;
/**
* This constructor inits state, but does not start the thread.
*
* @param minimap
* Minimap instance to initialize off
*/
public MapCalculator(ZanMinimap minimap) {
map = minimap.map;
}
/**
* Start up the other thread. The thread may return early at this point, as
* there might not be a Minecraft instance available yet. if that occurs,
* the thread will be restarted by the keep-alive in onRenderTick().
*/
public void start() {
}
public static int getHeightColor(short height, short reference) {
int hdiff = height - reference;
int color = 0x00000000;
if (hdiff != 0 && reference != -1) {
boolean ascending = hdiff < 0;
hdiff *= 2;
hdiff += 256;
if (hdiff > 255) {
hdiff = 255;
}
if (hdiff < 0) {
hdiff = 0;
}
if (!ascending) {
hdiff = 255 - hdiff;
}
color = 0x55000000 | ((hdiff << 16) + (hdiff << 8) + hdiff);
}
return color;
}
public static int getHeightColor(int x, int z, HeightMap heightMap) {
short height = heightMap.getHeight(x, z);
short reference = heightMap.getHeight(x + 1, z + 1);
return getHeightColor(height, reference);
}
}