All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.dailystruggle.rtp.common.selection.region.Region Maven / Gradle / Ivy

There is a newer version: 2.0.15
Show newest version
package io.github.dailystruggle.rtp.common.selection.region;

import io.github.dailystruggle.commandsapi.common.CommandsAPI;
import io.github.dailystruggle.rtp.common.RTP;
import io.github.dailystruggle.rtp.common.configuration.ConfigParser;
import io.github.dailystruggle.rtp.common.configuration.enums.*;
import io.github.dailystruggle.rtp.common.factory.Factory;
import io.github.dailystruggle.rtp.common.factory.FactoryValue;
import io.github.dailystruggle.rtp.common.playerData.TeleportData;
import io.github.dailystruggle.rtp.common.selection.region.selectors.memory.shapes.MemoryShape;
import io.github.dailystruggle.rtp.common.selection.region.selectors.shapes.Shape;
import io.github.dailystruggle.rtp.common.selection.region.selectors.verticalAdjustors.VerticalAdjustor;
import io.github.dailystruggle.rtp.common.selection.worldborder.WorldBorder;
import io.github.dailystruggle.rtp.common.serverSide.substitutions.*;
import io.github.dailystruggle.rtp.common.tasks.FillTask;
import io.github.dailystruggle.rtp.common.tasks.RTPRunnable;
import io.github.dailystruggle.rtp.common.tasks.RTPTaskPipe;
import io.github.dailystruggle.rtp.common.tasks.teleport.LoadChunks;
import org.jetbrains.annotations.Nullable;
import org.simpleyaml.configuration.MemorySection;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.stream.Collectors;

public class Region extends FactoryValue {
    public static final List> onPlayerQueuePush = new ArrayList<>();
    public static final List> onPlayerQueuePop = new ArrayList<>();
    //semaphore needed in case of async usage
    //storage for region verifiers to use for ALL regions
    private static final Semaphore regionVerifiersLock = new Semaphore(1);
    private static final List> regionVerifiers = new ArrayList<>();
    private static final Set unsafeBlocks = new ConcurrentSkipListSet<>();
    private static final AtomicLong lastUpdate = new AtomicLong(0);
    private static final AtomicInteger safetyRadius = new AtomicInteger(0);
    public static int maxBiomeChecksPerGen = 100;
    private final Semaphore cacheGuard = new Semaphore(1);
    /**
     * public/shared cache for this region
     */
    public ConcurrentLinkedQueue> locationQueue = new ConcurrentLinkedQueue<>();
    public ConcurrentHashMap locAssChunks = new ConcurrentHashMap<>();
    /**
     * When reserving/recycling locations for specific players,
     * I want to guard against
     */
    public ConcurrentHashMap>> perPlayerLocationQueue = new ConcurrentHashMap<>();
    /**
     *
     */
    public ConcurrentHashMap>> fastLocations = new ConcurrentHashMap<>();
    public RTPTaskPipe cachePipeline = new RTPTaskPipe();
    public RTPTaskPipe miscPipeline = new RTPTaskPipe();
    protected ConcurrentLinkedQueue playerQueue = new ConcurrentLinkedQueue<>();
    public Region(String name, EnumMap params) {
        super(RegionKeys.class, name);
        this.name = name;
        this.data.putAll(params);

        ConfigParser logging = (ConfigParser) RTP.configs.getParser(LoggingKeys.class);

        boolean detailed_region_init = true;
        if (logging != null) {
            Object o = logging.getConfigValue(LoggingKeys.detailed_region_init, false);
            if (o instanceof Boolean) {
                detailed_region_init = (Boolean) o;
            } else {
                detailed_region_init = Boolean.parseBoolean(o.toString());
            }
        }

        Object shape = getShape();
        Object world = params.get(RegionKeys.world);
        String worldName;
        if (world instanceof RTPWorld) worldName = ((RTPWorld) world).name();
        else {
            worldName = String.valueOf(world);
        }
        if (shape instanceof MemoryShape) {
            if (detailed_region_init) {
                RTP.log(Level.INFO, "&00FFFF[RTP] [" + name + "] memory shape detected, reading location data from file...");
            }

            ((MemoryShape) shape).load(name + ".yml", worldName);
            long iter = ((MemoryShape) shape).fillIter.get();
            if (iter > 0 && iter < Double.valueOf(((MemoryShape) shape).getRange()).longValue())
                RTP.getInstance().fillTasks.put(name, new FillTask(this, iter));
        }

        long cacheCap = getNumber(RegionKeys.cacheCap, 10L).longValue();
        for (long i = cachePipeline.size(); i < cacheCap; i++) {
            cachePipeline.add(new Cache());
        }
    }

