forked from SteamWar/SteamWar
259 lines
10 KiB
Java
259 lines
10 KiB
Java
/*
|
|
* This file is a part of the SteamWar software.
|
|
*
|
|
* Copyright (C) 2020 SteamWar.de-Serverteam
|
|
*
|
|
* 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 de.steamwar.bausystem.region;
|
|
|
|
import de.steamwar.bausystem.BauSystem;
|
|
import de.steamwar.bausystem.features.region.WireframeCommand;
|
|
import de.steamwar.bausystem.region.dynamic.*;
|
|
import de.steamwar.bausystem.region.dynamic.global.GlobalRegion;
|
|
import lombok.NonNull;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.ToString;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.Location;
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import javax.annotation.Nullable;
|
|
import java.io.BufferedReader;
|
|
import java.io.InputStreamReader;
|
|
import java.util.*;
|
|
import java.util.function.Function;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
|
|
public class DynamicRegionSystem implements RegionSystem {
|
|
|
|
public static DynamicRegionSystem INSTANCE;
|
|
|
|
private static final Map<Long, Region> regionCache = new LinkedHashMap<>(16, 0.75f, true) {
|
|
@Override
|
|
protected boolean removeEldestEntry(Map.Entry<Long, Region> eldest) {
|
|
return size() > 8192; // Tweak this number if needed!
|
|
}
|
|
}; // Will be cleared on region add/delete/remove!
|
|
private static final Map<UUID, Region> regionMap = new HashMap<>();
|
|
private static final Map<RegionType, Set<Region>> regionTypeMap = new EnumMap<>(RegionType.class);
|
|
|
|
public void add(DynamicRegion region) {
|
|
regionCache.clear();
|
|
regionMap.put(region.getID(), region);
|
|
regionTypeMap.computeIfAbsent(region.getType(), __ -> new HashSet<>()).add(region);
|
|
DynamicRegionVisualizer.INSTANCE.addRegion(region);
|
|
}
|
|
|
|
public void remove(DynamicRegion region) {
|
|
regionCache.clear();
|
|
regionMap.remove(region.getID());
|
|
regionTypeMap.getOrDefault(region.getType(), Collections.emptySet()).remove(region);
|
|
DynamicRegionVisualizer.INSTANCE.removeRegion(region);
|
|
}
|
|
|
|
public static Map<Class<? extends DynamicRegion>, RegionConstructorData> constructorDataMap = new HashMap<>();
|
|
public static Map<String, Class<? extends DynamicRegion>> identifierDataMap = new HashMap<>();
|
|
|
|
@Override
|
|
public void load() {
|
|
INSTANCE = this;
|
|
|
|
// Loading all Region Constructor Data that are defined inside the Code
|
|
constructorDataMap = new BufferedReader(new InputStreamReader(BauSystem.getInstance().getClass().getResourceAsStream("/META-INF/annotations/de.steamwar.bausystem.region.dynamic.RegionConstructorData")))
|
|
.lines()
|
|
.map(s -> {
|
|
try {
|
|
return Class.forName(s, false, BauSystem.getInstance().getClass().getClassLoader());
|
|
} catch (ClassNotFoundException | NoClassDefFoundError e) {
|
|
throw new SecurityException(e.getMessage(), e);
|
|
}
|
|
})
|
|
.filter(DynamicRegion.class::isAssignableFrom)
|
|
.map(clazz -> (Class<? extends DynamicRegion>) clazz)
|
|
.collect(Collectors.toUnmodifiableMap(Function.identity(), clazz -> clazz.getAnnotation(RegionConstructorData.class)));
|
|
identifierDataMap = constructorDataMap.entrySet()
|
|
.stream()
|
|
.collect(Collectors.toUnmodifiableMap(entry -> entry.getValue().identifier(), Map.Entry::getKey));
|
|
|
|
DynamicRegionRepository.loadRegions();
|
|
new DynamicRegionCommand();
|
|
new WireframeCommand();
|
|
}
|
|
|
|
@Override
|
|
public @NonNull Location getWorldSpawn() {
|
|
return Bukkit.getWorlds().get(0).getSpawnLocation(); // TODO: Temporary
|
|
}
|
|
|
|
@Override
|
|
public @NonNull Region getGlobalRegion() {
|
|
return GlobalRegion.INSTANCE;
|
|
}
|
|
|
|
public Set<Tile> getTilesOfRegion(@NonNull Region region) {
|
|
Point minPoint = region.getArea().getMinPoint(false);
|
|
Point maxPoint = region.getArea().getMaxPoint(false);
|
|
|
|
Set<Tile> tiles = new HashSet<>();
|
|
for (int x = minPoint.getX(); x < maxPoint.getX(); x += Tile.tileSize) {
|
|
for (int z = minPoint.getZ(); z < maxPoint.getZ(); z += Tile.tileSize) {
|
|
tiles.add(Tile.fromXZ(x, z).orElse(null));
|
|
}
|
|
}
|
|
tiles.remove(null);
|
|
return Collections.unmodifiableSet(tiles);
|
|
}
|
|
|
|
public @NonNull Region get(@Nullable Tile tile) {
|
|
return get(tile, true, regionMap.values());
|
|
}
|
|
|
|
private Region get(@Nullable Tile tile, boolean fastCache, Collection<Region> regions) {
|
|
if (tile == null) return getGlobalRegion();
|
|
if (regionCache.containsKey(tile.getId())) {
|
|
Region region = regionCache.get(tile.getId());
|
|
if (fastCache || regions.contains(region)) return region;
|
|
}
|
|
Location location = tile.getCenterLocation();
|
|
Region region = regions.stream()
|
|
.filter(rg -> rg.getArea().inRegion(location, false))
|
|
.findFirst()
|
|
.orElseGet(this::getGlobalRegion);
|
|
if (fastCache || regions.contains(region)) {
|
|
regionCache.put(tile.getId(), region);
|
|
}
|
|
return region;
|
|
}
|
|
|
|
@Override
|
|
public @NonNull Region get(@NonNull Location location) {
|
|
return get(Tile.fromLocation(location).orElse(null), true, regionMap.values());
|
|
}
|
|
|
|
@Override
|
|
public Optional<Region> getRegion(@NonNull UUID id) {
|
|
return Optional.ofNullable(regionMap.get(id));
|
|
}
|
|
|
|
@Override
|
|
public @NonNull Stream<Region> getRegions() {
|
|
return regionMap.values().stream();
|
|
}
|
|
|
|
public @NonNull Stream<Region> getRegionsByType(RegionType type) {
|
|
return regionTypeMap.getOrDefault(type, Collections.emptySet()).stream();
|
|
}
|
|
|
|
@RequiredArgsConstructor
|
|
@ToString
|
|
public static class Neighbour<T extends Region> {
|
|
public final T region;
|
|
public final Tile tile;
|
|
public final NeighbourDirection direction;
|
|
|
|
public <O extends Region> Neighbour<O> as(Class<O> clazz) {
|
|
if (!clazz.isInstance(region)) {
|
|
return null;
|
|
} else {
|
|
return (Neighbour<O>) this;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (!(o instanceof Neighbour<?> neighbour)) return false;
|
|
return Objects.equals(tile, neighbour.tile) && direction == neighbour.direction;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(tile, direction);
|
|
}
|
|
}
|
|
|
|
private Stream<Neighbour<Region>> getNeighbours(Region region, boolean noCorners, boolean fastCache, Collection<Region> regions) {
|
|
Tile minTile = Tile.fromPoint(region.getArea().getMinPoint(false)).orElse(null);
|
|
Tile maxTile = Tile.fromPoint(region.getArea().getMaxPoint(false)).orElse(null);
|
|
if (minTile == null || maxTile == null) return Stream.empty();
|
|
|
|
Set<Neighbour<Region>> neighbours = new HashSet<>();
|
|
|
|
if (!noCorners) {
|
|
neighbours.add(new Neighbour<>(get(minTile.add(-1, -1).orElse(null), fastCache, regions), minTile, NeighbourDirection.NorthWest));
|
|
|
|
Tile.fromTile(minTile.getTileX(), maxTile.getTileZ()).ifPresent(cornerMinMaxSelf -> {
|
|
neighbours.add(new Neighbour<>(get(cornerMinMaxSelf.add(-1, 1).orElse(null), fastCache, regions), cornerMinMaxSelf, NeighbourDirection.SouthWest));
|
|
});
|
|
|
|
Tile.fromTile(maxTile.getTileX(), minTile.getTileZ()).ifPresent(cornerMaxMinSelf -> {
|
|
neighbours.add(new Neighbour<>(get(cornerMaxMinSelf.add(1, -1).orElse(null), fastCache, regions), cornerMaxMinSelf, NeighbourDirection.NorthEast));
|
|
});
|
|
|
|
neighbours.add(new Neighbour<>(get(maxTile.add(1, 1).orElse(null), fastCache, regions), maxTile, NeighbourDirection.SouthEast));
|
|
}
|
|
|
|
for (int x = minTile.getTileX(); x <= maxTile.getTileX(); x++) {
|
|
Tile.fromTile(x, minTile.getTileZ()).ifPresent(tileMinZSelf -> {
|
|
neighbours.add(new Neighbour<>(get(tileMinZSelf.add(0, -1).orElse(null), fastCache, regions), tileMinZSelf, NeighbourDirection.North));
|
|
});
|
|
|
|
Tile.fromTile(x, maxTile.getTileZ()).ifPresent(tileMaxZSelf -> {
|
|
neighbours.add(new Neighbour<>(get(tileMaxZSelf.add(0, 1).orElse(null), fastCache, regions), tileMaxZSelf, NeighbourDirection.South));
|
|
});
|
|
}
|
|
|
|
for (int z = minTile.getTileZ(); z <= maxTile.getTileZ(); z++) {
|
|
Tile.fromTile(minTile.getTileX(), z).ifPresent(tileMinXSelf -> {
|
|
neighbours.add(new Neighbour<>(get(tileMinXSelf.add(-1, 0).orElse(null), fastCache, regions), tileMinXSelf, NeighbourDirection.West));
|
|
});
|
|
|
|
Tile.fromTile(maxTile.getTileX(), z).ifPresent(tileMaxXSelf -> {
|
|
neighbours.add(new Neighbour<>(get(tileMaxXSelf.add(1, 0).orElse(null), fastCache, regions), tileMaxXSelf, NeighbourDirection.East));
|
|
});
|
|
}
|
|
|
|
neighbours.removeIf(neighbour -> neighbour.region.getType().isGlobal());
|
|
return neighbours.stream();
|
|
}
|
|
|
|
public Stream<Neighbour<DynamicRegion>> getNeighbours(Region region) {
|
|
return getNeighbours(region, false, true, regionMap.values())
|
|
.map(neighbour -> neighbour.as(DynamicRegion.class))
|
|
.filter(Objects::nonNull);
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
public Stream<Region> getConnectedRegions(@NonNull Region region) {
|
|
Set<Region> regions = regionTypeMap.get(region.getType());
|
|
|
|
Set<Region> connected = new HashSet<>();
|
|
LinkedHashSet<Region> current = new LinkedHashSet<>();
|
|
current.add(region);
|
|
|
|
while (!current.isEmpty()) {
|
|
Region r = current.removeFirst();
|
|
if (!connected.add(r)) continue;
|
|
getNeighbours(r, true, false, regions)
|
|
.map(neighbour -> neighbour.region)
|
|
.forEach(current::add);
|
|
}
|
|
|
|
return connected.stream();
|
|
}
|
|
}
|