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/Config.java b/src/main/java/arcanitor/civilengineering/Config.java deleted file mode 100644 index 3a5611c..0000000 --- a/src/main/java/arcanitor/civilengineering/Config.java +++ /dev/null @@ -1,77 +0,0 @@ -package arcanitor.civilengineering; - -import arcanitor.civilengineering.eventhandlers.FMLEventHandler; -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"; - - public static boolean relayDeathEvents = false; - public static boolean relayAdvancements = false; //unused for now - public static boolean relayJoinLeave = false; - - public static String connectURL = "localhost"; - public static String authToken = ""; - public static String gateway = ""; - - public static void readConfig() { - Configuration config = FMLEventHandler.config; - try { - config.load(); - initConfig(config); - } catch (Exception expt) { - CivilEngineering.logger.log(Level.ERROR,"Could not read config file!", expt); - } finally { - if (config.hasChanged()) { - config.save(); - } - } - } - - private static void initConfig(Configuration cfg) { - 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" - ); - - - } - -} \ No newline at end of file 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..b4cff88 --- /dev/null +++ b/src/main/kotlin/civilengineering/CivilEngineering.kt @@ -0,0 +1,81 @@ +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.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.message.SimpleMessageFactory +import org.apache.logging.log4j.simple.SimpleLogger +import org.apache.logging.log4j.util.PropertiesUtil +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 { + //create fake logger to get around Nullability + 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") + + CivilEngineeringConfig(event.modConfigurationDirectory) + } + + @Mod.EventHandler + fun init(event: FMLInitializationEvent) { + logger.info("Bridge building init.") + } + + @Mod.EventHandler + fun postInit(event: FMLPostInitializationEvent) { +// 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(ServerChatHelper()) + 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/kotlin/civilengineering/CivilEngineeringConfig.kt b/src/main/kotlin/civilengineering/CivilEngineeringConfig.kt new file mode 100644 index 0000000..4d4f4d9 --- /dev/null +++ b/src/main/kotlin/civilengineering/CivilEngineeringConfig.kt @@ -0,0 +1,80 @@ +package civilengineering + +import net.minecraftforge.common.config.Configuration +import java.io.File + +var cfg: CivilEngineeringConfig? = null + +class CivilEngineeringConfig(file: File) { + private val CATEGORY_RELAY_OPTIONS = "relay" + private val CATEGORY_CONNECTION = "connection" + + val relay: RelayOptions + val connect: ConnectOptions + + data class RelayOptions( + val deathEvents: Boolean, + val advancements: Boolean, + val joinLeave: Boolean + ) + + data class ConnectOptions( + val url: String, + val authToken: String, + val gateway: String + ) + + init { + CivilEngineering.logger.info("Reading bridge blueprints...") + val config = Configuration(file.resolve("CivilEngineering.cfg")) + + config.addCustomCategoryComment(CATEGORY_RELAY_OPTIONS, "Relay options") + config.addCustomCategoryComment(CATEGORY_CONNECTION, "Connection settings") + + relay = RelayOptions( + deathEvents = config.getBoolean( + "deathEvents", + CATEGORY_RELAY_OPTIONS, + false, + "Relay player death messages" + ), + advancements = config.getBoolean( + "advancements", + CATEGORY_RELAY_OPTIONS, + false, + "Relay player advancements [NOT IMPLEMENTED]" + ), + joinLeave = config.getBoolean( + "joinLeave", + CATEGORY_RELAY_OPTIONS, + false, + "Relay when a player joins or leaves the game [NOT IMPLEMENTED]" + ) + ) + + connect = ConnectOptions( + url = config.getString( + "connectURL", + CATEGORY_CONNECTION, + "http://example.com:1234", + "The URL or IP address of the bridge server" + ), + authToken = config.getString( + "authToken", + CATEGORY_CONNECTION, + "", + "Auth token used to connect to the bridge server" + ), + gateway = config.getString( + "gateway", + CATEGORY_CONNECTION, + "minecraft", + "MatterBridge gateway" + ) + ) + + if (config.hasChanged()) config.save() + + cfg = this + } +} \ No newline at end of file diff --git a/src/main/kotlin/civilengineering/bridge/ApiMessage.kt b/src/main/kotlin/civilengineering/bridge/ApiMessage.kt new file mode 100644 index 0000000..c2f5cd8 --- /dev/null +++ b/src/main/kotlin/civilengineering/bridge/ApiMessage.kt @@ -0,0 +1,31 @@ +package civilengineering.bridge + +import civilengineering.cfg +import com.google.gson.Gson + +data class ApiMessage( + val username: String = "", + val text: String = "", + val gateway: String = cfg!!.connect.gateway, + val channel: String = "", + val userid: String = "", + val avatar: String = "", + val account: String = "", + val event: String = "", + val protocol: String = "", +// val timestamp: Date, + val id: String = "" +// val Extra: Any? = null +) { + companion object { + val gson = Gson() + + fun decode(json: String): ApiMessage { + return gson.fromJson(json, ApiMessage::class.java) + } + } + + fun encode(): String { + return gson.toJson(this) + } +} \ No newline at end of file diff --git a/src/main/kotlin/civilengineering/bridge/HttpStreamConnection.kt b/src/main/kotlin/civilengineering/bridge/HttpStreamConnection.kt new file mode 100644 index 0000000..35ed1f7 --- /dev/null +++ b/src/main/kotlin/civilengineering/bridge/HttpStreamConnection.kt @@ -0,0 +1,61 @@ +package civilengineering.bridge; + +import civilengineering.CivilEngineering +import org.apache.http.client.methods.HttpGet +import org.apache.http.impl.client.HttpClients +import java.io.InputStream +import java.net.SocketException + +val BUFFER_SIZE = 1000 + +class HttpStreamConnection(private val getClosure: () -> HttpGet, private val mhandler: (String) -> Unit) : Thread() { + private val client = HttpClients.createDefault() + private var stream: InputStream? = null + + val get = getClosure() + var cancelled: Boolean = false + private set + + + override fun run() { + val response = client.execute(get) + val content = response.entity.content.buffered() + stream = content + //val reader = content.bufferedReader() + var buffer = "" + val buf = ByteArray(BUFFER_SIZE) + try { + while (!get.isAborted) { + val chars = content.read(buf) + CivilEngineering.logger.debug("incoming: $chars ") + if (chars > 0) { + buffer += String(buf.dropLast(buf.count() - chars).toByteArray()) + + CivilEngineering.logger.info(buffer) + + while (buffer.contains("\n")) { + val line = buffer.substringBefore("\n") + buffer = buffer.substringAfter("\n") + mhandler(line) + } + } else if (chars < 0) { + break + } + } + } catch (e: SocketException) { + + } + CivilEngineering.logger.info("closing stream") + content.close() + CivilEngineering.logger.info("thread finished") + return + } + + fun close() { + cancelled = true + get.abort() + join() + println("killed thread") + + } +} \ 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..91342b2 --- /dev/null +++ b/src/main/kotlin/civilengineering/bridge/MessageHandler.kt @@ -0,0 +1,84 @@ +package civilengineering.bridge + +import civilengineering.CivilEngineering +import civilengineering.cfg +import org.apache.http.client.methods.HttpGet +import org.apache.http.client.methods.HttpPost +import org.apache.http.client.methods.HttpRequestBase +import org.apache.http.entity.ContentType +import org.apache.http.entity.StringEntity +import org.apache.http.impl.client.HttpClients +import java.io.IOException +import java.util.concurrent.ConcurrentLinkedQueue + + +object MessageHandler { + + fun HttpRequestBase.authorize() { + if (cfg!!.connect.authToken.isNotEmpty() && getHeaders("Authorization").isEmpty()) + setHeader("Authorization", "Bearer " + cfg!!.connect.authToken) + } + + private fun createThread(): HttpStreamConnection { + CivilEngineering.logger.info("building bridge") + return HttpStreamConnection( + { + HttpGet(cfg!!.connect.url + "/api/stream").apply { + authorize() + } + }, + { + rcvQueue.add( + ApiMessage.decode(it) + ) + CivilEngineering.logger.info("received: " + it) + } + ) + } + + private var streamConnection: HttpStreamConnection = createThread() + + var rcvQueue = ConcurrentLinkedQueue() + + fun transmit(msg: ApiMessage) { + CivilEngineering.logger.info("transmitting " + msg) + transmitMessage(msg) + } + + fun stop() { + CivilEngineering.logger.info("bridge closing") +// MessageHandler.transmit(ApiMessage(text="bridge closing", username="Server")) + streamConnection.close() + + CivilEngineering.logger.info("bridge closed") + } + + fun start(): Boolean { + if (streamConnection.cancelled) { + streamConnection = createThread() + } + if (!streamConnection.isAlive) { + streamConnection.start() +// MessageHandler.transmit(ApiMessage(text="bridge connected", username="Server")) + return true + } + return false + } + + @Throws(IOException::class) + private fun transmitMessage(message: ApiMessage) { + //open a connection + val client = HttpClients.createDefault() + val post = HttpPost(cfg!!.connect.url + "/api/message") + + post.entity = StringEntity(message.encode(), ContentType.APPLICATION_JSON) + post.authorize() + + val response = client.execute(post) + val code = response.statusLine.statusCode + if (code != 200) { + CivilEngineering.logger.error("Server returned $code for $post") + } + } +} + diff --git a/src/main/kotlin/civilengineering/bridge/ServerChatHelper.kt b/src/main/kotlin/civilengineering/bridge/ServerChatHelper.kt new file mode 100644 index 0000000..c188e04 --- /dev/null +++ b/src/main/kotlin/civilengineering/bridge/ServerChatHelper.kt @@ -0,0 +1,31 @@ +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 + +class ServerChatHelper { + @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..621cedb --- /dev/null +++ b/src/main/kotlin/civilengineering/command/BridgeCommand.kt @@ -0,0 +1,51 @@ +package civilengineering.command + +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..76ccfa3 --- /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.isNotBlank()) + MessageHandler.transmit(ApiMessage(username = event.username, text = 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..9e52d03 --- /dev/null +++ b/src/main/kotlin/civilengineering/eventhandlers/DeathEventHandler.kt @@ -0,0 +1,21 @@ +package civilengineering.eventhandlers + +import civilengineering.bridge.ApiMessage +import civilengineering.bridge.MessageHandler +import civilengineering.cfg +import net.minecraft.entity.player.EntityPlayer +import net.minecraftforge.event.entity.living.LivingDeathEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class DeathEventHandler { + @SubscribeEvent + fun handleLivingDeathEvent(event: LivingDeathEvent) { + if (cfg!!.relay.deathEvents) { + val entity = event.entityLiving + if (entity is EntityPlayer) { + val message = entity.getCombatTracker().deathMessage.unformattedText + MessageHandler.transmit(ApiMessage(username = "Server", text = 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..d657aa2 --- /dev/null +++ b/src/main/kotlin/civilengineering/eventhandlers/UserActionHandler.kt @@ -0,0 +1,27 @@ +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.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(username=user, text=message, event="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