diff --git a/EaglerMOTD.jar b/EaglerMOTD.jar index 1171299..58d230b 100644 Binary files a/EaglerMOTD.jar and b/EaglerMOTD.jar differ diff --git a/README.md b/README.md index 3824c05..1df5499 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ### This plugin can add animated MOTDs to your Eaglercraft server -![EaglerMOTD Sample](https://i.gyazo.com/ec23a9c60e9722209246fc2b2acea8e4.gif) +![EaglerMOTD Sample](https://i.gyazo.com/4e0105720c866990c82b221fe82f7cc9.gif) **It can also add custom "Accept:" query handlers for 3rd party sites to gather more information about your server** @@ -16,7 +16,140 @@ You will find a new 'EaglerMOTD' folder in the plugins folder you put the jar in ## Configuration Guide -Just a minute... +### Messages.json: + +```json +{ + "close_socket_after": 1200, + "max_sockets_per_ip": 10, + "max_total_sockets": 256, + "allow_banned_ips": false, + "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" + } + ] + } +} +``` + +- `close_socket_after` Defines the maximum number of ticks (1/20ths of a second) that an animated MOTD should be displayed to a player (default 1200 ticks, which is 60 seconds) + +- `max_sockets_per_ip` Defines the maximum number of MOTD connections that a single IP address can open (does not apply to local IPs like 127.0.0.1 and 192.168.0.0 have no limit) + +- `max_total_sockets` Defines the maximum number of MOTD connections that can be open on the server at any given time + +- `allow_banned_ips` Defines if animated MOTDs should play for banned IPs + +- `messages` Defines the list of possible messages to display on every listener + + - `all` Defines the list of possible messages to display on all listeners + + - To define a list for a specific listener, type the hostname of the listener. The default host defined in a fresh default `config.yml` in EaglercraftBungee is `0.0.0.0:25565` so to define a list of messages for that specific listener you would use `"0.0.0.0:25565"` instead of `"all"` to add the messages. Messages defined as `"all"` will be added to the list of messages for all listeners. + + - The list contains a set of JSON objects each containing these fields: + - `name` *(Optional)* Defines the name of the message, used for specifying a `next` property in a different message to jump to this message once it has timed out + - `frames` Defines a list of strings specifying the list of frames in the animation of this message. They are defined in `file.frame` format where `file` is the file the frame is located in (which would be `file.json`) and `frame` is the name of the frame in that file to use + - `interval` *(Optional)* defines the delay between frames. Setting to `0` (default) disables the animation and only shows the first frame + - `random` *(Optional)* defines if the animation should begin on a random frame (`true`) or the first frame (`false`, default) + - `shuffle` *(Optional)* defines if the animation should switch between frames sequentially (`false`, default) or randomly (`true`) + - `timeout` *(Optional)* defines how many ticks the animation should play before stopping or switching to `"next"` (default 500) + - `weight` *(Optional)* defines the random probability of choosing this message over any other messages in `"all"` and the list of messages for the specific listener the message is playing on. Default is 1.0 + - `next` *(Optional)* defines the `"name"` of the message to play once this message times out (`timeout`). Set to `"any"` to pick any message, or set to `null` to stop the animation once `timeout` is reached. Default is null + +### Frames.json: + +**You can name this file anything you want, and you can create more than one. In 'messages.json' just define the frame as, for example, 'file.name' and it will look in a file called 'file.json' for a frame named 'name'. The example 'frames.frame1' in 'messages.json' will load 'frame1' from this default file called 'frames.json'** + +```json +{ + "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" + } +} +``` + +**Every frame will retrieve the values from the previous frame in the message for the default value for every variable** + +- `text0` *(Optional)* Changes the first line of text in the server's current MOTD + +- `text1` *(Optional)* Changes the second line of text in the server's current MOTD + +- `online` *(Optional)* Changes the number of online players, use `"default"` to reset it + +- `max` *(Optional)* Changes the max number of players in the MOTD, use `"default"` to reset it + +- `players` *(Optional)* Changes the list of players shown when the mouse is hovering over the online/max count in the multiplayer screen. use `"default"` to reset it and show the real list of players instead of spoofing it + +- `icon` *(Optional)* A JPEG/PNG/BMP/GIF to display as the server icon. The icon must be at least 64x64 pixels. **Transparency is supported.** The top left 64x64 pixels of the image are displayed if the image is larger than 64x64 pixels. **Animated GIF files are not supported, they load but only display the first frame.** Setting the icon resets the values of `icon_spriteX`, `icon_spriteY`, `icon_color`, `icon_tint`, `icon_flipX`, `icon_flipY`, and `icon_rotate` **to their default values.** Setting to `"none"` will reset everything and set the icon to be black and 100% transparent + + - `icon_spriteX` *(Optional)* defines the X coordinate to read a 64x64 pixel portion of the current `"icon"` file, if the file is larger than 64x64. The value is multiplied by 64 to get the exact pixel coordinate in the image to read from. `"icon_spriteX": 2` will read a 64x64 pixel portion of a larger image beginning at 128 pixels X and 0 pixels Y of the current `"icon"` file. Default is 0 + - `icon_spriteY` *(Optional)* defines the Y coordinate to read a 64x64 pixel portion of the current `"icon"` file, if the file is larger than 64x64. The value is multiplied by 64 to get the exact pixel coordinate in the image to read from. `"icon_spriteY": 2` will read a 64x64 pixel portion of a larger image beginning at 0 pixels X and 128 pixels Y of the current `"icon"` file. Default is 0. Setting, for example, `"icon_spriteX": 1` and `"icon_spriteY": 2` will read a 64x64 pixel portion of the current `"icon"` file beginning at 64 pixels X and 128 pixels Y. + + - `icon_pixelX` *(Optional)* defines the exact X pixel coordinate to read a 64x64 pixel portion of the current `"icon"` file, if the file is larger than 64x64. Unlike `icon_spriteX`, setting this value will not multiply the input value by 64, it will read from the exact coordinate in the image. **Overrides icon_spriteX** + - `icon_pixelY` *(Optional)* defines the exact Y pixel coordinate to read a 64x64 pixel portion of the current `"icon"` file, if the file is larger than 64x64. Unlike `icon_spriteY`, setting this value will not multiply the input value by 64, it will read from the exact coordinate in the image. **Overrides icon_spriteY** + + - `icon_color` *(Optional)* mixes an RGBA color with the current `"icon"`, or displays the color directly if `"icon"` is `"none"`. `[1.0, 1.0, 1.0]` displays white, `[0.0, 0.0, 0.0]` displays black, `[0.0, 1.0, 0.0]` displays green, `[0.0, 1.0, 0.0, 0.5]` displays green with 50% transparency, blending it with the color of the current `"icon"` file's pixels if it is set. + + - `icon_tint` *(Optional)* multiplies an RGBA color by all the pixels in the current `"icon"` and/or `"icon_color"`. `[0.0, 0.0, 1.0]` would make the icon's pixels blue-colored, `[1.0, 0.0, 0.0]` would make the icon's pixels red-colored, `[0.0, 1.0, 0.0, 0.5]` would make the icon's pixels green-colored and 50% transparent. `[2.0, 2.0, 2.0]` would double the brightness of the icon. + + - `icon_flipX` *(Optional)* flips the pixels of the icon displayed horizontally + + - `icon_flipY` *(Optional)* flips the pixels of the icon displayed vertically + + - `icon_rotate` *(Optional)* rotates the icon 90°, 180°, or 270° clockwise (`0` = 0°, `1` = 90°, `2` = 180°, `3` = 270°) ## Compiling and Contributing diff --git a/src/default_frames.json b/src/default_frames.json index 901f731..65a0500 100644 --- a/src/default_frames.json +++ b/src/default_frames.json @@ -6,37 +6,39 @@ "online": "default", "max": "default", "players": "default", - "text0": "&6An Eaglercraft server", - "text1": "&7Running EaglerMOTD plugin" + "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.2 ], + "icon_color": [ 1.0, 0.0, 0.0, 0.15 ], "online": 10, "players": [ "fake player 1", "fake player 2" ], - "text0": "&bAn &6Eaglercraft server" + "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_tint": [ 0.6, 0.6, 1.0, 0.8 ], + "icon_color": [ 1.0, 0.0, 0.0, 0.15 ], + "icon_tint": [ 0.8, 0.8, 1.0 ], "online": 20, "players": [], - "text0": "&6An &bEaglercraft &6server" + "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.2 ], - "icon_tint": [ 0.5, 0.5, 1.0 ], + "icon_color": [ 1.0, 1.0, 0.0, 0.15 ], + "icon_tint": [ 0.8, 0.8, 1.0, 0.8 ], "online": 30, - "max": 69, "players": "default", - "text0": "&6An Eaglercraft &bserver", - "text1": "&7Running &k&oEaglerMOTD&r&7 plugin" + "text0": "&7An Eaglercraft &6&nserver&r", + "text1": "&8!!!&0!&8Running EaglerMOTD plugin" } } \ No newline at end of file diff --git a/src/net/lax1dude/eaglercraft/eaglermotd/BitmapFile.java b/src/net/lax1dude/eaglercraft/eaglermotd/BitmapFile.java index 24bc6f8..249f3f1 100644 --- a/src/net/lax1dude/eaglercraft/eaglermotd/BitmapFile.java +++ b/src/net/lax1dude/eaglercraft/eaglermotd/BitmapFile.java @@ -58,8 +58,8 @@ public class BitmapFile { if(x < 0 || y < 0) { return null; } - int offsetX = x * 64; - int offsetY = y * 64; + int offsetX = x; + int offsetY = y; if(offsetX + 64 > w || offsetY + 64 > h) { return null; } @@ -73,6 +73,14 @@ public class BitmapFile { } 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; @@ -90,6 +98,14 @@ public class BitmapFile { 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; @@ -101,9 +117,64 @@ public class BitmapFile { 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 int[] flipTmpBuffer = new int[64]; + + public static int[] flipX(int[] newIcon) { + for(int y = 0; y < 64; ++y) { + int o = y * 64; + System.arraycopy(newIcon, o, flipTmpBuffer, 0, 64); + for(int i = 0; i < 64; ++i) { + newIcon[o + i] = flipTmpBuffer[63 - i]; + } + } + return newIcon; + } + + public static int[] flipY(int[] newIcon) { + for(int x = 0; x < 64; ++x) { + for(int i = 0; i < 64; ++i) { + flipTmpBuffer[i] = newIcon[i * 64 + x]; + } + for(int i = 0; i < 64; ++i) { + newIcon[i * 64 + x] = flipTmpBuffer[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/net/lax1dude/eaglercraft/eaglermotd/MOTDConnection.java b/src/net/lax1dude/eaglercraft/eaglermotd/MOTDConnection.java index 561742c..ff47bd8 100644 --- a/src/net/lax1dude/eaglercraft/eaglermotd/MOTDConnection.java +++ b/src/net/lax1dude/eaglercraft/eaglermotd/MOTDConnection.java @@ -29,6 +29,9 @@ public class MOTDConnection { 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 }; @@ -158,6 +161,9 @@ public class MOTDConnection { } private boolean changeMessageTo(String group, String s) { + if(group == null || s == null) { + return false; + } List lst = EaglerMOTD.messages.get(group); if(lst == null) { return false; @@ -230,28 +236,64 @@ public class MOTDConnection { shouldPush = true; } boolean shouldRenderIcon = false; - String icon = frame.optString("icon", null); + Object icon = frame.opt("icon"); if(icon != null) { + String asString = (icon instanceof String) ? (String)icon : null; shouldRenderIcon = true; - if(icon.equalsIgnoreCase("none") || icon.equalsIgnoreCase("default") || icon.equalsIgnoreCase("null") || icon.equalsIgnoreCase("color")) { + if(icon == JSONObject.NULL || asString == null || asString.equalsIgnoreCase("none") || asString.equalsIgnoreCase("default") + || asString.equalsIgnoreCase("null") || asString.equalsIgnoreCase("color")) { bitmap = null; }else { - bitmap = BitmapFile.getCachedIcon(icon); + bitmap = BitmapFile.getCachedIcon(asString); } - spriteX = spriteY = 0; + 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 = frame.optInt("icon_spriteX", -1); + int sprtX = frame.optInt("icon_spriteX", -1) * 64; if(sprtX >= 0 && sprtX != spriteX) { shouldRenderIcon = true; spriteX = sprtX; } - int sprtY = frame.optInt("icon_spriteY", -1); + int sprtY = frame.optInt("icon_spriteY", -1) * 64; if(sprtY >= 0 && sprtY != spriteY) { shouldRenderIcon = true; spriteY = sprtY; } + sprtX = frame.optInt("icon_pixelX", -1); + if(sprtX >= 0 && sprtX != spriteX) { + shouldRenderIcon = true; + spriteX = sprtX; + } + sprtY = frame.optInt("icon_pixelY", -1); + if(sprtY >= 0 && sprtY != spriteY) { + shouldRenderIcon = true; + spriteY = sprtY; + } + Object flip = frame.opt("icon_flipX"); + if(flip != null) { + shouldRenderIcon = true; + if(flip instanceof Boolean) { + flipX = ((Boolean)flip).booleanValue(); + }else { + flipX = false; + } + } + flip = frame.opt("icon_flipY"); + if(flip != null) { + shouldRenderIcon = true; + if(flip instanceof Boolean) { + flipY = ((Boolean)flip).booleanValue(); + }else { + flipY = false; + } + } + int rot = frame.optInt("icon_rotate", -1); + if(rot >= 0) { + shouldRenderIcon = true; + rotate = rot % 4; + } JSONArray colorF = frame.optJSONArray("icon_color"); if(colorF != null && colorF.length() > 0) { shouldRenderIcon = true; @@ -271,7 +313,7 @@ public class MOTDConnection { if(shouldRenderIcon) { int[] newIcon = null; if(bitmap != null) { - newIcon = bitmap.getSprite(sprtX, sprtY); + newIcon = bitmap.getSprite(spriteX, spriteY); } if(newIcon == null) { newIcon = new int[64*64]; @@ -280,6 +322,17 @@ public class MOTDConnection { 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; } diff --git a/src/plugin.yml b/src/plugin.yml index 1e350aa..598d8e0 100644 --- a/src/plugin.yml +++ b/src/plugin.yml @@ -1,4 +1,4 @@ name: EaglerMOTD main: net.lax1dude.eaglercraft.eaglermotd.EaglerMOTD -version: 1.0.2 +version: 1.0.3 author: LAX1DUDE \ No newline at end of file diff --git a/src/server-icons-test.png b/src/server-icons-test.png index 54412ce..e1440d1 100644 Binary files a/src/server-icons-test.png and b/src/server-icons-test.png differ