Initial commit (1.0.0)

This commit is contained in:
LAX1DUDE 2022-04-02 01:25:36 -07:00
parent 9e980e382a
commit cfa513a4f8
17 changed files with 1323 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/.settings/
/bin/
.classpath

17
.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>EaglerMOTD</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

BIN
EaglerMOTD.jar Normal file

Binary file not shown.

29
README.md Normal file
View File

@ -0,0 +1,29 @@
# EaglerMOTD
### This plugin can add animated MOTDs to your Eaglercraft server
![EaglerMOTD Sample](https://i.gyazo.com/ec23a9c60e9722209246fc2b2acea8e4.gif)
**It can also add custom "Accept:" query handlers for 3rd party sites to gather more information about your server**
## How to Install
**Download [EaglerMOTD.jar](https://raw.githubusercontent.com/LAX1DUDE/eaglercraft-motd/main/EaglerMOTD.jar) and place it in your EaglercraftBungee '/plugins' directory. Then, restart EaglercraftBungee**
You will find a new 'EaglerMOTD' folder in the plugins folder you put the jar in, once you finish restarting your server. This contains the plugin's configuration files, you can edit any of them and then type `motd-reload` in the EaglercraftBungee console to reload all the variables.
## Configuration Guide
Just a minute...
## Compiling and Contributing
First, download the latest [EaglercraftBungee jar](https://github.com/LAX1DUDE/eaglercraft/blob/main/stable-download/java/bungee_command/bungee-dist.jar) in stable-download on [LAX1DUDE/eaglercraft](https://github.com/LAX1DUDE/eaglercraft/)
**Make a new java project in Eclipse/IDEA/etc and add 'src' folder in this repository as the source code folder**
**Then, add your EaglercraftBungee jar ([bungee-dist.jar](https://github.com/LAX1DUDE/eaglercraft/blob/main/stable-download/java/bungee_command/bungee-dist.jar)) to the java project's Build Path and refresh**
Export the contents of 'src' folder of the project to a JAR file to compile the plugin
**For a PR:** Tabs, not spaces, and format the code like the Eclipse auto format tool on factory settings.

42
src/default_frames.json Normal file
View File

@ -0,0 +1,42 @@
{
"frame1": {
"icon": "server-animation.png",
"icon_spriteX": 0,
"icon_spriteY": 0,
"online": "default",
"max": "default",
"players": "default",
"text0": "&6An Eaglercraft server",
"text1": "&7Running EaglerMOTD plugin"
},
"frame2": {
"icon": "server-animation.png",
"icon_spriteX": 1,
"icon_spriteY": 0,
"icon_color": [ 1.0, 0.0, 0.0, 0.2 ],
"online": 10,
"players": [ "fake player 1", "fake player 2" ],
"text0": "&bAn &6Eaglercraft server"
},
"frame3": {
"icon": "server-animation.png",
"icon_spriteX": 2,
"icon_spriteY": 0,
"icon_tint": [ 0.6, 0.6, 1.0, 0.8 ],
"online": 20,
"players": [],
"text0": "&6An &bEaglercraft &6server"
},
"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 ],
"online": 30,
"max": 69,
"players": "default",
"text0": "&6An Eaglercraft &bserver",
"text1": "&7Running &k&oEaglerMOTD&r&7 plugin"
}
}

25
src/default_messages.json Normal file
View File

@ -0,0 +1,25 @@
{
"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"
}
]
}
}

37
src/default_queries.json Normal file
View File

@ -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"
}
}
}

View File

@ -0,0 +1,109 @@
package net.lax1dude.eaglercraft.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;
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<String, BitmapFile> 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 * 64;
int offsetY = y * 64;
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) {
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);
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;
in[i] = ((int)(aa*255.0f) << 24) | ((int)(rr*255.0f) << 16) | ((int)(gg*255.0f) << 8) | (int)(bb*255.0f);
}
return in;
}
}

View File

