Merge between master and legacy/nms

This commit is contained in:
Stijn Bannink 2023-07-04 17:24:35 +02:00
parent 040a5014a4
commit fd225647f5
89 changed files with 5775 additions and 874 deletions

View file

@ -0,0 +1,56 @@
MIT License
Copyright (C) 2013-2015 bergerkiller Copyright (C) 2016-2020 Berger Healer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, and/or sublicense the Software,
and to permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
==========================================================================================
BKCommonLib utilizes Objensis and Javassist for some required functionality. Licenses are listed below:
====================================== Objenesis =========================================
http://objenesis.googlecode.com/svn/docs/index.html
Copyright (c) 2003-2013, Objenesis Team and all contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====================================== Javassist =========================================
The Source Code version of the Covered Code of Javassist is available under the
MOZILLA PUBLIC LICENSE Version 1.1. The project can be found (oct-2020) here:
https://github.com/jboss-javassist/javassist
Javassist was not modified for the purpose of this software.

View file

@ -0,0 +1,8 @@
# BKCommonLib
These classes are from [BKCommonLib](https://github.com/bergerhealer/BKCommonLib). Only the required classes and methods
are extracted.
**Current version:** master#3fc97d5981742f43348cac1f752404f95daa6c07
_Last update: 16-03-2023_

View file

@ -0,0 +1,99 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.bergerkiller.bukkit.common.io;
import java.io.IOException;
import java.io.InputStream;
/**
* Input Stream that can also read individual bits
*/
public class BitInputStream extends InputStream {
private int bitbuff = 0;
private int bitbuff_len = 0;
private boolean closed = false;
private final InputStream input;
private final boolean closeInput;
/**
* Initializes a new Bit Input Stream, reading from the Input Stream specified
*
* @param inputStream to read from
*/
public BitInputStream(InputStream inputStream) {
this(inputStream, true);
}
/**
* Initializes a new Bit Input Stream, reading from the Input Stream specified
*
* @param inputStream to read from
* @param closeInputStream whether to close the underlying input stream when closing this stream
*/
public BitInputStream(InputStream inputStream, boolean closeInputStream) {
this.input = inputStream;
this.closeInput = closeInputStream;
}
@Override
public int available() throws IOException {
if (this.closed) {
throw new IOException("Stream is closed");
}
return this.input.available();
}
@Override
public int read() throws IOException {
return readBits(8);
}
/**
* Reads bits from the stream
*
* @param nBits to read
* @return read value, -1 when end of stream is reached
* @throws IOException
*/
public int readBits(int nBits) throws IOException {
if (this.closed) {
throw new IOException("Stream is closed");
}
while (this.bitbuff_len < nBits) {
int readByte = this.input.read();
if (readByte == -1) return -1;
this.bitbuff |= (readByte << this.bitbuff_len);
this.bitbuff_len += 8;
}
int result = bitbuff & ((1 << nBits) - 1);
this.bitbuff >>= nBits;
this.bitbuff_len -= nBits;
return result;
}
@Override
public void close() throws IOException {
if (!this.closed) {
this.closed = true;
if (this.closeInput) {
this.input.close();
}
}
}
}

View file

@ -0,0 +1,46 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.bergerkiller.bukkit.common.io;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* Simple container for multiple bits of data.
*/
@EqualsAndHashCode
@ToString
public class BitPacket implements Cloneable {
public int data, bits;
public BitPacket() {
this.data = 0;
this.bits = 0;
}
public BitPacket(int data, int bits) {
this.data = data;
this.bits = bits;
}
@Override
public BitPacket clone() {
return new BitPacket(this.data, this.bits);
}
}

View file

@ -0,0 +1,221 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.bergerkiller.bukkit.common.map;
import com.bergerkiller.bukkit.common.map.color.MCSDBubbleFormat;
import com.bergerkiller.bukkit.common.map.color.MCSDGenBukkit;
import com.bergerkiller.bukkit.common.map.color.MapColorSpaceData;
import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
import java.awt.*;
import java.io.InputStream;
import java.util.Arrays;
/**
* Additional functionality on top of Bukkit's MapPalette
*/
public class MapColorPalette {
private static final MapColorSpaceData COLOR_MAP_DATA;
public static final byte[] COLOR_MAP_AVERAGE = new byte[0x10000];
public static final byte[] COLOR_MAP_ADD = new byte[0x10000];
public static final byte[] COLOR_MAP_SUBTRACT = new byte[0x10000];
public static final byte[] COLOR_MAP_MULTIPLY = new byte[0x10000];
public static final byte[] COLOR_MAP_SPECULAR = new byte[0x10000];
public static final byte COLOR_TRANSPARENT = 0;
static {
// Now we know java.awt exists we can initialize this one
COLOR_MAP_DATA = new MapColorSpaceData();
// Load color map data from the Bubble format file bundled with the library
{
boolean success = false;
MCSDBubbleFormat bubbleData = new MCSDBubbleFormat();
try {
String bub_path_postfix;
if (ReflectionUtil.supports(17)) {
bub_path_postfix = "map_1_17.bub";
} else if (ReflectionUtil.supports(16)) {
bub_path_postfix = "map_1_16.bub";
} else if (ReflectionUtil.supports(12)) {
bub_path_postfix = "map_1_12.bub";
} else {
bub_path_postfix = "map_1_8_8.bub";
}
String bub_path = "/tech/sbdevelopment/mapreflectionapi/libs/bkcommonlib/internal/resources/map/" + bub_path_postfix;
InputStream input = MapColorPalette.class.getResourceAsStream(bub_path);
if (input != null) {
bubbleData.readFrom(input);
success = true;
}
} catch (Exception e) {
e.printStackTrace();
}
if (success) {
COLOR_MAP_DATA.readFrom(bubbleData);
} else {
MCSDGenBukkit bukkitGen = new MCSDGenBukkit();
bukkitGen.generate();
COLOR_MAP_DATA.readFrom(bukkitGen);
}
}
// Generate 256 lightness values for all colors
for (int a = 0; a < 256; a++) {
int index = (a * 256);
Color color_a = getRealColor((byte) a);
if (color_a.getAlpha() < 128) {
// All specular colors for the transparent color are transparent
Arrays.fill(COLOR_MAP_SPECULAR, index, index + 256, COLOR_TRANSPARENT);
} else {
for (int b = 0; b < 256; b++) {
// 0.0 = black
// 1.0 = natural color
// 2.0 = white
float f = b / 128.0f;
int sr = (int) (color_a.getRed() * f);
int sg = (int) (color_a.getGreen() * f);
int sb = (int) (color_a.getBlue() * f);
COLOR_MAP_SPECULAR[index++] = getColor(sr, sg, sb);
}
}
}
// Initialize the color map tables for all possible color values
for (int c1 = 0; c1 < 256; c1++) {
for (int c2 = 0; c2 < 256; c2++) {
initTable((byte) c1, (byte) c2);
}
}
}
private static void initTable(byte color1, byte color2) {
int index = getMapIndex(color1, color2);
if (isTransparent(color1) || isTransparent(color2)) {
initTransparent(index, color2);
} else {
Color c1 = getRealColor(color1);
Color c2 = getRealColor(color2);
initColor(
index,
c1.getRed(), c1.getGreen(), c1.getBlue(),
c2.getRed(), c2.getGreen(), c2.getBlue()
);
}
}
private static void initTransparent(int index, byte color2) {
COLOR_MAP_AVERAGE[index] = color2;
COLOR_MAP_ADD[index] = color2;
COLOR_MAP_SUBTRACT[index] = color2;
COLOR_MAP_MULTIPLY[index] = (byte) 0;
}
private static void initColor(int index, int r1, int g1, int b1, int r2, int g2, int b2) {
initArray(COLOR_MAP_AVERAGE, index, (r1 + r2) >> 1, (g1 + g2) >> 1, (b1 + b2) >> 1);
initArray(COLOR_MAP_ADD, index, (r1 + r2), (g1 + g2), (b1 + b2));
initArray(COLOR_MAP_SUBTRACT, index, (r2 - r1), (g2 - g1), (b2 - b1));
initArray(COLOR_MAP_MULTIPLY, index, (r1 * r2) / 255, (g1 * g2) / 255, (b1 * b2) / 255);
}
private static void initArray(byte[] array, int index, int r, int g, int b) {
if (r < 0x00) r = 0x00;
if (r > 0xFF) r = 0xFF;
if (g < 0x00) g = 0x00;
if (g > 0xFF) g = 0xFF;
if (b < 0x00) b = 0x00;
if (b > 0xFF) b = 0xFF;
array[index] = getColor(r, g, b);
}
/**
* Gets whether a particular color code is a transparent color.
* There are 4 transparent colors available. Usually value 0 is used.
*
* @param color value
* @return True if transparent
*/
public static boolean isTransparent(byte color) {
return (color & 0xFF) < 0x4;
}
/**
* Gets the Minecraft map color code for an RGB color
*
* @param color input
* @return minecraft color
*/
public static byte getColor(Color color) {
if ((color.getAlpha() & 0x80) == 0) {
return COLOR_TRANSPARENT;
} else {
return COLOR_MAP_DATA.get(color.getRed(), color.getGreen(), color.getBlue());
}
}
/**
* Gets the Minecraft map color code for an RGB color
*
* @param r - red component
* @param g - green component
* @param b - blue component
* @return minecraft color
*/
public static byte getColor(int r, int g, int b) {
// This helps prevent dumb exceptions.
// Nobody likes random exceptions when all you're doing is color calculations
if (r < 0)
r = 0;
else if (r > 255)
r = 255;
if (g < 0)
g = 0;
else if (g > 255)
g = 255;
if (b < 0)
b = 0;
else if (b > 255)
b = 255;
return COLOR_MAP_DATA.get(r, g, b);
}
/**
* Gets the index into one of the palette remap arrays
*
* @param color_a first color
* @param color_b second color
* @return index
*/
public static int getMapIndex(byte color_a, byte color_b) {
return (color_a & 0xFF) | ((color_b & 0xFF) << 8);
}
/**
* Gets the real RGB color belonging to a color code
*
* @param color code input
* @return real RGB color
*/
public static Color getRealColor(byte color) {
return COLOR_MAP_DATA.getColor(color);
}
}

View file

@ -0,0 +1,277 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.bergerkiller.bukkit.common.map.color;
import com.bergerkiller.bukkit.common.io.BitInputStream;
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.IntPredicate;
import java.util.zip.InflaterInputStream;
/**
* Stores all map color space information in a highly compressed bubble format.
* In this format it is assumed the color data is in cell shapes. It stores the cell
* borders separate from the colors using the {@link MCSDWebbingCodec}. These cells
* are then filled with colors to reproduce the original image.
*/
public class MCSDBubbleFormat extends MapColorSpaceData {
public final boolean[][] strands = new boolean[256][256 * 256];
public final List<Bubble> bubbles = new ArrayList<>();
public void readFrom(InputStream stream) throws IOException {
try (BitInputStream bitStream = new BitInputStream(new InflaterInputStream(stream))) {
// Read all color RGB values
for (int i = 0; i < 256; i++) {
int r = bitStream.read();
int g = bitStream.read();
int b = bitStream.read();
int a = bitStream.read();
this.setColor((byte) i, new Color(r, g, b, a));
}
// Read all bubbles from the stream
while (true) {
Bubble bubble = new Bubble();
bubble.color = (byte) bitStream.read();
if (bubble.color == 0) {
break;
}
bubble.x = bitStream.read();
bubble.y = bitStream.read();
bubble.z_min = bitStream.read();
bubble.z_max = bubble.z_min + bitStream.read();
this.bubbles.add(bubble);
}
// Read bubble boundary information from the stream
MCSDWebbingCodec codec = new MCSDWebbingCodec();
for (int z = 0; z < 256; z++) {
Arrays.fill(this.strands[z], false);
codec.reset(strands[z], false);
while (codec.readNext(bitStream)) ;
}
// Initialize the colors with the bubble colors
this.initColors();
// Read color correction data for pixels unset (value = 0)
for (int i = 0; i < (1 << 24); i++) {
if (this.get(i) == 0) {
if (bitStream.readBits(1) == 0) {
this.set(i, this.get(i - 1));
} else {
int mode = bitStream.readBits(2);
if (mode == 0) {
this.set(i, this.get(i - 256));
} else if (mode == 1) {
this.set(i, this.get(i + 1));
} else if (mode == 2) {
this.set(i, this.get(i + 256));
} else {
this.set(i, (byte) bitStream.readBits(8));
}
}
}
}
}
}
private void initColors() {
// Set initial cell colors
this.clearRGBData();
for (MCSDBubbleFormat.Bubble cell : bubbles) {
for (int z = cell.z_min; z <= cell.z_max; z++) {
this.set(cell.x, cell.y, z, cell.color);
}
}
spreadColors();
}
private void spreadColors() {
// As we'll be processing pretty much every element, allocate the full space (60MB)
// The range of the buffer we process shrinks as we spread
StrandBuffer buf;
{
final int[] buffer = new int[1 << 24];
int count = -1;
for (int z = 0; z < 256; z++) {
boolean[] layerStrands = this.strands[z];
int indexOffset = z << 16;
for (int i = 0; i < (1 << 16); i++) {
if (!layerStrands[i]) {
buffer[++count] = indexOffset + i;
}
}
}
count++;
buf = new StrandBuffer(buffer, count);
}
// Process all until no more changes remain
buf.process(index -> {
byte color;
boolean col = ((index & 0xFF) < 0xFF);
boolean row = ((index & 0xFF00) < 0xFF00);
if (col && row) {
if ((color = this.get(index)) != 0) {
this.set(index + 1, color);
this.set(index + 256, color);
return true;
} else if ((color = this.get(index + 1)) != 0) {
this.set(index, color);
this.set(index + 256, color);
return true;
} else if ((color = this.get(index + 256)) != 0) {
this.set(index, color);
this.set(index + 1, color);
return true;
}
} else if (col) {
if ((color = this.get(index)) != 0) {
this.set(index + 1, color);
return true;
} else if ((color = this.get(index + 1)) != 0) {
this.set(index, color);
return true;
}
} else if (row) {
if ((color = this.get(index)) != 0) {
this.set(index + 256, color);
return true;
} else if ((color = this.get(index + 256)) != 0) {
this.set(index, color);
return true;
}
}
return false;
});
}
private static class StrandBuffer {
private final int[] buf;
private int start, end;
public StrandBuffer(int[] buffer, int count) {
this.buf = buffer;
this.start = 0;
this.end = count - 1;
}
public void process(IntPredicate strandIndexProc) {
while (forward(strandIndexProc) && reverse(strandIndexProc)) {
// Process alternating over and over until there are no more changes
}
}
public boolean forward(IntPredicate strandIndexProc) {
int[] buf = this.buf;
int writeIdx = start - 1;
int endIdx = end;
boolean changed = false;
for (int i = start; i <= endIdx; ++i) {
int strandIndex = buf[i];
if (strandIndexProc.test(strandIndex)) {
changed = true;
} else {
buf[++writeIdx] = strandIndex;
}
}
this.end = writeIdx;
return changed;
}
public boolean reverse(IntPredicate strandIndexProc) {
int[] buf = this.buf;
int writeIdx = end + 1;
int startIdx = start;
boolean changed = false;
for (int i = end; i >= startIdx; --i) {
int strandIndex = buf[i];
if (strandIndexProc.test(strandIndex)) {
changed = true;
} else {
buf[--writeIdx] = strandIndex;
}
}
this.start = writeIdx;
return changed;
}
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (o instanceof MCSDBubbleFormat) {
MCSDBubbleFormat other = (MCSDBubbleFormat) o;
for (int i = 0; i < strands.length; i++) {
if (other.strands[i] != this.strands[i]) {
return false;
}
}
if (bubbles.size() != other.bubbles.size()) {
return false;
}
for (int i = 0; i < bubbles.size(); i++) {
if (!bubbles.get(i).equals(other.bubbles.get(i))) {
return false;
}
}
return true;
} else {
return false;
}
}
public static class Bubble {
public int x, y;
public int z_min;
public int z_max;
public byte color;
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (o instanceof Bubble) {
Bubble other = (Bubble) o;
return other.x == x && other.y == y &&
other.z_min == z_min && other.z_max == z_max &&
other.color == color;
} else {
return false;
}
}
@Override
public String toString() {
return "cell{x=" + x + ", y=" + y + ", zmin=" + z_min + ", zmax=" + z_max + ", color=" + (color & 0xFF) + "}";
}
}
}

