/* * 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.dungeon.roomfinder; import com.google.common.collect.Sets; import kr.syeyoung.dungeonsguide.dungeon.DungeonContext; import kr.syeyoung.dungeonsguide.dungeon.MapProcessor; import kr.syeyoung.dungeonsguide.dungeon.data.DungeonRoomInfo; import kr.syeyoung.dungeonsguide.dungeon.doorfinder.DungeonDoor; 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.FeatureRegistry; import kr.syeyoung.dungeonsguide.features.impl.secret.FeaturePathfindStrategy; import kr.syeyoung.dungeonsguide.pathfinding.*; 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 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 doors = new ArrayList(); 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 cached = null; @Getter private World cachedWorld; public Map getMechanics() { if (cached == null || EditingContext.getEditingContext() != null) { cached = new HashMap(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 activeBetterAStar = new HashMap<>(); private Map activeBetterAStarCornerCut = new HashMap<>(); private Map activeThetaStar = new HashMap<>(); public ScheduledFuture> 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> 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> 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> 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> 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> sf = asyncPathFinder.schedule(() -> { PathFinder pathFinder = new PathFinder(nodeProcessorDungeonRoom); PathEntity latest = pathFinder.createEntityPathTo(blockaccess, entityIn, targetPos, dist); if (latest != null) { List 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 roomContext = new HashMap(); @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 points, short shape, byte color, BlockPos min, BlockPos max, DungeonContext context, Set> 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 directions = Sets.newHashSet(new Vector2d(0,16), new Vector2d(0, -16), new Vector2d(16, 0), new Vector2d(-16 , 0)); private void buildDoors(Set> doorsAndStates) { Set> positions = new HashSet<>(); BlockPos pos = context.getMapProcessor().roomPointToWorldPoint(minRoomPt).add(16,0,16); for (Tuple 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 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 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; } }