@ -0,0 +1,26 @@
package net.lax1dude.eaglercraft.eaglermotd;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
public class CommandMOTDReload extends Command {
public final EaglerMOTD plugin;
public CommandMOTDReload(EaglerMOTD plugin) {
super("motd-reload", "eaglermotd.command.reload");
this.plugin = plugin;
}
@Override
public void execute(CommandSender paramCommandSender, String[] paramArrayOfString) {
try {
plugin.loadConfiguration(paramCommandSender);
} catch (Exception e) {
paramCommandSender.sendMessage(ChatColor.RED + "[EaglerMOTD] Failed to reload! " + e.toString());
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,396 @@
package net.lax1dude.eaglercraft.eaglermotd;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import org.json.JSONArray;
import org.json.JSONObject;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.event.WebsocketMOTDEvent;
import net.md_5.bungee.api.event.WebsocketQueryEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.command.ConsoleCommandSender;
import net.md_5.bungee.eaglercraft.BanList;
import net.md_5.bungee.event.EventHandler;
public class EaglerMOTD extends Plugin implements Listener {
public static final Map<String,List<MessagePoolEntry>> messages = new HashMap();
public static final Map<String,MessagePool> messagePools = new HashMap();
public static final Map<String,JSONObject> framesCache = new HashMap();
public static final Map<String,QueryType> queryTypes = new HashMap();
public static EaglerMOTD instance = null;
public static int close_socket_after = 1200;
public static int max_sockets_per_ip = 10;
public static int max_total_sockets = 256;
public static boolean allow_banned_ips = false;
public final Timer tickTimer = new Timer("MOTD Tick Timer");
public final List<MOTDConnection> motdConnections = new LinkedList();
public EaglerMOTD() {
instance = this;
}
public void loadConfiguration(CommandSender cs) throws Exception {
messages.clear();
messagePools.clear();
framesCache.clear();
queryTypes.clear();
BitmapFile.bitmapCache.clear();
QueryCache.flush();
synchronized(motdConnections) {
if(motdConnections.size() > 0) {
for(MOTDConnection con : motdConnections) {
con.close();
}
motdConnections.clear();
}
}
getDataFolder().mkdirs();
byte[] damn = new byte[4096];
int i;
File msgs = new File(getDataFolder(), "messages.json");
if(!msgs.exists()) {
OutputStream msgsNew = new FileOutputStream(msgs);
InputStream msgsDefault = EaglerMOTD.class.getResourceAsStream("/default_messages.json");
while((i = msgsDefault.read(damn)) != -1) {
msgsNew.write(damn, 0, i);
}
msgsNew.close();
msgsDefault.close();
File f2 = new File(getDataFolder(), "frames.json");
if(!f2.exists()) {
msgsNew = new FileOutputStream(f2);
msgsDefault = EaglerMOTD.class.getResourceAsStream("/default_frames.json");
while((i = msgsDefault.read(damn)) != -1) {
msgsNew.write(damn, 0, i);
}
msgsNew.close();
msgsDefault.close();
}
f2 = new File(getDataFolder(), "queries.json");
if(!f2.exists()) {
msgsNew = new FileOutputStream(f2);
msgsDefault = EaglerMOTD.class.getResourceAsStream("/default_queries.json");
while((i = msgsDefault.read(damn)) != -1) {
msgsNew.write(damn, 0, i);
}
msgsNew.close();
msgsDefault.close();
}
f2 = new File("server-animation.png");
if(!f2.exists()) {
msgsNew = new FileOutputStream(f2);
msgsDefault = EaglerMOTD.class.getResourceAsStream("/server-icons-test.png");
while((i = msgsDefault.read(damn)) != -1) {
msgsNew.write(damn, 0, i);
}
msgsNew.close();
msgsDefault.close();
}
}
if(!msgs.exists()) {
throw new NullPointerException("messages.json is missing and could not be created");
}
InputStream is = new FileInputStream(msgs);
ByteArrayOutputStream bao = new ByteArrayOutputStream(is.available());
while((i = is.read(damn)) != -1) {
bao.write(damn, 0, i);
}
is.close();
JSONObject msgsObj = new JSONObject(new String(bao.toByteArray(), StandardCharsets.UTF_8));
framesCache.put("messages", msgsObj);
close_socket_after = msgsObj.optInt("close_socket_after", 1200);
max_sockets_per_ip = msgsObj.optInt("max_sockets_per_ip", 10);
max_total_sockets = msgsObj.optInt("max_total_sockets", 256);
allow_banned_ips = msgsObj.optBoolean("allow_banned_ips", false);
msgsObj = msgsObj.getJSONObject("messages");
for(String ss : msgsObj.keySet()) {
try {
List<MessagePoolEntry> poolEntries = new LinkedList();
JSONArray arr = msgsObj.getJSONArray(ss);
for(int j = 0, l = arr.length(); j < l; ++j) {
JSONObject entry = arr.getJSONObject(j);
List<JSONObject> frames = new LinkedList();
JSONArray framesJSON = entry.getJSONArray("frames");
for(int k = 0, l2 = framesJSON.length(); k < l2; ++k) {
JSONObject frame = resolveFrame(framesJSON.getString(k), cs);
if(frame != null) {
frames.add(frame);
}
}
if(frames.size() > 0) {
poolEntries.add(new MessagePoolEntry(entry.optInt("interval", 0), entry.optInt("timeout", 500),
entry.optBoolean("random", false), entry.optBoolean("shuffle", false), entry.optFloat("weight", 1.0f),
entry.optString("next", null), frames, entry.optString("name", null)));
}else {
cs.sendMessage(ChatColor.RED + "[EaglerMOTD] Message '" + ss + "' has no frames!");
}
}
if(poolEntries.size() > 0) {
List<MessagePoolEntry> existingList = messages.get(ss);
if(existingList == null) {
existingList = poolEntries;
messages.put(ss, existingList);
}else {
existingList.addAll(poolEntries);
}
}
}catch(Throwable t) {
cs.sendMessage(ChatColor.RED + "[EaglerMOTD] Could not parse messages for '" + ss + "' " + t.toString());
}
}
Collection<ListenerInfo> listeners = getProxy().getConfigurationAdapter().getListeners();
String flag = null;
for(String s : messages.keySet()) {
if(!s.equals("all")) {
boolean flag2 = false;
for(ListenerInfo l : listeners) {
if(s.equals(makeListenerString(l.getHost()))) {
flag2 = true;
}
}
if(!flag2) {
flag = s;
break;
}
}
}
if(flag != null) {
cs.sendMessage(ChatColor.RED + "[EaglerMOTD] Listener '" + flag + "' does not exist!");
String hostsString = "";
for(ListenerInfo l : listeners) {
if(hostsString.length() > 0) {
hostsString += " ";
}
hostsString += makeListenerString(l.getHost());
}
cs.sendMessage(ChatColor.RED + "[EaglerMOTD] Listeners configured: " + ChatColor.YELLOW + hostsString);
}
for(ListenerInfo l : listeners) {
String name = makeListenerString(l.getHost());
MessagePool m = new MessagePool(name);
List<MessagePoolEntry> e = messages.get("all");
if(e != null) {
m.messagePool.addAll(e);
}
e = messages.get(name);
if(e != null) {
m.messagePool.addAll(e);
}
if(m.messagePool.size() > 0) {
cs.sendMessage(ChatColor.GREEN + "[EaglerMOTD] Loaded " + m.messagePool.size() + " messages for " + name);
messagePools.put(name, m);
}
}
msgs = new File(getDataFolder(), "queries.json");
if(msgs.exists()) {
try {
is = new FileInputStream(msgs);
bao = new ByteArrayOutputStream(is.available());
while((i = is.read(damn)) != -1) {
bao.write(damn, 0, i);
}
is.close();
JSONObject queriesObject = new JSONObject(new String(bao.toByteArray(), StandardCharsets.UTF_8));
JSONObject queriesQueriesObject = queriesObject.getJSONObject("queries");
for(String s : queriesQueriesObject.keySet()) {
queryTypes.put(s.toLowerCase(), new QueryType(s, queriesQueriesObject.getJSONObject(s)));
}
if(queryTypes.size() > 0) {
cs.sendMessage(ChatColor.GREEN + "[EaglerMOTD] Loaded " + queryTypes.size() + " query types");
}
}catch(Throwable t) {
cs.sendMessage(ChatColor.RED + "[EaglerMOTD] Queries were not loaded: " + t.toString());
}
}
}
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();
}
}
public JSONObject resolveFrame(String s, CommandSender cs) {
int i = s.indexOf('.');
if(i == -1) {
cs.sendMessage(ChatColor.RED + "[EaglerMOTD] Frame '" + s + "' cannot be found! (it does not specify a filename)");
return null;
}
String f = s.substring(0, i);
JSONObject fc = framesCache.get(f);
if(fc == null) {
File ff = new File(getDataFolder(), f + ".json");
if(!ff.exists()) {
cs.sendMessage(ChatColor.RED + "[EaglerMOTD] File '" + f + ".json' cannot be found!");
return null;
}
try {
byte[] damn = new byte[4096];
InputStream is = new FileInputStream(ff);
ByteArrayOutputStream bao = new ByteArrayOutputStream(is.available());
int j;
while((j = is.read(damn)) != -1) {
bao.write(damn, 0, j);
}
is.close();
fc = new JSONObject(new String(bao.toByteArray(), StandardCharsets.UTF_8));
framesCache.put(f, fc);
}catch(Throwable t) {
cs.sendMessage(ChatColor.RED + "[EaglerMOTD] File '" + f + ".json' could not be loaded: " + t.toString());
return null;
}
}
f = s.substring(i + 1).trim();
if(fc.has(f)) {
return fc.getJSONObject(f);
}else {
cs.sendMessage(ChatColor.RED + "[EaglerMOTD] Frame '" + s + "' cannot be found!");
return null;
}
}
public void showRunCmd(CommandSender cs) {
cs.sendMessage(ChatColor.YELLOW + "[EaglerMOTD] Use /motd-reload to reload your MOTD config files");
}
public void onLoad() {
try {
getProxy().getPluginManager().registerCommand(this, new CommandMOTDReload(this));
loadConfiguration(ConsoleCommandSender.getInstance());
} catch (Exception e) {
System.err.println("[EaglerMOTD] Could not load!");
e.printStackTrace();
}
}
public void onEnable() {
getProxy().getPluginManager().registerListener(this, this);
tickTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
synchronized(motdConnections) {
Iterator<MOTDConnection> itr = motdConnections.iterator();
while(itr.hasNext()) {
MOTDConnection c = itr.next();
try {
if(!c.tick()) {
itr.remove();
}
}catch(Throwable t) {
System.err.println("Error ticking MOTD '" + (c.currentMessage == null ? "null" : c.currentMessage.name) + "' on listener " + c.listenerName);
t.printStackTrace();
c.close();
itr.remove();
}
}
}
}
}, 0, 50l);
}
public void onDisable() {
tickTimer.cancel();
}
@EventHandler
public void onMOTD(WebsocketMOTDEvent evt) {
MOTDConnection con = new MOTDConnection(evt.getListener(), evt.getMOTD());
if(con.execute()) {
synchronized(motdConnections) {
while(motdConnections.size() >= max_total_sockets) {
MOTDConnection c = motdConnections.remove(0);
c.close();
}
}
InetAddress addr = con.motd.getRemoteAddress();
boolean flag = false;
for(BanList.IPBan b : BanList.blockedBans) {
if(b.checkBan(addr)) {
flag = true;
break;
}
}
if(flag) {
synchronized(motdConnections) {
motdConnections.add(con);
}
}else {
if(allow_banned_ips || !BanList.checkIpBanned(addr).isBanned()) {
synchronized(motdConnections) {
int i = 0;
int c = 0;
while(i < motdConnections.size()) {
if(motdConnections.get(i).motd.getRemoteAddress().equals(addr)) {
++c;
if(c >= max_sockets_per_ip) {
motdConnections.remove(i).close();
--i;
}
}
++i;
}
motdConnections.add(0, con);
}
}else {
con.motd.keepAlive(false);
}
}
}
}
@EventHandler
public void onQuery(WebsocketQueryEvent evt) {
if(evt.getQuery().isClosed() || evt.getAccept().equalsIgnoreCase("MOTD")) {
return;
}
QueryType t = queryTypes.get(evt.getAccept().toLowerCase());
if(t != null) {
t.doQuery(evt.getQuery());
}
}
}

