13
0

CustomMap #40

Zusammengeführt
YoyoNow hat 2 Commits von CustomMap nach master 2024-07-17 21:17:34 +02:00 zusammengeführt
4 geänderte Dateien mit 321 neuen und 504 gelöschten Zeilen
Nur Änderungen aus Commit c7c49b43b2 werden angezeigt - Alle Commits anzeigen

Datei anzeigen

@ -27,8 +27,7 @@ import de.steamwar.lobby.command.PortalCommand;
import de.steamwar.lobby.jumpandrun.JumpAndRun;
import de.steamwar.lobby.jumpandrun.JumpAndRunCommand;
import de.steamwar.lobby.listener.*;
import de.steamwar.lobby.map.CustomMapCommand;
import de.steamwar.lobby.map.CustomMapNew;
import de.steamwar.lobby.map.CustomMap;
import de.steamwar.lobby.particle.ParticleListener;
import de.steamwar.lobby.special.advent.AdventsCalendar;
import de.steamwar.lobby.team.TeamPlayer;
@ -54,7 +53,7 @@ public class LobbySystem extends JavaPlugin {
entityServer = new REntityServer();
debugEntityServer = new REntityServer();
CustomMapNew.init();
CustomMap.init();
Fightserver.init();
new Portals();
@ -62,7 +61,6 @@ public class LobbySystem extends JavaPlugin {
new HologramCommand();
new FlyCommand();
new ModifyCommand();
new CustomMapCommand();
new JumpAndRun();
new JumpAndRunCommand();

Datei anzeigen

@ -1,110 +1,356 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2022 SteamWar.de-Serverteam
* Copyright (C) 2024 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 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.
* 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/>.
* 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.lobby.map;
import net.minecraft.world.level.saveddata.maps.WorldMap;
import de.steamwar.lobby.LobbySystem;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_20_R1.map.CraftMapView;
import org.bukkit.entity.Entity;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.MapMeta;
import org.bukkit.map.MapCanvas;
import org.bukkit.map.MapPalette;
import org.bukkit.map.MapRenderer;
import org.bukkit.map.MapView;
import org.bukkit.util.Vector;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.lang.reflect.Field;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class CustomMap {
public class CustomMap implements Listener {
public CustomMap(Player player, BufferedImage image) {
if (image.getWidth() % 128 != 0) {
throw new IllegalArgumentException("Image width must be a multiple of 128");
}
if (image.getHeight() % 128 != 0) {
throw new IllegalArgumentException("Image height must be a multiple of 128");
public static void init() {
}
private static final CustomMap LEFT = new CustomMap(new File(System.getProperty("user.home") + "/lobbyBanner/left.png"),
new Vector(2346, 48, 1297), new Vector(2345, 48, 1297), new Vector(2344, 48, 1297), new Vector(2343, 48, 1297), new Vector(2342, 48, 1297), new Vector(2341, 48, 1297), new Vector(2340, 48, 1297),
new Vector(2346, 47, 1297), new Vector(2345, 47, 1297), new Vector(2344, 47, 1297), new Vector(2343, 47, 1297), new Vector(2342, 47, 1297), new Vector(2341, 47, 1297), new Vector(2340, 47, 1297),
new Vector(2346, 46, 1297), new Vector(2345, 46, 1297), new Vector(2344, 46, 1297), new Vector(2343, 46, 1297), new Vector(2342, 46, 1297), new Vector(2341, 46, 1297), new Vector(2340, 46, 1297),
new Vector(2346, 45, 1297), new Vector(2345, 45, 1297), new Vector(2344, 45, 1297), new Vector(2343, 45, 1297), new Vector(2342, 45, 1297), new Vector(2341, 45, 1297), new Vector(2340, 45, 1297)
);
private static final CustomMap RIGHT = new CustomMap(new File(System.getProperty("user.home") + "/lobbyBanner/right.png"),
new Vector(2330, 48, 1297), new Vector(2329, 48, 1297), new Vector(2328, 48, 1297), new Vector(2327, 48, 1297), new Vector(2326, 48, 1297), new Vector(2325, 48, 1297), new Vector(2324, 48, 1297),
new Vector(2330, 47, 1297), new Vector(2329, 47, 1297), new Vector(2328, 47, 1297), new Vector(2327, 47, 1297), new Vector(2326, 47, 1297), new Vector(2325, 47, 1297), new Vector(2324, 47, 1297),
new Vector(2330, 46, 1297), new Vector(2329, 46, 1297), new Vector(2328, 46, 1297), new Vector(2327, 46, 1297), new Vector(2326, 46, 1297), new Vector(2325, 46, 1297), new Vector(2324, 46, 1297),
new Vector(2330, 45, 1297), new Vector(2329, 45, 1297), new Vector(2328, 45, 1297), new Vector(2327, 45, 1297), new Vector(2326, 45, 1297), new Vector(2325, 45, 1297), new Vector(2324, 45, 1297)
);
private File mapFile;
private Map<Vector, Integer> itemFrameIndex = new HashMap<>();
private ItemFrame[] itemFrames;
private long lastModified = Long.MAX_VALUE;
public CustomMap(File mapFile, Vector... itemFrames) {
this.mapFile = mapFile;
this.itemFrames = new ItemFrame[itemFrames.length];
for (int i = 0; i < itemFrames.length; i++) {
itemFrameIndex.put(itemFrames[i], i);
}
BufferedImage bufferedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
Color oldPixel = new Color(image.getRGB(x, y));
Color newPixel = getNearest(oldPixel);
bufferedImage.setRGB(x, y, newPixel.getRGB());
int quantErrorRed = oldPixel.getRed() - newPixel.getRed();
int quantErrorGreen = oldPixel.getGreen() - newPixel.getGreen();
int quantErrorBlue = oldPixel.getBlue() - newPixel.getBlue();
if (x < image.getWidth() - 1) {
image.setRGB(x + 1, y, updateViaQuantError(new Color(image.getRGB(x + 1, y)), quantErrorRed, quantErrorGreen, quantErrorBlue, 7 / 16.0).getRGB());
}
if (x > 0) {
image.setRGB(x - 1, y, updateViaQuantError(new Color(image.getRGB(x - 1, y)), quantErrorRed, quantErrorGreen, quantErrorBlue, 3 / 16.0).getRGB());
}
if (y < image.getHeight() - 1) {
image.setRGB(x, y + 1, updateViaQuantError(new Color(image.getRGB(x, y + 1)), quantErrorRed, quantErrorGreen, quantErrorBlue, 5 / 16.0).getRGB());
if (x < image.getWidth() - 1) {
image.setRGB(x + 1, y + 1, updateViaQuantError(new Color(image.getRGB(x + 1, y + 1)), quantErrorRed, quantErrorGreen, quantErrorBlue, 1 / 16.0).getRGB());
Bukkit.getScheduler().runTaskTimer(LobbySystem.getPlugin(), () -> {
long modified = mapFile.lastModified();
if (modified > lastModified) {
lastModified = modified;
System.out.println("Updating Banner: " + mapFile.getName());
Bukkit.getScheduler().runTaskAsynchronously(LobbySystem.getPlugin(), () -> {
try {
run();
} catch (IOException e) {
// Ignore
}
}
});
}
}
}, 200L, 200L);
Bukkit.getPluginManager().registerEvents(this, LobbySystem.getPlugin());
}
for (int x = 0; x < bufferedImage.getWidth(); x += 128) {
for (int y = 0; y < bufferedImage.getHeight(); y += 128) {
BufferedImage subImage = bufferedImage.getSubimage(x, y, 128, 128);
CraftMapView craftMapView = (CraftMapView) Bukkit.createMap(player.getWorld());
try {
Field field = CraftMapView.class.getDeclaredField("worldMap");
field.setAccessible(true);
WorldMap worldMap = (WorldMap) field.get(craftMapView);
for (int sx = 0; sx < 128; sx++) {
for (int sy = 0; sy < 128; sy++) {
byte color = MapPalette.matchColor(new Color(subImage.getRGB(sx, sy)));
worldMap.g[sx + sy * 128] = color;
@EventHandler
public void onChunkLoad(ChunkLoadEvent event) {
for (Entity entity : event.getChunk().getEntities()) {
if (!(entity instanceof ItemFrame)) continue;
ItemFrame itemFrame = (ItemFrame) entity;
ItemStack itemStack = itemFrame.getItem();
if (itemStack.getType() != Material.FILLED_MAP) continue;
Vector vector = itemFrame.getLocation().getBlock().getLocation().toVector();
if (itemFrameIndex.containsKey(vector)) {
if (itemFrames[itemFrameIndex.get(vector)] != null) continue;
itemFrames[itemFrameIndex.get(vector)] = itemFrame;
lastModified = 0;
MapView mapView = ((MapMeta) itemFrame.getItem().getItemMeta()).getMapView();
new ArrayList<>(mapView.getRenderers()).forEach(mapView::removeRenderer);
mapView.addRenderer(new MapRenderer() {
@Override
public void render(MapView map, MapCanvas canvas, Player player) {
for (int x = 0; x < 128; x++) {
for (int y = 0; y < 128; y++) {
canvas.setPixel(x, y, (byte) 32);
}
}
}
craftMapView.setLocked(true);
} catch (IllegalAccessException | NoSuchFieldException e) {
throw new SecurityException(e.getMessage(), e);
}
ItemStack itemStack = new ItemStack(Material.FILLED_MAP, 1);
MapMeta mapMeta = (MapMeta) itemStack.getItemMeta();
mapMeta.setMapView(craftMapView);
itemStack.setItemMeta(mapMeta);
player.getInventory().addItem(itemStack);
});
}
}
}
private Color updateViaQuantError(Color color, int quantErrorRed, int quantErrorGreen, int quantErrorBlue, double multiplier) {
int newRed = (int) (color.getRed() + quantErrorRed * multiplier);
int newGreen = (int) (color.getGreen() + quantErrorGreen * multiplier);
int newBlue = (int) (color.getBlue() + quantErrorBlue * multiplier);
return new Color(clamp(newRed), clamp(newGreen), clamp(newBlue));
private void run() throws IOException {
BufferedImage bufferedImage = ImageIO.read(mapFile);
Set<Point>[] patches = new Set[256];
for (int patch = 0; patch < patches.length; patch++) {
patches[patch] = new HashSet<>();
}
for (int y = 0; y < bufferedImage.getHeight(); y++) {
for (int x = 0; x < bufferedImage.getWidth(); x++) {
Color color = new Color(bufferedImage.getRGB(x, y));
double red = color.getRed() / 255.0;
double green = color.getGreen() / 255.0;
double blue = color.getBlue() / 255.0;
double luminance = Math.sqrt(0.299 * red * red + 0.587 * green * green + 0.114 * blue * blue);
luminance *= 255;
patches[(int) luminance].add(new Point(x, y));
}
}
for (int patch = 0; patch < patches.length; patch++) {
Set<Point> points = patches[patch];
if (points.isEmpty()) continue;
BufferedImage patchImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
points.forEach(point -> {
patchImage.setRGB(point.getX(), point.getY(), bufferedImage.getRGB(point.getX(), point.getY()));
});
floodFill(patchImage);
dither(patchImage);
points.forEach(point -> {
bufferedImage.setRGB(point.getX(), point.getY(), patchImage.getRGB(point.getX(), point.getY()));
});
}
for (int y = 0; y < bufferedImage.getHeight(); y += 128) {
for (int x = 0; x < bufferedImage.getWidth(); x += 128) {
ItemFrame itemFrame = itemFrames[y / 128 * 7 + x / 128];
if (itemFrame == null) continue;
int finalX = x;
int finalY = y;
Bukkit.getScheduler().runTaskLater(LobbySystem.getPlugin(), () -> {
ItemStack itemStack = itemFrame.getItem();
MapMeta mapMeta = (MapMeta) itemStack.getItemMeta();
MapView mapView = mapMeta.getMapView();
new ArrayList<>(mapView.getRenderers()).forEach(mapView::removeRenderer);
mapView.addRenderer(new MapRenderer() {
@Override
public void render(MapView map, MapCanvas canvas, Player player) {
for (int dy = 0; dy < 128; dy++) {
for (int dx = 0; dx < 128; dx++) {
int ax = dx + finalX;
int ay = dy + finalY;
Color color = new Color(bufferedImage.getRGB(ax, ay));
canvas.setPixel(dx, dy, ColorInit.getColorByte(color.getRed(), color.getGreen(), color.getBlue()));
}
}
}
});
mapMeta.setMapView(mapView);
itemStack.setItemMeta(mapMeta);
itemFrame.setItem(itemStack);
}, 1);
}
}
}
private int clamp(int value) {
return Math.max(0, Math.min(255, value));
private static void floodFill(BufferedImage bufferedImage) {
WritableRaster alpha = bufferedImage.getAlphaRaster();
int[] data = alpha.getPixels(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), new int[bufferedImage.getWidth() * bufferedImage.getHeight()]);
for (int i = 0; i < 5; i++) {
Set<Point> changes = new HashSet<>();
for (int y = 0; y < bufferedImage.getHeight(); y++) {
for (int x = 0; x < bufferedImage.getWidth(); x++) {
if (data[y * bufferedImage.getWidth() + x] == 0) continue;
int color = bufferedImage.getRGB(x, y);
if (x > 0 && data[y * bufferedImage.getWidth() + x - 1] == 0) {
bufferedImage.setRGB(x - 1, y, color);
changes.add(new Point(x - 1, y));
}
if (x < bufferedImage.getWidth() - 1 && data[y * bufferedImage.getWidth() + x + 1] == 0) {
bufferedImage.setRGB(x + 1, y, color);
changes.add(new Point(x + 1, y));
}
if (y > 0 && data[(y - 1) * bufferedImage.getWidth() + x] == 0) {
bufferedImage.setRGB(x, y - 1, color);
changes.add(new Point(x, y - 1));
}
if (y < bufferedImage.getHeight() - 1 && data[(y + 1) * bufferedImage.getWidth() + x] == 0) {
bufferedImage.setRGB(x, y + 1, color);
changes.add(new Point(x, y + 1));
}
}
}
if (changes.isEmpty()) return;
changes.forEach(point -> {
data[point.getY() * bufferedImage.getWidth() + point.getX()] = 255;
});
}
}
private static Color getNearest(Color color) {
return MapPalette.getColor(MapPalette.matchColor(color));
private static BufferedImage dither(BufferedImage image) {
final double multiplier1 = 7 / 48.0;
final double multiplier2 = 3 / 48.0;
final double multiplier3 = 5 / 48.0;
final double multiplier4 = 1 / 48.0;
WritableRaster alphaRaster = image.getAlphaRaster();
WritableRaster raster = image.getRaster();
int numBands = raster.getNumBands();
int[] pixels = raster.getPixels(0, 0, image.getWidth(), image.getHeight(), new int[image.getWidth() * image.getHeight() * numBands]);
int width = image.getWidth();
int height = image.getHeight();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (alphaRaster.getPixel(x, y, new int[1])[0] == 0) continue;
int red = pixels[(y * width + x) * numBands];
int i2 = (y * width + x) * numBands + 1;
int green = pixels[i2];
int i3 = (y * width + x) * numBands + 2;
int blue = pixels[i3];
Color nearest = MapPalette.getColor(ColorInit.getColorByte(red, green, blue));
pixels[(y * width + x) * numBands] = nearest.getRed();
pixels[i2] = nearest.getGreen();
pixels[i3] = nearest.getBlue();
int quantErrorRed = red - nearest.getRed();
int quantErrorGreen = green - nearest.getGreen();
int quantErrorBlue = blue - nearest.getBlue();
int mr1 = (int) (quantErrorRed * multiplier1);
int mg1 = (int) (quantErrorGreen * multiplier1);
int mb1 = (int) (quantErrorBlue * multiplier1);
int mr2 = (int) (quantErrorRed * multiplier2);
int mg2 = (int) (quantErrorGreen * multiplier2);
int mb2 = (int) (quantErrorBlue * multiplier2);
int mr3 = (int) (quantErrorRed * multiplier3);
int mg3 = (int) (quantErrorGreen * multiplier3);
int mb3 = (int) (quantErrorBlue * multiplier3);
int mr4 = (int) (quantErrorRed * multiplier4);
int mg4 = (int) (quantErrorGreen * multiplier4);
int mb4 = (int) (quantErrorBlue * multiplier4);
if (x < width - 1) {
int i = (y * width + x + 1) * numBands;
pixels[i] = clamp(pixels[i] + mr1);
pixels[i + 1] = clamp(pixels[i + 1] + mg1);
pixels[i + 2] = clamp(pixels[i + 2] + mb1);
}
if (x < width - 2) {
int i = (y * width + x + 2) * numBands;
pixels[i] = clamp(pixels[i] + mr3);
pixels[i + 1] = clamp(pixels[i + 1] + mg3);
pixels[i + 2] = clamp(pixels[i + 2] + mb3);
}
if (y < height - 1) {
if (x > 1) {
int i = ((y + 1) * width + x - 2) * numBands;
pixels[i] = clamp(pixels[i] + mr2);
pixels[i + 1] = clamp(pixels[i + 1] + mg2);
pixels[i + 2] = clamp(pixels[i + 2] + mb2);
}
if (x > 0) {
int i = ((y + 1) * width + x - 1) * numBands;
pixels[i] = clamp(pixels[i] + mr3);
pixels[i + 1] = clamp(pixels[i + 1] + mg3);
pixels[i + 2] = clamp(pixels[i + 2] + mb3);
}
if (x < width - 1) {
int i = ((y + 1) * width + x + 1) * numBands;
pixels[i] = clamp(pixels[i] + mr3);
pixels[i + 1] = clamp(pixels[i + 1] + mg3);
pixels[i + 2] = clamp(pixels[i + 2] + mb3);
}
if (x < width - 2) {
int i = ((y + 1) * width + x + 2) * numBands;
pixels[i] = clamp(pixels[i] + mr2);
pixels[i + 1] = clamp(pixels[i + 1] + mg2);
pixels[i + 2] = clamp(pixels[i + 2] + mb2);
}
int i = (y * width + x) * numBands;
pixels[i] = clamp(pixels[i] + mr1);
pixels[i + 1] = clamp(pixels[i + 1] + mg1);
pixels[i + 2] = clamp(pixels[i + 2] + mb1);
}
if (y < height - 2) {
if (x > 1) {
int i = ((y + 2) * width + x - 2) * numBands;
pixels[i] = clamp(pixels[i] + mr4);
pixels[i + 1] = clamp(pixels[i + 1] + mg4);
pixels[i + 2] = clamp(pixels[i + 2] + mb4);
}
if (x > 0) {
int i = ((y + 2) * width + x - 1) * numBands;
pixels[i] = clamp(pixels[i] + mr2);
pixels[i + 1] = clamp(pixels[i + 1] + mg2);
pixels[i + 2] = clamp(pixels[i + 2] + mb2);
}
if (x < width - 1) {
int i = ((y + 2) * width + x + 1) * numBands;
pixels[i] = clamp(pixels[i] + mr2);
pixels[i + 1] = clamp(pixels[i + 1] + mg2);
pixels[i + 2] = clamp(pixels[i + 2] + mb2);
}
if (x < width - 2) {
int i = ((y + 2) * width + x + 2) * numBands;
pixels[i] = clamp(pixels[i] + mr4);
pixels[i + 1] = clamp(pixels[i + 1] + mg4);
pixels[i + 2] = clamp(pixels[i + 2] + mb4);
}
int i = (y * width + x) * numBands;
pixels[i] = clamp(pixels[i] + mr3);
pixels[i + 1] = clamp(pixels[i + 1] + mg3);
pixels[i + 2] = clamp(pixels[i + 2] + mb3);
}
}
}
raster.setPixels(0, 0, width, height, pixels);
return image;
}
private static int clamp(int value) {
return Math.max(0, Math.min(value, 255));
}
}

Datei anzeigen

@ -1,73 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2022 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.lobby.map;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.command.TypeValidator;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import lombok.SneakyThrows;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.imageio.ImageIO;
import java.io.File;
import java.util.List;
public class CustomMapCommand extends SWCommand {
public CustomMapCommand() {
super("map");
}
@Register
@SneakyThrows
public void render(@Validator Player p, File file) {
if (!SteamwarUser.get(p.getUniqueId()).hasPerm(UserPerm.ADMINISTRATION)) {
return;
}
if (!file.exists()) {
p.sendMessage("§cDiese Datei existiert nicht!");
return;
}
new CustomMap(p, ImageIO.read(file));
}
@ClassValidator(value = Player.class, local = true)
public TypeValidator<Player> getGuardChecker() {
return (commandSender, player, messageSender) -> SteamwarUser.get(player.getUniqueId()).hasPerm(UserPerm.ADMINISTRATION);
}
@ClassMapper(value = File.class, local = true)
public TypeMapper<File> getTypeMapper() {
return new TypeMapper<File>() {
@Override
public List<String> tabCompletes(CommandSender commandSender, String[] strings, String s) {
return null;
}
@Override
public File map(CommandSender commandSender, String[] previousArguments, String s) {
return new File(s);
}
};
}
}

Datei anzeigen

@ -1,354 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.lobby.map;
import de.steamwar.lobby.LobbySystem;
import net.minecraft.world.level.saveddata.maps.WorldMap;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_20_R1.map.CraftMapView;
import org.bukkit.entity.ItemFrame;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.MapMeta;
import org.bukkit.map.MapPalette;
import org.bukkit.util.Vector;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class CustomMapNew {
public static void init() {
}
private static final World WORLD = Bukkit.getWorlds().get(0);
private static final CustomMapNew LEFT = new CustomMapNew(new File(System.getProperty("user.home") + "/lobbyBanner/left.png"),
new Vector(2346, 48, 1297), new Vector(2345, 48, 1297), new Vector(2344, 48, 1297), new Vector(2343, 48, 1297), new Vector(2342, 48, 1297), new Vector(2341, 48, 1297), new Vector(2340, 48, 1297),
new Vector(2346, 47, 1297), new Vector(2345, 47, 1297), new Vector(2344, 47, 1297), new Vector(2343, 47, 1297), new Vector(2342, 47, 1297), new Vector(2341, 47, 1297), new Vector(2340, 47, 1297),
new Vector(2346, 46, 1297), new Vector(2345, 46, 1297), new Vector(2344, 46, 1297), new Vector(2343, 46, 1297), new Vector(2342, 46, 1297), new Vector(2341, 46, 1297), new Vector(2340, 46, 1297),
new Vector(2346, 45, 1297), new Vector(2345, 45, 1297), new Vector(2344, 45, 1297), new Vector(2343, 45, 1297), new Vector(2342, 45, 1297), new Vector(2341, 45, 1297), new Vector(2340, 45, 1297)
);
private File mapFile;
private ItemFrame[] itemFrames = null;
public CustomMapNew(File mapFile, Vector... itemFrames) {
this.mapFile = mapFile;
Bukkit.getScheduler().runTaskTimer(LobbySystem.getPlugin(), () -> {
long lastModified = 0;
while (true) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
if (this.itemFrames == null) {
loadItemFrames(itemFrames);
continue;
}
long modified = mapFile.lastModified();
System.out.println(lastModified + " " + modified);
if (modified > lastModified) {
lastModified = modified;
System.out.println("Updating Banner: " + mapFile.getName());
Bukkit.getScheduler().runTaskAsynchronously(LobbySystem.getPlugin(), () -> {
try {
run();
} catch (IOException e) {
// Ignore
}
});
}
}
}, 200L, 200L);
}
private void loadItemFrames(Vector... itemFrames) {
for (int i = 0; i < itemFrames.length; i++) {
itemFrames[i] = itemFrames[i].toLocation(WORLD).getBlock().getLocation().toVector();
}
Map<Vector, ItemFrame> worldMaps = new HashMap<>();
WORLD.getEntitiesByClass(ItemFrame.class).forEach(itemFrame -> {
ItemStack itemStack = itemFrame.getItem();
System.out.println(itemFrame.getLocation().getBlock() + ": " + itemFrame + " " + itemStack);
if (itemStack.getType() != Material.FILLED_MAP) return;
worldMaps.put(itemFrame.getLocation().getBlock().getLocation().toVector(), itemFrame);
});
ItemFrame[] frames = new ItemFrame[itemFrames.length];
for (int i = 0; i < frames.length; i++) {
if (worldMaps.get(itemFrames[i]) == null) {
System.out.println("ItemFrame at location " + itemFrames[i] + " is missing!");
return;
}
frames[i] = worldMaps.get(itemFrames[i]);
}
this.itemFrames = frames;
}
private void run() throws IOException {
BufferedImage bufferedImage = ImageIO.read(mapFile);
Set<Point>[] patches = new Set[256];
for (int patch = 0; patch < patches.length; patch++) {
patches[patch] = new HashSet<>();
}
for (int y = 0; y < bufferedImage.getHeight(); y++) {
for (int x = 0; x < bufferedImage.getWidth(); x++) {
Color color = new Color(bufferedImage.getRGB(x, y));
double red = color.getRed() / 255.0;
double green = color.getGreen() / 255.0;
double blue = color.getBlue() / 255.0;
double luminance = Math.sqrt(0.299 * red * red + 0.587 * green * green + 0.114 * blue * blue);
luminance *= 255;
patches[(int) luminance].add(new Point(x, y));
}
}
for (int patch = 0; patch < patches.length; patch++) {
Set<Point> points = patches[patch];
if (points.isEmpty()) continue;
BufferedImage patchImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
points.forEach(point -> {
patchImage.setRGB(point.getX(), point.getY(), bufferedImage.getRGB(point.getX(), point.getY()));
});
floodFill(patchImage);
dither(patchImage);
points.forEach(point -> {
bufferedImage.setRGB(point.getX(), point.getY(), patchImage.getRGB(point.getX(), point.getY()));
});
}
for (int y = 0; y < bufferedImage.getHeight() / 128; y += 128) {
for (int x = 0; x < bufferedImage.getWidth() / 128; x += 128) {
ItemFrame itemFrame = itemFrames[y / 128 * 7 + x / 128];
int finalX = x;
int finalY = y;
Bukkit.getScheduler().runTaskLater(LobbySystem.getPlugin(), () -> {
ItemStack itemStack = itemFrame.getItem();
MapMeta mapMeta = (MapMeta) itemStack.getItemMeta();
CraftMapView mapView = (CraftMapView) mapMeta.getMapView();
try {
Field field = CraftMapView.class.getDeclaredField("worldMap");
field.setAccessible(true);
WorldMap worldMap = (WorldMap) field.get(mapView);
for (int dy = 0; dy < 128; dy++) {
for (int dx = 0; dx < 128; dx++) {
int ax = dx + finalX;
int ay = dy + finalY;
Color color = new Color(bufferedImage.getRGB(ax, ay));
worldMap.g[dx + dy * 128] = ColorInit.getColorByte(color.getRed(), color.getGreen(), color.getBlue());
}
}
} catch (IllegalAccessException | NoSuchFieldException e) {
throw new SecurityException(e.getMessage(), e);
}
mapMeta.setMapView(mapView);
itemStack.setItemMeta(mapMeta);
itemFrame.setItem(itemStack);
}, 1);
}
}
}
private static void floodFill(BufferedImage bufferedImage) {
WritableRaster alpha = bufferedImage.getAlphaRaster();
int[] data = alpha.getPixels(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), new int[bufferedImage.getWidth() * bufferedImage.getHeight()]);
for (int i = 0; i < 5; i++) {
Set<Point> changes = new HashSet<>();
for (int y = 0; y < bufferedImage.getHeight(); y++) {
for (int x = 0; x < bufferedImage.getWidth(); x++) {
if (data[y * bufferedImage.getWidth() + x] == 0) continue;
int color = bufferedImage.getRGB(x, y);
if (x > 0 && data[y * bufferedImage.getWidth() + x - 1] == 0) {
bufferedImage.setRGB(x - 1, y, color);
changes.add(new Point(x - 1, y));
}
if (x < bufferedImage.getWidth() - 1 && data[y * bufferedImage.getWidth() + x + 1] == 0) {
bufferedImage.setRGB(x + 1, y, color);
changes.add(new Point(x + 1, y));
}
if (y > 0 && data[(y - 1) * bufferedImage.getWidth() + x] == 0) {
bufferedImage.setRGB(x, y - 1, color);
changes.add(new Point(x, y - 1));
}
if (y < bufferedImage.getHeight() - 1 && data[(y + 1) * bufferedImage.getWidth() + x] == 0) {
bufferedImage.setRGB(x, y + 1, color);
changes.add(new Point(x, y + 1));
}
}
}
if (changes.isEmpty()) return;
changes.forEach(point -> {
data[point.getY() * bufferedImage.getWidth() + point.getX()] = 255;
});
}
}
private static BufferedImage dither(BufferedImage image) {
final double multiplier1 = 7 / 48.0;
final double multiplier2 = 3 / 48.0;
final double multiplier3 = 5 / 48.0;
final double multiplier4 = 1 / 48.0;
WritableRaster alphaRaster = image.getAlphaRaster();
WritableRaster raster = image.getRaster();
int numBands = raster.getNumBands();
int[] pixels = raster.getPixels(0, 0, image.getWidth(), image.getHeight(), new int[image.getWidth() * image.getHeight() * numBands]);
int width = image.getWidth();
int height = image.getHeight();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (alphaRaster.getPixel(x, y, new int[1])[0] == 0) continue;
int red = pixels[(y * width + x) * numBands];
int i2 = (y * width + x) * numBands + 1;
int green = pixels[i2];
int i3 = (y * width + x) * numBands + 2;
int blue = pixels[i3];
Color nearest = MapPalette.getColor(ColorInit.getColorByte(red, green, blue));
pixels[(y * width + x) * numBands] = nearest.getRed();
pixels[i2] = nearest.getGreen();
pixels[i3] = nearest.getBlue();
int quantErrorRed = red - nearest.getRed();
int quantErrorGreen = green - nearest.getGreen();
int quantErrorBlue = blue - nearest.getBlue();
int mr1 = (int) (quantErrorRed * multiplier1);
int mg1 = (int) (quantErrorGreen * multiplier1);
int mb1 = (int) (quantErrorBlue * multiplier1);
int mr2 = (int) (quantErrorRed * multiplier2);
int mg2 = (int) (quantErrorGreen * multiplier2);
int mb2 = (int) (quantErrorBlue * multiplier2);
int mr3 = (int) (quantErrorRed * multiplier3);
int mg3 = (int) (quantErrorGreen * multiplier3);
int mb3 = (int) (quantErrorBlue * multiplier3);
int mr4 = (int) (quantErrorRed * multiplier4);
int mg4 = (int) (quantErrorGreen * multiplier4);
int mb4 = (int) (quantErrorBlue * multiplier4);
if (x < width - 1) {
int i = (y * width + x + 1) * numBands;
pixels[i] = clamp(pixels[i] + mr1);
pixels[i + 1] = clamp(pixels[i + 1] + mg1);
pixels[i + 2] = clamp(pixels[i + 2] + mb1);
}
if (x < width - 2) {
int i = (y * width + x + 2) * numBands;
pixels[i] = clamp(pixels[i] + mr3);
pixels[i + 1] = clamp(pixels[i + 1] + mg3);
pixels[i + 2] = clamp(pixels[i + 2] + mb3);
}
if (y < height - 1) {
if (x > 1) {
int i = ((y + 1) * width + x - 2) * numBands;
pixels[i] = clamp(pixels[i] + mr2);
pixels[i + 1] = clamp(pixels[i + 1] + mg2);
pixels[i + 2] = clamp(pixels[i + 2] + mb2);
}
if (x > 0) {
int i = ((y + 1) * width + x - 1) * numBands;
pixels[i] = clamp(pixels[i] + mr3);
pixels[i + 1] = clamp(pixels[i + 1] + mg3);
pixels[i + 2] = clamp(pixels[i + 2] + mb3);
}
if (x < width - 1) {
int i = ((y + 1) * width + x + 1) * numBands;
pixels[i] = clamp(pixels[i] + mr3);
pixels[i + 1] = clamp(pixels[i + 1] + mg3);
pixels[i + 2] = clamp(pixels[i + 2] + mb3);
}
if (x < width - 2) {
int i = ((y + 1) * width + x + 2) * numBands;
pixels[i] = clamp(pixels[i] + mr2);
pixels[i + 1] = clamp(pixels[i + 1] + mg2);
pixels[i + 2] = clamp(pixels[i + 2] + mb2);
}
int i = (y * width + x) * numBands;
pixels[i] = clamp(pixels[i] + mr1);
pixels[i + 1] = clamp(pixels[i + 1] + mg1);
pixels[i + 2] = clamp(pixels[i + 2] + mb1);
}
if (y < height - 2) {
if (x > 1) {
int i = ((y + 2) * width + x - 2) * numBands;
pixels[i] = clamp(pixels[i] + mr4);
pixels[i + 1] = clamp(pixels[i + 1] + mg4);
pixels[i + 2] = clamp(pixels[i + 2] + mb4);
}
if (x > 0) {
int i = ((y + 2) * width + x - 1) * numBands;
pixels[i] = clamp(pixels[i] + mr2);
pixels[i + 1] = clamp(pixels[i + 1] + mg2);
pixels[i + 2] = clamp(pixels[i + 2] + mb2);
}
if (x < width - 1) {
int i = ((y + 2) * width + x + 1) * numBands;
pixels[i] = clamp(pixels[i] + mr2);
pixels[i + 1] = clamp(pixels[i + 1] + mg2);
pixels[i + 2] = clamp(pixels[i + 2] + mb2);
}
if (x < width - 2) {
int i = ((y + 2) * width + x + 2) * numBands;
pixels[i] = clamp(pixels[i] + mr4);
pixels[i + 1] = clamp(pixels[i + 1] + mg4);
pixels[i + 2] = clamp(pixels[i + 2] + mb4);
}
int i = (y * width + x) * numBands;
pixels[i] = clamp(pixels[i] + mr3);
pixels[i + 1] = clamp(pixels[i + 1] + mg3);
pixels[i + 2] = clamp(pixels[i + 2] + mb3);
}
}
}
raster.setPixels(0, 0, width, height, pixels);
return image;
}
private static int clamp(int value) {
return Math.max(0, Math.min(value, 255));
}
}