diff options
Diffstat (limited to 'mod/src/main/java/kr/syeyoung/dungeonsguide/dungeon/roomfinder')
3 files changed, 676 insertions, 0 deletions
diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/dungeon/roomfinder/DungeonRoom.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/dungeon/roomfinder/DungeonRoom.java new file mode 100755 index 00000000..83dbb889 --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/dungeon/roomfinder/DungeonRoom.java @@ -0,0 +1,380 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +package kr.syeyoung.dungeonsguide.dungeon.roomfinder; + +import com.google.common.collect.Sets; +import kr.syeyoung.dungeonsguide.dungeon.data.DungeonRoomInfo; +import kr.syeyoung.dungeonsguide.dungeon.doorfinder.EDungeonDoorType; +import kr.syeyoung.dungeonsguide.dungeon.events.DungeonStateChangeEvent; +import kr.syeyoung.dungeonsguide.dungeon.mechanics.DungeonMechanic; +import kr.syeyoung.dungeonsguide.dungeon.mechanics.DungeonRoomDoor; +import kr.syeyoung.dungeonsguide.features.impl.secret.FeaturePathfindStrategy; +import kr.syeyoung.dungeonsguide.dungeon.DungeonContext; +import kr.syeyoung.dungeonsguide.dungeon.MapProcessor; +import kr.syeyoung.dungeonsguide.dungeon.doorfinder.DungeonDoor; +import kr.syeyoung.dungeonsguide.features.FeatureRegistry; +import kr.syeyoung.dungeonsguide.roomedit.EditingContext; +import kr.syeyoung.dungeonsguide.roomprocessor.ProcessorFactory; +import kr.syeyoung.dungeonsguide.roomprocessor.RoomProcessor; +import kr.syeyoung.dungeonsguide.roomprocessor.RoomProcessorGenerator; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.pathfinding.PathEntity; +import net.minecraft.pathfinding.PathFinder; +import net.minecraft.pathfinding.PathPoint; +import net.minecraft.util.*; +import net.minecraft.world.ChunkCache; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; + +import javax.vecmath.Vector2d; +import java.awt.*; +import java.util.*; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@Getter +public class DungeonRoom { + private final List<Point> unitPoints; + private final short shape; + private final byte color; + + private final BlockPos min; + private final BlockPos max; + private final Point minRoomPt; + + private final DungeonContext context; + + private final List<DungeonDoor> doors = new ArrayList<DungeonDoor>(); + + private DungeonRoomInfo dungeonRoomInfo; + + private final int unitWidth; // X + private final int unitHeight; // Z + + @Setter + private int totalSecrets = -1; + private RoomState currentState = RoomState.DISCOVERED; + + private Map<String, DungeonMechanic> cached = null; + + @Getter + private World cachedWorld; + public Map<String, DungeonMechanic> getMechanics() { + if (cached == null || EditingContext.getEditingContext() != null) { + cached = new HashMap<String, DungeonMechanic>(dungeonRoomInfo.getMechanics()); + int index = 0; + for (DungeonDoor door : doors) { + if (door.getType().isExist()) cached.put((door.getType().getName())+"-"+(++index), new DungeonRoomDoor(this, door)); + } + } + return cached; + } + + public void setCurrentState(RoomState currentState) { + context.createEvent(new DungeonStateChangeEvent(unitPoints.get(0), dungeonRoomInfo.getName(), this.currentState, currentState)); + this.currentState = currentState; + } + + private Map<BlockPos, AStarFineGrid> activeBetterAStar = new HashMap<>(); + private Map<BlockPos, AStarCornerCut> activeBetterAStarCornerCut = new HashMap<>(); + private Map<BlockPos, ThetaStar> activeThetaStar = new HashMap<>(); + + public ScheduledFuture<List<Vec3>> createEntityPathTo(IBlockAccess blockaccess, Entity entityIn, BlockPos targetPos, float dist, int timeout) { + FeaturePathfindStrategy.PathfindStrategy pathfindStrategy = FeatureRegistry.SECRET_PATHFIND_STRATEGY.getPathfindStrat(); + if (pathfindStrategy == FeaturePathfindStrategy.PathfindStrategy.JPS_LEGACY) { + ScheduledFuture<List<Vec3>> sf = asyncPathFinder.schedule(() -> { + BlockPos min = new BlockPos(getMin().getX(), 0, getMin().getZ()); + BlockPos max= new BlockPos(getMax().getX(), 255, getMax().getZ()); + JPSPathfinder pathFinder = new JPSPathfinder(this); + pathFinder.pathfind(entityIn.getPositionVector(), new Vec3(targetPos).addVector(0.5, 0.5, 0.5), 1.5f,timeout); + return pathFinder.getRoute(); + }, 0, TimeUnit.MILLISECONDS); + return sf; + } else if (pathfindStrategy == FeaturePathfindStrategy.PathfindStrategy.A_STAR_FINE_GRID) { + ScheduledFuture<List<Vec3>> sf = asyncPathFinder.schedule(() -> { + AStarFineGrid pathFinder = + activeBetterAStar.computeIfAbsent(targetPos, (pos) -> new AStarFineGrid(this, new Vec3(pos.getX(), pos.getY(), pos.getZ()).addVector(0.5, 0.5, 0.5))); + pathFinder.pathfind(entityIn.getPositionVector(),timeout); + return pathFinder.getRoute(); + }, 0, TimeUnit.MILLISECONDS); + return sf; + }else if (pathfindStrategy == FeaturePathfindStrategy.PathfindStrategy.A_STAR_DIAGONAL) { + ScheduledFuture<List<Vec3>> sf = asyncPathFinder.schedule(() -> { + AStarCornerCut pathFinder = + activeBetterAStarCornerCut.computeIfAbsent(targetPos, (pos) -> new AStarCornerCut(this, new Vec3(pos.getX(), pos.getY(), pos.getZ()).addVector(0.5, 0.5, 0.5))); + pathFinder.pathfind(entityIn.getPositionVector(),timeout); + return pathFinder.getRoute(); + }, 0, TimeUnit.MILLISECONDS); + return sf; + } else if (pathfindStrategy == FeaturePathfindStrategy.PathfindStrategy.THETA_STAR) { + ScheduledFuture<List<Vec3>> sf = asyncPathFinder.schedule(() -> { + ThetaStar pathFinder = + activeThetaStar.computeIfAbsent(targetPos, (pos) -> new ThetaStar(this, new Vec3(pos.getX(), pos.getY(), pos.getZ()).addVector(0.5, 0.5, 0.5))); + pathFinder.pathfind(entityIn.getPositionVector(),timeout); + return pathFinder.getRoute(); + }, 0, TimeUnit.MILLISECONDS); + return sf; + } else { + ScheduledFuture<List<Vec3>> sf = asyncPathFinder.schedule(() -> { + PathFinder pathFinder = new PathFinder(nodeProcessorDungeonRoom); + PathEntity latest = pathFinder.createEntityPathTo(blockaccess, entityIn, targetPos, dist); + if (latest != null) { + List<Vec3> poses = new ArrayList<>(); + for (int i = 0; i < latest.getCurrentPathLength(); i++) { + PathPoint pathPoint = latest.getPathPointFromIndex(i); + poses.add(new Vec3(getMin().add(pathPoint.xCoord, pathPoint.yCoord, pathPoint.zCoord)).addVector(0.5,0.5,0.5)); + } + return poses; + } + return new ArrayList<>(); + }, 0, TimeUnit.MILLISECONDS); + return sf; + } + } + + private static final ScheduledExecutorService asyncPathFinder = Executors.newScheduledThreadPool(4); + @Getter + private final NodeProcessorDungeonRoom nodeProcessorDungeonRoom; + + @Getter + private final Map<String, Object> roomContext = new HashMap<String, Object>(); + + @AllArgsConstructor + @Getter + public enum RoomState { + DISCOVERED(0), COMPLETE_WITHOUT_SECRETS(0), FINISHED(0), FAILED(-14); + private final int scoreModifier; + } + + private RoomProcessor roomProcessor; + + public DungeonRoom(List<Point> points, short shape, byte color, BlockPos min, BlockPos max, DungeonContext context, Set<Tuple<Vector2d, EDungeonDoorType>> doorsAndStates) { + this.unitPoints = points; + this.shape = shape; + this.color = color; + this.min = min; + this.max = max; + this.context = context; + + minRoomPt = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE); + for (Point pt : unitPoints) { + if (pt.x < minRoomPt.x) minRoomPt.x = pt.x; + if (pt.y < minRoomPt.y) minRoomPt.y = pt.y; + } + unitWidth = (int) Math.ceil(max.getX() - min.getX() / 32.0); + unitHeight = (int) Math.ceil(max.getZ() - min.getZ() / 32.0); + + + ChunkCache chunkCache = new ChunkCache(getContext().getWorld(), min.add(-3, 0, -3), max.add(3,0,3), 0); + this.cachedWorld = new CachedWorld(chunkCache); + + + + minx = min.getX() * 2; miny = 0; minz = min.getZ() * 2; + maxx = max.getX() * 2 + 2; maxy = 255 * 2 + 2; maxz = max.getZ() * 2 + 2; + + lenx = maxx - minx; + leny = maxy - miny; + lenz = maxz - minz; + arr = new long[lenx *leny * lenz * 2 / 8];; + + buildDoors(doorsAndStates); + buildRoom(); + nodeProcessorDungeonRoom = new NodeProcessorDungeonRoom(this); + updateRoomProcessor(); + + + } + + private static final Set<Vector2d> directions = Sets.newHashSet(new Vector2d(0,16), new Vector2d(0, -16), new Vector2d(16, 0), new Vector2d(-16 , 0)); + + private void buildDoors(Set<Tuple<Vector2d, EDungeonDoorType>> doorsAndStates) { + Set<Tuple<BlockPos, EDungeonDoorType>> positions = new HashSet<>(); + BlockPos pos = context.getMapProcessor().roomPointToWorldPoint(minRoomPt).add(16,0,16); + for (Tuple<Vector2d, EDungeonDoorType> doorsAndState : doorsAndStates) { + Vector2d vector2d = doorsAndState.getFirst(); + BlockPos neu = pos.add(vector2d.x * 32, 0, vector2d.y * 32); + positions.add(new Tuple<>(neu, doorsAndState.getSecond())); + } + + for (Tuple<BlockPos, EDungeonDoorType> door : positions) { + doors.add(new DungeonDoor(context.getWorld(), door.getFirst(), door.getSecond())); + } + } + + private RoomMatcher roomMatcher = null; + private void buildRoom() { + if (roomMatcher == null) + roomMatcher = new RoomMatcher(this); + DungeonRoomInfo dungeonRoomInfo = roomMatcher.match(); + if (dungeonRoomInfo == null) { + dungeonRoomInfo = roomMatcher.createNew(); + if (color == 18) dungeonRoomInfo.setProcessorId("bossroom"); + } + this.dungeonRoomInfo = dungeonRoomInfo; + totalSecrets = dungeonRoomInfo.getTotalSecrets(); + } + + public void updateRoomProcessor() { + RoomProcessorGenerator roomProcessorGenerator = ProcessorFactory.getRoomProcessorGenerator(dungeonRoomInfo.getProcessorId()); + if (roomProcessorGenerator == null) this.roomProcessor = null; + else this.roomProcessor = roomProcessorGenerator.createNew(this); + } + + public Block getAbsoluteBlockAt(int x, int y, int z) { + // validate x y z's + BlockPos pos = new BlockPos(x,y,z); + if (canAccessAbsolute(pos)) { + return cachedWorld.getBlockState(pos).getBlock(); + } + return null; + } + + public Block getRelativeBlockAt(int x, int y, int z) { + // validate x y z's + if (canAccessRelative(x,z)) { + BlockPos pos = new BlockPos(x,y,z).add(min.getX(),min.getY(),min.getZ()); + return cachedWorld.getBlockState(pos).getBlock(); + } + return null; + } + + public BlockPos getRelativeBlockPosAt(int x, int y, int z) { + BlockPos pos = new BlockPos(x,y,z).add(min.getX(),min.getY(),min.getZ()); + return pos; + } + + public int getRelativeBlockDataAt(int x, int y, int z) { + // validate x y z's + if (canAccessRelative(x,z)) { + BlockPos pos = new BlockPos(x,y,z).add(min.getX(),min.getY(),min.getZ()); + IBlockState iBlockState = cachedWorld.getBlockState(pos); + return iBlockState.getBlock().getMetaFromState(iBlockState); + } + return -1; + } + + public int getAbsoluteBlockDataAt(int x, int y, int z) { + // validate x y z's + BlockPos pos = new BlockPos(x,y,z); + if (canAccessAbsolute(pos)) { + IBlockState iBlockState = cachedWorld.getBlockState(pos); + return iBlockState.getBlock().getMetaFromState(iBlockState); + } + return -1; + } + + public boolean canAccessAbsolute(BlockPos pos) { + MapProcessor mapProcessor = this.context.getMapProcessor(); + Point roomPt = mapProcessor.worldPointToRoomPoint(pos); + roomPt.translate(-minRoomPt.x, -minRoomPt.y); + + return (shape >>(roomPt.y *4 +roomPt.x) & 0x1) > 0; + } + public boolean canAccessRelative(int x, int z) { + return x>= 0 && z >= 0 && (shape >>((z/32) *4 +(x/32)) & 0x1) > 0; + } + + + + long arr[]; + // These values are doubled + private final int minx, miny, minz, maxx, maxy, maxz; + private final int lenx, leny, lenz; + private static final float playerWidth = 0.3f; + public boolean isBlocked(int x,int y, int z) { + if (x < minx || z < minz || x >= maxx || z >= maxz || y < miny || y >= maxy) return true; + int dx = x - minx, dy = y - miny, dz = z - minz; + int bitIdx = dx * leny * lenz + dy * lenz + dz; + int location = bitIdx / 4; + int bitStart = (2 * (bitIdx % 4)); + long theBit = arr[location]; + if (((theBit >> bitStart) & 0x2) > 0) return ((theBit >> bitStart) & 1) > 0; + float wX = x / 2.0f, wY = y / 2.0f, wZ = z / 2.0f; + + + AxisAlignedBB bb = AxisAlignedBB.fromBounds(wX - playerWidth, wY, wZ - playerWidth, wX + playerWidth, wY + 1.9f, wZ + playerWidth); + + int i = MathHelper.floor_double(bb.minX); + int j = MathHelper.floor_double(bb.maxX + 1.0D); + int k = MathHelper.floor_double(bb.minY); + int l = MathHelper.floor_double(bb.maxY + 1.0D); + int i1 = MathHelper.floor_double(bb.minZ); + int j1 = MathHelper.floor_double(bb.maxZ + 1.0D); + BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(); + + List<AxisAlignedBB> list = new ArrayList<>(); + for (int k1 = i; k1 < j; ++k1) { + for (int l1 = i1; l1 < j1; ++l1) { + for (int i2 = k - 1; i2 < l; ++i2) { + blockPos.set(k1, i2, l1); + IBlockState iblockstate1 = cachedWorld.getBlockState(blockPos); + Block b = iblockstate1.getBlock(); + if (!b.getMaterial().blocksMovement())continue; + if (b.isFullCube() && i2 == k-1) continue; + if (iblockstate1.equals( NodeProcessorDungeonRoom.preBuilt)) continue; + if (b.isFullCube()) { + theBit |= (3L << bitStart); + arr[location] = theBit; + return true; + } + try { + b.addCollisionBoxesToList(cachedWorld, blockPos, iblockstate1, bb, list, null); + } catch (Exception e) { + return true; + } + if (list.size() > 0) { + theBit |= (3L << bitStart); + arr[location] = theBit; + return true; + } + } + } + } + theBit |= 2L << bitStart; + arr[location] = theBit; + return false; + } + + + public void resetBlock(BlockPos pos) { + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + resetBlock(pos.getX()*2 + x, pos.getY()*2 + y, pos.getZ()*2 + z); + } + } + } + } + private void resetBlock(int x, int y, int z) { + if (x < minx || z < minz || x >= maxx || z >= maxz || y < miny || y >= maxy) return; + int dx = x - minx, dy = y - miny, dz = z - minz; + int bitIdx = dx * leny * lenz + dy * lenz + dz; + int location = bitIdx / 4; + arr[location] = 0; + } +} diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/dungeon/roomfinder/DungeonRoomInfoRegistry.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/dungeon/roomfinder/DungeonRoomInfoRegistry.java new file mode 100755 index 00000000..a97efa43 --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/dungeon/roomfinder/DungeonRoomInfoRegistry.java @@ -0,0 +1,142 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +package kr.syeyoung.dungeonsguide.dungeon.roomfinder; + +import com.google.common.io.Files; +import kr.syeyoung.dungeonsguide.dungeon.data.DungeonRoomInfo; +import kr.syeyoung.dungeonsguide.launcher.Main; +import lombok.Getter; +import net.minecraft.client.Minecraft; +import org.apache.commons.io.IOUtils; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.io.*; +import java.nio.charset.Charset; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.*; + +public class DungeonRoomInfoRegistry { + @Getter + private static final List<DungeonRoomInfo> registered = new ArrayList<DungeonRoomInfo>(); + private static final Map<Short, List<DungeonRoomInfo>> shapeMap = new HashMap<Short, List<DungeonRoomInfo>>(); + private static final Map<UUID, DungeonRoomInfo> uuidMap = new HashMap<UUID, DungeonRoomInfo>(); + + public static void register(DungeonRoomInfo dungeonRoomInfo) { + if (dungeonRoomInfo == null) throw new NullPointerException("what the fak parameter is noll?"); + if (uuidMap.containsKey(dungeonRoomInfo.getUuid())) { + DungeonRoomInfo dri1 = uuidMap.get(dungeonRoomInfo.getUuid()); + registered.remove(dri1); + shapeMap.get(dri1.getShape()).remove(dri1); + uuidMap.remove(dri1.getUuid()); + } + dungeonRoomInfo.setRegistered(true); + registered.add(dungeonRoomInfo); + uuidMap.put(dungeonRoomInfo.getUuid(), dungeonRoomInfo); + List<DungeonRoomInfo> roomInfos = shapeMap.get(dungeonRoomInfo.getShape()); + if (roomInfos == null) roomInfos = new ArrayList<DungeonRoomInfo>(); + roomInfos.add(dungeonRoomInfo); + shapeMap.put(dungeonRoomInfo.getShape(), roomInfos); + } + + + public static List<DungeonRoomInfo> getByShape(Short shape) { + List<DungeonRoomInfo> dungeonRoomInfos = shapeMap.get(shape); + return dungeonRoomInfos == null ? Collections.emptyList() : dungeonRoomInfos; + } + + public static DungeonRoomInfo getByUUID(UUID uid) { + return uuidMap.get(uid); + } + + public static void unregister(DungeonRoomInfo dungeonRoomInfo) { + if (!dungeonRoomInfo.isRegistered()) throw new IllegalStateException("what tha fak? that is not registered one"); + if (!uuidMap.containsKey(dungeonRoomInfo.getUuid())) throw new IllegalStateException("what tha fak? that is not registered one, but you desperately wanted to trick this program"); + dungeonRoomInfo.setRegistered(false); + registered.remove(dungeonRoomInfo); + shapeMap.get(dungeonRoomInfo.getShape()).remove(dungeonRoomInfo); + uuidMap.remove(dungeonRoomInfo.getUuid()); + } + + public static void saveAll(File dir) { + dir.mkdirs(); + boolean isDev = Minecraft.getMinecraft().getSession().getPlayerID().replace("-","").equals("e686fe0aab804a71ac7011dc8c2b534c"); + String nameidstring = "name,uuid,processsor,secrets"; + String ids = ""; + for (DungeonRoomInfo dungeonRoomInfo : registered) { + try { + if (!dungeonRoomInfo.isUserMade() && !isDev) continue; + FileOutputStream fos = new FileOutputStream(new File(dir, dungeonRoomInfo.getUuid().toString() + ".roomdata")); + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(dungeonRoomInfo); + oos.flush(); + oos.close(); + + nameidstring += "\n"+dungeonRoomInfo.getName()+","+dungeonRoomInfo.getUuid() +","+dungeonRoomInfo.getProcessorId()+","+dungeonRoomInfo.getTotalSecrets(); + ids += "roomdata/"+dungeonRoomInfo.getUuid() +".roomdata\n"; + } catch (Exception e) {e.printStackTrace();} + } + + try { + Files.write(nameidstring, new File(dir, "roomidmapping.csv"), Charset.defaultCharset()); + Files.write(ids, new File(dir, "datas.txt"), Charset.defaultCharset()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void loadAll(File dir) throws BadPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, IllegalBlockSizeException, NoSuchPaddingException, InvalidKeyException { + registered.clear(); + shapeMap.clear(); + uuidMap.clear(); + try { + List<String> lines = IOUtils.readLines(Main.class.getResourceAsStream("/roomdata/datas.txt")); + for (String name : lines) { + if (!name.endsWith(".roomdata")) continue; + try { + InputStream fis = Main.class.getResourceAsStream("/"+name); + ObjectInputStream ois = new ObjectInputStream(fis); + DungeonRoomInfo dri = (DungeonRoomInfo) ois.readObject(); + ois.close(); + fis.close(); + register(dri); + } catch (Exception e) { + System.out.println(name); + e.printStackTrace(); + } + } + } catch (Exception e) {e.printStackTrace();} + for (File f : dir.listFiles()) { + if (!f.getName().endsWith(".roomdata")) continue; + try { + InputStream fis = new FileInputStream(f); + ObjectInputStream ois = new ObjectInputStream(fis); + DungeonRoomInfo dri = (DungeonRoomInfo) ois.readObject(); + ois.close(); + fis.close(); + register(dri); + } catch (Exception e) { + System.out.println(f.getName());e.printStackTrace();} + } + } + +} diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/dungeon/roomfinder/RoomMatcher.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/dungeon/roomfinder/RoomMatcher.java new file mode 100755 index 00000000..f0da1f35 --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/dungeon/roomfinder/RoomMatcher.java @@ -0,0 +1,154 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +package kr.syeyoung.dungeonsguide.dungeon.roomfinder; + +import kr.syeyoung.dungeonsguide.dungeon.data.DungeonRoomInfo; +import kr.syeyoung.dungeonsguide.utils.ArrayUtils; +import kr.syeyoung.dungeonsguide.utils.ShortUtils; +import lombok.Getter; +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.world.World; + +import java.util.List; + +public class RoomMatcher { + private final DungeonRoom dungeonRoom; + + @Getter + private DungeonRoomInfo match; + @Getter + private int rotation; // how much the "found room" has to rotate clockwise to match the given dungeon room info. ! + private boolean triedMatch = false; + + private final World w; + + public RoomMatcher(DungeonRoom dungeonRoom) { + this.dungeonRoom = dungeonRoom; + w = dungeonRoom.getContext().getWorld(); + } + + public DungeonRoomInfo match() { + if (triedMatch) return match; + + int zz = dungeonRoom.getMax().getZ() - dungeonRoom.getMin().getZ() + 1; + int xx = dungeonRoom.getMax().getX() - dungeonRoom.getMin().getX() + 1; + for (int z = 0; z < zz; z ++) { + for (int x = 0; x < xx; x++) { + if (x % 8 == 0 && z % 8 == 0 && dungeonRoom.getContext().getWorld().getChunkFromBlockCoords(dungeonRoom.getRelativeBlockPosAt(x, 0, z)).isEmpty()) { + throw new IllegalStateException("chunk is not loaded"); + + } + } + } + + triedMatch = true; + int lowestcost = 10; + int lowestRot = 0; + DungeonRoomInfo bestMatch = null; + for (int rotation = 0; rotation < 4; rotation++) { + short shape = dungeonRoom.getShape(); + for (int j = 0; j<rotation; j++) + shape = ShortUtils.rotateClockwise(shape); + shape = ShortUtils.topLeftifyInt(shape); + + List<DungeonRoomInfo> roomInfoList = DungeonRoomInfoRegistry.getByShape(shape); + for (DungeonRoomInfo roomInfo : roomInfoList) { + int cost = tryMatching(roomInfo, rotation); + if (cost == 0) { + match = roomInfo; + this.rotation = rotation; + return match; + } + if (cost < lowestcost) { + lowestcost = cost; + bestMatch = roomInfo; + lowestRot = rotation; + } + } + } + match = bestMatch; + this.rotation = lowestRot; + return bestMatch; + } + + private int tryMatching(DungeonRoomInfo dungeonRoomInfo, int rotation) { + if (dungeonRoomInfo.getColor() != dungeonRoom.getColor()) return Integer.MAX_VALUE; + + int[][] res = dungeonRoomInfo.getBlocks(); + for (int i = 0; i < rotation; i++) + res = ArrayUtils.rotateCounterClockwise(res); + + int wrongs = 0; + for (int z = 0; z < res.length; z ++) { + for (int x = 0; x < res[0].length; x++) { + int data = res[z][x]; + if (data == -1) continue; + Block b = dungeonRoom.getRelativeBlockAt(x,0,z); + + if (b == null || Block.getIdFromBlock(b) != data) { + wrongs++; + + if (wrongs > 10) return wrongs; + } + } + } + return wrongs; + } + + private static final int offset = 3; + public DungeonRoomInfo createNew() { + DungeonRoomInfo roomInfo = new DungeonRoomInfo(dungeonRoom.getShape(), dungeonRoom.getColor()); + + int maxX = dungeonRoom.getMax().getX(); + int maxZ = dungeonRoom.getMax().getZ(); + int minX = dungeonRoom.getMin().getX(); + int minZ = dungeonRoom.getMin().getZ(); + int widthX = maxX - minX + 2; + int heightZ = maxZ - minZ + 2; + int[][] data = new int[dungeonRoom.getMax().getZ() - dungeonRoom.getMin().getZ() +2][dungeonRoom.getMax().getX() - dungeonRoom.getMin().getX() + 2]; + + for (int z = 0; z < data.length; z++) { + for (int x = 0; x < data[0].length; x++) { +// if (!(offset < x && widthX - offset > x && offset < z && heightZ - offset > z)) { +// data[z][x] = -1; +// continue; +// } + if (!(dungeonRoom.canAccessRelative(x + offset, z + offset) + && dungeonRoom.canAccessRelative(x - offset -1 , z - offset-1) + && dungeonRoom.canAccessRelative(x + offset , z - offset-1) + && dungeonRoom.canAccessRelative(x - offset -1 , z + offset))) { + data[z][x] = -1; + continue; + } + + Block b = dungeonRoom.getRelativeBlockAt(x,0,z); + if (b == null || b == Blocks.chest || b == Blocks.trapped_chest) { + data[z][x] = -1; + } else { + data[z][x] = Block.getIdFromBlock(b); + } + } + } + + roomInfo.setBlocks(data); + roomInfo.setUserMade(true); + return roomInfo; + } +} |