View file

@ -0,0 +1,45 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.bergerkiller.bukkit.common.map.color;
import org.bukkit.map.MapPalette;
public class MCSDGenBukkit extends MapColorSpaceData {
/**
* Generates the color map information by using Bukkit's algorithms.
*/
@SuppressWarnings("deprecation")
public void generate() {
this.clear();
for (int i = 0; i < 256; i++) {
try {
setColor((byte) i, MapPalette.getColor((byte) i));
} catch (Exception ignored) {
}
}
for (int r = 0; r < 256; r++) {
for (int g = 0; g < 256; g++) {
for (int b = 0; b < 256; b++) {
set(r, g, b, MapPalette.matchColor(r, g, b));
}
}
}
}
}

View file

@ -0,0 +1,123 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.bergerkiller.bukkit.common.map.color;
import com.bergerkiller.bukkit.common.io.BitInputStream;
import com.bergerkiller.bukkit.common.io.BitPacket;
import java.io.IOException;
/**
* Encodes or decodes a 256x256 grid of booleans by walking down the connected lines and encoding them
* using drawing instructions. For example, a diagonal line in the grid may be encoded as follows:
* <ul>
* <li>SET_POSITION(23, 56)</li>
* <li>SET_DX(-1)</li>
* <li>SET_DY(1)</li>
* <li>MOVE DX AND DRAW</li>
* <li>MOVE DX AND DRAW</li>
* <li>MOVE DY AND DRAW</li>
* <li>MOVE DX AND DRAW</li>
* <li>MOVE DX AND DRAW</li>
* <li>MOVE DY AND DRAW</li>
* <li>etc.</li>
* </ul>
* <p>
* For encoding the data, the follow bits are written out in sequence:
* <ul>
* <li>00 -> MOVE DX AND DRAW</li>
* <li>01 -> MOVE DY AND DRAW</li>
* <li>10 -> MOVE DX+DY AND DRAW</li>
* <li>11 100 -> SET DX = -1</li>
* <li>11 101 -> SET DX = 1</li>
* <li>11 110 -> SET DY = -1</li>
* <li>11 111 -> SET DY = 1</li>
* <li>11 00 [byte_x][byte_y] -> SET POSITION AND DRAW</li>
* <li>11 01 -> STOP</li>
* </ul>
*/
public class MCSDWebbingCodec {
private int last_x, last_y;
private int last_dx, last_dy;
public boolean[] strands = new boolean[1 << 16];
private final BitPacket[] packets = new BitPacket[1024];
public MCSDWebbingCodec() {
for (int i = 0; i < this.packets.length; i++) {
this.packets[i] = new BitPacket();
}
}
public void reset(boolean[] cells, boolean copyCells) {
if (copyCells) {
System.arraycopy(cells, 0, this.strands, 0, cells.length);
} else {
this.strands = cells;
}
this.last_x = -1000;
this.last_y = -1000;
this.last_dx = 1;
this.last_dy = 1;
}
public boolean readNext(BitInputStream stream) throws IOException {
int op = stream.readBits(2);
if (op == 0b11) {
if (stream.readBits(1) == 1) {
// Set DX/DY increment/decrement
int sub = stream.readBits(2);
if (sub == 0b00) {
last_dx = -1;
} else if (sub == 0b01) {
last_dx = 1;
} else if (sub == 0b10) {
last_dy = -1;
} else if (sub == 0b11) {
last_dy = 1;
}
} else {
// Command codes
if (stream.readBits(1) == 1) {
// End of slice
return false;
} else {
// Reset position
last_x = stream.readBits(8);
last_y = stream.readBits(8);
strands[last_x | (last_y << 8)] = true;
}
}
} else {
// Write next pixel
if (op == 0b00) {
last_x += last_dx;
} else if (op == 0b01) {
last_y += last_dy;
} else if (op == 0b10) {
last_x += last_dx;
last_y += last_dy;
} else if (op == -1) {
// End of stream
return false;
}
strands[last_x | (last_y << 8)] = true;
}
return true;
}
}

View file

