diff --git a/.gitignore b/.gitignore index caa34a2..429f7ba 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.iws .idea out +build #eclipse .project @@ -16,9 +17,12 @@ out org.* bin +#gradle +.gradle + #runtime run run_server #mac -.DS_Store \ No newline at end of file +.DS_Store diff --git a/build.gradle b/build.gradle index 5aacb6a..417fdca 100644 --- a/build.gradle +++ b/build.gradle @@ -1,18 +1,24 @@ buildscript { + ext.kotlin_version = '1.2.20' repositories { jcenter() maven { url = "http://files.minecraftforge.net/maven" } + mavenCentral() } dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT' + classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.4' } } +apply plugin: 'kotlin' apply plugin: 'net.minecraftforge.gradle.forge' +apply plugin: 'com.github.johnrengelman.shadow' //Only edit below this line, the above code adds and enables the necessary things for Forge to be setup. version = "0.0.1" -group = "arcanitor.civilengineering" // http://maven.apache.org/guides/mini/guide-naming-conventions.html +group = "ivilengineering" // http://maven.apache.org/guides/mini/guide-naming-conventions.html archivesBaseName = "CivilEngineering" sourceCompatibility = targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. @@ -33,7 +39,19 @@ minecraft { // makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable. } +repositories { + jcenter() + maven { + url = 'http://unascribed.com/maven/releases' + } + maven { + url "http://maven.shadowfacts.net/" + } +} + dependencies { + compile group: "net.shadowfacts", name: "Forgelin", version: "1.6.0" + compile group: 'io.github.microutils', name: 'kotlin-logging', version: '+' // you may put jars on which you depend on in ./libs // or you may define them like so.. //compile "some.group:artifact:version:classifier" @@ -57,6 +75,16 @@ dependencies { } +shadowJar { + relocate 'org.jetbrains.annotations', 'ivilengineering.jetbrains.annotations' + classifier '' + configurations = [project.configurations.shadow] +} + +reobf { + shadowJar { mappingType = 'SEARGE' } +} + processResources { // this will ensure that this task is redone when the versions change. inputs.property "version", project.version @@ -75,3 +103,7 @@ processResources { exclude 'mcmod.info' } } + +sourceSets { + main.java.srcDirs += 'src/main/kotlin' +} \ No newline at end of file diff --git a/src/main/java/arcanitor/civilengineering/CivilEngineering.java b/src/main/java/arcanitor/civilengineering/CivilEngineering.java deleted file mode 100644 index 8e45e75..0000000 --- a/src/main/java/arcanitor/civilengineering/CivilEngineering.java +++ /dev/null @@ -1,57 +0,0 @@ -package arcanitor.civilengineering; - -import arcanitor.civilengineering.eventhandlers.FMLEventHandler; -import arcanitor.civilengineering.bridge.MessageHandler; -import arcanitor.civilengineering.bridge.ServerChatHelper; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.event.*; -import org.apache.logging.log4j.Logger; - -@Mod( - modid = CivilEngineering.MODID, - name = CivilEngineering.NAME, - version = CivilEngineering.VERSION, - serverSideOnly = true, - useMetadata = true, - acceptableRemoteVersions = "*" -) -public class CivilEngineering { - public static final String MODID = "civilengineering"; - public static final String NAME = "Civil Engineering"; - public static final String VERSION = "0.0.1"; - - @Mod.Instance(value = CivilEngineering.MODID) - public static CivilEngineering instance; - - public static Logger logger; - public static Thread MessageNetworkThread = new Thread(new MessageHandler()); - - @Mod.EventHandler - public void preInit(FMLPreInitializationEvent event) { - logger = event.getModLog(); - - FMLEventHandler.preInit(event); - } - - @Mod.EventHandler - public void init(FMLInitializationEvent event) { - logger.info("Bridge building init."); - } - - @Mod.EventHandler - public void postInit(FMLPostInitializationEvent event) { - FMLEventHandler.postInit(event); - MinecraftForge.EVENT_BUS.register(ServerChatHelper.class); - } - - @Mod.EventHandler - public void serverStarting(FMLServerStartingEvent event){ - FMLEventHandler.serverStarting(event); - } - - @Mod.EventHandler - public void serverStopping(FMLServerStoppingEvent event) { - FMLEventHandler.serverStopping(event); - } -} diff --git a/src/main/java/arcanitor/civilengineering/bridge/ApiMessage.java b/src/main/java/arcanitor/civilengineering/bridge/ApiMessage.java deleted file mode 100644 index 76d7107..0000000 --- a/src/main/java/arcanitor/civilengineering/bridge/ApiMessage.java +++ /dev/null @@ -1,54 +0,0 @@ -package arcanitor.civilengineering.bridge; - -import arcanitor.civilengineering.CivilEngineering; -import arcanitor.civilengineering.Config; -import com.google.gson.Gson; - -public class ApiMessage { - private String text = ""; - private String channel = ""; - private String username = ""; - private String userid = ""; - private String avatar = ""; - private String gateway = ""; - private String event = ""; - - public ApiMessage(String user, String msg) { - this.username = user; - this.text = msg; - this.gateway = Config.gateway; - - } - public ApiMessage(String user, String msg, String event) { - this.username = user; - this.text = msg; - this.event = event; - } - - public static ApiMessage decode(String json) { - Gson gson = new Gson(); - ApiMessage msg = gson.fromJson(json, ApiMessage.class); - return msg; - - } - - public String encode() { - Gson gson = new Gson(); - return gson.toJson(this); - } - - public String getUsername() { - return this.username; - } - - public String getMessage() { - return this.text; - } - - public String getEvent() { - return this.event; - } - - - -} diff --git a/src/main/java/arcanitor/civilengineering/bridge/MessageHandler.java b/src/main/java/arcanitor/civilengineering/bridge/MessageHandler.java deleted file mode 100644 index 5ae3593..0000000 --- a/src/main/java/arcanitor/civilengineering/bridge/MessageHandler.java +++ /dev/null @@ -1,108 +0,0 @@ -package arcanitor.civilengineering.bridge; - -import arcanitor.civilengineering.CivilEngineering; -import arcanitor.civilengineering.Config; -import com.google.gson.Gson; - -import java.io.BufferedReader; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLConnection; -import java.util.concurrent.ConcurrentLinkedQueue; - -import static java.lang.Thread.sleep; - -public class MessageHandler implements Runnable { - - public static ConcurrentLinkedQueue xmitQueue = new ConcurrentLinkedQueue<>(); - public static ConcurrentLinkedQueue rcvQueue = new ConcurrentLinkedQueue<>(); - - public void run() { - CivilEngineering.logger.info("Connecting to bridge server @ "+Config.connectURL); - try { - while(true) { - transmitFromQueue(); - receiveToQueue(); - sleep(1000); - } - } catch (Exception e) { - - if (e instanceof InterruptedException) { - CivilEngineering.logger.info("Connection closed."); - } else if (e instanceof IOException) { - CivilEngineering.logger.error("Error connecting to bridge server!"); - CivilEngineering.logger.error(e.getMessage()); - - } - } - - } - - private void transmitFromQueue() throws IOException { - ApiMessage nextMessage = xmitQueue.poll(); - while(nextMessage != null) { - //open a connection - URL url = new URL(Config.connectURL + "/api/message"); - URLConnection urlConnection = url.openConnection(); - HttpURLConnection connection = (HttpURLConnection)urlConnection; - - //configure the connection - connection.setAllowUserInteraction(false); - connection.setInstanceFollowRedirects(true); - connection.setRequestProperty("Content-Type","application/json"); - connection.setRequestMethod("POST"); - if (Config.authToken != null) { - connection.setRequestProperty ("Authorization", "Bearer " + Config.authToken); - } - - //encode the ApiMessage for sending - String json = nextMessage.encode(); - - //send the message - connection.setDoOutput(true); - DataOutputStream post = new DataOutputStream(connection.getOutputStream()); - post.writeBytes(json); - post.flush(); - post.close(); - - if (connection.getResponseCode()!=200) { - CivilEngineering.logger.error("Server returned "+connection.getResponseCode()); - break; - } - nextMessage = xmitQueue.poll(); - } - - } - private void receiveToQueue() throws IOException { - ApiMessage[] messages; - - //open a connection - URL url = new URL(Config.connectURL + "/api/messages"); - HttpURLConnection con = (HttpURLConnection)url.openConnection(); - - //configure the connection - con.setAllowUserInteraction(false); - con.setInstanceFollowRedirects(true); - if (Config.authToken != null) { - con.setRequestProperty ("Authorization", "Bearer " + Config.authToken); - } - - //read the messages - BufferedReader input = new BufferedReader(new InputStreamReader(con.getInputStream())); - StringBuilder data = new StringBuilder(); - String line; - while((line = input.readLine( )) != null) { - data.append(line); - } - - //decode the messages - Gson gson = new Gson(); - messages = gson.fromJson(data.toString(),ApiMessage[].class); - - //enqueue the messages - if(messages.length>0) for (ApiMessage msg : messages) rcvQueue.add(msg); - } -} diff --git a/src/main/java/arcanitor/civilengineering/bridge/OutgoingMessageHandler.java b/src/main/java/arcanitor/civilengineering/bridge/OutgoingMessageHandler.java deleted file mode 100644 index 953d7f3..0000000 --- a/src/main/java/arcanitor/civilengineering/bridge/OutgoingMessageHandler.java +++ /dev/null @@ -1,72 +0,0 @@ -package arcanitor.civilengineering.bridge; - -import arcanitor.civilengineering.CivilEngineering; -import arcanitor.civilengineering.Config; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLConnection; -import java.util.concurrent.ConcurrentLinkedQueue; - -import static java.lang.Thread.sleep; - -public class OutgoingMessageHandler implements Runnable { - public static ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); - - public void run() { - CivilEngineering.logger.info("Sending network thread starting"); - try { - while(true) { - ApiMessage nextMessage = queue.poll(); - if (nextMessage!=null) { - int response = postMessage(nextMessage); - if (response != 200) { - CivilEngineering.logger.error("Server returned error "+response); - break; - } - } - sleep(50); - } - } catch (Exception e) { - if (e instanceof InterruptedException) { - CivilEngineering.logger.info("Sending connection closed."); - } else if (e instanceof IOException) { - CivilEngineering.MessageNetworkThread.interrupt(); - CivilEngineering.logger.error("Error connecting to bridge server!"); - CivilEngineering.logger.error(e.getMessage()); - } - - } - } - - public int postMessage(ApiMessage message) throws IOException { - - //open a connection - URL url = new URL(Config.connectURL + "/api/message"); - URLConnection urlConnection = url.openConnection(); - HttpURLConnection connection = (HttpURLConnection)urlConnection; - - //configure the connection - connection.setAllowUserInteraction(false); - connection.setInstanceFollowRedirects(true); - connection.setRequestProperty("Content-Type","application/json"); - connection.setRequestMethod("POST"); - if (Config.authToken != null) { - connection.setRequestProperty ("Authorization", "Bearer " + Config.authToken); - } - - //encode the ApiMessage for sending - String json = message.encode(); - - //send the message - connection.setDoOutput(true); - DataOutputStream post = new DataOutputStream(connection.getOutputStream()); - post.writeBytes(json); - post.flush(); - post.close(); - - return connection.getResponseCode(); - } -} diff --git a/src/main/java/arcanitor/civilengineering/bridge/ServerChatHelper.java b/src/main/java/arcanitor/civilengineering/bridge/ServerChatHelper.java deleted file mode 100644 index 30166c1..0000000 --- a/src/main/java/arcanitor/civilengineering/bridge/ServerChatHelper.java +++ /dev/null @@ -1,33 +0,0 @@ -package arcanitor.civilengineering.bridge; - -import arcanitor.civilengineering.bridge.ApiMessage; -import arcanitor.civilengineering.bridge.MessageHandler; -import net.minecraft.util.text.TextComponentString; -import net.minecraftforge.fml.common.FMLCommonHandler; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.fml.common.gameevent.TickEvent; - -public class ServerChatHelper { - //public static ConcurrentLinkedQueue messages = new ConcurrentLinkedQueue(); - - @SubscribeEvent - public static void onServerUpdate(TickEvent.ServerTickEvent event) { - ApiMessage nextMessage = MessageHandler.rcvQueue.poll(); - - if (nextMessage != null) { - String user = nextMessage.getUsername(); - String text = nextMessage.getMessage().trim(); - - String message; - - if (!text.isEmpty()) { - if (nextMessage.getEvent().equals("user_action")) { - message = "* " + user + " " + text; - } else { - message = "<" + user + "> " + text; - } - FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerList().sendMessage(new TextComponentString(message)); - } - } - } -} diff --git a/src/main/java/arcanitor/civilengineering/command/BridgeCommand.java b/src/main/java/arcanitor/civilengineering/command/BridgeCommand.java deleted file mode 100644 index 9889f85..0000000 --- a/src/main/java/arcanitor/civilengineering/command/BridgeCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -package arcanitor.civilengineering.command; - -import arcanitor.civilengineering.CivilEngineering; -import arcanitor.civilengineering.bridge.MessageHandler; -import com.google.common.collect.Lists; -import net.minecraft.command.CommandBase; -import net.minecraft.command.ICommandSender; -import net.minecraft.server.MinecraftServer; - -import javax.annotation.Nonnull; -import java.util.List; - -public class BridgeCommand extends CommandBase { - private final List aliases; - - public BridgeCommand(){ - aliases = Lists.newArrayList(CivilEngineering.MODID,"bridge","BRIDGE"); - } - - @Override - @Nonnull - public String getName() { - return "bridge"; - } - - @Override - public String getUsage(ICommandSender sender) { - return "bridge "; - } - - @Override - @Nonnull - public List getAliases() { - return aliases; - } - - @Override - public void execute(MinecraftServer server, ICommandSender sender, String[] args) /*throws CommandException*/ { - if (args.length < 1) { - //throw new WrongUsageException("") - return; - } - String cmd = args[0]; - if (cmd.toLowerCase().equals("connect")) { - if(!CivilEngineering.MessageNetworkThread.isAlive()) { - CivilEngineering.MessageNetworkThread = new Thread(new MessageHandler()); - CivilEngineering.MessageNetworkThread.start(); - } - } else if (cmd.toLowerCase().equals("disconnect")) { - CivilEngineering.MessageNetworkThread.interrupt(); - } - } - - -} diff --git a/src/main/java/arcanitor/civilengineering/eventhandlers/ChatMessageHandler.java b/src/main/java/arcanitor/civilengineering/eventhandlers/ChatMessageHandler.java deleted file mode 100644 index bb07e20..0000000 --- a/src/main/java/arcanitor/civilengineering/eventhandlers/ChatMessageHandler.java +++ /dev/null @@ -1,17 +0,0 @@ -package arcanitor.civilengineering.eventhandlers; - -import arcanitor.civilengineering.bridge.ApiMessage; -import arcanitor.civilengineering.bridge.MessageHandler; -import net.minecraftforge.event.ServerChatEvent; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; - -@Mod.EventBusSubscriber -public class ChatMessageHandler { - @SubscribeEvent - public static void handleServerChatEvent (ServerChatEvent event) { - String message = event.getMessage().trim(); - if (!message.isEmpty()) - MessageHandler.xmitQueue.add(new ApiMessage(event.getUsername(),message)); - } -} diff --git a/src/main/java/arcanitor/civilengineering/eventhandlers/DeathEventHandler.java b/src/main/java/arcanitor/civilengineering/eventhandlers/DeathEventHandler.java deleted file mode 100644 index 8dad89f..0000000 --- a/src/main/java/arcanitor/civilengineering/eventhandlers/DeathEventHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -package arcanitor.civilengineering.eventhandlers; - -import arcanitor.civilengineering.Config; -import arcanitor.civilengineering.bridge.ApiMessage; -import arcanitor.civilengineering.bridge.MessageHandler; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraftforge.event.entity.living.LivingDeathEvent; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; - -@Mod.EventBusSubscriber -public class DeathEventHandler { - @SubscribeEvent - public static void handleLivingDeathEvent (LivingDeathEvent event) { - if(Config.relayDeathEvents) { - EntityLivingBase entity = event.getEntityLiving(); - if (entity instanceof EntityPlayer) { - String message = entity.getCombatTracker().getDeathMessage().getUnformattedText(); - MessageHandler.xmitQueue.add(new ApiMessage("Server",message)); - } - } - } -} diff --git a/src/main/java/arcanitor/civilengineering/eventhandlers/FMLEventHandler.java b/src/main/java/arcanitor/civilengineering/eventhandlers/FMLEventHandler.java deleted file mode 100644 index b572474..0000000 --- a/src/main/java/arcanitor/civilengineering/eventhandlers/FMLEventHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -package arcanitor.civilengineering.eventhandlers; - - -import arcanitor.civilengineering.CivilEngineering; -import arcanitor.civilengineering.Config; -import arcanitor.civilengineering.command.BridgeCommand; -import net.minecraftforge.common.config.Configuration; -import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; -import net.minecraftforge.fml.common.event.FMLServerStartingEvent; -import net.minecraftforge.fml.common.event.FMLServerStoppingEvent; - -import java.io.File; - -public class FMLEventHandler { - public static Configuration config; - - public static void preInit(FMLPreInitializationEvent event) { - CivilEngineering.logger.info("Reading bridge blueprints..."); - File directory = event.getModConfigurationDirectory(); - config = new Configuration(new File(directory.getPath(), "CivilEngineering.cfg")); - Config.readConfig(); - } - - public static void postInit(FMLPostInitializationEvent event) { - if (config.hasChanged()) { - config.save(); - } - } - - public static void serverStarting(FMLServerStartingEvent event) { - event.registerServerCommand(new BridgeCommand()); - CivilEngineering.MessageNetworkThread.start(); - } - - public static void serverStopping(FMLServerStoppingEvent event) { - CivilEngineering.MessageNetworkThread.interrupt(); - } -} diff --git a/src/main/java/arcanitor/civilengineering/eventhandlers/UserActionHandler.java b/src/main/java/arcanitor/civilengineering/eventhandlers/UserActionHandler.java deleted file mode 100644 index 66806fd..0000000 --- a/src/main/java/arcanitor/civilengineering/eventhandlers/UserActionHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -package arcanitor.civilengineering.eventhandlers; - -import arcanitor.civilengineering.bridge.ApiMessage; -import arcanitor.civilengineering.bridge.MessageHandler; -import net.minecraft.command.server.CommandEmote; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraftforge.event.CommandEvent; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; - -@Mod.EventBusSubscriber -public class UserActionHandler { - @SubscribeEvent - public static void handleCommandEvent(CommandEvent event) { - if(event.getCommand() instanceof CommandEmote && event.getSender() instanceof EntityPlayer) { - String[] args = event.getParameters(); - - String user = event.getSender().getName(); - String message = ""; - - for(String word:args) { - message = message + " " + word; - } - message = message.trim(); - - MessageHandler.xmitQueue.add(new ApiMessage(user,message,"user_action")); - } - } -} diff --git a/src/main/kotlin/civilengineering/CivilEngineering.kt b/src/main/kotlin/civilengineering/CivilEngineering.kt new file mode 100644 index 0000000..93d0c0d --- /dev/null +++ b/src/main/kotlin/civilengineering/CivilEngineering.kt @@ -0,0 +1,98 @@ +package civilengineering + +import civilengineering.bridge.MessageHandler +import civilengineering.bridge.ServerChatHelper +import civilengineering.command.BridgeCommand +import civilengineering.eventhandlers.ChatMessageHandler +import civilengineering.eventhandlers.DeathEventHandler +import civilengineering.eventhandlers.UserActionHandler +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.common.config.Configuration +import net.minecraftforge.fml.common.Mod +import net.minecraftforge.fml.common.event.* +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.Logger +import org.apache.logging.log4j.Marker +import org.apache.logging.log4j.message.Message +import org.apache.logging.log4j.message.MessageFactory +import org.apache.logging.log4j.message.SimpleMessageFactory +import org.apache.logging.log4j.simple.SimpleLogger +import org.apache.logging.log4j.spi.AbstractLogger +import org.apache.logging.log4j.util.PropertiesUtil +import java.io.File +import java.time.format.DateTimeFormatter +import java.time.format.DateTimeFormatterBuilder +import java.util.* + +const val MODID = "civilengineering" +const val NAME = "Civil Engineering" +const val VERSION = "0.0.1" + +@Mod( + modid = MODID, + name = NAME, version = VERSION, + serverSideOnly = true, + useMetadata = true, + acceptableRemoteVersions = "*", + modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter" +) +object CivilEngineering { + init { + } + + var config: Configuration = Configuration() +// var messageNetworkThread = Thread(MessageHandler()) + var logger: Logger = SimpleLogger ("", + Level.OFF, + false, + false, + false, + false, + "", + SimpleMessageFactory(), + PropertiesUtil(Properties()), + System.out) + + @Mod.EventHandler + fun preInit(event: FMLPreInitializationEvent) { + logger = event.modLog + logger!!.info("loading logger") + + CivilEngineering.logger!!.info("Reading bridge blueprints...") + val directory = event.modConfigurationDirectory + config = Configuration(File(directory.path, "CivilEngineering.cfg")) + Config.readConfig() + } + + @Mod.EventHandler + fun init(event: FMLInitializationEvent) { + logger!!.info("Bridge building init.") + } + + @Mod.EventHandler + fun postInit(event: FMLPostInitializationEvent) { + if (config.hasChanged()) { + config.save() + } + MinecraftForge.EVENT_BUS.register(ServerChatHelper::class.java) + + } + + @Mod.EventHandler + fun serverStarting(event: FMLServerStartingEvent) { + event.registerServerCommand(BridgeCommand()) + logger!!.info("Bridge building starting.") + MessageHandler.start() + + //maybe try registering them manually + MinecraftForge.EVENT_BUS.register(ChatMessageHandler()) + MinecraftForge.EVENT_BUS.register(DeathEventHandler()) + MinecraftForge.EVENT_BUS.register(UserActionHandler()) + } + + @Mod.EventHandler + fun serverStopping(event: FMLServerStoppingEvent) { + logger!!.info("Bridge shutting down.") + MessageHandler.stop() + } +} diff --git a/src/main/java/arcanitor/civilengineering/Config.java b/src/main/kotlin/civilengineering/Config.kt similarity index 51% rename from src/main/java/arcanitor/civilengineering/Config.java rename to src/main/kotlin/civilengineering/Config.kt index 3a5611c..4a29f1f 100644 --- a/src/main/java/arcanitor/civilengineering/Config.java +++ b/src/main/kotlin/civilengineering/Config.kt @@ -1,76 +1,75 @@ -package arcanitor.civilengineering; +package civilengineering -import arcanitor.civilengineering.eventhandlers.FMLEventHandler; -import net.minecraftforge.common.config.Configuration; -import org.apache.logging.log4j.Level; +import net.minecraftforge.common.config.Configuration +import org.apache.logging.log4j.Level -public class Config { - private static final String CATEGORY_RELAY_OPTIONS = "relay_options"; - private static final String CATEGORY_CONNECTION = "connection"; +object Config { + private val CATEGORY_RELAY_OPTIONS = "relay_options" + private val CATEGORY_CONNECTION = "connection" - public static boolean relayDeathEvents = false; - public static boolean relayAdvancements = false; //unused for now - public static boolean relayJoinLeave = false; + var relayDeathEvents = false + var relayAdvancements = false //unused for now + var relayJoinLeave = false - public static String connectURL = "localhost"; - public static String authToken = ""; - public static String gateway = ""; + var connectURL = "http://localhost" + var authToken = "" + var gateway = "" - public static void readConfig() { - Configuration config = FMLEventHandler.config; + fun readConfig() { + + val config = CivilEngineering.config try { - config.load(); - initConfig(config); - } catch (Exception expt) { - CivilEngineering.logger.log(Level.ERROR,"Could not read config file!", expt); + config.load() + initConfig(config) + } catch (e: Exception) { + CivilEngineering.logger!!.log(Level.ERROR, "Could not read config file!", e) } finally { if (config.hasChanged()) { - config.save(); + config.save() } } } - private static void initConfig(Configuration cfg) { - cfg.addCustomCategoryComment(CATEGORY_RELAY_OPTIONS,"Relay options"); - cfg.addCustomCategoryComment(CATEGORY_CONNECTION,"Connection settings"); + private fun initConfig(cfg: Configuration) { + cfg.addCustomCategoryComment(CATEGORY_RELAY_OPTIONS, "Relay options") + cfg.addCustomCategoryComment(CATEGORY_CONNECTION, "Connection settings") relayDeathEvents = cfg.getBoolean( "relayDeathEvents", CATEGORY_RELAY_OPTIONS, false, "Relay player death messages" - ); + ) relayAdvancements = cfg.getBoolean( "relayAdvancements", CATEGORY_RELAY_OPTIONS, false, "Relay player advancements [NOT IMPLEMENTED]" - ); + ) relayJoinLeave = cfg.getBoolean( "relayJoinLeave", CATEGORY_RELAY_OPTIONS, false, "Relay when a player joins or leaves the game [NOT IMPLEMENTED]" - ); + ) connectURL = cfg.getString( "connectURL", CATEGORY_CONNECTION, "http://example.com:1234", "The URL or IP address of the bridge server, ex. http://example.com:1234" - ); + ) authToken = cfg.getString( "auth_token", CATEGORY_CONNECTION, "", "Auth token used to connect to the bridge server" - ); + ) gateway = cfg.getString( "gateway", CATEGORY_CONNECTION, "", "MatterBridge gateway" - ); - + ) } diff --git a/src/main/kotlin/civilengineering/bridge/ApiMessage.kt b/src/main/kotlin/civilengineering/bridge/ApiMessage.kt new file mode 100644 index 0000000..6e9d983 --- /dev/null +++ b/src/main/kotlin/civilengineering/bridge/ApiMessage.kt @@ -0,0 +1,32 @@ +package civilengineering.bridge + +import civilengineering.Config +import com.google.gson.Gson +import java.util.* + +data class ApiMessage ( + val text: String = "", + val channel: String = "", + val username: String = "", + val userid: String = "", + val avatar: String = "", + val account: String = "", + val event: String = "", + val protocol: String = "", + val gateway: String = "", +// val timestamp: Date, + val id: String = "" +// val Extra: Any? = null +) { + fun encode(): String { + return gson.toJson(this) + } + + companion object { + val gson = Gson() + + fun decode(json: String): ApiMessage { + return gson.fromJson(json, ApiMessage::class.java) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/civilengineering/bridge/CancellableConnectionFollowThread.kt b/src/main/kotlin/civilengineering/bridge/CancellableConnectionFollowThread.kt new file mode 100644 index 0000000..e4cf255 --- /dev/null +++ b/src/main/kotlin/civilengineering/bridge/CancellableConnectionFollowThread.kt @@ -0,0 +1,51 @@ +package civilengineering.bridge + +import java.io.InputStreamReader +import java.net.HttpURLConnection + +/** + * Created by nikky on 20/01/18. + * @author Nikky + * @version 1.0 + */ + +class CancellableConnectionFollowThread (val httpConnClosure: () -> HttpURLConnection, val mhandler: (String) -> Unit): Thread() { + val cancelGuard = Object() + var waitingOnNetwork = true + var cancelled = false + val httpConn = httpConnClosure() + + override fun run() { + try { + httpConn.allowUserInteraction = false + httpConn.instanceFollowRedirects = true + httpConn.requestMethod = "GET" + + InputStreamReader(httpConn.inputStream).useLines { + it.forEach{ + synchronized(cancelGuard) { + if (cancelled) return + waitingOnNetwork = false + } + mhandler(it) + synchronized(cancelGuard) { + if (cancelled) return + waitingOnNetwork = true + } + } + } + } catch (e: Exception) { + } finally { + httpConn.disconnect() + } + } + + fun abort() { + synchronized(cancelGuard) { + httpConn.disconnect() + cancelled = true + if (waitingOnNetwork) stop() + } + join() + } +} \ No newline at end of file diff --git a/src/main/kotlin/civilengineering/bridge/MessageHandler.kt b/src/main/kotlin/civilengineering/bridge/MessageHandler.kt new file mode 100644 index 0000000..cb32559 --- /dev/null +++ b/src/main/kotlin/civilengineering/bridge/MessageHandler.kt @@ -0,0 +1,187 @@ +package civilengineering.bridge + +import civilengineering.CivilEngineering +import civilengineering.Config +import com.google.gson.Gson +import java.io.BufferedReader +import java.io.DataOutputStream +import java.io.IOException +import java.io.InputStreamReader +import java.lang.Thread.sleep +import java.net.HttpURLConnection +import java.net.URL +import java.util.concurrent.ConcurrentLinkedQueue + +class MessageHandler : Runnable { + + override fun run() { + CivilEngineering.logger!!.info("Connecting to bridge server @ " + Config.connectURL) + try { + while (true) { + transmitFromQueue() +// receiveToQueue() + sleep(1000) + } + } catch (e: Exception) { + + if (e is InterruptedException) { + CivilEngineering.logger!!.info("Connection closed.") + } else if (e is IOException) { + CivilEngineering.logger!!.error("Error connecting to bridge server!") + CivilEngineering.logger!!.error(e.message) + + } + } + } + + @Throws(IOException::class) + private fun transmitFromQueue() { + var nextMessage: ApiMessage? = xmitQueue.poll() + while (nextMessage != null) { + //open a connection + val url = URL(Config.connectURL + "/api/message") + val urlConnection = url.openConnection() + val connection = urlConnection as HttpURLConnection + + //configure the connection + connection.allowUserInteraction = false + connection.instanceFollowRedirects = true + connection.setRequestProperty("Content-Type", "application/json") + connection.requestMethod = "POST" + if (Config.authToken.isNotEmpty()) { + connection.setRequestProperty("Authorization", "Bearer " + Config.authToken) + } + + //encode the ApiMessage for sending + val json = nextMessage.encode() + + //send the message + connection.doOutput = true + val post = DataOutputStream(connection.outputStream) + post.writeBytes(json) + post.flush() + post.close() + + if (connection.responseCode != 200) { + CivilEngineering.logger!!.error("Server returned " + connection.responseCode) + break + } + nextMessage = xmitQueue.poll() + } + } + + @Throws(IOException::class) + private fun receiveToQueue() { + val messages: Array + + //open a connection + val url = URL(Config.connectURL + "/api/messages") + val con = url.openConnection() as HttpURLConnection + + //configure the connection + con.allowUserInteraction = false + con.instanceFollowRedirects = true + if (Config.authToken.isNotEmpty()) { + con.setRequestProperty("Authorization", "Bearer " + Config.authToken) + } + + //read the messages + val input = BufferedReader(InputStreamReader(con.inputStream)) + val data = StringBuilder() + var line: String? + while (true) { + line = input.readLine() + if (line == null) { + break + } + data.append(line) + } + //decode the messages + val gson = Gson() + messages = gson.fromJson(data.toString(), Array::class.java) + + //enqueue the messages + if (messages.isNotEmpty()) for (msg in messages) rcvQueue.add(msg) + } + + companion object { + + private fun createThread(): CancellableConnectionFollowThread { + return CancellableConnectionFollowThread( + { + CivilEngineering.logger!!.info("Connecting to bridge server @ " + Config.connectURL) + val httpConn = URL(Config.connectURL + "/api/stream").openConnection() as HttpURLConnection + if (Config.authToken.isNotBlank()) + httpConn.setRequestProperty("Authorization", "Bearer ${Config.authToken}") + httpConn + }, + { + rcvQueue.add( + ApiMessage.decode(it) + ) + CivilEngineering.logger!!.trace("received: " + it) + } + ) + } + + private var cancellableThread: CancellableConnectionFollowThread = createThread() + + private var xmitQueue = ConcurrentLinkedQueue() + + var rcvQueue = ConcurrentLinkedQueue() + + fun transmit(msg: ApiMessage) { + CivilEngineering.logger!!.info("transmitting " + msg) + transmitMessage(msg) + //TODO: create thread with Runnable(sendstuff).execute() + } + + fun stop() { + cancellableThread.abort() + CivilEngineering.logger!!.info("bridge closed ") + } + + fun start(): Boolean { + if (cancellableThread.isInterrupted) { + CivilEngineering.logger!!.info("brebuilding bridge") + cancellableThread = createThread() + } + if (!cancellableThread.isAlive) { + cancellableThread.start() + return true + } + return false + } + + @Throws(IOException::class) + private fun transmitMessage(message: ApiMessage) { + //open a connection + val url = URL(Config.connectURL + "/api/message") + val urlConnection = url.openConnection() + val connection = urlConnection as HttpURLConnection + + //configure the connection + connection.allowUserInteraction = false + connection.instanceFollowRedirects = true + connection.setRequestProperty("Content-Type", "application/json") + connection.requestMethod = "POST" + if (Config.authToken.isNotEmpty()) { + connection.setRequestProperty("Authorization", "Bearer " + Config.authToken) + } + + //encode the ApiMessage for sending + val json = message.encode() + + //send the message + connection.doOutput = true + val post = DataOutputStream(connection.outputStream) + post.writeBytes(json) + post.flush() + post.close() + + if (connection.responseCode != 200) { + CivilEngineering.logger!!.error("Server returned " + connection.responseCode) + } + } + } +} diff --git a/src/main/kotlin/civilengineering/bridge/ServerChatHelper.kt b/src/main/kotlin/civilengineering/bridge/ServerChatHelper.kt new file mode 100644 index 0000000..1175571 --- /dev/null +++ b/src/main/kotlin/civilengineering/bridge/ServerChatHelper.kt @@ -0,0 +1,33 @@ +package civilengineering.bridge + +import civilengineering.CivilEngineering +import net.minecraft.util.text.TextComponentString +import net.minecraftforge.fml.common.FMLCommonHandler +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.TickEvent + +object ServerChatHelper { + //public static ConcurrentLinkedQueue messages = new ConcurrentLinkedQueue(); + + @SubscribeEvent + fun onServerUpdate(event: TickEvent.ServerTickEvent) { + if(MessageHandler.rcvQueue.isNotEmpty()) + CivilEngineering.logger!!.info("incoming: " + MessageHandler.rcvQueue.toString()) + val nextMessage = MessageHandler.rcvQueue.poll() + + if (nextMessage != null) { + val user = nextMessage.username + val text = nextMessage.text.trim() + + val message: String + + if (!text.isEmpty()) { + message = when(nextMessage.event) { + "user_action" -> "* $user $text" + else -> "<$user> $text" + } + FMLCommonHandler.instance().minecraftServerInstance.playerList.sendMessage(TextComponentString(message)) + } + } + } +} diff --git a/src/main/kotlin/civilengineering/command/BridgeCommand.kt b/src/main/kotlin/civilengineering/command/BridgeCommand.kt new file mode 100644 index 0000000..87c82d8 --- /dev/null +++ b/src/main/kotlin/civilengineering/command/BridgeCommand.kt @@ -0,0 +1,52 @@ +package civilengineering.command + +import civilengineering.CivilEngineering +import civilengineering.* +import civilengineering.CivilEngineering.logger +import civilengineering.bridge.MessageHandler +import com.google.common.collect.Lists +import net.minecraft.command.CommandBase +import net.minecraft.command.ICommandSender +import net.minecraft.server.MinecraftServer + + +class BridgeCommand : CommandBase() { + private val aliases: List + + init { + aliases = Lists.newArrayList(MODID, "bridge", "BRIDGE") + } + + override fun getName(): String { + return "bridge" + } + + override fun getUsage(sender: ICommandSender): String { + return "bridge " + } + + override fun getAliases(): List { + return aliases + } + + override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array) /*throws CommandException*/ { + if (args.isEmpty()) { + //throw new WrongUsageException("") + return + } + //TODO: check if sender is OP or test if normal users cannot send this + + + val cmd = args[0].toLowerCase() + when (cmd) { + "connect" -> if (MessageHandler.start()) { + logger!!.info("connected to matterbridge") + } else { + logger!!.error("connection to matterbridge failed") + } + "disconnect" -> MessageHandler.stop() + } + } + + +} diff --git a/src/main/kotlin/civilengineering/eventhandlers/ChatMessageHandler.kt b/src/main/kotlin/civilengineering/eventhandlers/ChatMessageHandler.kt new file mode 100644 index 0000000..c5d7b62 --- /dev/null +++ b/src/main/kotlin/civilengineering/eventhandlers/ChatMessageHandler.kt @@ -0,0 +1,15 @@ +package civilengineering.eventhandlers + +import civilengineering.bridge.ApiMessage +import civilengineering.bridge.MessageHandler +import net.minecraftforge.event.ServerChatEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class ChatMessageHandler { + @SubscribeEvent + fun handleServerChatEvent(event: ServerChatEvent) { + val message = event.message.trim { it <= ' ' } + if (!message.isEmpty()) + MessageHandler.transmit(ApiMessage(event.username, message)) + } +} diff --git a/src/main/kotlin/civilengineering/eventhandlers/DeathEventHandler.kt b/src/main/kotlin/civilengineering/eventhandlers/DeathEventHandler.kt new file mode 100644 index 0000000..c45c413 --- /dev/null +++ b/src/main/kotlin/civilengineering/eventhandlers/DeathEventHandler.kt @@ -0,0 +1,23 @@ +package civilengineering.eventhandlers + +import civilengineering.Config +import civilengineering.bridge.ApiMessage +import civilengineering.bridge.MessageHandler +import net.minecraft.entity.EntityLivingBase +import net.minecraft.entity.player.EntityPlayer +import net.minecraftforge.event.entity.living.LivingDeathEvent +import net.minecraftforge.fml.common.Mod +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class DeathEventHandler { + @SubscribeEvent + fun handleLivingDeathEvent(event: LivingDeathEvent) { + if (Config.relayDeathEvents) { + val entity = event.entityLiving + if (entity is EntityPlayer) { + val message = entity.getCombatTracker().deathMessage.unformattedText + MessageHandler.transmit(ApiMessage("Server", message)) + } + } + } +} diff --git a/src/main/kotlin/civilengineering/eventhandlers/UserActionHandler.kt b/src/main/kotlin/civilengineering/eventhandlers/UserActionHandler.kt new file mode 100644 index 0000000..cff1073 --- /dev/null +++ b/src/main/kotlin/civilengineering/eventhandlers/UserActionHandler.kt @@ -0,0 +1,28 @@ +package civilengineering.eventhandlers + +import civilengineering.bridge.ApiMessage +import civilengineering.bridge.MessageHandler +import net.minecraft.command.server.CommandEmote +import net.minecraft.entity.player.EntityPlayer +import net.minecraftforge.event.CommandEvent +import net.minecraftforge.fml.common.Mod +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class UserActionHandler { + @SubscribeEvent + fun handleCommandEvent(event: CommandEvent) { + if (event.command is CommandEmote && event.sender is EntityPlayer) { + val args = event.parameters + + val user = event.sender.name + var message = "" + + for (word in args) { + message = message + " " + word + } + message = message.trim { it <= ' ' } + + MessageHandler.transmit(ApiMessage(user, message, "user_action")) + } + } +} diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index 543704e..9fd36b6 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -4,6 +4,6 @@ "description": "Minecraft Server Matterbridge link", "version": "0.0.1", "mcversion": "1.12.2", - "authorList":["Arcanitor"], + "authorList":["Arcanitor", "NikkyAi"], "credits": "Blame Nikky for talking me into this." }] \ No newline at end of file