View File

@ -0,0 +1,285 @@
package net.lax1dude.eaglercraft.eaglermotd;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import org.json.JSONArray;
import org.json.JSONObject;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.MOTD;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
public class MOTDConnection {
public final ListenerInfo listener;
public final String listenerName;
public final MOTD 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 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 MOTDConnection(ListenerInfo l, MOTD m) {
this.motd = m;
this.listener = l;
this.listenerName = EaglerMOTD.makeListenerString(l.getHost());
}
public boolean execute() {
MessagePool p = EaglerMOTD.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.keepAlive(true);
return true;
}else {
this.motd.keepAlive(false);
return false;
}
}
public boolean tick() {
ageTimer++;
if(this.motd.isClosed()) {
return false;
}
if(ageTimer > EaglerMOTD.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 = EaglerMOTD.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 : EaglerMOTD.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();
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();
}
}
return true;
}
}
private boolean changeMessageTo(String group, String s) {
List<MessagePoolEntry> lst = EaglerMOTD.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;
Object v = frame.opt("online");
if(v != null) {
if(v instanceof Number) {
motd.setOnlinePlayers(((Number)v).intValue());
}else {
motd.setOnlinePlayers(BungeeCord.getInstance().getPlayers().size());
}
shouldPush = true;
}
v = frame.opt("max");
if(v != null) {
if(v instanceof Number) {
motd.setMaxPlayers(((Number)v).intValue());
}else {
motd.setMaxPlayers(listener.getMaxPlayers());
}
shouldPush = true;
}
v = frame.opt("players");
if(v != null) {
if(v instanceof JSONArray) {
List<String> players = new ArrayList();
JSONArray vv = (JSONArray) v;
for(int i = 0, l = vv.length(); i < l; ++i) {
players.add(ChatColor.translateAlternateColorCodes('&', vv.getString(i)));
}
motd.setPlayerList(players);
}else {
List<String> players = new ArrayList();
Collection<ProxiedPlayer> ppl = BungeeCord.getInstance().getPlayers();
for(ProxiedPlayer pp : ppl) {
players.add(pp.getDisplayName());
if(players.size() >= 9) {
players.add("" + ChatColor.GRAY + ChatColor.ITALIC + "(" + (ppl.size() - players.size()) + " more)");
break;
}
}
motd.setPlayerList(players);
}
shouldPush = true;
}
String line = frame.optString("text0", frame.optString("text", null));
if(line != null) {
int ix = line.indexOf('\n');
if(ix != -1) {
motd.setLine1(ChatColor.translateAlternateColorCodes('&', line.substring(0, ix)));
motd.setLine2(ChatColor.translateAlternateColorCodes('&', line.substring(ix + 1)));
}else {
motd.setLine1(ChatColor.translateAlternateColorCodes('&', line));
}
line = frame.optString("text1", null);
if(line != null) {
motd.setLine2(ChatColor.translateAlternateColorCodes('&', line));
}
shouldPush = true;
}
boolean shouldRenderIcon = false;
String icon = frame.optString("icon", null);
if(icon != null) {
shouldRenderIcon = true;
if(icon.equalsIgnoreCase("none") || icon.equalsIgnoreCase("default") || icon.equalsIgnoreCase("null") || icon.equalsIgnoreCase("color")) {
bitmap = null;
}else {
bitmap = BitmapFile.getCachedIcon(icon);
}
spriteX = spriteY = 0;
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);
if(sprtX >= 0 && sprtX != spriteX) {
shouldRenderIcon = true;
spriteX = sprtX;
}
int sprtY = frame.optInt("icon_spriteY", -1);
if(sprtY >= 0 && sprtY != spriteY) {
shouldRenderIcon = true;
spriteY = sprtY;
}
JSONArray colorF = frame.optJSONArray("icon_color");
if(colorF != null && colorF.length() > 0) {
shouldRenderIcon = true;
color[0] = colorF.getFloat(0);
color[1] = colorF.length() > 1 ? colorF.getFloat(1) : color[1];
color[2] = colorF.length() > 2 ? colorF.getFloat(2) : color[2];
color[3] = colorF.length() > 3 ? colorF.getFloat(3) : 1.0f;
}
colorF = frame.optJSONArray("icon_tint");
if(colorF != null && colorF.length() > 0) {
shouldRenderIcon = true;
tint[0] = colorF.getFloat(0);
tint[1] = colorF.length() > 1 ? colorF.getFloat(1) : tint[1];
tint[2] = colorF.length() > 2 ? colorF.getFloat(2) : tint[2];
tint[3] = colorF.length() > 3 ? colorF.getFloat(3) : 1.0f;
}
if(shouldRenderIcon) {
int[] newIcon = null;
if(bitmap != null) {
newIcon = bitmap.getSprite(sprtX, sprtY);
}
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]);
}
motd.setBitmap(newIcon);
shouldPush = true;
}
if(shouldPush) {
motd.sendToUser();
}
}
public void close() {
motd.close();
}
}

