Merge pull request #1 from NikkyAI/master

port to kotlin
This commit is contained in:
DaMachinator 2018-01-22 18:43:40 -05:00 committed by GitHub
commit 652f786064
24 changed files with 521 additions and 568 deletions

6
.gitignore vendored
View File

@ -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
.DS_Store

View File

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

View File

@ -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);
}
}

View File

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

View File

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

View File

@ -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<ApiMessage> xmitQueue = new ConcurrentLinkedQueue<>();
public static ConcurrentLinkedQueue<ApiMessage> 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);
}
}

View File

@ -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<ApiMessage> 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();
}
}

View File

@ -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<ApiMessage> 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));
}
}
}
}

View File

@ -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<String> 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 <connect|disconnect>";
}
@Override
@Nonnull
public List<String> 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();
}
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}
}
}

View File

@ -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();
}
}

View File

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

View File

@ -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()
}
}

View File

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

View File

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

View File

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

View File

@ -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<ApiMessage>()
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")
}
}
}

View File

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

View File

@ -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<String>
init {
aliases = Lists.newArrayList(MODID, "bridge", "BRIDGE")
}
override fun getName(): String {
return "bridge"
}
override fun getUsage(sender: ICommandSender): String {
return "bridge <connect|disconnect>"
}
override fun getAliases(): List<String> {
return aliases
}
override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) /*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()
}
}
}

View File

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

View File

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

View File

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

View File

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