@ -0,0 +1,155 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.bergerkiller.bukkit.common.map.color;
import java.awt.*;
import java.util.Arrays;
/**
* Stores the raw map color space data, enabling transformation between different storage methods.
*/
public class MapColorSpaceData implements Cloneable {
private final Color[] colors = new Color[256];
private final byte[] data = new byte[1 << 24];
public MapColorSpaceData() {
Arrays.fill(this.colors, new Color(0, 0, 0, 0));
}
/**
* Clears only the RGB data. Equivalent to using {@link #set(int, byte)} on all RGB colors.
*/
public final void clearRGBData() {
Arrays.fill(this.data, (byte) 0);
}
/**
* Clears all data, setting all colors to transparent
*/
public final void clear() {
Arrays.fill(this.colors, new Color(0, 0, 0, 0));
Arrays.fill(this.data, (byte) 0);
}
/**
* Sets all color data of this color space data to that from the input color space data
*
* @param data to set
*/
public void readFrom(MapColorSpaceData data) {
System.arraycopy(data.data, 0, this.data, 0, this.data.length);
System.arraycopy(data.colors, 0, this.colors, 0, this.colors.length);
}
/**
* Sets a single map palette color
*
* @param code of the color
* @param color to set to
*/
public final void setColor(byte code, Color color) {
this.colors[code & 0xFF] = color;
}
/**
* Gets a single map palette color
*
* @param code of the color
* @return map palette color
*/
public final Color getColor(byte code) {
return this.colors[code & 0xFF];
}
/**
* Sets the map color code value for an rgb value
*
* @param r component
* @param g component
* @param b component
* @param code to set to
*/
public final void set(int r, int g, int b, byte code) {
this.data[getDataIndex(r, g, b)] = code;
}
/**
* Gets the map color code value for an rgb value
*
* @param r component
* @param g component
* @param b component
* @return color code
*/
public final byte get(int r, int g, int b) {
return this.data[getDataIndex(r, g, b)];
}
/**
* Sets the map color code for an rgb value
*
* @param index rgb compound value
* @param code to set to
*/
public final void set(int index, byte code) {
this.data[index] = code;
}
/**
* Gets the map color code for an rgb value
*
* @param index rgb compound value
* @return color code
*/
public final byte get(int index) {
return this.data[index];
}
@Override
public MapColorSpaceData clone() {
MapColorSpaceData clone = new MapColorSpaceData();
System.arraycopy(this.colors, 0, clone.colors, 0, this.colors.length);
System.arraycopy(this.data, 0, clone.data, 0, this.data.length);
return clone;
}
/**
* Gets the mapping index of an rgb value
*
* @param r component
* @param g component
* @param b component
* @return index
*/
private static int getDataIndex(byte r, byte g, byte b) {
return (r & 0xFF) + ((g & 0xFF) << 8) + ((b & 0xFF) << 16);
}
/**
* Gets the mapping index of an rgb value
*
* @param r component
* @param g component
* @param b component
* @return index
*/
private static int getDataIndex(int r, int g, int b) {
return (r & 0xFF) + ((g & 0xFF) << 8) + ((b & 0xFF) << 16);
}
}

View file

@ -0,0 +1,193 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi;
import com.bergerkiller.bukkit.common.map.MapColorPalette;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SingleLineChart;
import org.bukkit.Bukkit;
import org.bukkit.map.MapView;
import org.bukkit.plugin.java.JavaPlugin;
import tech.sbdevelopment.mapreflectionapi.api.MapManager;
import tech.sbdevelopment.mapreflectionapi.cmd.MapManagerCMD;
import tech.sbdevelopment.mapreflectionapi.listeners.MapListener;
import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener;
import tech.sbdevelopment.mapreflectionapi.managers.Configuration;
import tech.sbdevelopment.mapreflectionapi.utils.MainUtil;
import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
import tech.sbdevelopment.mapreflectionapi.utils.UpdateManager;
import java.util.logging.Level;
public class MapReflectionAPI extends JavaPlugin {
private static MapReflectionAPI instance;
private static MapManager mapManager;
private static PacketListener packetListener;
/**
* Get the plugin instance
*
* @return The {@link MapReflectionAPI} instance
*/
public static MapReflectionAPI getInstance() {
if (instance == null) throw new IllegalStateException("The plugin is not enabled yet!");
return instance;
}
/**
* Get the {@link MapManager}
*
* @return The manager
*/
public static MapManager getMapManager() {
if (mapManager == null) throw new IllegalStateException("The plugin is not enabled yet!");
return mapManager;
}
@Override
public void onEnable() {
instance = this;
getLogger().info("----------------");
getLogger().info("MapReflectionAPI v" + getDescription().getVersion());
getLogger().info("Made by © Copyright SBDevelopment 2023");
if (!ReflectionUtil.supports(12)) {
getLogger().severe("MapReflectionAPI only supports Minecraft 1.12 - 1.19.4!");
Bukkit.getPluginManager().disablePlugin(this);
return;
}
getLogger().info("Loading Java AWT runtime library support...");
if (MainUtil.isHeadlessJDK()) {
getLogger().severe("MapReflectionAPI requires the Java AWT runtime library, but is not available!");
getLogger().severe("This is usually because a headless JVM is used for the server.");
getLogger().severe("Please install and configure a non-headless JVM to make this plugin work.");
Bukkit.getPluginManager().disablePlugin(this);
return;
}
MapColorPalette.getColor(0, 0, 0); //Initializes the class
getLogger().info("Loading the configuration...");
Configuration.init(this);
getLogger().info("Loading the commands...");
getCommand("mapmanager").setExecutor(new MapManagerCMD());
getLogger().info("Loading the packet listener...");
try {
packetListener = PacketListener.construct(this);
} catch (IllegalStateException e) {
getLogger().log(Level.SEVERE, e.getMessage(), e);
Bukkit.getPluginManager().disablePlugin(this);
return;
}
packetListener.init(this);
getLogger().info("Loading the map manager...");
try {
mapManager = new MapManager(this);
} catch (IllegalStateException e) {
getLogger().log(Level.SEVERE, e.getMessage(), e);
Bukkit.getPluginManager().disablePlugin(this);
return;
}
if (Configuration.getInstance().isAllowVanilla()) {
getLogger().info("Vanilla Maps are allowed. Discovering occupied Map IDs...");
int occupiedIDs = 0;
for (int s = 0; s < Short.MAX_VALUE; s++) {
try {
MapView view = Bukkit.getMap(s);
if (view != null) {
mapManager.registerOccupiedID(s);
occupiedIDs++;
}
} catch (Exception e) {
if (!e.getMessage().toLowerCase().contains("invalid map dimension")) {
getLogger().log(Level.WARNING, e.getMessage(), e);
}
}
}
getLogger().info("Found " + occupiedIDs + " occupied Map IDs" + (occupiedIDs > 0 ? ", these will not be used." : "."));
}
getLogger().info("Registering the listeners...");
Bukkit.getPluginManager().registerEvents(new MapListener(), this);
getLogger().info("Loading metrics...");
Metrics metrics = new Metrics(this, 16033);
metrics.addCustomChart(new SingleLineChart("managed_maps", () -> mapManager.getManagedMapsCount()));
if (Configuration.getInstance().isUpdaterCheck()) {
try {
UpdateManager updateManager = new UpdateManager(this, 103011);
updateManager.handleResponse((versionResponse, version) -> {
switch (versionResponse) {
case FOUND_NEW:
getLogger().warning("There is a new version available! Current: " + getDescription().getVersion() + " New: " + version.get());
if (Configuration.getInstance().isUpdaterDownload()) {
getLogger().info("Trying to download the update. This could take some time...");
updateManager.handleDownloadResponse((downloadResponse, fileName) -> {
switch (downloadResponse) {
case DONE:
getLogger().info("Update downloaded! If you restart your server, it will be loaded. Filename: " + fileName);
break;
case ERROR:
getLogger().severe("Something went wrong when trying downloading the latest version.");
break;
case UNAVAILABLE:
getLogger().warning("Unable to download the latest version.");
break;
}
}).runUpdate();
}
break;
case LATEST:
getLogger().info("You are running the latest version [" + getDescription().getVersion() + "]!");
break;
case THIS_NEWER:
getLogger().info("You are running a newer version [" + getDescription().getVersion() + "]! This is probably fine.");
break;
case UNAVAILABLE:
getLogger().severe("Unable to perform an update check.");
break;
}
}).check();
} catch (IllegalStateException ex) {
ex.printStackTrace();
}
}
getLogger().info("MapReflectionAPI is enabled!");
getLogger().info("----------------");
}
@Override
public void onDisable() {
getLogger().info("Disabling the packet handler...");
if (packetListener != null) Bukkit.getOnlinePlayers().forEach(p -> packetListener.removePlayer(p));
getLogger().info("MapReflectionAPI is disabled!");
instance = null;
}
}

View file

@ -0,0 +1,36 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api;
/**
* The abstract class of a wrapped map.
*/
public abstract class AbstractMapWrapper {
/**
* Get the controller of this wrapper
*
* @return The {@link MapController}
*/
public abstract IMapController getController();
protected void unwrap() {
getController().cancelSend();
getController().clearViewers();
}
}

View file

@ -0,0 +1,86 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api;
import com.bergerkiller.bukkit.common.map.MapColorPalette;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* An {@link ArrayImage} contains an image converted to a Minecraft byte array.
*/
@RequiredArgsConstructor
@EqualsAndHashCode
@ToString
public class ArrayImage {
public final byte[] array;
public int minX = 0;
public int minY = 0;
public int maxX = 128;
public int maxY = 128;
private int width;
private int height;
private int imageType = BufferedImage.TYPE_4BYTE_ABGR;
/**
* Convert a {@link BufferedImage} to an ArrayImage
*
* @param image image to convert
*/
public ArrayImage(BufferedImage image) {
this.imageType = image.getType();
this.width = image.getWidth();
this.height = image.getHeight();
BufferedImage temp = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = temp.createGraphics();
graphics.drawImage(image, 0, 0, null);
graphics.dispose();
int[] pixels = new int[temp.getWidth() * temp.getHeight()];
temp.getRGB(0, 0, temp.getWidth(), temp.getHeight(), pixels, 0, temp.getWidth());
byte[] result = new byte[temp.getWidth() * temp.getHeight()];
for (int i = 0; i < pixels.length; i++) {
result[i] = MapColorPalette.getColor(new Color(pixels[i], true));
}
this.array = result;
}
/**
* Get the {@link BufferedImage} of this ArrayImage
*
* @return The converted image
*/
public BufferedImage toBuffered() {
BufferedImage img = new BufferedImage(width, height, this.imageType);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
img.setRGB(x, y, MapColorPalette.getRealColor(array[y * width + x]).getRGB());
}
}
return img;
}
}

View file

@ -0,0 +1,83 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException;
/**
* This interface contains the methods for both the {@link MapController} and the {@link MultiMapController}.
*/
public interface IMapController {
/**
* Add a viewer
*
* @param player {@link Player} to add
*/
void addViewer(Player player) throws MapLimitExceededException;
/**
* Remove a viewer
*
* @param player {@link OfflinePlayer} to remove
*/
void removeViewer(OfflinePlayer player);
/**
* Remove all viewers
*/
void clearViewers();
/**
* Check if a player is viewing
*
* @param player {@link OfflinePlayer} to check
* @return <code>true</code> if the player is viewing
*/
boolean isViewing(OfflinePlayer player);
/**
* Update the image
*
* @param content new {@link ArrayImage} content
*/
void update(@NotNull ArrayImage content);
/**
* Send the content to a player
*
* @param player {@link Player} receiver of the content
*/
void sendContent(Player player);
/**
* Send the content to a player
*
* @param player {@link Player} receiver of the content
* @param withoutQueue if <code>true</code>, the content will be sent immediately
*/
void sendContent(Player player, boolean withoutQueue);
/**
* Cancels the 'send events' in the queue
*/
void cancelSend();
}

View file