View File

@ -0,0 +1,50 @@
package net.lax1dude.eaglercraft.eaglermotd;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
public class MessagePool {
public final String poolName;
public final List<MessagePoolEntry> 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();
}
}

View File

@ -0,0 +1,34 @@
package net.lax1dude.eaglercraft.eaglermotd;
import java.util.List;
import org.json.JSONObject;
public class MessagePoolEntry implements Comparable<MessagePoolEntry> {
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<JSONObject> frames;
public MessagePoolEntry(int interval, int timeout, boolean random, boolean shuffle, float weight, String next, List<JSONObject> 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);
}
}

View File

@ -0,0 +1,157 @@
package net.lax1dude.eaglercraft.eaglermotd;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import org.json.JSONObject;
public class QueryCache {
private static class CachedFile {
protected final String name;
protected final File file;
protected long lastReload;
protected long lastRescan;
protected CachedFile(String name, File file) {
this.name = name;
this.file = file;
this.lastReload = this.lastRescan = 0l;
}
protected boolean needsReload() {
long l = System.currentTimeMillis();
if(l - lastRescan > 5000l) {
lastRescan = l;
return file.exists() && lastReload < file.lastModified();
}else {
return false;
}
}
}
private static class CachedFileBinary extends CachedFile {
protected byte[] bytes = null;
protected CachedFileBinary(String name, File file) {
super(name, file);
}
protected byte[] getOrReload() {
if(needsReload()) {
if(file.exists()) {
try(FileInputStream fis = new FileInputStream(file)) {
ByteArrayOutputStream read = new ByteArrayOutputStream(fis.available());
byte[] d = new byte[8192];
int i;
while((i = fis.read(d)) != -1) {
read.write(d, 0, i);
}
lastRescan = lastReload = System.currentTimeMillis();
bytes = read.toByteArray();
}catch(Throwable t) {
bytes = null;
System.err.println("[EaglerMOTD] Failed to load binary: " + name);
t.printStackTrace();
}
}
}
return bytes;
}
}
private static class CachedFileString extends CachedFile {
protected String chars = null;
protected CachedFileString(String name, File file) {
super(name, file);
}
protected String getOrReload() {
if(needsReload()) {
if(file.exists()) {
try(FileInputStream fis = new FileInputStream(file)) {
ByteArrayOutputStream read = new ByteArrayOutputStream(fis.available());
byte[] d = new byte[8192];
int i;
while((i = fis.read(d)) != -1) {
read.write(d, 0, i);
}
lastRescan = lastReload = System.currentTimeMillis();
chars = new String(read.toByteArray(), StandardCharsets.UTF_8);
}catch(Throwable t) {
chars = null;
System.err.println("[EaglerMOTD] Failed to load text: " + name);
t.printStackTrace();
}
}
}
return chars;
}
}
private static class CachedFileJSON extends CachedFile {
protected JSONObject json = null;
protected CachedFileJSON(String name, File file) {
super(name, file);
}
protected JSONObject getOrReload() {
if(needsReload()) {
if(file.exists()) {
try(FileInputStream fis = new FileInputStream(file)) {
ByteArrayOutputStream read = new ByteArrayOutputStream(fis.available());
byte[] d = new byte[8192];
int i;
while((i = fis.read(d)) != -1) {
read.write(d, 0, i);
}
lastRescan = lastReload = System.currentTimeMillis();
json = new JSONObject(new String(read.toByteArray(), StandardCharsets.UTF_8));
}catch(Throwable t) {
json = null;
System.err.println("[EaglerMOTD] Failed to load json: " + name);
t.printStackTrace();
}
}
}
return json;
}
}
private static final HashMap<String, CachedFileBinary> cachedBinary = new HashMap();
private static final HashMap<String, CachedFileString> cachedString = new HashMap();
private static final HashMap<String, CachedFileJSON> cachedJSON = new HashMap();
public static void flush() {
cachedBinary.clear();
cachedString.clear();
cachedJSON.clear();
}
public static byte[] getBinaryFile(String s) {
CachedFileBinary fb = cachedBinary.get(s);
if(fb == null) {
fb = new CachedFileBinary(s, new File(s));
cachedBinary.put(s, fb);
}
return fb.getOrReload();
}
public static String getStringFile(String s) {
CachedFileString fb = cachedString.get(s);
if(fb == null) {
fb = new CachedFileString(s, new File(s));
cachedString.put(s, fb);
}
return fb.getOrReload();
}
public static JSONObject getJSONFile(String s) {
CachedFileJSON fb = cachedJSON.get(s);
if(fb == null) {
fb = new CachedFileJSON(s, new File(s));
cachedJSON.put(s, fb);
}
return fb.getOrReload();
}
}