    /**
     * addGlobalRegionVerifier - add a region verifier to use for ALL regions
     *
     * @param locationCheck verifier method to reference.
     *                      param: world name, 3D point
     *                      return: boolean - true on good location, false on bad location
     */
    public static void addGlobalRegionVerifier(Predicate locationCheck) {
        try {
            regionVerifiersLock.acquire();
        } catch (InterruptedException e) {
            regionVerifiersLock.release();
            return;
        }
        regionVerifiers.add(locationCheck);
        regionVerifiersLock.release();
    }

    public static void clearGlobalRegionVerifiers() {
        try {
            regionVerifiersLock.acquire();
        } catch (InterruptedException e) {
            regionVerifiersLock.release();
            return;
        }
        regionVerifiers.clear();
        regionVerifiersLock.release();
    }

    public static boolean checkGlobalRegionVerifiers(RTPLocation location) {
        try {
            regionVerifiersLock.acquire();
        } catch (InterruptedException e) {
            regionVerifiersLock.release();
            return false;
        }

        for (Predicate verifier : regionVerifiers) {
            try {
                //if invalid placement, stop and return invalid
                //clone location to prevent methods from messing with the data
                if (!verifier.test(location)) {
                    regionVerifiersLock.release();
                    return false;
                }
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
        regionVerifiersLock.release();
        return true;
    }

    public void execute(long availableTime) {
        long start = System.nanoTime();

        miscPipeline.execute(availableTime);

        long cacheCap = getNumber(RegionKeys.cacheCap, 10L).longValue();
        cacheCap = Math.max(cacheCap, playerQueue.size());
        try {
            cacheGuard.acquire();
            if (locationQueue.size() >= cacheCap) return;
            while (cachePipeline.size() + locationQueue.size() < cacheCap + playerQueue.size())
                cachePipeline.add(new Cache());
            cachePipeline.execute(availableTime - (System.nanoTime() - start)); //todo: too fast for server
//            cachePipeline.execute(0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            cacheGuard.release();
        }

        while (locationQueue.size() > 0 && playerQueue.size() > 0) {
            UUID playerId = playerQueue.poll();

            TeleportData teleportData = RTP.getInstance().latestTeleportData.get(playerId);
            if (teleportData == null || teleportData.completed) {
                RTP.getInstance().processingPlayers.remove(playerId);
                return;
            }

            RTPPlayer player = RTP.serverAccessor.getPlayer(playerId);
            if (player == null) continue;

            Map.Entry pair = locationQueue.poll();
            if (pair == null) {
                playerQueue.add(playerId);
                continue;
            }

            teleportData.attempts = pair.getValue();

            RTPCommandSender sender = RTP.serverAccessor.getSender(CommandsAPI.serverId);
            LoadChunks loadChunks = new LoadChunks(sender, player, pair.getKey(), this);
            teleportData.nextTask = loadChunks;
            RTP.getInstance().latestTeleportData.put(playerId, teleportData);
            RTP.getInstance().loadChunksPipeline.add(loadChunks);
            onPlayerQueuePop.forEach(consumer -> consumer.accept(this, playerId));

            Iterator iterator = playerQueue.iterator();
            int i = 0;
            while (iterator.hasNext()) {
                UUID id = iterator.next();
                ++i;
                TeleportData data = RTP.getInstance().latestTeleportData.get(id);
                RTP.getInstance().processingPlayers.add(id);
                if (data == null) {
                    data = new TeleportData();
                    data.completed = false;
                    data.sender = RTP.serverAccessor.getSender(CommandsAPI.serverId);
                    data.time = System.currentTimeMillis();
                    data.delay = sender.delay();
                    data.targetRegion = this;
                    data.originalLocation = player.getLocation();
                    RTP.getInstance().latestTeleportData.put(id, data);
                }
                data.queueLocation = i;
                RTP.serverAccessor.sendMessage(id, MessagesKeys.queueUpdate);
            }
        }
    }

    public boolean hasLocation(@Nullable UUID uuid) {
        boolean res = locationQueue.size() > 0;
        res |= (uuid != null) && (perPlayerLocationQueue.containsKey(uuid));
        return res;
    }

    public Map.Entry getLocation(RTPCommandSender sender, RTPPlayer player, @Nullable Set biomeNames) {
        Map.Entry pair = null;

        UUID playerId = player.uuid();

        boolean custom = biomeNames != null && biomeNames.size() > 0;

        if (!custom && perPlayerLocationQueue.containsKey(playerId)) {
            ConcurrentLinkedQueue> playerLocationQueue = perPlayerLocationQueue.get(playerId);
            RTPChunk chunk = null;
            while (playerLocationQueue.size() > 0) {
                if (chunk != null) chunk.unload();
                pair = playerLocationQueue.poll();
                if (pair == null || pair.getKey() == null) continue;
                RTPLocation left = pair.getKey();
                boolean pass = true;

                int cx = (left.x() > 0) ? left.x() / 16 : left.x() / 16 - 1;
                int cz = (left.z() > 0) ? left.z() / 16 : left.z() / 16 - 1;
                CompletableFuture chunkAt = left.world().getChunkAt(cx, cz);
                try {
                    chunk = chunkAt.get();
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                    continue;
                }
                if(chunk == null) return null;

                long t = System.currentTimeMillis();
                long dt = t - lastUpdate.get();
                if (dt > 5000 || dt < 0) {
                    ConfigParser safety = (ConfigParser) RTP.configs.getParser(SafetyKeys.class);
                    Object value = safety.getConfigValue(SafetyKeys.unsafeBlocks, new ArrayList<>());
                    unsafeBlocks.clear();
                    if (value instanceof Collection) {
                        unsafeBlocks.addAll(((Collection) value).stream().filter(Objects::nonNull).map(Object::toString).collect(Collectors.toSet()));
                    }
                    lastUpdate.set(t);
                    safetyRadius.set(Math.max(safety.getNumber(SafetyKeys.safetyRadius, 0).intValue(), 7));
                }


                //todo: waterlogged check
                int safe = safetyRadius.get();
                RTPBlock block;
                RTPChunk chunk1;
                Map,RTPChunk> chunks = new HashMap<>();
                chunks.put(Arrays.asList(chunk.x(), chunk.z()), chunk);
                chunk.keep(true);
                for (int x = left.x() - safe; x < left.x() + safe && pass; x++) {
                    int xx = x;
                    int dx = Math.abs(xx/16);
                    int chunkX = chunk.x();

                    if(xx < 0) {
                        chunkX-=dx+1;
                        if(xx%16==0) xx+=16*dx;
                        else xx+=16*(dx+1);
                    } else if(xx >= 16) {
                        chunkX+=dx;
                        xx-=16*dx;
                    }

                    for (int z = left.z() - safe; z < left.z() + safe && pass; z++) {
                        int zz = z;

                        int dz = Math.abs(zz/16);

                        int chunkZ = chunk.z();

                        if(zz < 0) {
                            chunkZ-=dz+1;
                            if(zz%16==0) zz+=16*dz;
                            else zz+=16*(dz+1);
                        } else if(zz >= 16) {
                            chunkZ+=dz;
                            zz-=16*dz;
                        }

                        List xz = Arrays.asList(chunkX, chunkZ);
                        if(chunks.containsKey(xz)) chunk1 = chunks.get(xz);
                        else {
                            try {
                                chunk1 = getWorld().getChunkAt(chunkX, chunkZ).get();
                                chunks.put(xz,chunk1);
                                chunk1.keep(true);
                            } catch (InterruptedException | ExecutionException e) {
                                return null;
                            }
                        }

                        for (int y = left.y() - safe; y < left.y() + safe && pass; y++) {
                            if(y>getWorld().getMaxHeight() || y 0) {
            pair = locationQueue.poll();
            if (pair == null) return null;
            RTPLocation left = pair.getKey();
            if (left == null) return pair;
            boolean pass = checkGlobalRegionVerifiers(left);
            if (pass) return pair;
        }

        if (custom || sender.hasPermission("rtp.unqueued")) {
            pair = getLocation(biomeNames);
            long attempts = pair.getValue();
            TeleportData data = RTP.getInstance().latestTeleportData.get(playerId);
            if (data != null && !data.completed) {
                data.attempts = attempts;
            }
        } else {
            RTP.getInstance().processingPlayers.add(playerId);
            TeleportData data = RTP.getInstance().latestTeleportData.get(playerId);
            if (data == null) {
                data = new TeleportData();
                data.sender = (sender != null) ? sender : player;
                data.completed = false;
                data.time = System.currentTimeMillis();
                data.delay = sender.delay();
                data.targetRegion = this;
                data.originalLocation = player.getLocation();
                RTP.getInstance().latestTeleportData.put(playerId, data);
            }
            onPlayerQueuePush.forEach(consumer -> consumer.accept(this, playerId));
            playerQueue.add(playerId);
            data.queueLocation = playerQueue.size();
            RTP.serverAccessor.sendMessage(playerId, MessagesKeys.queueUpdate);
        }
        return pair;
    }

    protected enum FailTypes {
        biome,
        worldBorder,
        timeout,
        vert,
        safety,
        safetyExternal,
        misc
    }
    @Nullable
    public Map.Entry getLocation(@Nullable Set biomeNames) {

        boolean defaultBiomes = false;
        ConfigParser performance = (ConfigParser) RTP.configs.getParser(PerformanceKeys.class);
        ConfigParser safety = (ConfigParser) RTP.configs.getParser(SafetyKeys.class);
        ConfigParser logging = (ConfigParser) RTP.configs.getParser(LoggingKeys.class);
        Object o;
        if (biomeNames == null || biomeNames.size() == 0) {
            defaultBiomes = true;
            o = safety.getConfigValue(SafetyKeys.biomeWhitelist, false);
            boolean whitelist = (o instanceof Boolean) ? (Boolean) o : Boolean.parseBoolean(o.toString());

            o = safety.getConfigValue(SafetyKeys.biomes, null);
            List biomeList = (o instanceof List) ? ((List) o).stream().map(Object::toString).collect(Collectors.toList()) : null;
            Set biomeSet = (biomeList == null)
                    ? new HashSet<>()
                    : biomeList.stream().map(String::toUpperCase).collect(Collectors.toSet());
            if (whitelist) {
                biomeNames = biomeSet;
            } else {
                Set biomes = RTP.serverAccessor.getBiomes(getWorld());
                Set set = new HashSet<>();
                for (String s : biomes) {
                    if (!biomeSet.contains(s.toUpperCase())) {
                        set.add(s);
                    }
                }
                biomeNames = set;
            }
        }

        boolean verbose = false;
        if (logging != null) {
            o = logging.getConfigValue(LoggingKeys.selection_failure, false);
            if (o instanceof Boolean) {
                verbose = (Boolean) o;
            } else {
                verbose = Boolean.parseBoolean(o.toString());
            }
        }

        Shape shape = getShape();
        if (shape == null) return null;

        VerticalAdjustor vert = getVert();
        if (vert == null) return null;

        o = safety.getConfigValue(SafetyKeys.unsafeBlocks, new ArrayList<>());
        Set unsafeBlocks = (o instanceof Collection) ? ((Collection) o)
                .stream().map(o1 -> o1.toString().toUpperCase()).collect(Collectors.toSet())
                : new HashSet<>();

        int safetyRadius = safety.getNumber(SafetyKeys.safetyRadius, 0).intValue();

        long maxAttemptsBase = performance.getNumber(PerformanceKeys.maxAttempts, 20).longValue();
        maxAttemptsBase = Math.max(maxAttemptsBase, 1);
        long maxAttempts = maxAttemptsBase;
        long maxBiomeChecks = maxBiomeChecksPerGen * maxAttempts;
        if(!defaultBiomes) maxBiomeChecks *= 10;
        long biomeChecks = 0L;

        RTPWorld world = getWorld();

        Map> failMap = new EnumMap<>(FailTypes.class);
        for(FailTypes f : FailTypes.values()) failMap.put(f,new HashMap<>());
        List> selections = new ArrayList<>();

        RTPLocation location = null;
        long i = 1;

        boolean biomeRecall = Boolean.parseBoolean(performance.getConfigValue(PerformanceKeys.biomeRecall, false).toString());

        for (; i <= maxAttempts; i++) {
            long l = -1;
            int[] select;
            if (shape instanceof MemoryShape) {
                MemoryShape memoryShape = (MemoryShape) shape;
                if (biomeRecall && !defaultBiomes) {
                    List> biomes = new ArrayList<>();
                    for (String biomeName : biomeNames) {
                        ConcurrentSkipListMap map = memoryShape.biomeLocations.get(biomeName);
                        if (map != null) {
                            biomes.addAll(map.entrySet());
                        }
                    }
//                    RTP.log(Level.CONFIG,"biome input - " + biomes);
                    Map.Entry entry;
                    if(biomes.size()>0) {
                        int nextInt = ThreadLocalRandom.current().nextInt(biomes.size());
                        entry = biomes.get(nextInt);
//                        RTP.log(Level.CONFIG,"target selected - " + entry.toString());
                        l = entry.getKey() + ThreadLocalRandom.current().nextLong(entry.getValue());
//                        RTP.log(Level.CONFIG,"final selection - " + l);
                    }
                    else l = memoryShape.rand();
                } else {
                    l = memoryShape.rand();
                }

                select = memoryShape.locationToXZ(l);

//                if (verbose) selections.add(new AbstractMap.SimpleEntry<>((long) selections.size(), l));
            } else {
                select = shape.select();
//                if (verbose) selections.add(new AbstractMap.SimpleEntry<>((long) select[0], (long) select[1]));
            }

            String currBiome = world.getBiome(select[0] * 16 + 7, (vert.minY() + vert.maxY()) / 2, select[1] * 16 + 7);

            for (; biomeChecks < maxBiomeChecks && !biomeNames.contains(currBiome); biomeChecks++, maxAttempts++, i++) {
                if (shape instanceof MemoryShape) {
                    MemoryShape memoryShape = (MemoryShape) shape;
                    if (defaultBiomes && biomeRecall) {
                        memoryShape.addBadLocation(l);
                    }
                    if (biomeRecall && !defaultBiomes) {
                        List> biomes = new ArrayList<>();
                        for (String biomeName : biomeNames) {
                            ConcurrentSkipListMap map = memoryShape.biomeLocations.get(biomeName);
                            if (map != null) {
                                biomes.addAll(map.entrySet());
                            }
                        }
                        Map.Entry entry;
                        if(biomes.size()>0) {
                            int nextInt = ThreadLocalRandom.current().nextInt(biomes.size());
                            entry = biomes.get(nextInt);
                            l = entry.getKey() + ThreadLocalRandom.current().nextLong(entry.getValue());
                        }
                        else l = memoryShape.rand();
//                        RTP.log(Level.WARNING,"A");
                    } else {
                        l = memoryShape.rand();
//                        RTP.log(Level.WARNING,"B");
                    }

                    select = memoryShape.locationToXZ(l);
                } else {
                    select = shape.select();
//                    if (verbose) selections.add(new AbstractMap.SimpleEntry<>((long) select[0], (long) select[1]));
                }
                String key = "biome="+currBiome;
                if(verbose) {
                    failMap.get(FailTypes.biome).compute(key, (s, aLong) -> {
                        if (aLong == null) return 1L;
                        return ++aLong;
                    });
                }
                currBiome = world.getBiome(select[0] * 16 + 7, (vert.minY() + vert.maxY()) / 2, select[1] * 16 + 7);
            }
            if (biomeChecks >= maxBiomeChecks) return new AbstractMap.SimpleEntry<>(null, i);

            if (verbose) {
                if(shape instanceof MemoryShape) selections.add(new AbstractMap.SimpleEntry<>((long) selections.size(), l));
                else selections.add(new AbstractMap.SimpleEntry<>((long) select[0], (long) select[1]));
            }

            WorldBorder border = RTP.serverAccessor.getWorldBorder(world.name());
            if (!border.isInside().apply(new RTPLocation(world, select[0] * 16, (vert.maxY() + vert.minY()) / 2, select[1] * 16))) {
                maxAttempts++;
                Long worldBorderFails = failMap.get(FailTypes.worldBorder).getOrDefault("OUTSIDE_BORDER", 0L);
                worldBorderFails++;
                if (worldBorderFails > 1000) {
                    new IllegalStateException("1000 worldborder checks failed. region/selection is likely outside the worldborder").printStackTrace();
                    return new AbstractMap.SimpleEntry<>(null, i);
                }
                failMap.get(FailTypes.worldBorder).put("OUTSIDE_BORDER",worldBorderFails);
                continue;
            }

            CompletableFuture cfChunk = world.getChunkAt(select[0], select[1]);
            RTP.futures.add(cfChunk);

            RTPChunk chunk;

            try {
                chunk = cfChunk.get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                return new AbstractMap.SimpleEntry<>(null, i);
            }
            if(chunk == null) return null;

            location = vert.adjust(chunk);
            if (location == null) {
                if (defaultBiomes && shape instanceof MemoryShape && biomeRecall) {
                    ((MemoryShape) shape).addBadLocation(l);
                }
                if(verbose) {
                    failMap.get(FailTypes.vert).compute("biome=" + currBiome,
                            (s, aLong) -> (aLong==null) ? (1L) : (++aLong));
                }
                chunk.unload();
                continue;
            }

            currBiome = world.getBiome(location.x(), location.y(), location.z());

            if (!biomeNames.contains(currBiome)) {
                biomeChecks++;
                maxAttempts++;
                if(defaultBiomes && shape instanceof MemoryShape && biomeRecall) {
                    ((MemoryShape) shape).addBadLocation(l);
                }

                if(verbose) {
                    failMap.get(FailTypes.biome).compute("biome=" + currBiome,
                            (s, aLong) -> (aLong == null) ? 1L : ++aLong);
                }
                chunk.unload();
                continue;
            }

            boolean pass = true;

            //todo: waterlogged check
            RTPBlock block;
            RTPChunk chunk1;
            Map,RTPChunk> chunks = new HashMap<>();
            chunks.put(Arrays.asList(chunk.x(),chunk.z()),chunk);
            chunk.keep(true);
            for (int x = location.x() - safetyRadius; x < location.x() + safetyRadius && pass; x++) {
                int xx = x;
                int dx = Math.abs(xx/16);
                int chunkX = chunk.x();

                if(xx < 0) {
                    chunkX-=dx+1;
                    if(xx%16==0) xx+=16*dx;
                    else xx+=16*(dx+1);
                } else if(xx >= 16) {
                    chunkX+=dx;
                    xx-=16*dx;
                }

                for (int z = location.z() - safetyRadius; z < location.z() + safetyRadius && pass; z++) {
                    int zz = z;

                    int dz = Math.abs(zz/16);

                    int chunkZ = chunk.z();

                    if(zz < 0) {
                        chunkZ-=dz+1;
                        if(zz%16==0) zz+=16*dz;
                        else zz+=16*(dz+1);
                    } else if(zz >= 16) {
                        chunkZ+=dz;
                        zz-=16*dz;
                    }

                    List xz = Arrays.asList(chunkX, chunkZ);
                    if(chunks.containsKey(xz)) chunk1 = chunks.get(xz);
                    else {
                        try {
                            chunk1 = getWorld().getChunkAt(chunkX, chunkZ).get();
                            chunks.put(xz,chunk1);
                            chunk1.keep(true);
                        } catch (InterruptedException | ExecutionException e) {
                            return null;
                        }
                    }

                    for (int y = location.y() - safetyRadius; y < location.y() + safetyRadius && pass; y++) {
                        if(y>getWorld().getMaxHeight() || y {
                                    if (aLong == null) return 1L;
                                    return ++aLong;
                                });
                            }
                        }
                    }
                }
            }
            for(RTPChunk usedChunk : chunks.values()) usedChunk.keep(false);

            pass &= checkGlobalRegionVerifiers(location);

            if (pass) {
                if (shape instanceof MemoryShape) {
                    if(l>0) ((MemoryShape) shape).addBiomeLocation(l, currBiome);
                }
                break;
            } else {
                if (verbose) failMap.get(FailTypes.misc).compute("location="+"("+location.x()+","+location.y()+","+location.z(),
                        (s, aLong) -> (aLong==null) ? 1L : ++aLong);
                if (shape instanceof MemoryShape) {
                    ((MemoryShape) shape).addBadLocation(l);
                }
                location = null;
            }
            chunk.unload();
        }

//        if (verbose) {
        if (verbose && i >= maxAttempts || i > maxAttemptsBase*maxBiomeChecksPerGen) {
            RTP.log(Level.INFO, "#00ff80[RTP] [" + name + "] failed to generate a location within " + maxAttempts + " tries. Adjust your configuration.");
            for(Map.Entry> mapEntry : failMap.entrySet()) {
                Map map = mapEntry.getValue();
                String[] output = new String[map.size()];
                int pos = 0;
                long count = 0;
                for(Map.Entry entry : map.entrySet()) {
                    output[pos] = "#00ff80[RTP] [" + name + "] " + " cause=" + mapEntry.getKey() + " " + entry.getKey() + " fails=" + entry.getValue();
                    count+=entry.getValue();
                    pos++;
                }
                RTP.log(Level.INFO,"#00ff80[RTP] [" + name + "] " + " cause=" + mapEntry.getKey() + " fails=" + count);
                for(String out : output) {
                    RTP.log(Level.INFO,out);
                }
            }

            StringBuilder selectionsStr = new StringBuilder();
            boolean first = true;
            selectionsStr = selectionsStr.append("{");
            for(Map.Entry entry : selections) {
                if(!first) {
                    selectionsStr = selectionsStr.append(",");
                }
                selectionsStr = selectionsStr.append("(").append(entry.getKey()).append(",").append(entry.getValue()).append(")");
            }
            selectionsStr = selectionsStr.append("}");
            RTP.log(Level.INFO, "#0f0080[RTP] [" + name + "] selections: " + selectionsStr);
        }

        i = Math.min(i, maxAttempts);

        return new AbstractMap.SimpleEntry<>(location, i);
    }

    public void shutDown() {
        Shape shape = getShape();
        if (shape == null) return;

        RTPWorld world = getWorld();
        if (world == null) return;

        if (shape instanceof MemoryShape) {
            ((MemoryShape) shape).save(this.name + ".yml", world.name());
        }

        cachePipeline.stop();
        cachePipeline.clear();

        playerQueue.clear();
        perPlayerLocationQueue.clear();
        fastLocations.clear();
        locationQueue.clear();
        locAssChunks.forEach((rtpLocation, chunkSet) -> chunkSet.keep(false));
        locAssChunks.clear();
    }

    @Override
    public Region clone() {
        Region clone = (Region) super.clone();
        clone.data = data.clone();
        clone.locationQueue = new ConcurrentLinkedQueue<>();
        clone.locAssChunks = new ConcurrentHashMap<>();
        clone.playerQueue = new ConcurrentLinkedQueue<>();
        clone.perPlayerLocationQueue = new ConcurrentHashMap<>();
        clone.fastLocations = new ConcurrentHashMap<>();
        return clone;
    }

    public Map params() {
        Map res = new ConcurrentHashMap<>();
        for (Map.Entry, ?> e : data.entrySet()) {
            Object value = e.getValue();
            if (value instanceof RTPWorld) {
                res.put("world", ((RTPWorld) value).name());
            } else if (value instanceof Shape) {
                res.put("shape", ((Shape) value).name);
                EnumMap, Object> data = ((Shape) value).getData();
                for (Map.Entry, ?> dataEntry : data.entrySet()) {
                    res.put(dataEntry.getKey().name(), dataEntry.getValue().toString());
                }
            } else if (value instanceof VerticalAdjustor) {
                res.put("vert", ((VerticalAdjustor) value).name);
                EnumMap, Object> data = ((VerticalAdjustor) value).getData();
                for (Map.Entry, ?> dataEntry : data.entrySet()) {
                    res.put(dataEntry.getKey().name(), dataEntry.getValue().toString());
                }
            } else if (value instanceof String)
                res.put(e.getKey().name(), (String) value);
            else {
                res.put(e.getKey().name(), value.toString());
            }
        }
        return res;
    }

    public ChunkSet chunks(RTPLocation location, long radius) {
        long sz = (radius * 2 + 1) * (radius * 2 + 1);
        if (locAssChunks.containsKey(location)) {
            ChunkSet chunkSet = locAssChunks.get(location);
            if (chunkSet.chunks.size() >= sz) return chunkSet;
            chunkSet.keep(false);
            locAssChunks.remove(location);
        }

        int cx = location.x();
        int cz = location.z();
        cx = (cx > 0) ? cx / 16 : cx / 16 - 1;
        cz = (cz > 0) ? cz / 16 : cz / 16 - 1;

        List> chunks = new ArrayList<>();

        Shape shape = getShape();
        if (shape == null) return null;

        VerticalAdjustor vert = getVert();
        if (vert == null) return null;

        RTPWorld rtpWorld = getWorld();
        if (rtpWorld == null) return null;

        for (long i = -radius; i <= radius; i++) {
            for (long j = -radius; j <= radius; j++) {
                CompletableFuture cfChunk = location.world().getChunkAt((int) (cx + i), (int) (cz + j));
                chunks.add(cfChunk);
            }
        }

        ChunkSet chunkSet = new ChunkSet(chunks, new CompletableFuture<>());
        chunkSet.keep(true);
        locAssChunks.put(location, chunkSet);
        return chunkSet;
    }

    public void removeChunks(RTPLocation location) {
        if (!locAssChunks.containsKey(location)) return;
        ChunkSet chunkSet = locAssChunks.get(location);
        chunkSet.keep(false);
        locAssChunks.remove(location);
    }

    public CompletableFuture> fastQueue(UUID id) {
        if (fastLocations.containsKey(id)) return fastLocations.get(id);
        CompletableFuture> res = new CompletableFuture<>();
        fastLocations.put(id, res);
        miscPipeline.add(new Cache(id));
        return res;
    }

    public void queue(UUID id) {
        perPlayerLocationQueue.putIfAbsent(id, new ConcurrentLinkedQueue<>());
        miscPipeline.add(new Cache(id));
    }

    public long getTotalQueueLength(UUID uuid) {
        long res = locationQueue.size();
        ConcurrentLinkedQueue> queue = perPlayerLocationQueue.get(uuid);
        if (queue != null) res += queue.size();
        if (fastLocations.containsKey(uuid)) res++;
        return res;
    }

    public long getPublicQueueLength() {
        return locationQueue.size();
    }

    public long getPersonalQueueLength(UUID uuid) {
        long res = 0;
        ConcurrentLinkedQueue> queue = perPlayerLocationQueue.get(uuid);
        if (queue != null) res += queue.size();
        if (fastLocations.containsKey(uuid)) res++;
        return res;
    }

    public Shape getShape() {
        boolean wbo = false;
        Object o = data.getOrDefault(RegionKeys.worldBorderOverride, false);
        if (o instanceof Boolean) wbo = (Boolean) o;
        else if (o instanceof String) {
            wbo = Boolean.parseBoolean((String) o);
            data.put(RegionKeys.worldBorderOverride, wbo);
        }

        RTPWorld world;
        o = data.get(RegionKeys.world);
        if (o instanceof RTPWorld) world = (RTPWorld) o;
        else if (o instanceof String) {
            world = RTP.serverAccessor.getRTPWorld((String) o);
        } else world = null;
        if (world == null) world = RTP.serverAccessor.getRTPWorlds().get(0);

        Object shapeObj = data.get(RegionKeys.shape);
        Shape shape;
        if (shapeObj instanceof Shape) {
            shape = (Shape) shapeObj;
        } else if (shapeObj instanceof MemorySection) {
            final Map shapeMap = ((MemorySection) shapeObj).getMapValues(true);
            String shapeName = String.valueOf(shapeMap.get("name"));
            Factory> factory = (Factory>) RTP.factoryMap.get(RTP.factoryNames.shape);
            shape = (Shape) factory.get(shapeName);
            EnumMap shapeData = shape.getData();
            for (Map.Entry, Object> e : shapeData.entrySet()) {
                String name = e.getKey().name();
                if (shapeMap.containsKey(name)) {
                    e.setValue(shapeMap.get(name));
                } else {
                    Object altName = shape.language_mapping.get(name);
                    if (altName != null && shapeMap.containsKey(altName.toString())) {
                        e.setValue(shapeMap.get(altName.toString()));
                    }
                }
            }
            shape.setData(shapeData);
            data.put(RegionKeys.shape, shape);
        } else throw new IllegalArgumentException("invalid shape\n" + shapeObj);

        if (wbo) {
            Shape worldShape;
            try {
                worldShape = RTP.serverAccessor.getWorldBorder(world.name()).getShape().get();
            } catch (IllegalStateException ignored) {
                return shape;
            }
            if (!worldShape.equals(shape)) {
                shape = worldShape;
                data.put(RegionKeys.shape, shape);
            }
        }

        return shape;
    }

    public VerticalAdjustor getVert() {
        Object vertObj = data.get(RegionKeys.vert);
        VerticalAdjustor vert;
        if (vertObj instanceof VerticalAdjustor) {
            vert = (VerticalAdjustor) vertObj;
        } else if (vertObj instanceof MemorySection) {
            final Map vertMap = ((MemorySection) vertObj).getMapValues(true);
            String vertName = String.valueOf(vertMap.get("name"));
            Factory> factory = (Factory>) RTP.factoryMap.get(RTP.factoryNames.vert);
            vert = (VerticalAdjustor) factory.get(vertName);
            EnumMap vertData = vert.getData();
            for (Map.Entry, Object> e : vertData.entrySet()) {
                String name = e.getKey().name();
                if (vertMap.containsKey(name)) {
                    e.setValue(vertMap.get(name));
                } else {
                    Object altName = vert.language_mapping.get(name);
                    if (altName != null && vertMap.containsKey(altName.toString())) {
                        e.setValue(vertMap.get(altName.toString()));
                    }
                }
            }
            Map validY = new HashMap<>();
            validY.put("minY",Math.max(getWorld().getMinHeight(),vert.minY()));
            validY.put("maxY",Math.min(getWorld().getMaxHeight(),vert.maxY()));

            vert.setData(vertData);
            vert.setData(validY);

            data.put(RegionKeys.vert, vert);
        } else throw new IllegalArgumentException("invalid shape\n" + vertObj);

        return vert;
    }

    public RTPWorld getWorld() {
        Object world = data.get(RegionKeys.world);
        if (world instanceof RTPWorld) return (RTPWorld) world;
        else {
            String worldName = String.valueOf(world);
            RTPWorld rtpWorld;
            if (worldName.startsWith("[") && worldName.endsWith("]")) {
                int num = Integer.parseInt(worldName.substring(1, worldName.length() - 1));
                rtpWorld = RTP.serverAccessor.getRTPWorlds().get(num);
            } else rtpWorld = RTP.serverAccessor.getRTPWorld(worldName);
            if (rtpWorld == null) rtpWorld = RTP.serverAccessor.getRTPWorlds().get(0);
            data.put(RegionKeys.world, rtpWorld);
            return rtpWorld;
        }
    }

    //localized generic task for
    protected class Cache extends RTPRunnable {
        private static final long lastUpdate = 0;
        private final UUID playerId;

        public Cache() {
            playerId = null;
        }

        public Cache(UUID playerId) {
            this.playerId = playerId;
        }

        @Override
        public void run() {
            long cacheCap = getNumber(RegionKeys.cacheCap, 10L).longValue();
            cacheCap = Math.max(cacheCap, playerQueue.size());
            Map.Entry pair = getLocation(null);
            if (pair != null) {
                RTPLocation location = pair.getKey();
                if (location == null) {
                    if (cachePipeline.size() + locationQueue.size() < cacheCap + playerQueue.size())
                        cachePipeline.add(new Cache());
                    return;
                }

                ConfigParser perf = (ConfigParser) RTP.configs.getParser(PerformanceKeys.class);
                long radius = perf.getNumber(PerformanceKeys.viewDistanceSelect, 0L).longValue();

                ChunkSet chunkSet = chunks(location, radius);

                chunkSet.whenComplete(aBoolean -> {
                    if (aBoolean) {
                        if (playerId == null) {
                            locationQueue.add(pair);
                            locAssChunks.put(pair.getKey(), chunkSet);
                        } else if (fastLocations.containsKey(playerId) && !fastLocations.get(playerId).isDone()) {
                            fastLocations.get(playerId).complete(pair);
                        } else {
                            perPlayerLocationQueue.putIfAbsent(playerId, new ConcurrentLinkedQueue<>());
                            perPlayerLocationQueue.get(playerId).add(pair);
                        }
                    } else chunkSet.keep(false);
                });
            }
            if (cachePipeline.size() + locationQueue.size() < cacheCap + playerQueue.size())
                cachePipeline.add(new Cache());
        }
    }

    @Override
    public boolean equals(Object other) {
        if(!(other instanceof Region)) return false;
        Region region = (Region) other;

        if(!getShape().equals(region.getShape())) return false;
        if(!getVert().equals(region.getVert())) return false;
        if(!getWorld().equals(region.getWorld())) return false;
        return Boolean.parseBoolean(region.data.get(RegionKeys.worldBorderOverride).toString()) == Boolean.parseBoolean(data.get(RegionKeys.worldBorderOverride).toString());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy