package hunternif.mc.atlas.client.gui;
import hunternif.mc.atlas.AntiqueAtlasMod;
import hunternif.mc.atlas.api.AtlasAPI;
import hunternif.mc.atlas.client.*;
import hunternif.mc.atlas.client.gui.core.*;
import hunternif.mc.atlas.client.gui.core.GuiStates.IState;
import hunternif.mc.atlas.client.gui.core.GuiStates.SimpleState;
import hunternif.mc.atlas.core.DimensionData;
import hunternif.mc.atlas.marker.DimensionMarkersData;
import hunternif.mc.atlas.marker.Marker;
import hunternif.mc.atlas.marker.MarkersData;
import hunternif.mc.atlas.network.PacketDispatcher;
import hunternif.mc.atlas.network.server.BrowsingPositionPacket;
import hunternif.mc.atlas.registry.MarkerRenderInfo;
import hunternif.mc.atlas.registry.MarkerType;
import hunternif.mc.atlas.util.*;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.VertexBuffer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.MathHelper;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
public class GuiAtlas extends GuiComponent {
public static final int WIDTH = 310;
public static final int HEIGHT = 218;
private static final int CONTENT_X = 17;
private static final int CONTENT_Y = 11;
private static final int MAP_WIDTH = WIDTH - 17*2;
private static final int MAP_HEIGHT = 194;
private static final float PLAYER_ROTATION_STEPS = 16;
private static final int PLAYER_ICON_WIDTH = 7;
private static final int PLAYER_ICON_HEIGHT = 8;
public static final int MARKER_SIZE = 32;
/** If the map scale goes below this value, the tiles will not scale down
* visually, but will instead span greater area. */
private static final double MIN_SCALE_THRESHOLD = 0.5;
private final long[] renderTimes = new long[30];
private int renderTimesIndex = 0;
// States ==================================================================
private final GuiStates state = new GuiStates();
/** If on, navigate the map normally. */
private final IState NORMAL = new SimpleState();
/** If on, all markers as well as the player icon are hidden. */
private final IState HIDING_MARKERS = new IState() {
@Override
public void onEnterState() {
// Set the button as not selected so that it can be clicked again:
btnShowMarkers.setSelected(false);
btnShowMarkers.setTitle(I18n.format("gui.antiqueatlas.showMarkers"));
btnShowMarkers.setIconTexture(Textures.ICON_SHOW_MARKERS);
}
@Override
public void onExitState() {
btnShowMarkers.setSelected(false);
btnShowMarkers.setTitle(I18n.format("gui.antiqueatlas.hideMarkers"));
btnShowMarkers.setIconTexture(Textures.ICON_HIDE_MARKERS);
}
};
/** If on, a semi-transparent marker is attached to the cursor, and the
* player's icon becomes semi-transparent as well. */
private final IState PLACING_MARKER = new IState() {
@Override
public void onEnterState() {
btnMarker.setSelected(true);
}
@Override
public void onExitState() {
btnMarker.setSelected(false);
}
};
/** If on, the closest marker will be deleted upon mouseclick. */
private final IState DELETING_MARKER = new IState() {
@Override
public void onEnterState() {
mc.mouseHelper.grabMouseCursor();
addChild(eraser);
btnDelMarker.setSelected(true);
}
@Override
public void onExitState() {
mc.mouseHelper.ungrabMouseCursor();
removeChild(eraser);
btnDelMarker.setSelected(false);
}
};
private final GuiCursor eraser = new GuiCursor();
private final IState EXPORTING_IMAGE = new IState() {
@Override
public void onEnterState() {
btnExportPng.setSelected(true);
}
@Override
public void onExitState() {
btnExportPng.setSelected(false);
}
};
// Buttons =================================================================
/** Arrow buttons for navigating the map view via mouse clicks. */
private final GuiArrowButton btnUp, btnDown, btnLeft, btnRight;
/** Button for exporting PNG image of the Atlas's contents. */
private final GuiBookmarkButton btnExportPng;
/** Button for placing a marker at current position, local to this Atlas instance. */
private final GuiBookmarkButton btnMarker;
/** Button for deleting local markers. */
private final GuiBookmarkButton btnDelMarker;
/** Button for showing/hiding all markers. */
private final GuiBookmarkButton btnShowMarkers;
/** Button for restoring player's position at the center of the Atlas. */
private final GuiPositionButton btnPosition;
// Navigation ==============================================================
/** Pause between after the arrow button is pressed and continuous
* navigation starts, in ticks. */
private static final int BUTTON_PAUSE = 8;
/** How much the map view is offset, in blocks, per click (or per tick). */
private static final int navigateStep = 24;
/** The button which is currently being pressed. Used for continuous
* navigation using the arrow buttons. Also used to prevent immediate
* canceling of placing marker. */
private GuiComponentButton selectedButton = null;
/** Time in world ticks when the button was pressed. Used to create a pause
* before continuous navigation using the arrow buttons. */
private long timeButtonPressed = 0;
/** Set to true when dragging the map view. */
private boolean isDragging = false;
/** The starting cursor position when dragging. */
private int dragMouseX, dragMouseY;
/** Map offset at the beginning of drag. */
private int dragMapOffsetX, dragMapOffsetY;
/** Offset to the top left corner of the tile at (0, 0) from the center of
* the map drawing area, in pixels. */
private int mapOffsetX, mapOffsetY;
/** If true, the player's icon will be in the center of the GUI, and the
* offset of the tiles will be calculated accordingly. Otherwise it's the
* position of the player that will be calculated with respect to the
* offset. */
private boolean followPlayer = true;
private final GuiScaleBar scaleBar = new GuiScaleBar();
/** Pixel-to-block ratio. */
private double mapScale;
/** The visual size of a tile in pixels. */
private int tileHalfSize;
/** The number of chunks a tile spans. */
private int tile2ChunkScale;
// Markers =================================================================
/** Local markers in the current dimension */
private DimensionMarkersData localMarkersData;
/** Global markers in the current dimension */
private DimensionMarkersData globalMarkersData;
/** The marker highlighted by the eraser. Even though multiple markers may
* be highlighted at the same time, only one of them will be deleted. */
private Marker toDelete;
private final GuiMarkerFinalizer markerFinalizer = new GuiMarkerFinalizer();
/** Displayed where the marker is about to be placed when the Finalizer GUI is on. */
private final GuiBlinkingMarker blinkingIcon = new GuiBlinkingMarker();
// Misc stuff ==============================================================
private EntityPlayer player;
private ItemStack stack;
private DimensionData biomeData;
/** Coordinate scale factor relative to the actual screen size. */
private int screenScale;
/** Progress bar for exporting images. */
private final ProgressBarOverlay progressBar = new ProgressBarOverlay(100, 2);
private long lastUpdateMillis = System.currentTimeMillis();
private int scaleAlpha = 255;
private int scaleClipIndex = 0;
private int zoomLevelOne = 8;
private int zoomLevel = zoomLevelOne;
private String[] zoomNames = new String[] { "256", "128", "64", "32", "16", "8", "4", "2", "1", "1/2", "1/4", "1/8", "1/16", "1/32", "1/64", "1/128", "1/256" };
private Thread exportThread;
@SuppressWarnings("rawtypes")
public GuiAtlas() {
setSize(WIDTH, HEIGHT);
setMapScale(0.5);
followPlayer = true;
setInterceptKeyboard(false);
btnUp = GuiArrowButton.up();
addChild(btnUp).offsetGuiCoords(148, 10);
btnDown = GuiArrowButton.down();
addChild(btnDown).offsetGuiCoords(148, 194);
btnLeft = GuiArrowButton.left();
addChild(btnLeft).offsetGuiCoords(15, 100);
btnRight = GuiArrowButton.right();
addChild(btnRight).offsetGuiCoords(283, 100);
btnPosition = new GuiPositionButton();
btnPosition.setEnabled(!followPlayer);
addChild(btnPosition).offsetGuiCoords(283, 194);
IButtonListener positionListener = button -> {
selectedButton = button;
if (button.equals(btnPosition)) {
followPlayer = true;
btnPosition.setEnabled(false);
} else {
// Navigate once, before enabling pause:
navigateByButton(selectedButton);
timeButtonPressed = player.getEntityWorld().getTotalWorldTime();
}
};
btnUp.addListener(positionListener);
btnDown.addListener(positionListener);
btnLeft.addListener(positionListener);
btnRight.addListener(positionListener);
btnPosition.addListener(positionListener);
btnExportPng = new GuiBookmarkButton(1, Textures.ICON_EXPORT, I18n.format("gui.antiqueatlas.exportImage")) {
@Override
public boolean isEnabled() {
return !ExportImageUtil.isExporting;
}
};
addChild(btnExportPng).offsetGuiCoords(300, 75);
btnExportPng.addListener(button -> {
if (stack != null || !AntiqueAtlasMod.settings.itemNeeded) {
exportThread = new Thread(() -> exportImage(getAtlasID()), "Atlas file export thread");
exportThread.start();
}
});
btnMarker = new GuiBookmarkButton(0, Textures.ICON_ADD_MARKER, I18n.format("gui.antiqueatlas.addMarker"));
addChild(btnMarker).offsetGuiCoords(300, 14);
btnMarker.addListener(button -> {
if (stack != null || !AntiqueAtlasMod.settings.itemNeeded) {
if (state.is(PLACING_MARKER)) {
selectedButton = null;
state.switchTo(NORMAL);
} else {
selectedButton = button;
state.switchTo(PLACING_MARKER);
}
}
});
btnDelMarker = new GuiBookmarkButton(2, Textures.ICON_DELETE_MARKER, I18n.format("gui.antiqueatlas.delMarker"));
addChild(btnDelMarker).offsetGuiCoords(300, 33);
btnDelMarker.addListener(button -> {
if (stack != null || !AntiqueAtlasMod.settings.itemNeeded) {
if (state.is(DELETING_MARKER)) {
selectedButton = null;
state.switchTo(NORMAL);
} else {
selectedButton = button;
state.switchTo(DELETING_MARKER);
}
}
});
btnShowMarkers = new GuiBookmarkButton(3, Textures.ICON_HIDE_MARKERS, I18n.format("gui.antiqueatlas.hideMarkers"));
addChild(btnShowMarkers).offsetGuiCoords(300, 52);
btnShowMarkers.addListener(button -> {
if (stack != null || !AntiqueAtlasMod.settings.itemNeeded) {
selectedButton = null;
state.switchTo(state.is(HIDING_MARKERS) ? NORMAL : HIDING_MARKERS);
}
});
addChild(scaleBar).offsetGuiCoords(20, 198);
scaleBar.setMapScale(1);
markerFinalizer.addListener(blinkingIcon);
eraser.setTexture(Textures.ERASER, 12, 14, 2, 11);
}
public GuiAtlas prepareToOpen(ItemStack stack) {
this.stack = stack;
return prepareToOpen();
}
public GuiAtlas prepareToOpen() {
this.player = Minecraft.getMinecraft().player;
updateAtlasData();
if (!followPlayer && AntiqueAtlasMod.settings.doSaveBrowsingPos) {
loadSavedBrowsingPosition();
}
return this;
}
public void loadSavedBrowsingPosition() {
// Apply zoom first, because browsing position depends on it:
setMapScale(biomeData.getBrowsingZoom());
mapOffsetX = biomeData.getBrowsingX();
mapOffsetY = biomeData.getBrowsingY();
isDragging = false;
}
@Override
public void initGui() {
super.initGui();
if(!state.equals(EXPORTING_IMAGE)) {
state.switchTo(NORMAL); //TODO: his causes the Export PNG progress bar to disappear when resizing game window
}
Keyboard.enableRepeatEvents(true);
screenScale = new ScaledResolution(mc).getScaleFactor();
setCentered();
}
@Override
protected void mouseClicked(int mouseX, int mouseY, int mouseState) throws IOException {
super.mouseClicked(mouseX, mouseY, mouseState);
if (state.is(EXPORTING_IMAGE)) {
return; // Don't remove the progress bar.
}
// If clicked on the map, start dragging
int mapX = (width - MAP_WIDTH)/2;
int mapY = (height - MAP_HEIGHT)/2;
boolean isMouseOverMap = mouseX >= mapX && mouseX <= mapX + MAP_WIDTH &&
mouseY >= mapY && mouseY <= mapY + MAP_HEIGHT;
if (!state.is(NORMAL) && !state.is(HIDING_MARKERS)) {
int atlasID = getAtlasID();
if (state.is(PLACING_MARKER) // If clicked on the map, place marker:
&& isMouseOverMap && mouseState == 0 /* left click */) {
markerFinalizer.setMarkerData(player.getEntityWorld(),
atlasID, player.dimension,
screenXToWorldX(mouseX), screenYToWorldZ(mouseY));
addChild(markerFinalizer);
blinkingIcon.setTexture(markerFinalizer.selectedType.getIcon(),
MARKER_SIZE, MARKER_SIZE);
addChildBehind(markerFinalizer, blinkingIcon)
.setRelativeCoords(mouseX - getGuiX() - MARKER_SIZE/2,
mouseY - getGuiY() - MARKER_SIZE/2);
// Need to intercept keyboard events to type in the label:
setInterceptKeyboard(true);
// Un-press all keys to prevent player from walking infinitely:
KeyBinding.unPressAllKeys();
} else if (state.is(DELETING_MARKER) // If clicked on a marker, delete it:
&& toDelete != null && isMouseOverMap && mouseState == 0) {
AtlasAPI.markers.deleteMarker(player.getEntityWorld(),
atlasID, toDelete.getId());
}
state.switchTo(NORMAL);
} else if (isMouseOverMap && selectedButton == null) {
isDragging = true;
dragMouseX = mouseX;
dragMouseY = mouseY;
dragMapOffsetX = mapOffsetX;
dragMapOffsetY = mapOffsetY;
}
}
/** Opens a dialog window to select which file to save to, then performs
* rendering of the map of current dimension into a PNG image. */
private void exportImage(int atlasID) {
boolean showMarkers = !state.is(HIDING_MARKERS);
state.switchTo(EXPORTING_IMAGE);
// Default file name is "Atlas <N>.png"
ExportImageUtil.isExporting = true;
File file = ExportImageUtil.selectPngFileToSave("Atlas " + atlasID);
if (file != null) {
try {
Log.info("Exporting image from Atlas #%d to file %s", atlasID, file.getAbsolutePath());
ExportImageUtil.exportPngImage(biomeData, globalMarkersData, localMarkersData, file, showMarkers);
Log.info("Finished exporting image");
} catch (OutOfMemoryError e) {
Log.warn(e, "Image is too large, trying to export in strips");
try {
ExportImageUtil.exportPngImageTooLarge(biomeData, globalMarkersData, localMarkersData, file, showMarkers);
} catch (OutOfMemoryError e2) {
int minX = (biomeData.getScope().minX - 1) * ExportImageUtil.TILE_SIZE;
int minY = (biomeData.getScope().minY - 1) * ExportImageUtil.TILE_SIZE;
int outWidth = (biomeData.getScope().maxX + 2) * ExportImageUtil.TILE_SIZE - minX;
int outHeight = (biomeData.getScope().maxY + 2) * ExportImageUtil.TILE_SIZE - minY;
Log.error(e2, "Image is STILL too large, how massive is this map?! Answer: (%dx%d)", outWidth, outHeight);
ExportUpdateListener.INSTANCE.setStatusString(I18n.format("gui.antiqueatlas.export.tooLarge"));
ExportImageUtil.isExporting = false;
return; //Don't switch to normal state yet so that the error message can be read.
}
}
}
ExportImageUtil.isExporting = false;
state.switchTo(showMarkers ? NORMAL : HIDING_MARKERS);
}
@Override
public void handleKeyboardInput() throws IOException {
super.handleKeyboardInput();
if (Keyboard.getEventKeyState()) {
int key = Keyboard.getEventKey();
if (key == Keyboard.KEY_UP) {
navigateMap(0, navigateStep);
} else if (key == Keyboard.KEY_DOWN) {
navigateMap(0, -navigateStep);
} else if (key == Keyboard.KEY_LEFT) {
navigateMap(navigateStep, 0);
} else if (key == Keyboard.KEY_RIGHT) {
navigateMap(-navigateStep, 0);
} else if (key == Keyboard.KEY_ADD || key == Keyboard.KEY_EQUALS) {
setMapScale(mapScale * 2);
} else if (key == Keyboard.KEY_SUBTRACT || key == Keyboard.KEY_MINUS) {
setMapScale(mapScale / 2);
}
// Close the GUI if a hotbar key is pressed
else {
KeyBinding[] hotbarKeys = mc.gameSettings.keyBindsHotbar;
for (KeyBinding bind : hotbarKeys) {
if (key == bind.getKeyCode()) {
close();
break;
}
}
}
}
}
@Override
public void handleMouseInput() throws IOException {
super.handleMouseInput();
int wheelMove = Mouse.getEventDWheel();
if (wheelMove != 0) {
wheelMove = wheelMove > 0 ? 1 : -1;
if (AntiqueAtlasMod.settings.doReverseWheelZoom) {
wheelMove *= -1;
}
int mouseOffsetX = mc.displayWidth / screenScale / 2 - getMouseX();
int mouseOffsetY = mc.displayHeight / screenScale / 2 - getMouseY();
double newScale = mapScale * Math.pow(2, wheelMove);
int addOffsetX = 0;
int addOffsetY = 0;
if (Math.abs(mouseOffsetX) < MAP_WIDTH / 2 && Math.abs(mouseOffsetY) < MAP_HEIGHT / 2) {
addOffsetX = mouseOffsetX * wheelMove;
addOffsetY = mouseOffsetY * wheelMove;
if (wheelMove > 0) {
addOffsetX *= mapScale / newScale;
addOffsetY *= mapScale / newScale;
}
}
setMapScale(newScale, addOffsetX, addOffsetY);
}
}
@Override
protected void mouseReleased(int mouseX, int mouseY, int mouseState) {
super.mouseReleased(mouseX, mouseY, mouseState);
if (mouseState != -1) {
selectedButton = null;
isDragging = false;
}
}
@Override
protected void mouseClickMove(int mouseX, int mouseY, int lastMouseButton, long timeSinceMouseClick) {
super.mouseClickMove(mouseX, mouseY, lastMouseButton, timeSinceMouseClick);
if (isDragging) {
followPlayer = false;
btnPosition.setEnabled(true);
mapOffsetX = dragMapOffsetX + mouseX - dragMouseX;
mapOffsetY = dragMapOffsetY + mouseY - dragMouseY;
}
}
@Override
public void updateScreen() {
super.updateScreen();
if (player == null) return;
if (followPlayer) {
mapOffsetX = (int)(-player.posX * mapScale);
mapOffsetY = (int)(-player.posZ * mapScale);
}
if (player.getEntityWorld().getTotalWorldTime() > timeButtonPressed + BUTTON_PAUSE) {
navigateByButton(selectedButton);
}
updateAtlasData();
}
/** Update {@link #biomeData}, {@link #localMarkersData},
* {@link #globalMarkersData} */
private void updateAtlasData() {
int atlasID = getAtlasID();
biomeData = AntiqueAtlasMod.atlasData
.getAtlasData(atlasID, player.getEntityWorld())
.getDimensionData(player.dimension);
globalMarkersData = AntiqueAtlasMod.globalMarkersData.getData()
.getMarkersDataInDimension(player.dimension);
MarkersData markersData = AntiqueAtlasMod.markersData
.getMarkersData(atlasID, player.getEntityWorld());
if (markersData != null) {
localMarkersData = markersData
.getMarkersDataInDimension(player.dimension);
} else {
localMarkersData = null;
}
}
/** Offset the map view depending on which button was pressed. */
private void navigateByButton(GuiComponentButton btn) {
if (btn == null) return;
if (btn.equals(btnUp)) {
navigateMap(0, navigateStep);
} else if (btn.equals(btnDown)) {
navigateMap(0, -navigateStep);
} else if (btn.equals(btnLeft)) {
navigateMap(navigateStep, 0);
} else if (btn.equals(btnRight)) {
navigateMap(-navigateStep, 0);
}
}
/** Offset the map view by given values, in blocks. */
private void navigateMap(int dx, int dy) {
mapOffsetX += dx;
mapOffsetY += dy;
followPlayer = false;
btnPosition.setEnabled(true);
}
/** Set the pixel-to-block ratio, maintaining the current center of the screen. */
public void setMapScale(double scale) {
setMapScale(scale, 0, 0);
}
/** Set the pixel-to-block ratio, maintaining the current center of the screen with additional offset. */
private void setMapScale(double scale, int addOffsetX, int addOffsetY) {
double oldScale = mapScale;
mapScale = Math.min(Math.max(scale, AntiqueAtlasMod.settings.minScale), AntiqueAtlasMod.settings.maxScale);
// Scaling not needed
if (oldScale == mapScale) {
return;
}
if (mapScale >= MIN_SCALE_THRESHOLD) {
tileHalfSize = (int)Math.round(8 * mapScale);
tile2ChunkScale = 1;
} else {
tileHalfSize = (int)Math.round(8 * MIN_SCALE_THRESHOLD);
tile2ChunkScale = (int)Math.round(MIN_SCALE_THRESHOLD / mapScale);
}
// Times 2 because the contents of the Atlas are rendered at resolution 2 times smaller:
scaleBar.setMapScale(mapScale * 2);
mapOffsetX = (int) ((mapOffsetX + addOffsetX) * (mapScale / oldScale));
mapOffsetY = (int) ((mapOffsetY + addOffsetY) * (mapScale / oldScale));
dragMapOffsetX *= mapScale / oldScale;
dragMapOffsetY *= mapScale / oldScale;
// 2^13 = 8192
scaleClipIndex = MathHelper.log2((int)(mapScale * 8192)) + 1 - 13;
zoomLevel = -scaleClipIndex + zoomLevelOne;
scaleAlpha = 255;
if (followPlayer && (addOffsetX != 0 || addOffsetY != 0)) {
followPlayer = false;
btnPosition.setEnabled(true);
}
}
@Override
public void drawScreen(int mouseX, int mouseY, float par3) {
long currentMillis = System.currentTimeMillis();
long deltaMillis = currentMillis - lastUpdateMillis;
lastUpdateMillis = currentMillis;
if (AntiqueAtlasMod.settings.debugRender) {
renderTimes[renderTimesIndex++] = System.currentTimeMillis();
if (renderTimesIndex == renderTimes.length) {
renderTimesIndex = 0;
double elapsed = 0;
for (int i = 0; i < renderTimes.length - 1; i++) {
elapsed += renderTimes[i + 1] - renderTimes[i];
}
System.out.printf("GuiAtlas avg. render time: %.3f\n", elapsed / renderTimes.length);
}
}
GlStateManager.color(1, 1, 1, 1);
GlStateManager.alphaFunc(GL11.GL_GREATER, 0); // So light detail on tiles is visible
AtlasRenderHelper.drawFullTexture(Textures.BOOK, getGuiX(), getGuiY(), WIDTH, HEIGHT);
if ((stack == null && AntiqueAtlasMod.settings.itemNeeded) || biomeData == null)
return;
if (state.is(DELETING_MARKER)) {
GlStateManager.color(1, 1, 1, 0.5f);
}
GL11.glEnable(GL11.GL_SCISSOR_TEST);
GL11.glScissor((getGuiX() + CONTENT_X) * screenScale,
mc.displayHeight - (getGuiY() + CONTENT_Y + MAP_HEIGHT)*screenScale,
MAP_WIDTH * screenScale, MAP_HEIGHT*screenScale);
GlStateManager.enableBlend();
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
// Find chunk coordinates of the top left corner of the map.
// The 'roundToBase' is required so that when the map scales below the
// threshold the tiles don't change when map position changes slightly.
// The +-2 at the end provide margin so that tiles at the edges of
// the page have their stitched texture correct.
int mapStartX = MathUtil.roundToBase((int)Math.floor(-((double)MAP_WIDTH / 2d + mapOffsetX + 2 * tileHalfSize) / mapScale / 16d), tile2ChunkScale);
int mapStartZ = MathUtil.roundToBase((int)Math.floor(-((double)MAP_HEIGHT / 2d + mapOffsetY + 2 * tileHalfSize) / mapScale / 16d), tile2ChunkScale);
int mapEndX = MathUtil.roundToBase((int)Math.ceil(((double)MAP_WIDTH / 2d - mapOffsetX + 2 * tileHalfSize) / mapScale / 16d), tile2ChunkScale);
int mapEndZ = MathUtil.roundToBase((int)Math.ceil(((double)MAP_HEIGHT / 2d - mapOffsetY + 2 * tileHalfSize) / mapScale / 16d), tile2ChunkScale);
int mapStartScreenX = getGuiX() + WIDTH / 2 + (int)((mapStartX << 4) * mapScale) + mapOffsetX;
int mapStartScreenY = getGuiY() + HEIGHT / 2 + (int)((mapStartZ << 4) * mapScale) + mapOffsetY;
TileRenderIterator iter = new TileRenderIterator(biomeData);
iter.setScope(new Rect().setOrigin(mapStartX, mapStartZ).
set(mapStartX, mapStartZ, mapEndX, mapEndZ));
iter.setStep(tile2ChunkScale);
while (iter.hasNext()) {
SubTileQuartet subtiles = iter.next();
for (SubTile subtile : subtiles) {
if (subtile == null || subtile.tile == null) continue;
AtlasRenderHelper.drawAutotileCorner(
BiomeTextureMap.instance().getTexture(subtile.tile),
mapStartScreenX + subtile.x * tileHalfSize,
mapStartScreenY + subtile.y * tileHalfSize,
subtile.getTextureU(), subtile.getTextureV(), tileHalfSize);
}
}
int markersStartX = MathUtil.roundToBase(mapStartX, MarkersData.CHUNK_STEP) / MarkersData.CHUNK_STEP - 1;
int markersStartZ = MathUtil.roundToBase(mapStartZ, MarkersData.CHUNK_STEP) / MarkersData.CHUNK_STEP - 1;
int markersEndX = MathUtil.roundToBase(mapEndX, MarkersData.CHUNK_STEP) / MarkersData.CHUNK_STEP + 1;
int markersEndZ = MathUtil.roundToBase(mapEndZ, MarkersData.CHUNK_STEP) / MarkersData.CHUNK_STEP + 1;
double iconScale = getIconScale();
// Draw global markers:
for (int x = markersStartX; x <= markersEndX; x++) {
for (int z = markersStartZ; z <= markersEndZ; z++) {
List<Marker> markers = globalMarkersData.getMarkersAtChunk(x, z);
if (markers == null) continue;
for (Marker marker : markers) {
renderMarker(marker, iconScale);
}
}
}
// Draw local markers:
if (localMarkersData != null) {
for (int x = markersStartX; x <= markersEndX; x++) {
for (int z = markersStartZ; z <= markersEndZ; z++) {
List<Marker> markers = localMarkersData.getMarkersAtChunk(x, z);
if (markers == null) continue;
for (Marker marker : markers) {
renderMarker(marker, iconScale);
}
}
}
}
GL11.glDisable(GL11.GL_SCISSOR_TEST);
// Overlay the frame so that edges of the map are smooth:
GlStateManager.color(1, 1, 1, 1);
AtlasRenderHelper.drawFullTexture(Textures.BOOK_FRAME, getGuiX(), getGuiY(), WIDTH, HEIGHT);
renderScaleOverlay(deltaMillis);
iconScale = getIconScale();
// Draw player icon:
if (!state.is(HIDING_MARKERS)) {
// How much the player has moved from the top left corner of the map, in pixels:
int playerOffsetX = (int)(player.posX * mapScale) + mapOffsetX;
int playerOffsetZ = (int)(player.posZ * mapScale) + mapOffsetY;
if (playerOffsetX < -MAP_WIDTH/2) playerOffsetX = -MAP_WIDTH/2;
if (playerOffsetX > MAP_WIDTH/2) playerOffsetX = MAP_WIDTH/2;
if (playerOffsetZ < -MAP_HEIGHT/2) playerOffsetZ = -MAP_HEIGHT/2;
if (playerOffsetZ > MAP_HEIGHT/2 - 2) playerOffsetZ = MAP_HEIGHT/2 - 2;
// Draw the icon:
GlStateManager.color(1, 1, 1, state.is(PLACING_MARKER) ? 0.5f : 1);
GlStateManager.pushMatrix();
GlStateManager.translate(getGuiX() + WIDTH/2 + playerOffsetX, getGuiY() + HEIGHT/2 + playerOffsetZ, 0);
float playerRotation = (float) Math.round(player.rotationYaw / 360f * PLAYER_ROTATION_STEPS) / PLAYER_ROTATION_STEPS * 360f;
GlStateManager.rotate(180 + playerRotation, 0, 0, 1);
GlStateManager.translate(-PLAYER_ICON_WIDTH/2*iconScale, -PLAYER_ICON_HEIGHT/2*iconScale, 0);
AtlasRenderHelper.drawFullTexture(Textures.PLAYER, 0, 0,
(int)Math.round(PLAYER_ICON_WIDTH*iconScale), (int)Math.round(PLAYER_ICON_HEIGHT*iconScale));
GlStateManager.popMatrix();
GlStateManager.color(1, 1, 1, 1);
}
// Draw buttons:
super.drawScreen(mouseX, mouseY, par3);
// Draw the semi-transparent marker attached to the cursor when placing a new marker:
GlStateManager.enableBlend();
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
if (state.is(PLACING_MARKER)) {
GlStateManager.color(1, 1, 1, 0.5f);
markerFinalizer.selectedType.calculateMip(iconScale, mapScale, screenScale);
MarkerRenderInfo renderInfo = markerFinalizer.selectedType.getRenderInfo(iconScale, mapScale, screenScale);
markerFinalizer.selectedType.resetMip();
AtlasRenderHelper.drawFullTexture(
renderInfo.tex,
mouseX + renderInfo.x, mouseY + renderInfo.y,
renderInfo.width, renderInfo.height);
GlStateManager.color(1, 1, 1, 1);
}
// Draw progress overlay:
if (state.is(EXPORTING_IMAGE)) {
drawDefaultBackground();
progressBar.draw((width - 100)/2, height/2 - 34);
}
}
private void renderScaleOverlay(long deltaMillis) {
if(scaleAlpha > 0) {
GlStateManager.translate(getGuiX() + WIDTH-13, getGuiY() + 12, 0);
String text;
int textWidth, xWidth;
text = "x";
xWidth = textWidth = fontRenderer.getStringWidth(text); xWidth++;
fontRenderer.drawString(text, -textWidth, 0, scaleAlpha << 24);
text = zoomNames[zoomLevel];
if(text.contains("/")) {
String[] parts = text.split("/");
text = parts[0];
int centerXtranslate = Math.max(fontRenderer.getStringWidth(parts[0]), fontRenderer.getStringWidth(parts[1]) )/2;
GlStateManager.translate(-xWidth-centerXtranslate, -fontRenderer.FONT_HEIGHT/2, 0);
GlStateManager.disableTexture2D();
Tessellator t = Tessellator.getInstance();
VertexBuffer vb = t.getBuffer();
vb.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION);
vb.pos( centerXtranslate, fontRenderer.FONT_HEIGHT - 1, 0.0D).endVertex();
vb.pos(-centerXtranslate-1, fontRenderer.FONT_HEIGHT - 1, 0.0D).endVertex();
vb.pos(-centerXtranslate-1, fontRenderer.FONT_HEIGHT , 0.0D).endVertex();
vb.pos( centerXtranslate, fontRenderer.FONT_HEIGHT , 0.0D).endVertex();
t.draw();
GlStateManager.enableTexture2D();
textWidth = fontRenderer.getStringWidth(text);
fontRenderer.drawString(text, -textWidth/2, 0, scaleAlpha << 24);
text = parts[1];
GlStateManager.translate(0, fontRenderer.FONT_HEIGHT + 1, 0);
textWidth = fontRenderer.getStringWidth(text);
fontRenderer.drawString(text, -textWidth/2, 0, scaleAlpha << 24);
GlStateManager.translate(xWidth+centerXtranslate, ( -fontRenderer.FONT_HEIGHT/2 ) -2, 0);
} else {
textWidth = fontRenderer.getStringWidth(text);
fontRenderer.drawString(text, -textWidth-xWidth + 1, 1, scaleAlpha << 24);
}
GlStateManager.translate(-(getGuiX()+WIDTH-13), -(getGuiY()+12), 0);
int deltaScaleAlpha = (int)(deltaMillis * 0.256);
// because of some crazy high frame rate
if(deltaScaleAlpha == 0) {
deltaScaleAlpha = 1;
}
scaleAlpha -= 20 * deltaScaleAlpha;
if(scaleAlpha < 0)
scaleAlpha = 0;
}
}
private void renderMarker(Marker marker, double scale) {
MarkerType type = marker.getType();
if (type.shouldHide(state.is(HIDING_MARKERS), scaleClipIndex)) {
return;
}
int markerX = worldXToScreenX(marker.getX());
int markerY = worldZToScreenY(marker.getZ());
if (!marker.isVisibleAhead() &&
!biomeData.hasTileAt(marker.getChunkX(), marker.getChunkZ())) {
return;
}
type.calculateMip(scale, mapScale, screenScale);
MarkerRenderInfo info = type.getRenderInfo(scale, mapScale, screenScale);
boolean mouseIsOverMarker = type.shouldHover((getMouseX()-(markerX+info.x))/info.width, (getMouseY()-(markerY+info.y))/info.height);
type.resetMip();
if (state.is(PLACING_MARKER)) {
GlStateManager.color(1, 1, 1, 0.5f);
} else if (state.is(DELETING_MARKER)) {
if (marker.isGlobal()) {
GlStateManager.color(1, 1, 1, 0.5f);
} else {
if (mouseIsOverMarker) {
GlStateManager.color(0.5f, 0.5f, 0.5f, 1);
toDelete = marker;
} else {
GlStateManager.color(1, 1, 1, 1);
if (toDelete == marker) {
toDelete = null;
}
}
}
} else {
GlStateManager.color(1, 1, 1, 1);
}
if (AntiqueAtlasMod.settings.debugRender){
System.out.println("Rendering Marker: "+info.tex);
}
AtlasRenderHelper.drawFullTexture(
info.tex,
markerX + info.x,
markerY + info.y,
info.width, info.height);
if (isMouseOver && mouseIsOverMarker && marker.getLabel().length() > 0) {
drawTooltip(Collections.singletonList(marker.getLocalizedLabel()), mc.fontRenderer);
}
}
@Override
public boolean doesGuiPauseGame() {
return false;
}
@Override
public void onGuiClosed() {
super.onGuiClosed();
removeChild(markerFinalizer);
removeChild(blinkingIcon);
Keyboard.enableRepeatEvents(false);
biomeData.setBrowsingPosition(mapOffsetX, mapOffsetY, mapScale);
PacketDispatcher.sendToServer(new BrowsingPositionPacket(getAtlasID(), player.dimension, mapOffsetX, mapOffsetY, mapScale));
}
/** Returns the Y coordinate that the cursor is pointing at. */
private int screenXToWorldX(int mouseX) {
return (int)Math.round((double)(mouseX - this.width/2 - mapOffsetX) / mapScale);
}
/** Returns the Y block coordinate that the cursor is pointing at. */
private int screenYToWorldZ(int mouseY) {
return (int)Math.round((double)(mouseY - this.height/2 - mapOffsetY) / mapScale);
}
private int worldXToScreenX(int x) {
return (int)Math.round((double)x * mapScale + this.width/2 + mapOffsetX);
}
private int worldZToScreenY(int z) {
return (int)Math.round((double)z * mapScale + this.height/2 + mapOffsetY);
}
@Override
protected void onChildClosed(GuiComponent child) {
if (child.equals(markerFinalizer)) {
setInterceptKeyboard(false);
removeChild(blinkingIcon);
}
}
/** Update all text labels to current localization. */
public void updateL18n() {
btnExportPng.setTitle(I18n.format("gui.antiqueatlas.exportImage"));
btnMarker.setTitle(I18n.format("gui.antiqueatlas.addMarker"));
}
/** Returns the scale of markers and player icon at given mapScale. */
private double getIconScale() {
return AntiqueAtlasMod.settings.doScaleMarkers ? (mapScale < 0.5 ? 0.5 : mapScale > 1 ? 2 : 1) : 1;
}
/** Returns atlas id based on "itemNeeded" option */
private int getAtlasID() {
return AntiqueAtlasMod.settings.itemNeeded ? stack.getItemDamage() : player.getUniqueID().hashCode();
}
}