@ -0,0 +1,129 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
/**
* A {@link MapController} controls one {@link MapWrapper}.
*/
public interface MapController extends IMapController {
/**
* Get the map ID for a player
*
* @param player {@link OfflinePlayer} to get the ID for
* @return the ID, or <code>-1</code> if no ID exists (i.e. the player is not viewing)
*/
int getMapId(OfflinePlayer player);
/**
* Show in a player's inventory
*
* @param player {@link Player}
* @param slot slot to show the map in
* @param force if <code>false</code>, the map will not be shown if the player is in creative mode
*/
void showInInventory(Player player, int slot, boolean force);
/**
* Show in a player's inventory
*
* @param player {@link Player}
* @param slot slot to show the map in
*/
void showInInventory(Player player, int slot);
/**
* Show in a player's hand
*
* @param player {@link Player}
* @param force if <code>false</code>, the map will not be shown if the player is not holding a map, or is in creative mode
* @see #showInFrame(Player, ItemFrame, boolean)
*/
void showInHand(Player player, boolean force);
/**
* Show in a player's hand
*
* @param player {@link Player}
*/
void showInHand(Player player);
/**
* Show in an {@link ItemFrame}
*
* @param player {@link Player} that will be able to see the map
* @param frame {@link ItemFrame} to show the map in
*/
void showInFrame(Player player, ItemFrame frame);
/**
* Show in an {@link ItemFrame}
*
* @param player {@link Player} that will be able to see the map
* @param frame {@link ItemFrame} to show the map in
* @param force if <code>false</code>, the map will not be shown if there is not Map-Item in the ItemFrame
*/
void showInFrame(Player player, ItemFrame frame, boolean force);
/**
* Show in an {@link ItemFrame}
*
* @param player {@link Player} that will be able to see the map
* @param entityId Entity-ID of the {@link ItemFrame} to show the map in
*/
void showInFrame(Player player, int entityId);
/**
* Show in an {@link ItemFrame}
*
* @param player {@link Player} that will be able to see the map
* @param entityId Entity-ID of the {@link ItemFrame} to show the map in
* @param debugInfo {@link String} to show when a player looks at the map, or <code>null</code>
*/
void showInFrame(Player player, int entityId, String debugInfo);
/**
* Clear a frame
*
* @param player {@link Player} that will be able to see the cleared frame
* @param entityId Entity-ID of the {@link ItemFrame} to clear
*/
void clearFrame(Player player, int entityId);
/**
* Clear a frame
*
* @param player {@link Player} that will be able to see the cleared frame
* @param frame {@link ItemFrame} to clear
*/
void clearFrame(Player player, ItemFrame frame);
/**
* Get an {@link ItemFrame} by its entity ID
*
* @param world The world the {@link ItemFrame} is in
* @param entityId Entity-ID of the {@link ItemFrame}
* @return The found {@link ItemFrame}, or <code>null</code>
*/
ItemFrame getItemFrameById(World world, int entityId);
}

View file

@ -0,0 +1,313 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.Nullable;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException;
import tech.sbdevelopment.mapreflectionapi.managers.Configuration;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* The {@link MapManager} manages all the maps. It also contains functions for wrapping.
*/
public class MapManager {
protected final Set<Integer> occupiedIds = new HashSet<>();
protected final List<MapWrapper> managedMaps = new CopyOnWriteArrayList<>();
private final Class<?> wrapperClass;
public MapManager(JavaPlugin plugin) throws IllegalStateException {
String packageName = Bukkit.getServer().getClass().getPackage().getName();
String version = packageName.substring(packageName.lastIndexOf('.') + 1);
MapReflectionAPI.getInstance().getLogger().info("Initializing the map manager for Minecraft version " + version + "...");
try {
final Class<?> clazz = Class.forName("tech.sbdevelopment.mapreflectionapi.nms.MapWrapper_" + version);
if (MapWrapper.class.isAssignableFrom(clazz)) {
wrapperClass = clazz;
} else {
throw new IllegalStateException("Plugin corrupted! Detected invalid MapWrapper class.");
}
} catch (Exception ex) {
throw new IllegalStateException("This Spigot version (" + version + ") is not supported! Contact the developer to get support.");
}
}
/**
* Get the amount of maps managed by the plugin
*
* @return The managed maps amount
*/
public int getManagedMapsCount() {
return managedMaps.size();
}
/**
* Wrap a {@link BufferedImage} in a {@link MapWrapper}
*
* @param image The image to wrap
* @return The wrapper
*/
public MapWrapper wrapImage(BufferedImage image) {
return wrapImage(new ArrayImage(image));
}
/**
* Wrap a {@link ArrayImage} in a {@link MapWrapper}
*
* @param image The image to wrap
* @return The wrapper
*/
public MapWrapper wrapImage(ArrayImage image) {
if (Configuration.getInstance().isImageCache()) {
for (MapWrapper wrapper : managedMaps) {
if (wrapper.getContent().equals(image)) return wrapper;
}
}
return wrapNewImage(image);
}
/**
* Wrap a {@link BufferedImage} and split it into multiple maps
*
* @param image The image to wrap
* @param rows Rows of the split (i.e. height)
* @param columns Columns of the split (i.e. width)
* @return The wrapper
*/
public MultiMapWrapper wrapMultiImage(BufferedImage image, int rows, int columns) {
//Don't add to managedMaps, because the MultiMapWrapper will do that for us
return new MultiMapWrapper(image, rows, columns);
}
/**
* Wrap an {@link ArrayImage} and split it into multiple maps
*
* @param image The image to wrap
* @param rows Rows of the split (i.e. height)
* @param columns Columns of the split (i.e. width)
* @return The wrapper
*/
public MultiMapWrapper wrapMultiImage(ArrayImage image, int rows, int columns) {
//Don't add to managedMaps, because the MultiMapWrapper will do that for us
return new MultiMapWrapper(image, rows, columns);
}
/**
* Wrap multiple {@link BufferedImage}s
*
* @param images The images to wrap
* @return The wrapper
*/
public MultiMapWrapper wrapMultiImage(BufferedImage[][] images) {
return new MultiMapWrapper(images);
}
/**
* Wrap multiple {@link ArrayImage}s
*
* @param images The images to wrap
* @return The wrapper
*/
public MultiMapWrapper wrapMultiImage(ArrayImage[][] images) {
return new MultiMapWrapper(images);
}
/**
* Wrap a new image
*
* @param image The image to wrap
* @return The wrapper
*/
private MapWrapper wrapNewImage(ArrayImage image) {
try {
MapWrapper wrapper = (MapWrapper) wrapperClass.getDeclaredConstructor(ArrayImage.class).newInstance(image);
managedMaps.add(wrapper);
return wrapper;
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
e.printStackTrace();
return null;
}
}
/**
* Unwrap an image (will remove the wrapper)
*
* @param wrapper The {@link MapWrapper} to unwrap
*/
public void unwrapImage(MapWrapper wrapper) {
wrapper.unwrap();
managedMaps.remove(wrapper);
}
/**
* Get the maps a player can see
*
* @param player The {@link Player} to check for
* @return A {@link Set} with the {@link MapWrapper}s
*/
public Set<MapWrapper> getMapsVisibleTo(OfflinePlayer player) {
Set<MapWrapper> visible = new HashSet<>();
for (MapWrapper wrapper : managedMaps) {
if (wrapper.getController().isViewing(player)) {
visible.add(wrapper);
}
}
return visible;
}
/**
* Get the wrapper by a player and map id
*
* @param player The {@link OfflinePlayer} to check for
* @param id The ID of the map
* @return The {@link MapWrapper} for that map or null
*/
@Nullable
public MapWrapper getWrapperForId(OfflinePlayer player, int id) {
for (MapWrapper wrapper : getMapsVisibleTo(player)) {
if (wrapper.getController().getMapId(player) == id) {
return wrapper;
}
}
return null;
}
/**
* Register an occupied map ID
*
* @param id The map ID to register
*/
public void registerOccupiedID(int id) {
occupiedIds.add(id);
}
/**
* Unregister an occupied map ID
*
* @param id The map ID to unregister
*/
public void unregisterOccupiedID(int id) {
occupiedIds.remove(id);
}
/**
* Get the occupied IDs for a player
*
* @param player The {@link OfflinePlayer} to check for
* @return A {@link Set} with the found map IDs
*/
public Set<Integer> getOccupiedIdsFor(OfflinePlayer player) {
Set<Integer> ids = new HashSet<>();
for (MapWrapper wrapper : managedMaps) {
int s = wrapper.getController().getMapId(player);
if (s >= 0) {
ids.add(s);
}
}
return ids;
}
/**
* Check if a player uses a map ID
*
* @param player The {@link OfflinePlayer} to check for
* @param id The map ID to check for
* @return true/false
*/
public boolean isIdUsedBy(OfflinePlayer player, int id) {
return id > 0 && getOccupiedIdsFor(player).contains(id);
}
/**
* Get the next ID that can be used for this player
*
* @param player The {@link Player} to check for
* @return The next ID
* @throws MapLimitExceededException If no IDs are available
*/
public int getNextFreeIdFor(Player player) throws MapLimitExceededException {
Set<Integer> occupied = getOccupiedIdsFor(player);
//Add the 'default' occupied IDs
occupied.addAll(occupiedIds);
int largest = 0;
for (Integer s : occupied) {
if (s > largest) {
largest = s;
}
}
//Simply increase the maximum id if it's still small enough
if (largest + 1 < Integer.MAX_VALUE) {
return largest + 1;
}
//Otherwise iterate through all options until there is an unused id
for (int s = 0; s < Integer.MAX_VALUE; s++) {
if (!occupied.contains(s)) {
return s;
}
}
//If we end up here, this player has no more free ids. Let's hope nobody uses this many Maps.
throw new MapLimitExceededException("'" + player + "' reached the maximum amount of available Map-IDs");
}
/**
* Clear all the maps of a player
* This makes them no longer viewable for this player
*
* @param player The {@link OfflinePlayer} to clear for
*/
public void clearAllMapsFor(OfflinePlayer player) {
for (MapWrapper wrapper : getMapsVisibleTo(player)) {
wrapper.getController().removeViewer(player);
}
}
/**
* Check if a MapWrapper exists for this image
* If so, the same MapWrapper can be used
*
* @param image The {@link ArrayImage} to check for
* @return A {@link MapWrapper} if duplicate, or null if not
*/
@Nullable
public MapWrapper getDuplicate(ArrayImage image) {
for (MapWrapper wrapper : managedMaps) {
if (image.equals(wrapper.getContent())) {
return wrapper;
}
}
return null;
}
}

View file

@ -0,0 +1,16 @@
package tech.sbdevelopment.mapreflectionapi.api;
public abstract class MapWrapper extends AbstractMapWrapper {
protected ArrayImage content;
public MapWrapper(ArrayImage image) {
this.content = image;
}
public ArrayImage getContent() {
return content;
}
@Override
public abstract MapController getController();
}