View File

@ -0,0 +1,109 @@
package net.lax1dude.eaglercraft.eaglermotd;
import org.json.JSONObject;
import net.md_5.bungee.api.QueryConnection;
public class QueryType {
public final String name;
public final String type;
public final String dataString;
public final String dataJSONFile;
public final JSONObject dataJSONObject;
public final String dataTextFile;
public final String dataBinaryFile;
public QueryType(String name, JSONObject tag) {
this.name = name;
this.dataJSONObject = tag.optJSONObject("json", null);
if(this.dataJSONObject == null) {
this.dataJSONFile = tag.optString("json", null);
if(this.dataJSONFile == null) {
this.dataTextFile = tag.optString("txt", null);
if(this.dataTextFile == null) {
this.dataString = tag.optString("string", null);
}else {
this.dataString = null;
}
}else {
this.dataTextFile = null;
this.dataString = null;
}
}else {
this.dataJSONFile = null;
this.dataTextFile = null;
this.dataString = null;
}
this.dataBinaryFile = tag.optString("file", null);
String t = tag.optString("type", null);
if(t == null) {
if(this.dataJSONObject != null || this.dataJSONFile != null) {
t = "json";
}else if(this.dataString != null || this.dataTextFile != null) {
t = "text";
}else {
t = "binary";
}
}
this.type = t;
}
public void doQuery(QueryConnection query) {
byte[] bin = null;
if(dataBinaryFile != null) {
bin = QueryCache.getBinaryFile(dataBinaryFile);
if(bin == null) {
query.setReturnType("error");
query.writeResponse("Error: could not load binary file '" + dataBinaryFile + "' for query '" + type + "'");
return;
}
}
boolean flag = false;
if(dataJSONObject != null) {
query.setReturnType(type);
query.writeResponse(dataJSONObject);
flag = true;
}else if(dataJSONFile != null) {
JSONObject obj = QueryCache.getJSONFile(dataJSONFile);
if(obj == null) {
query.setReturnType("error");
query.writeResponse("Error: could not load or parse JSON file '" + dataJSONFile + "' for query '" + type + "'");
return;
}else {
query.setReturnType(type);
query.writeResponse(obj);
flag = true;
}
}else if(dataTextFile != null) {
String txt = QueryCache.getStringFile(dataTextFile);
if(txt == null) {
query.setReturnType("error");
query.writeResponse("Error: could not load text file '" + dataJSONFile + "' for query '" + type + "'");
return;
}else {
query.setReturnType(type);
query.writeResponse(txt);
flag = true;
}
}else if(dataString != null) {
query.setReturnType(type);
query.writeResponse(dataString);
flag = true;
}
if(!flag) {
query.setReturnType(type);
if(bin != null) {
query.writeResponse((new JSONObject()).put("binary", true).put("file", dataBinaryFile).put("size", bin.length));
}else {
query.writeResponse("<No Content>");
}
}
if(bin != null) {
query.writeResponseBinary(bin);
}
query.close();
}
}

4
src/plugin.yml Normal file
View File

@ -0,0 +1,4 @@
name: EaglerMOTD
main: net.lax1dude.eaglercraft.eaglermotd.EaglerMOTD
version: 1.0
author: LAX1DUDE

BIN
src/server-icons-test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB