From ee06da5fb672578c1348b80777af5fa05f8e249e Mon Sep 17 00:00:00 2001 From: lax1dude Date: Fri, 3 May 2024 17:27:39 -0700 Subject: [PATCH] bungee impl half completed --- .../v1_8/plugin/eaglermotd/BitmapFile.java | 197 ++++++++++ .../eaglermotd/EaglerMOTDConfiguration.java | 52 +++ .../EaglerMOTDConnectionAdapter.java | 55 +++ .../EaglerMOTDConnectionUpdater.java | 364 ++++++++++++++++++ .../eaglermotd/EaglerMOTDLoggerAdapter.java | 26 ++ .../plugin/eaglermotd/EaglerMOTDUtils.java | 33 ++ .../v1_8/plugin/eaglermotd/MessagePool.java | 65 ++++ .../plugin/eaglermotd/MessagePoolEntry.java | 49 +++ .../bungee/EaglerMOTDConnectionBungee.java | 191 +++++++++ .../bungee/EaglerMOTDListenerBungee.java | 29 ++ .../bungee/EaglerMOTDPluginBungee.java | 34 ++ .../plugin/eaglermotd/default_frames.json | 44 +++ .../plugin/eaglermotd/default_messages.json | 24 ++ .../plugin/eaglermotd/default_queries.json | 37 ++ .../plugin/eaglermotd/server-icons-test.png | Bin 0 -> 35663 bytes src/main/resources/plugin.yml | 5 + 16 files changed, 1205 insertions(+) create mode 100644 src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/BitmapFile.java create mode 100644 src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDConfiguration.java create mode 100644 src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDConnectionAdapter.java create mode 100644 src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDConnectionUpdater.java create mode 100644 src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDLoggerAdapter.java create mode 100644 src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDUtils.java create mode 100644 src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/MessagePool.java create mode 100644 src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/MessagePoolEntry.java create mode 100644 src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/bungee/EaglerMOTDConnectionBungee.java create mode 100644 src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/bungee/EaglerMOTDListenerBungee.java create mode 100644 src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/bungee/EaglerMOTDPluginBungee.java create mode 100644 src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/default_frames.json create mode 100644 src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/default_messages.json create mode 100644 src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/default_queries.json create mode 100644 src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/server-icons-test.png create mode 100644 src/main/resources/plugin.yml diff --git a/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/BitmapFile.java b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/BitmapFile.java new file mode 100644 index 0000000..9db1c07 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/BitmapFile.java @@ -0,0 +1,197 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.imageio.ImageIO; + +/** + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class BitmapFile { + + public final String name; + public final int[][] frame; + public final int w, h; + + public BitmapFile(String name, int[][] frame, int w, int h) { + this.name = name; + this.frame = frame; + this.w = w; + this.h = h; + } + + public static final Map bitmapCache = new HashMap(); + + public static BitmapFile getCachedIcon(String name) { + BitmapFile ret = bitmapCache.get(name); + if(ret == null) { + File f = new File(name); + if(f.exists()) { + try { + BufferedImage img = ImageIO.read(f); + int w = img.getWidth(); + int h = img.getHeight(); + if(w < 64 || h < 64) { + System.err.println("[EaglerMOTD] Icon '" + name + "' must be at least be 64x64 pixels large (it is " + w + "x" + h + ")"); + }else { + int[][] load = new int[w][h]; + for(int y = 0; y < h; ++y) { + for(int x = 0; x < w; ++x) { + load[x][y] = img.getRGB(x, y); + } + } + ret = new BitmapFile(name, load, w, h); + bitmapCache.put(name, ret); + } + } catch (IOException e) { + System.err.println("[EaglerMOTD] Could not load icon file: '" + name + "'"); + System.err.println("[EaglerMOTD] Place the file in the same directory as 'messages.json'"); + e.printStackTrace(); + } + } + } + return ret; + } + + public int[] getSprite(int x, int y) { + if(x < 0 || y < 0) { + return null; + } + int offsetX = x; + int offsetY = y; + if(offsetX + 64 > w || offsetY + 64 > h) { + return null; + } + int[] ret = new int[64 * 64]; + for(int i = 0; i < ret.length; ++i) { + int xx = i % 64; + int yy = i / 64; + ret[i] = frame[offsetX + xx][offsetY + yy]; + } + return ret; + } + + public static int[] makeColor(int[] in, float r, float g, float b, float a) { + if(r < 0.0f) r = 0.0f; + if(r > 1.0f) r = 1.0f; + if(g < 0.0f) g = 0.0f; + if(g > 1.0f) g = 1.0f; + if(b < 0.0f) b = 0.0f; + if(b > 1.0f) b = 1.0f; + if(a < 0.0f) a = 0.0f; + if(a > 1.0f) a = 1.0f; + int c = ((int)(a*255.0f) << 24) | ((int)(r*255.0f) << 16) | ((int)(g*255.0f) << 8) | (int)(b*255.0f); + for(int i = 0; i < in.length; ++i) { + in[i] = c; + } + return in; + } + + public static int[] applyColor(int[] in, float r, float g, float b, float a) { + for(int i = 0; i < in.length; ++i) { + float rr = ((in[i] >> 16) & 0xFF) / 255.0f; + float gg = ((in[i] >> 8) & 0xFF) / 255.0f; + float bb = (in[i] & 0xFF) / 255.0f; + float aa = ((in[i] >> 24) & 0xFF) / 255.0f; + rr = r * a + rr * (1.0f - a); + gg = g * a + gg * (1.0f - a); + bb = b * a + bb * (1.0f - a); + aa = a + aa * (1.0f - a); + if(rr < 0.0f) rr = 0.0f; + if(rr > 1.0f) rr = 1.0f; + if(gg < 0.0f) gg = 0.0f; + if(gg > 1.0f) gg = 1.0f; + if(bb < 0.0f) bb = 0.0f; + if(bb > 1.0f) bb = 1.0f; + if(aa < 0.0f) aa = 0.0f; + if(aa > 1.0f) aa = 1.0f; + in[i] = ((int)(aa*255.0f) << 24) | ((int)(rr*255.0f) << 16) | ((int)(gg*255.0f) << 8) | (int)(bb*255.0f); + } + return in; + } + + public static int[] applyTint(int[] in, float r, float g, float b, float a) { + for(int i = 0; i < in.length; ++i) { + float rr = ((in[i] >> 16) & 0xFF) / 255.0f * r; + float gg = ((in[i] >> 8) & 0xFF) / 255.0f * g; + float bb = (in[i] & 0xFF) / 255.0f * b; + float aa = ((in[i] >> 24) & 0xFF) / 255.0f * a; + if(rr < 0.0f) rr = 0.0f; + if(rr > 1.0f) rr = 1.0f; + if(gg < 0.0f) gg = 0.0f; + if(gg > 1.0f) gg = 1.0f; + if(bb < 0.0f) bb = 0.0f; + if(bb > 1.0f) bb = 1.0f; + if(aa < 0.0f) aa = 0.0f; + if(aa > 1.0f) aa = 1.0f; + in[i] = ((int)(aa*255.0f) << 24) | ((int)(rr*255.0f) << 16) | ((int)(gg*255.0f) << 8) | (int)(bb*255.0f); + } + return in; + } + + private static final ThreadLocal flipTmpBuffer = ThreadLocal.withInitial(() -> new int[64]); + + public static int[] flipX(int[] newIcon) { + int[] tmp = flipTmpBuffer.get(); + for(int y = 0; y < 64; ++y) { + int o = y * 64; + System.arraycopy(newIcon, o, tmp, 0, 64); + for(int i = 0; i < 64; ++i) { + newIcon[o + i] = tmp[63 - i]; + } + } + return newIcon; + } + + public static int[] flipY(int[] newIcon) { + int[] tmp = flipTmpBuffer.get(); + for(int x = 0; x < 64; ++x) { + for(int i = 0; i < 64; ++i) { + tmp[i] = newIcon[i * 64 + x]; + } + for(int i = 0; i < 64; ++i) { + newIcon[i * 64 + x] = tmp[63 - i]; + } + } + return newIcon; + } + + private static int[] transpose(int[] in) { + int[] ret = new int[64*64]; + for(int y = 0; y < 64; ++y) { + for(int x = 0; x < 64; ++x) { + ret[x * 64 + y] = in[y * 64 + x]; + } + } + return ret; + } + + public static int[] rotate(int[] newIcon, int rotate) { + rotate = rotate % 4; + if(rotate == 1) { + newIcon = flipX(transpose(newIcon)); + }else if(rotate == 2) { + newIcon = flipY(flipX(newIcon)); + }else if(rotate == 3) { + newIcon = flipY(transpose(newIcon)); + } + return newIcon; + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDConfiguration.java b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDConfiguration.java new file mode 100644 index 0000000..36c2d07 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDConfiguration.java @@ -0,0 +1,52 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gson.JsonObject; + +/** + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class EaglerMOTDConfiguration { + + public final Map> messages = new HashMap(); + public final Map messagePools = new HashMap(); + public final Map framesCache = new HashMap(); + public int close_socket_after = 1200; + public int max_sockets_per_ip = 10; + public int max_total_sockets = 256; + + public void reload(File pluginDir) { + + } + + public static final char COLOR_CHAR = '\u00A7'; + public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoRrXx"; + + public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) { + char[] b = textToTranslate.toCharArray(); + for (int i = 0; i < b.length - 1; i++) { + if (b[i] == altColorChar && ALL_CODES.indexOf(b[i + 1]) > -1) { + b[i] = EaglerMOTDConfiguration.COLOR_CHAR; + b[i + 1] = Character.toLowerCase(b[i + 1]); + } + } + return new String(b); + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDConnectionAdapter.java b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDConnectionAdapter.java new file mode 100644 index 0000000..1b8eac6 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDConnectionAdapter.java @@ -0,0 +1,55 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd; + +import java.net.InetAddress; +import java.util.List; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public interface EaglerMOTDConnectionAdapter { + + boolean isClosed(); + void close(); + + String getAccept(); + InetAddress getAddress(); + String getListener(); + long getConnectionTimestamp(); + long getConnectionAge(); + + void sendToUser(); + + String getLine1(); + String getLine2(); + List getPlayerList(); + int[] getBitmap(); + int getOnlinePlayers(); + int getMaxPlayers(); + String getSubType(); + + void setLine1(String p); + void setLine2(String p); + void setPlayerList(List p); + void setPlayerList(String... p); + void setBitmap(int[] p); + void setOnlinePlayers(int i); + void setMaxPlayers(int i); + void setKeepAlive(boolean b); + + int getDefaultMaxPlayers(); + int getDefaultOnlinePlayers(); + List getDefaultOnlinePlayersList(int maxLen); + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDConnectionUpdater.java b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDConnectionUpdater.java new file mode 100644 index 0000000..edd5109 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDConnectionUpdater.java @@ -0,0 +1,364 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +/** + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class EaglerMOTDConnectionUpdater { + + public final EaglerMOTDConfiguration conf; + public final String listenerName; + public final int defaultMaxPlayers; + public final EaglerMOTDConnectionAdapter motd; + + public MessagePoolEntry currentMessage = null; + public int messageTimeTimer = 0; + public int messageIntervalTimer = 0; + public int currentFrame = 0; + public int ageTimer = 0; + + public BitmapFile bitmap = null; + public int spriteX = 0; + public int spriteY = 0; + public boolean flipX = false; + public boolean flipY = false; + public int rotate = 0; + public float[] color = new float[] { 0.0f, 0.0f, 0.0f, 0.0f }; + public float[] tint = new float[] { 0.0f, 0.0f, 0.0f, 0.0f }; + + private Random rand = null; + + public EaglerMOTDConnectionUpdater(EaglerMOTDConfiguration conf, String listenerName, int defaultMaxPlayers, EaglerMOTDConnectionAdapter m) { + this.conf = conf; + this.motd = m; + this.listenerName = listenerName; + this.defaultMaxPlayers = defaultMaxPlayers; + } + + public boolean execute() { + MessagePool p = conf.messagePools.get(listenerName); + if(p == null) { + return false; + } + + messageTimeTimer = 0; + messageIntervalTimer = 0; + currentMessage = p.pickDefault(); + if(currentMessage.random || currentMessage.shuffle) { + rand = new Random(); + } + + currentFrame = currentMessage.random ? rand.nextInt(currentMessage.frames.size()) : 0; + + applyFrame(currentMessage.frames.get(currentFrame)); + if(currentMessage.interval > 0 || currentMessage.next != null) { + this.motd.setKeepAlive(true); + return true; + }else { + this.motd.setKeepAlive(false); + return false; + } + } + + public boolean tick() { + ageTimer++; + if(this.motd.isClosed()) { + return false; + } + if(ageTimer > conf.close_socket_after) { + this.motd.close(); + return false; + } + messageTimeTimer++; + if(messageTimeTimer >= currentMessage.timeout) { + if(currentMessage.next != null) { + if(currentMessage.next.equalsIgnoreCase("any") || currentMessage.next.equalsIgnoreCase("random")) { + MessagePool p = conf.messagePools.get(listenerName); + if(p == null) { + this.motd.close(); + return false; + } + if(p.messagePool.size() > 1) { + MessagePoolEntry m; + do { + m = p.pickNew(); + }while(m == currentMessage); + currentMessage = m; + } + }else { + if(!changeMessageTo(listenerName, currentMessage.next)) { + boolean flag = false; + for(String s : conf.messages.keySet()) { + if(!s.equalsIgnoreCase(listenerName) && changeMessageTo(s, currentMessage.next)) { + flag = true; + break; + } + } + if(!flag) { + this.motd.close(); + return false; + } + } + } + if(currentMessage == null) { + this.motd.close(); + return false; + } + messageTimeTimer = 0; + messageIntervalTimer = 0; + if(rand == null && (currentMessage.random || currentMessage.shuffle)) { + rand = new Random(); + } + currentFrame = currentMessage.random ? rand.nextInt(currentMessage.frames.size()) : 0; + applyFrame(currentMessage.frames.get(currentFrame)); + motd.sendToUser(); + if(currentMessage.next == null && currentMessage.interval <= 0) { + motd.close(); + return false; + }else { + return true; + } + }else { + this.motd.close(); + return false; + } + }else { + messageIntervalTimer++; + if(currentMessage.interval > 0 && messageIntervalTimer >= currentMessage.interval) { + messageIntervalTimer = 0; + if(currentMessage.frames.size() > 1) { + if(currentMessage.shuffle) { + int i; + do { + i = rand.nextInt(currentMessage.frames.size()); + }while(i == currentFrame); + currentFrame = i; + }else { + ++currentFrame; + if(currentFrame >= currentMessage.frames.size()) { + currentFrame = 0; + } + } + applyFrame(currentMessage.frames.get(currentFrame)); + motd.sendToUser(); + } + } + if(currentMessage.next == null && currentMessage.interval <= 0) { + motd.close(); + return false; + }else { + return true; + } + } + } + + private boolean changeMessageTo(String group, String s) { + if(group == null || s == null) { + return false; + } + List lst = conf.messages.get(group); + if(lst == null) { + return false; + } + for(MessagePoolEntry m : lst) { + if(m.name.equalsIgnoreCase(s)) { + currentMessage = m; + return true; + } + } + return false; + } + + public void applyFrame(JsonObject frame) { + boolean shouldPush = false; + JsonElement v = frame.get("online"); + if(v != null) { + if(v.isJsonPrimitive() && ((JsonPrimitive)v).isNumber()) { + motd.setOnlinePlayers(v.getAsInt()); + }else { + motd.setOnlinePlayers(motd.getDefaultOnlinePlayers()); + } + shouldPush = true; + } + v = frame.get("max"); + if(v != null) { + if(v.isJsonPrimitive() && ((JsonPrimitive)v).isNumber()) { + motd.setMaxPlayers(v.getAsInt()); + }else { + motd.setMaxPlayers(motd.getDefaultMaxPlayers()); + } + shouldPush = true; + } + v = frame.get("players"); + if(v != null) { + if(v.isJsonArray()) { + List players = new ArrayList(); + JsonArray vv = (JsonArray) v; + for(int i = 0, l = vv.size(); i < l; ++i) { + players.add(EaglerMOTDConfiguration.translateAlternateColorCodes('&', vv.get(i).getAsString())); + } + motd.setPlayerList(players); + }else { + motd.setPlayerList(motd.getDefaultOnlinePlayersList(9)); + } + shouldPush = true; + } + String line = optString(frame.get("text0"), optString(frame.get("text"), null)); + if(line != null) { + int ix = line.indexOf('\n'); + if(ix != -1) { + motd.setLine1(EaglerMOTDConfiguration.translateAlternateColorCodes('&', line.substring(0, ix))); + motd.setLine2(EaglerMOTDConfiguration.translateAlternateColorCodes('&', line.substring(ix + 1))); + }else { + motd.setLine1(EaglerMOTDConfiguration.translateAlternateColorCodes('&', line)); + } + line = optString(frame.get("text1"), null); + if(line != null) { + motd.setLine2(EaglerMOTDConfiguration.translateAlternateColorCodes('&', line)); + } + shouldPush = true; + } + if(!this.motd.getAccept().equalsIgnoreCase("motd.noicon")) { + boolean shouldRenderIcon = false; + JsonElement icon = frame.get("icon"); + if(icon != null) { + String asString = (icon.isJsonPrimitive() && ((JsonPrimitive)icon).isString()) ? icon.getAsString() : null; + shouldRenderIcon = true; + if(icon.isJsonNull() || asString == null || asString.equalsIgnoreCase("none") || asString.equalsIgnoreCase("default") + || asString.equalsIgnoreCase("null") || asString.equalsIgnoreCase("color")) { + bitmap = null; + }else { + bitmap = BitmapFile.getCachedIcon(asString); + } + spriteX = spriteY = rotate = 0; + flipX = flipY = false; + color = new float[] { 0.0f, 0.0f, 0.0f, 0.0f }; + tint = new float[] { 1.0f, 1.0f, 1.0f, 1.0f }; + } + int sprtX = optInt(frame.get("icon_spriteX"), -1) * 64; + if(sprtX >= 0 && sprtX != spriteX) { + shouldRenderIcon = true; + spriteX = sprtX; + } + int sprtY = optInt(frame.get("icon_spriteY"), -1) * 64; + if(sprtY >= 0 && sprtY != spriteY) { + shouldRenderIcon = true; + spriteY = sprtY; + } + sprtX = optInt(frame.get("icon_pixelX"), -1); + if(sprtX >= 0 && sprtX != spriteX) { + shouldRenderIcon = true; + spriteX = sprtX; + } + sprtY = optInt(frame.get("icon_pixelY"), -1); + if(sprtY >= 0 && sprtY != spriteY) { + shouldRenderIcon = true; + spriteY = sprtY; + } + JsonElement flip = frame.get("icon_flipX"); + if(flip != null) { + shouldRenderIcon = true; + if(flip.isJsonPrimitive() && ((JsonPrimitive)flip).isBoolean()) { + flipX = flip.getAsBoolean(); + }else { + flipX = false; + } + } + flip = frame.get("icon_flipY"); + if(flip != null) { + shouldRenderIcon = true; + if(flip.isJsonPrimitive() && ((JsonPrimitive)flip).isBoolean()) { + flipY = flip.getAsBoolean(); + }else { + flipY = false; + } + } + int rot = optInt(frame.get("icon_rotate"), -1); + if(rot >= 0) { + shouldRenderIcon = true; + rotate = rot % 4; + } + JsonArray colorF = optJSONArray(frame.get("icon_color")); + if(colorF != null && colorF.size() > 0) { + shouldRenderIcon = true; + color[0] = colorF.get(0).getAsFloat(); + color[1] = colorF.size() > 1 ? colorF.get(1).getAsFloat() : color[1]; + color[2] = colorF.size() > 2 ? colorF.get(2).getAsFloat() : color[2]; + color[3] = colorF.size() > 3 ? colorF.get(3).getAsFloat() : 1.0f; + } + colorF = optJSONArray(frame.get("icon_tint")); + if(colorF != null && colorF.size() > 0) { + shouldRenderIcon = true; + tint[0] = colorF.get(0).getAsFloat(); + tint[1] = colorF.size() > 1 ? colorF.get(1).getAsFloat() : tint[1]; + tint[2] = colorF.size() > 2 ? colorF.get(2).getAsFloat() : tint[2]; + tint[3] = colorF.size() > 3 ? colorF.get(3).getAsFloat() : 1.0f; + } + if(shouldRenderIcon) { + int[] newIcon = null; + if(bitmap != null) { + newIcon = bitmap.getSprite(spriteX, spriteY); + } + if(newIcon == null) { + newIcon = new int[64*64]; + } + newIcon = BitmapFile.applyTint(newIcon, tint[0], tint[1], tint[2], tint[3]); + if(color[3] > 0.0f) { + newIcon = BitmapFile.applyColor(newIcon, color[0], color[1], color[2], color[3]); + } + if(bitmap != null) { + if(flipX) { + newIcon = BitmapFile.flipX(newIcon); + } + if(flipY) { + newIcon = BitmapFile.flipY(newIcon); + } + if(rotate != 0) { + newIcon = BitmapFile.rotate(newIcon, rotate); + } + } + motd.setBitmap(newIcon); + shouldPush = true; + } + } + if(shouldPush) { + motd.sendToUser(); + } + } + + public void close() { + motd.close(); + } + + private static String optString(JsonElement el, String def) { + return (el != null && el.isJsonPrimitive() && ((JsonPrimitive)el).isString()) ? el.getAsString() : def; + } + + private static int optInt(JsonElement el, int def) { + return (el != null && el.isJsonPrimitive() && ((JsonPrimitive)el).isNumber()) ? el.getAsInt() : def; + } + + private static JsonArray optJSONArray(JsonElement el) { + return (el != null && el instanceof JsonArray) ? (JsonArray)el : null; + } +} diff --git a/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDLoggerAdapter.java b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDLoggerAdapter.java new file mode 100644 index 0000000..75ed523 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDLoggerAdapter.java @@ -0,0 +1,26 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public interface EaglerMOTDLoggerAdapter { + + void info(String msg); + + void warn(String msg); + + void error(String msg); + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDUtils.java b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDUtils.java new file mode 100644 index 0000000..197a137 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/EaglerMOTDUtils.java @@ -0,0 +1,33 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; + +/** + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class EaglerMOTDUtils { + + public static String makeListenerString(InetSocketAddress addr) { + InetAddress addrHost = addr.getAddress(); + if(addrHost instanceof Inet6Address) { + return "[" + addrHost.getHostAddress() + "]:" + addr.getPort(); + }else { + return addrHost.getHostAddress() + ":" + addr.getPort(); + } + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/MessagePool.java b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/MessagePool.java new file mode 100644 index 0000000..ceccbe9 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/MessagePool.java @@ -0,0 +1,65 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +/** + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class MessagePool { + + public final String poolName; + public final List messagePool = new LinkedList(); + public final Random random = new Random(); + + public MessagePool(String s) { + this.poolName = s; + } + + public void sort() { + Collections.sort(messagePool); + } + + public MessagePoolEntry pickNew() { + if(messagePool.size() <= 0) { + return null; + } + float f = 0.0f; + for(MessagePoolEntry m : messagePool) { + f += m.weight; + } + f *= random.nextFloat(); + float f2 = 0.0f; + for(MessagePoolEntry m : messagePool) { + f2 += m.weight; + if(f2 >= f) { + return m; + } + } + return messagePool.get(0); + } + + public MessagePoolEntry pickDefault() { + for(MessagePoolEntry m : messagePool) { + if("default".equalsIgnoreCase(m.name)) { + return m; + } + } + return pickNew(); + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/MessagePoolEntry.java b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/MessagePoolEntry.java new file mode 100644 index 0000000..09def8f --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/MessagePoolEntry.java @@ -0,0 +1,49 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd; + +import java.util.List; + +import com.google.gson.JsonObject; + +/** + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class MessagePoolEntry implements Comparable { + + public final String name; + public final int interval; + public final int timeout; + public final boolean random; + public final boolean shuffle; + public final float weight; + public final String next; + public final List frames; + + public MessagePoolEntry(int interval, int timeout, boolean random, boolean shuffle, float weight, String next, List frames, String name) { + this.interval = interval; + this.timeout = timeout; + this.random = random; + this.shuffle = shuffle; + this.weight = weight; + this.next = next; + this.frames = frames; + this.name = name; + } + + @Override + public int compareTo(MessagePoolEntry o) { + return Float.compare(weight, o.weight); + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/bungee/EaglerMOTDConnectionBungee.java b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/bungee/EaglerMOTDConnectionBungee.java new file mode 100644 index 0000000..ab4c3fa --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/bungee/EaglerMOTDConnectionBungee.java @@ -0,0 +1,191 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd.bungee; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd.EaglerMOTDConnectionAdapter; +import net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd.EaglerMOTDUtils; +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.MOTDConnection; +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig; +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query.MOTDQueryHandler; +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class EaglerMOTDConnectionBungee implements EaglerMOTDConnectionAdapter { + + private final MOTDConnection con; + private String listenerString = null; + + public EaglerMOTDConnectionBungee(MOTDConnection con) { + this.con = con; + } + + @Override + public boolean isClosed() { + return con.isClosed(); + } + + @Override + public void close() { + con.close(); + } + + @Override + public String getAccept() { + return con.getAccept(); + } + + @Override + public InetAddress getAddress() { + return con.getAddress(); + } + + @Override + public String getListener() { + if(listenerString == null) { + EaglerListenerConfig config = con.getListener(); + if(config.getAddress() != null) { + this.listenerString = EaglerMOTDUtils.makeListenerString(config.getAddress()); + }else if(config.getAddressV6() != null) { + this.listenerString = EaglerMOTDUtils.makeListenerString(config.getAddressV6()); + }else { + throw new RuntimeException("Listener does not have an address!?"); + } + } + return listenerString; + } + + @Override + public long getConnectionTimestamp() { + return con.getConnectionTimestamp(); + } + + @Override + public long getConnectionAge() { + return con.getConnectionAge(); + } + + @Override + public void sendToUser() { + con.sendToUser(); + } + + @Override + public String getLine1() { + return con.getLine1(); + } + + @Override + public String getLine2() { + return con.getLine2(); + } + + @Override + public List getPlayerList() { + return con.getPlayerList(); + } + + @Override + public int[] getBitmap() { + return con.getBitmap(); + } + + @Override + public int getOnlinePlayers() { + return con.getOnlinePlayers(); + } + + @Override + public int getMaxPlayers() { + return con.getMaxPlayers(); + } + + @Override + public String getSubType() { + return con.getSubType(); + } + + @Override + public void setLine1(String p) { + con.setLine1(p); + } + + @Override + public void setLine2(String p) { + con.setLine2(p); + } + + @Override + public void setPlayerList(List p) { + con.setPlayerList(p); + } + + @Override + public void setPlayerList(String... p) { + con.setPlayerList(p); + } + + @Override + public void setBitmap(int[] p) { + con.setBitmap(p); + } + + @Override + public void setOnlinePlayers(int i) { + con.setOnlinePlayers(i); + } + + @Override + public void setMaxPlayers(int i) { + con.setMaxPlayers(i); + } + + @Override + public void setKeepAlive(boolean b) { + // workaround for pre-1.2.0 EaglerXBungee + ((MOTDQueryHandler)con).setKeepAlive(b); + } + + @Override + public int getDefaultMaxPlayers() { + return con.getListener().getMaxPlayer(); + } + + @Override + public int getDefaultOnlinePlayers() { + return 0; + } + + @Override + public List getDefaultOnlinePlayersList(int maxLen) { + Collection ppl = BungeeCord.getInstance().getPlayers(); + List players = new ArrayList(Math.min(ppl.size(), maxLen + 1)); + for(ProxiedPlayer pp : ppl) { + players.add(pp.getDisplayName()); + if(players.size() >= maxLen) { + players.add("" + ChatColor.GRAY + ChatColor.ITALIC + "(" + (ppl.size() - players.size()) + " more)"); + break; + } + } + return players; + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/bungee/EaglerMOTDListenerBungee.java b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/bungee/EaglerMOTDListenerBungee.java new file mode 100644 index 0000000..2826558 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/bungee/EaglerMOTDListenerBungee.java @@ -0,0 +1,29 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd.bungee; + +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftMOTDEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class EaglerMOTDListenerBungee implements Listener { + + @EventHandler + public void handleMOTDEvent(EaglercraftMOTDEvent evt) { + + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/bungee/EaglerMOTDPluginBungee.java b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/bungee/EaglerMOTDPluginBungee.java new file mode 100644 index 0000000..9b38b38 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/bungee/EaglerMOTDPluginBungee.java @@ -0,0 +1,34 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd.bungee; + +import java.util.LinkedList; +import java.util.List; +import java.util.Timer; + +import net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd.EaglerMOTDConfiguration; +import net.lax1dude.eaglercraft.v1_8.plugin.eaglermotd.EaglerMOTDConnectionUpdater; +import net.md_5.bungee.api.plugin.Plugin; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class EaglerMOTDPluginBungee extends Plugin { + + private static EaglerMOTDPluginBungee instance = null; + + public final EaglerMOTDConfiguration conf = new EaglerMOTDConfiguration(); + public final Timer tickTimer = new Timer("MOTD Tick Timer"); + public final List motdConnections = new LinkedList(); + +} diff --git a/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/default_frames.json b/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/default_frames.json new file mode 100644 index 0000000..65a0500 --- /dev/null +++ b/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/default_frames.json @@ -0,0 +1,44 @@ +{ + "frame1": { + "icon": "server-animation.png", + "icon_spriteX": 0, + "icon_spriteY": 0, + "online": "default", + "max": "default", + "players": "default", + "text0": "&7An Eaglercraft server", + "text1": "&0!!!!&8Running EaglerMOTD plugin" + }, + "frame2": { + "icon": "server-animation.png", + "icon_spriteX": 1, + "icon_spriteY": 0, + "icon_color": [ 1.0, 0.0, 0.0, 0.15 ], + "online": 10, + "players": [ "fake player 1", "fake player 2" ], + "text0": "&6&nAn&r &7Eaglercraft server", + "text1": "&0!!&8!&0!&8Running EaglerMOTD plugin" + }, + "frame3": { + "icon": "server-animation.png", + "icon_spriteX": 2, + "icon_spriteY": 0, + "icon_color": [ 1.0, 0.0, 0.0, 0.15 ], + "icon_tint": [ 0.8, 0.8, 1.0 ], + "online": 20, + "players": [], + "text0": "&7An &6&nEaglercraft&r &7server", + "text1": "&0!&8!!&0!&8Running EaglerMOTD plugin" + }, + "frame4": { + "icon": "server-animation.png", + "icon_spriteX": 3, + "icon_spriteY": 0, + "icon_color": [ 1.0, 1.0, 0.0, 0.15 ], + "icon_tint": [ 0.8, 0.8, 1.0, 0.8 ], + "online": 30, + "players": "default", + "text0": "&7An Eaglercraft &6&nserver&r", + "text1": "&8!!!&0!&8Running EaglerMOTD plugin" + } +} \ No newline at end of file diff --git a/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/default_messages.json b/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/default_messages.json new file mode 100644 index 0000000..cefd9c1 --- /dev/null +++ b/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/default_messages.json @@ -0,0 +1,24 @@ +{ + "close_socket_after": 1200, + "max_sockets_per_ip": 10, + "max_total_sockets": 256, + "messages": { + "all": [ + { + "name": "default", + "frames": [ + "frames.frame1", + "frames.frame2", + "frames.frame3", + "frames.frame4" + ], + "interval": 8, + "random": false, + "shuffle": false, + "timeout": 500, + "weight": 1.0, + "next": "any" + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/default_queries.json b/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/default_queries.json new file mode 100644 index 0000000..c522042 --- /dev/null +++ b/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/default_queries.json @@ -0,0 +1,37 @@ +{ + "queries": { + "ExampleQuery1": { + "type": "ExampleQuery1_result", + "string": "This is a string" + }, + "ExampleQuery2": { + "type": "ExampleQuery2_result", + "txt": "query2.txt" + }, + "ExampleQuery3": { + "type": "ExampleQuery3_result", + "string": "This query returns binary", + "file": "binary.dat" + }, + "ExampleQuery4": { + "type": "ExampleQuery4_result", + "json": "query4.json" + }, + "ExampleQuery5": { + "type": "ExampleQuery5_result", + "json": { + "key1": "value1", + "key2": "value2" + } + }, + "ExampleQuery6": { + "type": "ExampleQuery6_result", + "json": { + "desc": "This query returns JSON and a file", + "filename": "test_file.dat", + "size": 69 + }, + "file": "test_file.dat" + } + } +} \ No newline at end of file diff --git a/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/server-icons-test.png b/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/eaglermotd/server-icons-test.png new file mode 100644 index 0000000000000000000000000000000000000000..e1440d13b12dd4b6768465875a0d27024f25ba43 GIT binary patch literal 35663 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