View file

@ -0,0 +1,154 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException;
/**
* A {@link MultiMapController} controls multiple {@link MapWrapper}s (a.k.a. the {@link MultiMapWrapper}).
*/
public interface MultiMapController extends IMapController {
/**
* Add a viewer
*
* @param player {@link Player} to add
*/
void addViewer(Player player) throws MapLimitExceededException;
/**
* Remove a viewer
*
* @param player {@link OfflinePlayer} to remove
*/
void removeViewer(OfflinePlayer player);
/**
* Remove all viewers
*/
void clearViewers();
/**
* Check if a player is viewing
*
* @param player {@link OfflinePlayer} to check
* @return <code>true</code> if the player is viewing
*/
boolean isViewing(OfflinePlayer player);
/**
* Update the image
*
* @param content new {@link ArrayImage} content
*/
void update(@NotNull ArrayImage content);
/**
* Send the content to a player
*
* @param player {@link Player} receiver of the content
*/
void sendContent(Player player);
/**
* Send the content to a player
*
* @param player {@link Player} receiver of the content
* @param withoutQueue if <code>true</code>, the content will be sent immediately
*/
void sendContent(Player player, boolean withoutQueue);
/**
* Cancels the 'send events' in the queue
*/
void cancelSend();
/**
* Show this {@link MultiMapController} in {@link ItemFrame}s
*
* @param player {@link Player} that will be able to see the maps
* @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (<code>int[width][height]</code>)
* @see MapController#showInFrame(Player, int)
*/
void showInFrames(Player player, Integer[][] entityIdMatrix);
/**
* Show this {@link MultiMapController} in {@link ItemFrame}s
*
* @param player {@link Player} that will be able to see the maps
* @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (<code>int[width][height]</code>)
* @param callable {@link DebugCallable} which will be called to display debug information, or <code>null</code>
* @see MapController#showInFrame(Player, int, String)
*/
void showInFrames(Player player, Integer[][] entityIdMatrix, DebugCallable callable);
/**
* Show this {@link MultiMapController} in {@link ItemFrame}s
*
* @param player {@link Player} that will be able to see the maps
* @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (<code>ItemFrame[width][height]</code>)
* @param force if <code>false</code>, the map will not be shown if there is not Map-Item in the ItemFrames
* @see MapController#showInFrame(Player, ItemFrame, boolean)
*/
void showInFrames(Player player, ItemFrame[][] itemFrameMatrix, boolean force);
/**
* Show this {@link MultiMapController} in {@link ItemFrame}s
*
* @param player {@link Player} that will be able to see the maps
* @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (<code>ItemFrame[width][height]</code>)
* @see MapController#showInFrame(Player, ItemFrame)
*/
void showInFrames(Player player, ItemFrame[][] itemFrameMatrix);
/**
* Clear the frames
*
* @param player {@link Player} that will be able to see the cleared frames
* @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (<code>int[width][height]</code>)
*/
void clearFrames(Player player, Integer[][] entityIdMatrix);
/**
* Clear the frames
*
* @param player {@link Player} that will be able to see the cleared frames
* @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (<code>ItemFrame[width][height]</code>)
*/
void clearFrames(Player player, ItemFrame[][] itemFrameMatrix);
/**
* The DebugCallable can be used to get debug information for a frame
*/
interface DebugCallable {
/**
* Called to get debug information for a frame
*
* @param controller the {@link MapController}
* @param x X-Position of the current frame
* @param y Y-Position of the current frame
* @return {@link String} to show when a player looks at the map, or <code>null</code>
* @see MapController#showInFrame(Player, int, String)
*/
String call(MapController controller, int x, int y);
}
}

View file

@ -0,0 +1,239 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import static tech.sbdevelopment.mapreflectionapi.utils.MainUtil.validateArrayDimensions;
/**
* A {@link MultiMapWrapper} wraps one image split in pieces.
*/
public class MultiMapWrapper extends AbstractMapWrapper {
private final MapWrapper[][] wrapperMatrix;
public MultiMapWrapper(BufferedImage image, int rows, int columns) {
this(splitImage(image, columns, rows));
}
public MultiMapWrapper(ArrayImage image, int rows, int columns) {
this(splitImage(image.toBuffered(), columns, rows));
}
public MultiMapWrapper(ArrayImage[][] imageMatrix) {
wrapperMatrix = new MapWrapper[imageMatrix.length][imageMatrix[0].length];
for (int x = 0; x < imageMatrix.length; x++) {
if (imageMatrix[x].length != imageMatrix[0].length) {
throw new IllegalArgumentException("An image in a MultiMapWrapper is not rectangular!");
}
for (int y = 0; y < imageMatrix[x].length; y++) {
wrapperMatrix[x][y] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[x][y]);
}
}
}
public MultiMapWrapper(BufferedImage[][] imageMatrix) {
wrapperMatrix = new MapWrapper[imageMatrix.length][imageMatrix[0].length];
for (int x = 0; x < imageMatrix.length; x++) {
if (imageMatrix[x].length != imageMatrix[0].length) {
throw new IllegalArgumentException("An image in a MultiMapWrapper is not rectangular!");
}
for (int y = 0; y < imageMatrix[x].length; y++) {
wrapperMatrix[x][y] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[x][y]);
}
}
}
private final MultiMapController controller = new MultiMapController() {
private final Set<UUID> viewers = new HashSet<>();
@Override
public void addViewer(Player player) throws MapLimitExceededException {
if (!viewers.contains(player.getUniqueId())) {
for (MapWrapper[] mapWrappers : wrapperMatrix) {
for (MapWrapper wrapper : mapWrappers) {
wrapper.getController().addViewer(player);
}
}
viewers.add(player.getUniqueId());
}
}
@Override
public void removeViewer(OfflinePlayer player) {
for (MapWrapper[] mapWrappers : wrapperMatrix) {
for (MapWrapper wrapper : mapWrappers) {
wrapper.getController().removeViewer(player);
}
}
viewers.remove(player.getUniqueId());
}
@Override
public void clearViewers() {
for (MapWrapper[] mapWrappers : wrapperMatrix) {
for (MapWrapper wrapper : mapWrappers) {
wrapper.getController().clearViewers();
}
}
viewers.clear();
}
@Override
public boolean isViewing(OfflinePlayer player) {
return viewers.contains(player.getUniqueId());
}
@Override
public void update(@NotNull ArrayImage content) {
ArrayImage[][] split = splitImage(content.toBuffered(), wrapperMatrix[0].length, wrapperMatrix.length);
for (int x = 0; x < wrapperMatrix.length; x++) {
for (int y = 0; y < wrapperMatrix[x].length; y++) {
wrapperMatrix[x][y].getController().update(split[x][y]);
}
}
}
@Override
public void sendContent(Player player) {
sendContent(player, false);
}
@Override
public void sendContent(Player player, boolean withoutQueue) {
for (MapWrapper[] mapWrappers : wrapperMatrix) {
for (MapWrapper wrapper : mapWrappers) {
wrapper.getController().sendContent(player, withoutQueue);
}
}
}
@Override
public void cancelSend() {
for (MapWrapper[] mapWrappers : wrapperMatrix) {
for (MapWrapper wrapper : mapWrappers) {
wrapper.getController().cancelSend();
}
}
}
@Override
public void showInFrames(Player player, Integer[][] entityIdMatrix) {
validateArrayDimensions(wrapperMatrix, entityIdMatrix);
for (int x = 0; x < entityIdMatrix.length; x++) {
for (int y = 0; y < entityIdMatrix[x].length; y++) {
wrapperMatrix[y][x].getController().showInFrame(player, entityIdMatrix[x][wrapperMatrix.length - 1 - y]);
}
}
}
@Override
public void showInFrames(Player player, Integer[][] entityIdMatrix, DebugCallable callable) {
validateArrayDimensions(wrapperMatrix, entityIdMatrix);
for (int x = 0; x < entityIdMatrix.length; x++) {
for (int y = 0; y < entityIdMatrix[x].length; y++) {
wrapperMatrix[y][x].getController().showInFrame(player, entityIdMatrix[x][wrapperMatrix.length - 1 - y], callable.call(wrapperMatrix[y][x].getController(), x, y));
}
}
}
@Override
public void showInFrames(Player player, ItemFrame[][] itemFrameMatrix, boolean force) {
validateArrayDimensions(wrapperMatrix, itemFrameMatrix);
for (int x = 0; x < itemFrameMatrix.length; x++) {
for (int y = 0; y < itemFrameMatrix[x].length; y++) {
wrapperMatrix[y][x].getController().showInFrame(player, itemFrameMatrix[x][wrapperMatrix.length - 1 - y], force);
}
}
}
@Override
public void showInFrames(Player player, ItemFrame[][] itemFrameMatrix) {
showInFrames(player, itemFrameMatrix, false);
}
@Override
public void clearFrames(Player player, Integer[][] entityIdMatrix) {
validateArrayDimensions(wrapperMatrix, entityIdMatrix);
for (int x = 0; x < entityIdMatrix.length; x++) {
for (int y = 0; y < entityIdMatrix[x].length; y++) {
wrapperMatrix[y][x].getController().clearFrame(player, entityIdMatrix[x][y]);
}
}
}
@Override
public void clearFrames(Player player, ItemFrame[][] itemFrameMatrix) {
validateArrayDimensions(wrapperMatrix, itemFrameMatrix);
for (int x = 0; x < itemFrameMatrix.length; x++) {
for (int y = 0; y < itemFrameMatrix[x].length; y++) {
wrapperMatrix[y][x].getController().clearFrame(player, itemFrameMatrix[x][y]);
}
}
}
};
/*
* Modified Method from http://kalanir.blogspot.de/2010/02/how-to-split-image-into-chunks-java.html
*/
private static ArrayImage[][] splitImage(final BufferedImage image, final int columns, final int rows) {
int chunkWidth = image.getWidth() / columns;
int chunkHeight = image.getHeight() / rows;
ArrayImage[][] images = new ArrayImage[rows][columns];
for (int x = 0; x < rows; x++) {
for (int y = 0; y < columns; y++) {
BufferedImage raw = new BufferedImage(chunkWidth, chunkHeight, image.getType());
Graphics2D gr = raw.createGraphics();
gr.drawImage(image, 0, 0, chunkWidth, chunkHeight, chunkWidth * y, chunkHeight * x, chunkWidth * y + chunkWidth, chunkHeight * x + chunkHeight, null);
gr.dispose();
images[x][y] = new ArrayImage(raw);
raw.flush();
}
}
return images;
}
@Override
public MultiMapController getController() {
return controller;
}
}

View file

