/*
* Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
* Copyright (C) 2021 cyoung06
*
* This program 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, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package kr.syeyoung.dungeonsguide.roomprocessor.boxpuzzle;
import kr.syeyoung.dungeonsguide.config.Config;
import kr.syeyoung.dungeonsguide.config.types.AColor;
import kr.syeyoung.dungeonsguide.dungeon.data.OffsetPointSet;
import kr.syeyoung.dungeonsguide.dungeon.roomfinder.DungeonRoom;
import kr.syeyoung.dungeonsguide.features.FeatureRegistry;
import kr.syeyoung.dungeonsguide.roomprocessor.GeneralRoomProcessor;
import kr.syeyoung.dungeonsguide.roomprocessor.RoomProcessorGenerator;
import kr.syeyoung.dungeonsguide.utils.RenderUtils;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.init.Blocks;
import net.minecraft.util.*;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import java.awt.*;
import java.util.*;
import java.util.List;
public class RoomProcessorBoxSolver extends GeneralRoomProcessor {
private BlockPos[][] poses = new BlockPos[7][7];
private boolean bugged= true;
private BoxPuzzleSolvingThread puzzleSolvingThread;
public RoomProcessorBoxSolver(DungeonRoom dungeonRoom) {
super(dungeonRoom);
OffsetPointSet ops = (OffsetPointSet) dungeonRoom.getDungeonRoomInfo().getProperties().get("board");
try {
if (ops != null) {
for (int y = 0; y < 7; y++) {
for (int x = 0; x < 7; x++) {
poses[y][x] = ops.getOffsetPointList().get(y * 7 + x).getBlockPos(dungeonRoom);
}
}
bugged = false;
}
} catch (Exception e) {
e.printStackTrace();
}
}
private byte[][] buildCurrentState() {
World w = getDungeonRoom().getContext().getWorld();
byte[][] board = new byte[poses.length][poses[0].length];
for (int y = 0; y < poses.length; y++) {
for (int x = 0; x < poses[0].length; x++) {
if (y == 6) {
board[y][x] = -1;
continue;
}
BlockPos pos = poses[y][x];
Block b = w.getChunkFromBlockCoords(pos).getBlock(pos);
if (b == Blocks.air)
board[y][x] = 0;
else
board[y][x] = 1;
}
}
return board;
}
private boolean calcReq = true;
private boolean calcDone= false;
private boolean calcDone2 = false;
private int step = 0;
private byte[][] lastState;
@Override
public void tick() {
super.tick();
if (!FeatureRegistry.SOLVER_BOX.isEnabled()) return;
if (bugged) return;
byte[][] currboard = buildCurrentState();
if (puzzleSolvingThread == null) {
calcDone = false;
puzzleSolvingThread = new BoxPuzzleSolvingThread(currboard, 0, 6, new Runnable() {
@Override
public void run() {
calcDone = true;
calcDone2 = true;
}
});
puzzleSolvingThread.start();
}
if (calcReq) {
OffsetPointSet ops = (OffsetPointSet) getDungeonRoom().getDungeonRoomInfo().getProperties().get("board");
if (ops != null) {
poses = new BlockPos[7][7];
for (int y = 0; y < 7; y++) {
for (int x = 0; x < 7; x++) {
poses[y][x] = ops.getOffsetPointList().get(y * 7 + x).getBlockPos(getDungeonRoom());
}
}
bugged = false;
}
calcDone = false;
if (puzzleSolvingThread != null)
puzzleSolvingThread.stop();
puzzleSolvingThread = new BoxPuzzleSolvingThread(currboard, 0, 6, new Runnable() {
@Override
public void run() {
calcDone = true;
calcDone2 = true;
}
});
puzzleSolvingThread.start();
calcReq = false;
}
boolean pathFindReq = false;
if (calcDone2) {
BoxPuzzleSolvingThread.Route semi_solution = puzzleSolvingThread.solution;
if (semi_solution == null) {
Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("§eDungeons Guide §7:: §eBox Solver §7:: §cCouldn't find solution involving less than 20 box moves within 3m concurrent possibility"));
step = 0;
calcDone2 = false;
pathFindReq = true;
totalPath = new LinkedList();
totalPushedBlocks = new LinkedList();
solution = new LinkedList();
return;
} else{
solution = semi_solution.boxMoves;
Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("§eDungeons Guide §7:: §eBox Solver §7:: Solution Found!"));
}
step = 0;
lastState = currboard;
calcDone2 = false;
pathFindReq = true;
calcTotalPath();
}
if (lastState == null) return;
boolean moved = false;
label:
for (int y = 0 ; y < currboard.length; y++) {
for (int x = 0; x < currboard[y].length; x++) {
if (lastState[y][x] != currboard[y][x]) {
moved = true;
lastState = currboard;
break label;
}
}
}
if (moved) {
step++;
}
Point player = getPlayerPos(currboard);
boolean currYState = Minecraft.getMinecraft().thePlayer.getPosition().getY() < 68;
if (((currYState && !player.equals(lastPlayer)) || (currYState != yState) || (moved) || pathFindReq) && solution != null) {
Point target = null;
if (step < solution.size()) {
BoxPuzzleSolvingThread.BoxMove boxMove = solution.get(step);
target = new Point(boxMove.x - boxMove.dx, boxMove.y - boxMove.dy);
}
List semi_pathFound = pathfind(currboard, player, target);
pathFound = new LinkedList();
for (Point point : semi_pathFound) {
pathFound.add(poses[point.y][point.x].add(0,-1,0));
}
lastPlayer = player;
yState = currYState;
}
}
public void calcTotalPath() {
Point player = new Point(0,6);
totalPath = new LinkedList();
totalPushedBlocks = new LinkedList();
byte[][] currboard = buildCurrentState();
for (int i = 0; i <= solution.size(); i++) {
Point target = null;
BoxPuzzleSolvingThread.BoxMove boxMove = null;
if (i < solution.size()) {
boxMove = solution.get(i);
target = new Point(boxMove.x - boxMove.dx, boxMove.y - boxMove.dy);
}
List semi_pathFound = pathfind(currboard, player, target);
for (int i1 = semi_pathFound.size() - 1; i1 >= 0; i1--) {
Point point = semi_pathFound.get(i1);
totalPath.add(poses[point.y][point.x].add(0, -1, 0));
}
player = target;
if (boxMove != null) {
BoxPuzzleSolvingThread.push(currboard, boxMove.x, boxMove.y, boxMove.dx, boxMove.dy);
int fromX = boxMove.x - boxMove.dx;
int fromY = boxMove.y - boxMove.dy;
BlockPos pos = poses[fromY][fromX];
BlockPos pos2 = poses[boxMove.y][boxMove.x];
BlockPos dir = pos.subtract(pos2);
dir = new BlockPos(MathHelper.clamp_int(dir.getX(), -1,1), 0, MathHelper.clamp_double(dir.getZ(), -1, 1));
BlockPos highlight = pos2.add(dir);
totalPushedBlocks.add(highlight);
}
}
}
private boolean yState = true;
public Point getPlayerPos(byte[][] map) {
BlockPos playerPos = Minecraft.getMinecraft().thePlayer.getPosition();
int minDir = Integer.MAX_VALUE;
Point pt = null;
for (int y = 0; y < poses.length; y++) {
for (int x = 0; x < poses[0].length; x++) {
if (map[y][x] == 1) continue;
int dir = (int) poses[y][x].distanceSq(playerPos);
if (dir < minDir) {
minDir = dir;
pt = new Point(x,y);
}
}
}
return pt;
}
private List solution;
private List pathFound;
private List totalPath;
private List totalPushedBlocks;
private Point lastPlayer;
private static final java.util.List directions = Arrays.asList(new Point(-1,0), new Point(1,0), new Point(0,1), new Point(0,-1));
public List pathfind(byte[][] map, Point start, Point target2) {
int[][] distances = new int[map.length][map[0].length];
Queue evalulate = new LinkedList();
evalulate.add(start);
Point target = null;
while (!evalulate.isEmpty()) {
Point p = evalulate.poll();
if (p.equals(target2) || (target2 == null &&p.y == 0)) {
target = p;
break;
}
int max = 0;
for (Point dir:directions) {
int resX= p.x + dir.x;
int resY = p.y + dir.y;
if (resX < 0 || resY < 0 || resX >= distances[0].length || resY >= distances.length) {
continue;
}
if (max < distances[resY][resX]) {
max = distances[resY][resX];
}
if (distances[resY][resX] == 0 && (map[resY][resX] == 0 ||map[resY][resX] == -1)) {
evalulate.add(new Point(resX, resY));
}
}
distances[p.y][p.x] = max + 1;
}
if (target == null) return Collections.emptyList();
List route = new LinkedList();
while(!target.equals(start)) {
route.add(target);
int min = Integer.MAX_VALUE;
Point minPoint = null;
for (Point dir:directions) {
int resX= target.x + dir.x;
int resY = target.y + dir.y;
if (resX < 0 || resY < 0 || resX >= distances[0].length || resY >= distances.length) {
continue;
}
if (min > distances[resY][resX] && distances[resY][resX] != 0) {
min = distances[resY][resX];
minPoint = new Point(resX, resY);
}
}
target = minPoint;
}
route.add(start);
return route;
}
@Override
public void chatReceived(IChatComponent chat) {
if (!FeatureRegistry.SOLVER_BOX.isEnabled()) return;
if (chat.getFormattedText().toLowerCase().contains("recalc")) {
if (calcDone) {
calcReq = true;
Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("§eDungeons Guide :::: Recalculating Route..."));
} else {
calcReq = true;
Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("§eDungeons Guide :::: Currently Calculating Route..."));
}
}
}
@Override
public void drawScreen(float partialTicks) {
super.drawScreen(partialTicks);
if (!FeatureRegistry.SOLVER_BOX.isEnabled()) return;
if (FeatureRegistry.SOLVER_BOX.disableText()) return;
FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
GlStateManager.enableBlend();
GL14.glBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
fr.drawString("Type \"recalc\" in chat to recalculate the solution", 0, 0, 0xFFFFFFFF);
}
@Override
public void drawWorld(float partialTicks) {
super.drawWorld(partialTicks);
if (!FeatureRegistry.SOLVER_BOX.isEnabled()) return;
if (bugged) return;
if (!calcDone) return;
if (solution == null) return;
if (Minecraft.getMinecraft().thePlayer.getPosition().getY() < 68) {
if (step < solution.size()) {
BoxPuzzleSolvingThread.BoxMove boxMove = solution.get(step);
int fromX = boxMove.x - boxMove.dx;
int fromY = boxMove.y - boxMove.dy;
BlockPos pos = poses[fromY][fromX];
BlockPos pos2 = poses[boxMove.y][boxMove.x];
BlockPos dir = pos.subtract(pos2);
dir = new BlockPos(MathHelper.clamp_int(dir.getX(), -1, 1), 0, MathHelper.clamp_double(dir.getZ(), -1, 1));
BlockPos highlight = pos2.add(dir);
AColor color = FeatureRegistry.SOLVER_BOX.getTargetColor().multiplyAlpha(MathHelper.clamp_double(Minecraft.getMinecraft().thePlayer.getPosition().distanceSq(highlight), 100, 255) / 255);
RenderUtils.highlightBoxAColor(AxisAlignedBB.fromBounds(highlight.getX(), highlight.getY(), highlight.getZ(), highlight.getX()+1, highlight.getY() + 1, highlight.getZ() + 1), color, partialTicks, false);
}
if (pathFound != null) {
RenderUtils.drawLines(pathFound, FeatureRegistry.SOLVER_BOX.getLineColor(),FeatureRegistry.SOLVER_BOX.getLineWidth(), partialTicks, true);
}
} else {
if (totalPath != null) {
RenderUtils.drawLines(totalPath, FeatureRegistry.SOLVER_BOX.getLineColor(),FeatureRegistry.SOLVER_BOX.getLineWidth(), partialTicks, false);
}
if (totalPushedBlocks != null) {
for (int i = 0; i < totalPushedBlocks.size(); i++) {
BlockPos pos = totalPushedBlocks.get(i);
RenderUtils.highlightBoxAColor(AxisAlignedBB.fromBounds(pos.getX(), pos.getY(), pos.getZ(), pos.getX()+1, pos.getY() + 1, pos.getZ() + 1), FeatureRegistry.SOLVER_BOX.getTargetColor(), partialTicks, false);
RenderUtils.drawTextAtWorld("#"+i, pos.getX()+0.5f, pos.getY() +0.5f, pos.getZ() + 0.5f, i != step ?
RenderUtils.getColorAt(pos.getX(), pos.getY(), pos.getZ(), FeatureRegistry.SOLVER_BOX.getTextColor2()) : RenderUtils.getColorAt(pos.getX(), pos.getY(), pos.getZ(), FeatureRegistry.SOLVER_BOX.getTextColor()), 0.1f, false, false, partialTicks);
}
}
}
}
public static class Generator implements RoomProcessorGenerator {
@Override
public RoomProcessorBoxSolver createNew(DungeonRoom dungeonRoom) {
RoomProcessorBoxSolver defaultRoomProcessor = new RoomProcessorBoxSolver(dungeonRoom);
return defaultRoomProcessor;
}
}
}