@ -0,0 +1,85 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api.events;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.api.MapWrapper;
/**
* This event gets fired when a map in the creative inventory gets updated
*/
@RequiredArgsConstructor
@Getter
public class CreateInventoryMapUpdateEvent extends Event implements Cancellable {
private static final HandlerList handlerList = new HandlerList();
@Setter
private boolean cancelled;
private final Player player;
private final int slot;
private final ItemStack item;
private MapWrapper mapWrapper;
/**
* Construct a new {@link CreateInventoryMapUpdateEvent}
*
* @param player The player whose inventory is updated
* @param slot The new slot
* @param item The item in the new slot
* @param isAsync Is this event called async?
*/
public CreateInventoryMapUpdateEvent(Player player, int slot, ItemStack item, boolean isAsync) {
super(isAsync);
this.player = player;
this.slot = slot;
this.item = item;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
/**
* Get the {@link MapWrapper} of the map of this event
*
* @return The {@link MapWrapper}
*/
@Nullable
public MapWrapper getMapWrapper() {
if (mapWrapper == null) {
if (item == null) return null;
if (item.getType() != Material.MAP) return null;
mapWrapper = MapReflectionAPI.getMapManager().getWrapperForId(player, item.getDurability());
}
return mapWrapper;
}
}

View file

@ -0,0 +1,60 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api.events;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event gets fired when a map creation is cancelled
*/
@RequiredArgsConstructor
@Getter
public class MapCancelEvent extends Event implements Cancellable {
private static final HandlerList handlerList = new HandlerList();
@Setter
private boolean cancelled;
private final Player player;
private final int id;
/**
* Construct a new {@link MapCancelEvent}
*
* @param player The player who tried to create the map
* @param id The ID of the map
* @param isAsync Is this event called async?
*/
public MapCancelEvent(Player player, int id, boolean isAsync) {
super(isAsync);
this.player = player;
this.id = id;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
}

View file

@ -0,0 +1,60 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api.events;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import tech.sbdevelopment.mapreflectionapi.api.MapWrapper;
import tech.sbdevelopment.mapreflectionapi.api.ArrayImage;
/**
* This event gets fired when the content of a {@link MapWrapper} is updated
*/
@RequiredArgsConstructor
@Getter
public class MapContentUpdateEvent extends Event {
private static final HandlerList handlerList = new HandlerList();
private final MapWrapper wrapper;
private final ArrayImage content;
@Setter
private boolean sendContent = true;
/**
* Construct a new {@link MapContentUpdateEvent}
*
* @param wrapper The wrapper that will be updated
* @param content The content that will be shown
* @param isAsync Is this event called async?
*/
public MapContentUpdateEvent(MapWrapper wrapper, ArrayImage content, boolean isAsync) {
super(isAsync);
this.wrapper = wrapper;
this.content = content;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
}

View file

@ -0,0 +1,105 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api.events;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.api.MapWrapper;
/**
* This event gets fired when a player interact with a map
*/
@RequiredArgsConstructor
@Getter
public class MapInteractEvent extends Event implements Cancellable {
private static final HandlerList handlerList = new HandlerList();
@Setter
private boolean cancelled;
private final Player player;
private final int entityID;
private final int action;
private final Vector vector;
private final int hand;
private ItemFrame frame;
private MapWrapper mapWrapper;
/**
* Construct a new {@link MapInteractEvent}
*
* @param player The player who interacted
* @param entityID The ID of the entity the map is in
* @param action The interact action
* @param vector The location of the entity
* @param hand The hand the player clicked with
* @param isAsync Is this event called async?
*/
public MapInteractEvent(Player player, int entityID, int action, Vector vector, int hand, boolean isAsync) {
super(isAsync);
this.player = player;
this.entityID = entityID;
this.action = action;
this.vector = vector;
this.hand = hand;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
/**
* Get the {@link ItemFrame} the map is in
*
* @return The frame the map is in, or null if it's not a map
*/
@Nullable
public ItemFrame getFrame() {
if (getMapWrapper() == null) return null;
if (frame == null) {
frame = getMapWrapper().getController().getItemFrameById(player.getWorld(), entityID);
}
return frame;
}
/**
* Get the {@link MapWrapper} of the map
*
* @return The wrapper
*/
@Nullable
public MapWrapper getMapWrapper() {
if (mapWrapper == null) {
mapWrapper = MapReflectionAPI.getMapManager().getWrapperForId(player, entityID);
}
return mapWrapper;
}
}

View file

@ -0,0 +1,22 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* The Events package contains all the custom events the API fires.
*/
package tech.sbdevelopment.mapreflectionapi.api.events;

View file

@ -0,0 +1,28 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api.exceptions;
import lombok.experimental.StandardException;
/**
* This exception gets thrown if no map IDs are available
*/
@StandardException
public class MapLimitExceededException extends Exception {
}

View file

@ -0,0 +1,22 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* The Exceptions package contains all the custom exceptions the API may throw.
*/
package tech.sbdevelopment.mapreflectionapi.api.exceptions;

View file

@ -0,0 +1,22 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* The API package contains all the API you can interact with.
*/
package tech.sbdevelopment.mapreflectionapi.api;

View file

@ -0,0 +1,58 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.cmd;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import tech.sbdevelopment.mapreflectionapi.managers.Configuration;
import java.util.ArrayList;
import java.util.List;
public class MapManagerCMD implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, @NotNull String[] args) {
if (!sender.hasPermission("mapmanager.reload")) {
sender.sendMessage(ChatColor.RED + "You don't have the permissions to use this command!");
return false;
}
if (args.length == 1 && args[0].equalsIgnoreCase("reload")) {
Configuration.getInstance().reload();
sender.sendMessage(ChatColor.GREEN + "The configuration has been reloaded!");
return true;
}
sender.sendMessage(ChatColor.GREEN + "Usage: " + ChatColor.WHITE + "/mapmanager reload");
return false;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, @NotNull String[] args) {
if (args.length == 1) return StringUtil.copyPartialMatches(args[0], List.of("reload"), new ArrayList<>());
return new ArrayList<>();
}
}

View file

@ -0,0 +1,44 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.listeners;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.MapInitializeEvent;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.managers.Configuration;
public class MapListener implements Listener {
@EventHandler
public void onQuit(PlayerQuitEvent e) {
MapReflectionAPI.getMapManager().clearAllMapsFor(e.getPlayer());
}
@EventHandler
public void onMapInitialize(MapInitializeEvent e) {
if (Configuration.getInstance().isAllowVanilla()) {
int id = e.getMap().getId();
if (id > 0) {
MapReflectionAPI.getInstance().getLogger().info("Detected that the Map ID " + id + " got occupied. It will now not be used.");
MapReflectionAPI.getMapManager().registerOccupiedID(id);
}
}
}
}

View file

@ -0,0 +1,85 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.sbdevelopment.mapreflectionapi.listeners;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.util.Vector;
public abstract class PacketListener implements Listener {
protected JavaPlugin plugin;
public static PacketListener construct(JavaPlugin plugin) throws IllegalStateException {
String packageName = Bukkit.getServer().getClass().getPackage().getName();
String version = packageName.substring(packageName.lastIndexOf('.') + 1);
plugin.getLogger().info("Initializing the packet handler for Minecraft version " + version + "...");
try {
final Class<?> clazz = Class.forName("tech.sbdevelopment.mapreflectionapi.nms.PacketListener_" + version);
if (PacketListener.class.isAssignableFrom(clazz)) {
return (PacketListener) clazz.getDeclaredConstructor().newInstance();
} else {
throw new IllegalStateException("Plugin corrupted! Detected invalid PacketListener class.");
}
} catch (Exception ex) {
throw new IllegalStateException("This Minecraft version (" + version + ") is not supported! Contact the developer to get support.");
}
}
public void init(JavaPlugin plugin) {
this.plugin = plugin;
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@EventHandler
public void onJoin(PlayerJoinEvent e) {
injectPlayer(e.getPlayer());
}
@EventHandler
public void onQuit(PlayerQuitEvent e) {
removePlayer(e.getPlayer());
}
protected abstract void injectPlayer(Player p);
public abstract void removePlayer(Player p);
protected abstract Vector vec3DToVector(Object vec3d);
protected boolean hasField(Object packet, String field) {
try {
packet.getClass().getDeclaredField(field);
return true;
} catch (NoSuchFieldException ex) {
return false;
}
}
}

View file

@ -0,0 +1,58 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.managers;
import lombok.Getter;
import org.bukkit.plugin.java.JavaPlugin;
import tech.sbdevelopment.mapreflectionapi.utils.YamlFile;
public class Configuration {
private static Configuration instance;
private final YamlFile file;
@Getter
private boolean allowVanilla = true;
@Getter
private boolean imageCache = true;
@Getter
private boolean updaterCheck = true;
@Getter
private boolean updaterDownload = true;
private Configuration(JavaPlugin plugin) {
this.file = new YamlFile(plugin, "config");
reload();
}
public static void init(JavaPlugin plugin) {
instance = new Configuration(plugin);
}
public static Configuration getInstance() {
if (instance == null) throw new IllegalStateException("The plugin is not enabled yet!");
return instance;
}
public void reload() {
allowVanilla = this.file.getFile().getBoolean("allowVanilla");
imageCache = this.file.getFile().getBoolean("imageCache");
updaterCheck = this.file.getFile().getBoolean("updater.check");
updaterDownload = this.file.getFile().getBoolean("updater.download");
}
}

View file

@ -0,0 +1,44 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.utils;
public class MainUtil {
private MainUtil() {
}
/**
* Gets whether this is a headless JDK that doesn't contain the Java AWT library
*
* @return True if java.awt is not available
*/
public static boolean isHeadlessJDK() {
try {
Class.forName("java.awt.Color");
return false;
} catch (ClassNotFoundException ex) {
return true;
}
}
public static <A, B> void validateArrayDimensions(A[][] arrayOne, B[][] arrayTwo) {
if (arrayOne.length != arrayTwo.length || arrayOne[0].length != arrayTwo[0].length) {
throw new IllegalArgumentException("The dimensions of two provided arrays (" + arrayOne.getClass().getName() + ", " + arrayTwo.getClass().getName() + ") do not match!");
}
}
}

View file

@ -0,0 +1,537 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.utils;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.CompletableFuture;
/**
* <b>ReflectionUtil</b> - Reflection handler for NMS and CraftBukkit.<br>
* Caches the packet related methods and is asynchronous.
* <p>
* This class does not handle null checks as most of the requests are from the
* other utility classes that already handle null checks.
* <p>
* <a href="https://wiki.vg/Protocol">Clientbound Packets</a> are considered fake
* updates to the client without changing the actual data. Since all the data is handled
* by the server.
*
* @author Crypto Morin, Stijn Bannink
* @version 2.1
*/
public class ReflectionUtil {
/**
* We use reflection mainly to avoid writing a new class for version barrier.
* The version barrier is for NMS that uses the Minecraft version as the main package name.
* <p>
* E.g. EntityPlayer in 1.15 is in the class {@code net.minecraft.server.v1_15_R1}
* but in 1.14 it's in {@code net.minecraft.server.v1_14_R1}
* In order to maintain cross-version compatibility we cannot import these classes.
* <p>
* Performance is not a concern for these specific statically initialized values.
*/
public static final String VERSION;
static { // This needs to be right below VERSION because of initialization order.
// This package loop is used to avoid implementation-dependant strings like Bukkit.getVersion() or Bukkit.getBukkitVersion()
// which allows easier testing as well.
String found = null;
for (Package pack : Package.getPackages()) {
String name = pack.getName();
// .v because there are other packages.
if (name.startsWith("org.bukkit.craftbukkit.v")) {
found = pack.getName().split("\\.")[3];
// Just a final guard to make sure it finds this important class.
// As a protection for forge+bukkit implementation that tend to mix versions.
// The real CraftPlayer should exist in the package.
// Note: Doesn't seem to function properly. Will need to separate the version
// handler for NMS and CraftBukkit for softwares like catmc.
try {
Class.forName("org.bukkit.craftbukkit." + found + ".entity.CraftPlayer");
break;
} catch (ClassNotFoundException e) {
found = null;
}
}
}
if (found == null)
throw new IllegalArgumentException("Failed to parse server version. Could not find any package starting with name: 'org.bukkit.craftbukkit.v'");
VERSION = found;
}
/**
* The raw minor version number.
* E.g. {@code v1_17_R1} to {@code 17}
*
* @since 4.0.0
*/
public static final int VER = Integer.parseInt(VERSION.substring(1).split("_")[1]);
/**
* The raw minor version number.
* E.g. {@code v1_18_R2} to {@code 2}
*
* @since 4.0.0
*/
public static final int VER_MINOR = toInt(VERSION.substring(1).split("_")[2].substring(1), 0);
/**
* Mojang remapped their NMS in 1.17 https://www.spigotmc.org/threads/spigot-bungeecord-1-17.510208/#post-4184317
*/
public static final String
CRAFTBUKKIT = "org.bukkit.craftbukkit." + VERSION + '.',
NMS = v(17, "net.minecraft.").orElse("net.minecraft.server." + VERSION + '.');
/**
* A nullable public accessible field only available in {@code EntityPlayer}.
* This can be null if the player is offline.
*/
private static final MethodHandle PLAYER_CONNECTION;
/**
* Responsible for getting the NMS handler {@code EntityPlayer} object for the player.
* {@code CraftPlayer} is simply a wrapper for {@code EntityPlayer}.
* Used mainly for handling packet related operations.
* <p>
* This is also where the famous player {@code ping} field comes from!
*/
private static final MethodHandle GET_HANDLE;
private static final MethodHandle GET_HANDLE_WORLD;
/**
* Sends a packet to the player's client through a {@code NetworkManager} which
* is where {@code ProtocolLib} controls packets by injecting channels!
*/
private static final MethodHandle SEND_PACKET;
static {
Class<?> entityPlayer = getNMSClass("server.level", "EntityPlayer");
Class<?> worldServer = getNMSClass("server.level", "WorldServer");
Class<?> craftPlayer = getCraftClass("entity.CraftPlayer");
Class<?> craftWorld = getCraftClass("CraftWorld");
Class<?> playerConnection = getNMSClass("server.network", "PlayerConnection");
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle sendPacket = null;
MethodHandle getHandle = null;
MethodHandle getHandleWorld = null;
MethodHandle connection = null;
try {
connection = lookup.findGetter(entityPlayer,
supports(20) ? "c" : supports(17) ? "b" : "playerConnection", playerConnection);
getHandle = lookup.findVirtual(craftPlayer, "getHandle", MethodType.methodType(entityPlayer));
getHandleWorld = lookup.findVirtual(craftWorld, "getHandle", MethodType.methodType(worldServer));
sendPacket = lookup.findVirtual(playerConnection,
v(18, "a").orElse("sendPacket"),
MethodType.methodType(void.class, getNMSClass("network.protocol", "Packet")));
} catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
}
PLAYER_CONNECTION = connection;
SEND_PACKET = sendPacket;
GET_HANDLE = getHandle;
GET_HANDLE_WORLD = getHandleWorld;
}
private ReflectionUtil() {
}
/**
* This method is purely for readability.
* No performance is gained.
*
* @since 5.0.0
*/
public static <T> VersionHandler<T> v(int version, T handle) {
return new VersionHandler<>(version, handle);
}
/**
* Checks whether the server version is equal or greater than the given version.
*
* @param version the version to compare the server version with.
* @return true if the version is equal or newer, otherwise false.
* @since 4.0.0
*/
public static boolean supports(int version) {
return VER >= version;
}
/**
* Checks whether the server version is equal or greater than the given version.
* <p>
* PAY ATTENTION! The minor version is based on the NMS version.
* This means that v1_19_R3 has major version 19 and minor version 3.
*
* @param major the major version to compare the server version with.
* @param minor the minor version to compare the server version with.
* @return true if the version is equal or newer, otherwise false.
* @since 4.0.0
*/
public static boolean supports(int major, int minor) {
return VER >= major && VER_MINOR >= minor;
}
/**
* Helper class converted to {@link List}
*
* @param <E> The storage type
*/
public static class ListParam<E> extends ArrayList<E> {
}
/**
* Helper class converted to {@link Collection}
*
* @param <E> The storage type
*/
public static class CollectionParam<E> extends ArrayList<E> {
}
private static Class<?> wrapperToPrimitive(Class<?> clazz) {
if (clazz == Boolean.class) return boolean.class;
if (clazz == Integer.class) return int.class;
if (clazz == Double.class) return double.class;
if (clazz == Float.class) return float.class;
if (clazz == Long.class) return long.class;
if (clazz == Short.class) return short.class;
if (clazz == Byte.class) return byte.class;
if (clazz == Void.class) return void.class;
if (clazz == Character.class) return char.class;
if (clazz == CollectionParam.class) return Collection.class;
if (clazz == ListParam.class) return List.class;
if (clazz == ArrayList.class) return Collection.class; //LEGACY!
if (clazz == HashMap.class) return Map.class;
return clazz;
}
private static Class<?>[] toParamTypes(Object... params) {
return Arrays.stream(params)
.map(obj -> obj != null ? wrapperToPrimitive(obj.getClass()) : null)
.toArray(Class<?>[]::new);
}
@Nullable
public static Class<?> getClass(@NotNull String name) {
try {
return Class.forName(name);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
}
}
@Nullable
public static Object callConstructorNull(Class<?> clazz, Class<?> paramClass) {
try {
Constructor<?> con = clazz.getConstructor(paramClass);
con.setAccessible(true);
return con.newInstance(clazz.cast(null));
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
InvocationTargetException ex) {
ex.printStackTrace();
return null;
}
}
@Nullable
public static Object callFirstConstructor(Class<?> clazz, Object... params) {
try {
Constructor<?> con = clazz.getConstructors()[0];
con.setAccessible(true);
return con.newInstance(params);
} catch (IllegalAccessException | InstantiationException |
InvocationTargetException ex) {
ex.printStackTrace();
return null;
}
}
@Nullable
public static Object callConstructor(Class<?> clazz, Object... params) {
try {
Constructor<?> con = clazz.getConstructor(toParamTypes(params));
con.setAccessible(true);
return con.newInstance(params);
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
InvocationTargetException ex) {
ex.printStackTrace();
return null;
}
}
@Nullable
public static Object callDeclaredConstructor(Class<?> clazz, Object... params) {
try {
Constructor<?> con = clazz.getDeclaredConstructor(toParamTypes(params));
con.setAccessible(true);
return con.newInstance(params);
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
InvocationTargetException ex) {
ex.printStackTrace();
return null;
}
}
@Nullable
public static Object callMethod(Class<?> clazz, String method, Object... params) {
try {
Method m = clazz.getMethod(method, toParamTypes(params));
m.setAccessible(true);
return m.invoke(null, params);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
ex.printStackTrace();
return null;
}
}
@Nullable
public static Object callMethod(Object obj, String method, Object... params) {
try {
Method m = obj.getClass().getMethod(method, toParamTypes(params));
m.setAccessible(true);
return m.invoke(obj, params);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
ex.printStackTrace();
return null;
}
}
@Nullable
public static Object callDeclaredMethod(Object obj, String method, Object... params) {
try {
Method m = obj.getClass().getDeclaredMethod(method, toParamTypes(params));
m.setAccessible(true);
return m.invoke(obj, params);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
ex.printStackTrace();
return null;
}
}
public static boolean hasField(Object packet, String field) {
try {
packet.getClass().getDeclaredField(field);
return true;
} catch (NoSuchFieldException ex) {
return false;
}
}
@Nullable
public static Object getField(Object object, String field) {
try {
Field f = object.getClass().getField(field);
f.setAccessible(true);
return f.get(object);
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
return null;
}
}
@Nullable
public static Object getDeclaredField(Class<?> clazz, String field) {
try {
Field f = clazz.getDeclaredField(field);
f.setAccessible(true);
return f.get(null);
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
return null;
}
}
@Nullable
public static Object getDeclaredField(Object object, String field) {
try {
Field f = object.getClass().getDeclaredField(field);
f.setAccessible(true);
return f.get(object);
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
return null;
}
}
public static void setDeclaredField(Object object, String field, Object value) {
try {
Field f = object.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(object, value);
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
}
}
/**
* Get a NMS (net.minecraft.server) class which accepts a package for 1.17 compatibility.
*
* @param newPackage the 1.17 package name.
* @param name the name of the class.
* @return the NMS class or null if not found.
* @since 4.0.0
*/
@javax.annotation.Nullable
public static Class<?> getNMSClass(@Nonnull String newPackage, @Nonnull String name) {
if (supports(17)) name = newPackage + '.' + name;
return getNMSClass(name);
}
/**
* Get a NMS (net.minecraft.server) class.
*
* @param name the name of the class.
* @return the NMS class or null if not found.
* @since 1.0.0
*/
@javax.annotation.Nullable
public static Class<?> getNMSClass(@Nonnull String name) {
try {
return Class.forName(NMS + name);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
}
}
/**
* Sends a packet to the player asynchronously if they're online.
* Packets are thread-safe.
*
* @param player the player to send the packet to.
* @param packets the packets to send.
* @return the async thread handling the packet.
* @see #sendPacketSync(Player, Object...)
* @since 1.0.0
*/
@Nonnull
public static CompletableFuture<Void> sendPacket(@Nonnull Player player, @Nonnull Object... packets) {
return CompletableFuture.runAsync(() -> sendPacketSync(player, packets))
.exceptionally(ex -> {
ex.printStackTrace();
return null;
});
}
/**
* Sends a packet to the player synchronously if they're online.
*
* @param player the player to send the packet to.
* @param packets the packets to send.
* @see #sendPacket(Player, Object...)
* @since 2.0.0
*/
public static void sendPacketSync(@Nonnull Player player, @Nonnull Object... packets) {
try {
Object handle = GET_HANDLE.invoke(player);
Object connection = PLAYER_CONNECTION.invoke(handle);
// Checking if the connection is not null is enough. There is no need to check if the player is online.
if (connection != null) {
for (Object packet : packets) SEND_PACKET.invoke(connection, packet);
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@javax.annotation.Nullable
public static Object getHandle(@Nonnull Player player) {
Objects.requireNonNull(player, "Cannot get handle of null player");
try {
return GET_HANDLE.invoke(player);
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
@javax.annotation.Nullable
public static Object getHandle(@Nonnull World world) {
Objects.requireNonNull(world, "Cannot get handle of null world");
try {
return GET_HANDLE_WORLD.invoke(world);
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
/**
* Get a CraftBukkit (org.bukkit.craftbukkit) class.
*
* @param name the name of the class to load.
* @return the CraftBukkit class or null if not found.
* @since 1.0.0
*/
@javax.annotation.Nullable
public static Class<?> getCraftClass(@Nonnull String name) {
try {
return Class.forName(CRAFTBUKKIT + name);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
}
}
public static final class VersionHandler<T> {
private int version;
private T handle;
private VersionHandler(int version, T handle) {
if (supports(version)) {
this.version = version;
this.handle = handle;
}
}
public VersionHandler<T> v(int version, T handle) {
if (version == this.version)
throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version);
if (version > this.version && supports(version)) {
this.version = version;
this.handle = handle;
}
return this;
}
public T orElse(T handle) {
return this.version == 0 ? handle : this.handle;
}
}
private static int toInt(String string, int def) {
return string.isBlank() ? def : Integer.parseInt(string);
}
}

View file

@ -0,0 +1,284 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.utils;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.function.BiConsumer;
/**
* Update checker class
*
* @author Stijn [SBDeveloper]
* @version 2.3 [27-09-2022] - Added Polymart support ; fixed Spigot support
* @since 05-03-2020
*/
public class UpdateManager {
private static final String SPIGOT_API = "https://api.spigotmc.org/legacy/update.php?resource=%d";
private static final String SPIGOT_DOWNLOAD = "https://api.spiget.org/v2/resources/%s/download";
private static final String POLYMART_API = "https://api.polymart.org/v1/getResourceInfoSimple/?resource_id=%d&key=version";
private static final String POLYMART_DOWNLOAD = "https://api.polymart.org/v1/requestUpdateURL/?inject_version=%d&resource_id=%d&user_id=%d&nonce=%d&download_agent=%d&download_time=%d&download_token=%s";
private final Plugin plugin;
private final Version currentVersion;
private final CheckType type;
//Spigot & Polymart
private final int resourceID;
//Polymart only
private int injector_version;
private int user_id;
private int nonce;
private int download_agent;
private int download_time;
private String download_token;
private BiConsumer<VersionResponse, Version> versionResponse;
private BiConsumer<DownloadResponse, String> downloadResponse;
/**
* Construct a new UpdateManager
*
* @param plugin The plugin instance
*/
public UpdateManager(Plugin plugin) {
this.plugin = plugin;
this.currentVersion = new Version(plugin.getDescription().getVersion());
this.type = CheckType.POLYMART_PAID;
this.resourceID = Integer.parseInt("%%__RESOURCE__%%");
this.injector_version = Integer.parseInt("%%__INJECT_VER__%%");
this.user_id = Integer.parseInt("%%__USER__%%");
this.nonce = Integer.parseInt("%%__NONCE__%%");
this.download_agent = Integer.parseInt("%%__AGENT__%%");
this.download_time = Integer.parseInt("%%__TIMESTAMP__%%");
this.download_token = "%%__VERIFY_TOKEN__%%";
}
public UpdateManager(Plugin plugin, int resourceID) {
this.plugin = plugin;
this.currentVersion = new Version(plugin.getDescription().getVersion());
this.type = CheckType.SPIGOT;
this.resourceID = resourceID;
}
/**
* Handle the response given by check();
*
* @param versionResponse The response
* @return The updatemanager
*/
public UpdateManager handleResponse(BiConsumer<VersionResponse, Version> versionResponse) {
this.versionResponse = versionResponse;
return this;
}
public UpdateManager handleDownloadResponse(BiConsumer<DownloadResponse, String> downloadResponse) {
this.downloadResponse = downloadResponse;
return this;
}
/**
* Check for a new version
*/
public void check() {
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> {
try {
HttpsURLConnection con;
if (type == CheckType.POLYMART_PAID) {
con = (HttpsURLConnection) new URL(String.format(POLYMART_API, this.resourceID)).openConnection();
} else {
con = (HttpsURLConnection) new URL(String.format(SPIGOT_API, this.resourceID)).openConnection();
}
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", "SBDChecker/2.1");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
Version onlineVersion = new Version(response.toString());
VersionResponse verRes = this.currentVersion.check(onlineVersion);
Bukkit.getScheduler().runTask(this.plugin, () -> this.versionResponse.accept(verRes, onlineVersion));
} catch (IOException | NullPointerException e) {
e.printStackTrace();
Bukkit.getScheduler().runTask(this.plugin, () -> this.versionResponse.accept(VersionResponse.UNAVAILABLE, null));
}
});
}
public void runUpdate() {
File pluginFile = getPluginFile(); // /plugins/XXX.jar
if (pluginFile == null) {
this.downloadResponse.accept(DownloadResponse.ERROR, null);
Bukkit.getLogger().info("Pluginfile is null");
return;
}
File updateFolder = Bukkit.getUpdateFolderFile();
if (!updateFolder.exists()) {
if (!updateFolder.mkdirs()) {
this.downloadResponse.accept(DownloadResponse.ERROR, null);
Bukkit.getLogger().info("Updatefolder doesn't exists, and can't be made");
return;
}
}
final File updateFile = new File(updateFolder, pluginFile.getName());
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> {
ReadableByteChannel channel;
try {
//https://stackoverflow.com/questions/921262/how-to-download-and-save-a-file-from-internet-using-java
HttpsURLConnection connection;
if (type == CheckType.POLYMART_PAID) {
connection = (HttpsURLConnection) new URL(String.format(POLYMART_DOWNLOAD, this.injector_version, this.resourceID, this.user_id, this.nonce, this.download_agent, this.download_time, this.download_token)).openConnection();
} else {
connection = (HttpsURLConnection) new URL(String.format(SPIGOT_DOWNLOAD, this.resourceID)).openConnection();
}
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
InputStream stream = connection.getInputStream();
if (connection.getResponseCode() != 200) {
BufferedReader in = new BufferedReader(new InputStreamReader(stream));
String inputLine;
StringBuilder responsestr = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
responsestr.append(inputLine);
}
in.close();
throw new RuntimeException("Download returned status #" + connection.getResponseCode(), new Throwable(responsestr.toString()));
}
channel = Channels.newChannel(stream);
} catch (IOException e) {
Bukkit.getScheduler().runTask(this.plugin, () -> this.downloadResponse.accept(DownloadResponse.ERROR, null));
e.printStackTrace();
return;
}
FileChannel fileChannel = null;
try {
FileOutputStream fosForDownloadedFile = new FileOutputStream(updateFile);
fileChannel = fosForDownloadedFile.getChannel();
fileChannel.transferFrom(channel, 0, Long.MAX_VALUE);
} catch (IOException e) {
Bukkit.getScheduler().runTask(this.plugin, () -> this.downloadResponse.accept(DownloadResponse.ERROR, null));
e.printStackTrace();
return;
} finally {
if (channel != null) {
try {
channel.close();
} catch (IOException ioe) {
System.out.println("Error while closing response body channel");
}
}
if (fileChannel != null) {
try {
fileChannel.close();
} catch (IOException ioe) {
System.out.println("Error while closing file channel for downloaded file");
}
}
}
Bukkit.getScheduler().runTask(this.plugin, () -> this.downloadResponse.accept(DownloadResponse.DONE, updateFile.getPath()));
});
}
private File getPluginFile() {
if (!(this.plugin instanceof JavaPlugin)) {
return null;
}
try {
Method method = JavaPlugin.class.getDeclaredMethod("getFile");
method.setAccessible(true);
return (File) method.invoke(this.plugin);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Could not get plugin file", e);
}
}
public enum CheckType {
SPIGOT, POLYMART_PAID
}
public enum VersionResponse {
LATEST, //Latest version
FOUND_NEW, //Newer available
THIS_NEWER, //Local version is newer?
UNAVAILABLE //Error
}
public enum DownloadResponse {
DONE, ERROR, UNAVAILABLE
}
public static class Version {
private final String version;
public final String get() {
return this.version;
}
private Version(String version) {
if (version == null)
throw new IllegalArgumentException("Version can not be null");
if (!version.matches("[0-9]+(\\.[0-9]+)*"))
throw new IllegalArgumentException("Invalid version format");
this.version = version;
}
private VersionResponse check(Version that) {
String[] thisParts = this.get().split("\\.");
String[] thatParts = that.get().split("\\.");
int length = Math.max(thisParts.length, thatParts.length);
for (int i = 0; i < length; i++) {
int thisPart = i < thisParts.length ? Integer.parseInt(thisParts[i]) : 0;
int thatPart = i < thatParts.length ? Integer.parseInt(thatParts[i]) : 0;
if (thisPart < thatPart)
return VersionResponse.FOUND_NEW;
if (thisPart > thatPart)
return VersionResponse.THIS_NEWER;
}
return VersionResponse.LATEST;
}
}
}

View file

@ -0,0 +1,82 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.utils;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.logging.Level;
public class YamlFile {
private final JavaPlugin plugin;
private final String name;
private FileConfiguration fileConfiguration;
private File file;
public YamlFile(JavaPlugin plugin, String name) {
this.plugin = plugin;
this.name = name;
saveDefaultFile();
}
public void reloadFile() {
if (this.file == null)
this.file = new File(this.plugin.getDataFolder(), name + ".yml");
this.fileConfiguration = YamlConfiguration.loadConfiguration(this.file);
InputStream defaultStream = this.plugin.getResource(name + ".yml");
if (defaultStream != null) {
YamlConfiguration defaultConfig = YamlConfiguration.loadConfiguration(new InputStreamReader(defaultStream));
this.fileConfiguration.setDefaults(defaultConfig);
}
}
public FileConfiguration getFile() {
if (this.fileConfiguration == null)
reloadFile();
return this.fileConfiguration;
}
public void saveFile() {
if (this.fileConfiguration == null || this.file == null)
return;
try {
this.fileConfiguration.save(this.file);
} catch (IOException e) {
plugin.getLogger().log(Level.SEVERE, "Couldn't save the file " + this.name + ".yml.", e);
}
}
public void saveDefaultFile() {
if (this.file == null)
this.file = new File(this.plugin.getDataFolder(), name + ".yml");
if (!this.file.exists())
this.plugin.saveResource(name + ".yml", false);
}
}