Compare commits

...

30 Commits

Author SHA1 Message Date
Agatha Lovelace dd0626b8e9
make default avatar template actually work in discord 2021-08-27 21:31:54 +03:00
Agatha Lovelace eda95f3dfe
fix system user avatar not being registered 2021-08-27 21:26:26 +03:00
Agatha Lovelace 8df4024320
update readme, fix build errors (hopefully) 2021-08-27 17:38:20 +03:00
NikkyAI e561423884 update cursegradle 2019-04-04 01:11:30 +02:00
NikkyAI b12704d507 remove gson dependency, update dependency checks 2018-12-11 15:02:28 +13:00
NikkyAI 43ce9491b9 fix update check for CI builds 2018-11-28 21:53:20 +01:00
NikkyAI e506f14f76 update gitignore 2018-11-28 20:01:49 +01:00
NikkyAI e410515e05 make location logging also debug instead of info 2018-11-28 18:23:59 +01:00
NikkyAI 4392b442da reduce info logging 2018-11-28 17:57:35 +01:00
NikkyAI ecbb95391f accept older forge versions 2018-11-28 16:24:58 +01:00
NikkyAI 05ed3b4a9b do not use the same arguments as nonce and perm level 2018-11-28 15:23:47 +01:00
NikkyAI fe9cb31d72 little bit of debugging help 2018-11-28 15:15:39 +01:00
NikkyAI ef66087044 fix potential library mismatch.. again 2018-11-25 23:37:07 +01:00
NikkyAI 4ce29f0eb6 bumping forgelin, kotlin and libraries version 2018-11-25 00:34:03 +01:00
NikkyAI a0b0e3d24d fix strict json parsing 2018-11-24 23:39:54 +01:00
NikkyAI 04bc439880 make sure debug logging is actually debug 2018-11-23 19:38:18 +01:00
NikkyAI d4802d372f fix idiotic errors in config 2018-11-23 04:00:38 +01:00
NikkyAI a12a9ae74f make json parsing nonstrict 2018-11-18 02:19:41 +01:00
NikkyAI 4d854e5af8 update coroutines 2018-11-18 02:04:19 +01:00
NikkyAI 1bcafb47e1 fix dependency mismatch with Forgelin 2018-11-18 01:59:47 +01:00
NikkyAI 16c66782c6 forgot to shadow kotlinx-serialization-runtime 2018-11-18 00:05:21 +01:00
NikkyAI a2a24c6ed9 bump Fuel version 2018-11-17 14:57:36 +01:00
NikkyAI 2091d59219 fix async mess 2018-11-17 13:21:12 +01:00
NikkyAI f1cc1349ef version bump 2018-11-17 11:33:00 +01:00
NikkyAI 34887a833c update to kotlin 1.3
[1.7.10] fixed send to player NPE
2018-11-02 04:22:56 +01:00
nikky 3082d3e592 update Jankson 2018-08-26 18:47:35 +02:00
nikky 79c9ac1572 revert dropping 1.7.10, adding 1.9.4 instead of 1.10/1.11 2018-08-16 23:54:50 +02:00
nikky 7818afc920 move api into core 2018-08-15 18:42:43 +02:00
nikky e30e8132ca update 1.12.2 with locations 2018-08-15 00:20:21 +02:00
nikky 6cdd7ade7c drop 1.7.10 support 2018-08-08 20:44:42 +02:00
85 changed files with 2644 additions and 1819 deletions

6
.gitignore vendored
View File

@ -87,10 +87,6 @@ gradle-app.setting
# Cache of project # Cache of project
.gradletasknamecache .gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
# End of https://www.gitignore.io/api/gradle,kotlin,intellij+iml # End of https://www.gitignore.io/api/gradle,kotlin,intellij+iml
### MatterLink ### ### MatterLink ###
@ -102,3 +98,5 @@ run/
\.floo \.floo
\.flooignore \.flooignore
bugreport/

3
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "api"]
path = api
url = https://github.com/NikkyAI/MatterLinkApi.git
[submodule "Jankson"] [submodule "Jankson"]
path = Jankson path = Jankson
url = https://github.com/falkreon/Jankson.git url = https://github.com/falkreon/Jankson.git

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"java.configuration.updateBuildConfiguration": "automatic",
"java.home": "/usr/lib/jvm/java-8-openjdk/"
}

View File

@ -1,3 +0,0 @@
mc_version = 1.10.2
mcp_mappings = stable_29
forge_version = 12.18.3.2185

View File

@ -1,108 +0,0 @@
package matterlink
import matterlink.api.ApiMessage.Companion.USER_ACTION
import matterlink.config.cfg
import matterlink.handlers.*
import net.minecraft.command.server.CommandBroadcast
import net.minecraft.command.server.CommandEmote
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.server.dedicated.DedicatedServer
import net.minecraftforge.event.CommandEvent
import net.minecraftforge.event.ServerChatEvent
import net.minecraftforge.event.entity.living.LivingDeathEvent
import net.minecraftforge.event.entity.player.AchievementEvent
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.common.gameevent.PlayerEvent
import net.minecraftforge.fml.common.gameevent.TickEvent
//FORGE-DEPENDENT
@Mod.EventBusSubscriber
object EventHandler {
//MC-VERSION & FORGE DEPENDENT
@SubscribeEvent
@JvmStatic
fun progressEvent(e: AchievementEvent) {
val achievement = e.achievement
val entityPlayer = e.entityPlayer as? EntityPlayerMP ?: return
val statFile = entityPlayer.statFile
if (!statFile.canUnlockAchievement(achievement) || statFile.hasAchievementUnlocked(achievement)) {
return
}
ProgressHandler.handleProgress(
name = e.entityPlayer.displayName.unformattedText,
message = "has earned the achievement",
display = e.achievement.statName.unformattedText
)
}
//FORGE-DEPENDENT
@SubscribeEvent
@JvmStatic
fun chatEvent(e: ServerChatEvent) {
if(e.isCanceled) return
e.isCanceled = ChatProcessor.sendToBridge(
user = e.player.displayName.unformattedText,
msg = e.message,
event = "",
uuid = e.player.gameProfile.id
)
}
//FORGE-DEPENDENT
@SubscribeEvent
@JvmStatic
fun commandEvent(e: CommandEvent) {
val sender = when {
e.sender is DedicatedServer -> cfg.outgoing.systemUser
else -> e.sender.displayName.unformattedText
}
val args = e.parameters.joinToString(" ")
val type = when {
e.command is CommandEmote -> USER_ACTION
e.command.name == "me" -> USER_ACTION
e.command is CommandBroadcast -> ""
else -> return
}
ChatProcessor.sendToBridge(user = sender, msg = args, event = type)
}
//FORGE-DEPENDENT
@SubscribeEvent
@JvmStatic
fun deathEvent(e: LivingDeathEvent) {
if (e.entityLiving is EntityPlayer) {
DeathHandler.handleDeath(
player = e.entityLiving.displayName.unformattedText,
deathMessage = e.entityLiving.combatTracker.deathMessage.unformattedText,
damageType = e.source.damageType
)
}
}
//FORGE-DEPENDENT
@SubscribeEvent
@JvmStatic
fun joinEvent(e: PlayerEvent.PlayerLoggedInEvent) {
JoinLeaveHandler.handleJoin(e.player.displayName.unformattedText)
}
//FORGE-DEPENDENT
@SubscribeEvent
@JvmStatic
fun leaveEvent(e: PlayerEvent.PlayerLoggedOutEvent) {
JoinLeaveHandler.handleLeave(e.player.displayName.unformattedText)
}
//FORGE-DEPENDENT
@SubscribeEvent
@JvmStatic
fun serverTickEvent(e: TickEvent.ServerTickEvent) {
if (e.phase == TickEvent.Phase.END)
TickHandler.handleTick()
}
}

View File

@ -1,143 +0,0 @@
import net.minecraftforge.gradle.user.TaskSourceCopy
buildscript {
repositories {
jcenter()
maven {
url = 'http://files.minecraftforge.net/maven'
}
mavenCentral()
maven {
url = 'https://oss.sonatype.org/content/groups/public'
}
maven {
url = 'https://plugins.gradle.org/m2/'
}
}
dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '2.2-SNAPSHOT'
classpath group: 'com.github.jengelman.gradle.plugins', name: 'shadow', version: shadow_version
classpath group: 'gradle.plugin.com.matthewprenger', name: 'CurseGradle', version: cursegradle_version
}
}
apply plugin: 'net.minecraftforge.gradle.forge'
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'com.matthewprenger.cursegradle'
version = project.mc_version + '-' + project.mod_version
archivesBaseName = project.mod_name
sourceCompatibility = targetCompatibility = '1.8'
dependencies {
compile project(':core')
compile group: 'net.shadowfacts', name: 'Forgelin', version: project.forgelin_version
}
shadowJar {
classifier = ''
relocate 'blue.endless', 'matterlink.repack.blue.endless'
dependencies {
include project(':core')
include project(':api')
include project(':Jankson')
}
exclude 'dummyThing'
}
// Mad hacks to make source replacements work for Kotlin
// source: https://github.com/PaleoCrafter/VanillaImmersion/blob/ee82ecafb76659cf7d7822a722c8f63f43f41d01/build.gradle#L119
for (set in sourceSets) {
def taskName = "source${set.name.capitalize()}Kotlin"
def dir = new File(project.getBuildDir(), "sources/${set.name}/kotlin")
task(taskName, type: TaskSourceCopy) {
source = set.getKotlin()
output = dir
}
def compileTask = tasks[set.getCompileTaskName('kotlin')]
compileTask.source = dir
compileTask.dependsOn taskName
def dirPath = dir.toPath()
compileKotlin.include {
return it.file.toPath().startsWith(dirPath)
}
}
sourceJar.from sourceSets.main.kotlin
minecraft {
version = project.mc_version + '-' + project.forge_version
runDir = 'run'
mappings = project.mcp_mappings
replaceIn 'Constants.kt'
replace '@MODVERSION@', project.mod_version
replace '@MCVERSION@', project.mc_version
replace '@FORGELIN-VERSION@', project.forgelin_version
replace '@FORGE-VERSION@', project.forge_version
replace '-1//@BUILD_NUMBER@', System.env.BUILD_NUMBER ?: -1
}
processResources {
// this will ensure that this task is redone when the versions change.
inputs.property 'version', project.mod_version
inputs.property 'mcversion', project.minecraft.version
// replace stuff in mcmod.info, nothing else
from(project(':core').sourceSets.main.resources.srcDirs) {
include 'mcmod.info'
// replace version and mcversion
expand 'version': project.mod_version, 'mcversion': project.minecraft.version
}
// copy everything else except the mcmod.info
from(project(':core').sourceSets.main.resources.srcDirs) {
exclude 'mcmod.info'
}
}
sourceJar {
classifier = 'sources'
// copy all the minecraftforge specific classes
from sourceSets.main.allSource
// copy everything else except the mcmod.info
from(project(':core').sourceSets.main.allSource) {
exclude 'mcmod.info'
}
}
reobf {
shadowJar { mappingType = 'SEARGE' }
}
tasks.shadowJar.finalizedBy reobfShadowJar
curseforge {
if (project.hasProperty('CURSEFORGE_API_TOKEN') && project.hasProperty('release')) {
apiKey = CURSEFORGE_API_TOKEN
}
project {
id = project.curse_id
releaseType = project.curse_release_type
if (project.hasProperty('changelog_file')) {
println("changelog = $changelog_file")
changelogType = 'markdown'
changelog = file(changelog_file)
}
relations {
requiredLibrary 'shadowfacts-forgelin'
}
mainArtifact(shadowJar) {
displayName = "MatterLink $version"
}
}
}

View File

@ -1,3 +0,0 @@
mc_version = 1.11.2
mcp_mappings = stable_32
forge_version = 13.20.1.2386

View File

@ -1,8 +0,0 @@
package matterlink
const val MODID = "matterlink"
const val NAME = "MatterLink"
const val MODVERSION = "@MODVERSION@"
const val MCVERSION = "@MCVERSION@"
const val DEPENDENCIES = "required-after:forgelin@[@FORGELIN-VERSION@,);required-after:forge@[@FORGE-VERSION@,);"
const val BUILD_NUMBER = -1//@BUILD_NUMBER@

View File

@ -1,109 +0,0 @@
package matterlink
import matterlink.api.ApiMessage.Companion.USER_ACTION
import matterlink.config.cfg
import matterlink.handlers.*
import net.minecraft.command.server.CommandBroadcast
import net.minecraft.command.server.CommandEmote
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.server.dedicated.DedicatedServer
import net.minecraftforge.event.CommandEvent
import net.minecraftforge.event.ServerChatEvent
import net.minecraftforge.event.entity.living.LivingDeathEvent
import net.minecraftforge.event.entity.player.AchievementEvent
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.common.gameevent.PlayerEvent
import net.minecraftforge.fml.common.gameevent.TickEvent
//FORGE-DEPENDENT
@Mod.EventBusSubscriber
object EventHandler {
//MC-VERSION & FORGE DEPENDENT
@SubscribeEvent
@JvmStatic
fun progressEvent(e: AchievementEvent) {
val achievement = e.achievement
val entityPlayer = e.entityPlayer as? EntityPlayerMP ?: return
val statFile = entityPlayer.statFile
if (!statFile.canUnlockAchievement(achievement) || statFile.hasAchievementUnlocked(achievement)) {
return
}
ProgressHandler.handleProgress(
name = e.entityPlayer.displayName.unformattedText,
message = "has earned the achievement",
display = e.achievement.statName.unformattedText
)
}
//FORGE-DEPENDENT
@SubscribeEvent
@JvmStatic
fun chatEvent(e: ServerChatEvent) {
if(e.isCanceled) return
e.isCanceled = ChatProcessor.sendToBridge(
user = e.player.displayName.unformattedText,
msg = e.message,
event = "",
uuid = e.player.gameProfile.id
)
}
//FORGE-DEPENDENT
@SubscribeEvent
@JvmStatic
fun commandEvent(e: CommandEvent) {
val sender = when {
e.sender is DedicatedServer -> cfg.outgoing.systemUser
else -> e.sender.displayName.unformattedText
}
val args = e.parameters.joinToString(" ")
val type = when {
e.command is CommandEmote -> USER_ACTION
e.command.name == "me" -> USER_ACTION
e.command is CommandBroadcast -> ""
else -> return
}
ChatProcessor.sendToBridge(user = sender, msg = args, event = type)
}
//FORGE-DEPENDENT
@SubscribeEvent
@JvmStatic
fun deathEvent(e: LivingDeathEvent) {
if (e.entityLiving is EntityPlayer) {
DeathHandler.handleDeath(
player = e.entityLiving.displayName.unformattedText,
deathMessage = e.entityLiving.combatTracker.deathMessage.unformattedText,
damageType = e.source.damageType
)
}
}
//FORGE-DEPENDENT
@SubscribeEvent
@JvmStatic
fun joinEvent(e: PlayerEvent.PlayerLoggedInEvent) {
JoinLeaveHandler.handleJoin(e.player.displayName.unformattedText)
}
//FORGE-DEPENDENT
@SubscribeEvent
@JvmStatic
fun leaveEvent(e: PlayerEvent.PlayerLoggedOutEvent) {
JoinLeaveHandler.handleLeave(e.player.displayName.unformattedText)
}
//FORGE-DEPENDENT
@SubscribeEvent
@JvmStatic
fun serverTickEvent(e: TickEvent.ServerTickEvent) {
if (e.phase == TickEvent.Phase.END)
TickHandler.handleTick()
}
}

View File

@ -1,125 +0,0 @@
package matterlink
import com.mojang.authlib.GameProfile
import jline.internal.Log.warn
import matterlink.bridge.command.IBridgeCommand
import matterlink.command.MatterLinkCommand
import matterlink.command.MatterLinkCommandSender
import matterlink.config.BaseConfig
import matterlink.config.cfg
import net.minecraft.entity.ai.EntityMoveHelper
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.util.text.TextComponentString
import net.minecraftforge.common.ForgeVersion
import net.minecraftforge.fml.common.FMLCommonHandler
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.common.event.FMLInitializationEvent
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent
import net.minecraftforge.fml.common.event.FMLServerStartingEvent
import net.minecraftforge.fml.common.event.FMLServerStoppingEvent
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
import java.util.*
@Mod(
modid = MODID,
name = NAME, version = MODVERSION,
serverSideOnly = true,
useMetadata = true,
acceptableRemoteVersions = "*",
modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter",
dependencies = DEPENDENCIES
)
object MatterLink : IMatterLink() {
init {
instance = this
}
@Mod.EventHandler
fun preInit(event: FMLPreInitializationEvent) {
logger = event.modLog as org.apache.logging.log4j.core.Logger
logger.info("Building bridge!")
cfg = BaseConfig(event.modConfigurationDirectory).load()
}
@Mod.EventHandler
fun init(event: FMLInitializationEvent) {
this.registerBridgeCommands()
}
@Mod.EventHandler
fun serverStarting(event: FMLServerStartingEvent) {
logger.debug("Registering server commands")
event.registerServerCommand(MatterLinkCommand())
start()
}
@Mod.EventHandler
fun serverStopping(event: FMLServerStoppingEvent) {
stop()
}
//FORGE-DEPENDENT
override fun wrappedSendToPlayers(msg: String) {
FMLCommonHandler.instance().minecraftServerInstance.playerList.sendMessage(TextComponentString(msg))
}
override fun wrappedSendToPlayer(username: String, msg: String) {
val profile = profileByName(username) ?: run {
logger.error("cannot find player by name $username")
return
}
val player = playerByProfile(profile) ?: run {
logger.error("${profile.name} is not online")
return
}
player.sendMessage(TextComponentString(msg))
}
override fun wrappedSendToPlayer(uuid: UUID, msg: String) {
val profile = profileByUUID(uuid) ?: run {
logger.error("cannot find player by uuid $uuid")
return
}
val player = playerByProfile(profile) ?: run {
logger.error("${profile.name} is not online")
return
}
player.sendMessage(TextComponentString(msg))
}
override fun isOnline(username: String) = FMLCommonHandler.instance().minecraftServerInstance.onlinePlayerNames.contains(username)
private fun playerByProfile(gameProfile: GameProfile): EntityPlayerMP? = FMLCommonHandler.instance().minecraftServerInstance.playerList.getPlayerByUUID(gameProfile.id)
private fun profileByUUID(uuid: UUID): GameProfile? = try {
FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getProfileByUUID(uuid)
} catch (e: IllegalArgumentException) {
logger.warn("cannot find profile by uuid $uuid")
null
}
private fun profileByName(username: String): GameProfile? = try {
FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getGameProfileForUsername(username)
} catch (e: IllegalArgumentException) {
logger.warn("cannot find profile by username $username")
null
}
override fun nameToUUID(username: String): UUID? = profileByName(username)?.id
override fun uuidToName(uuid: UUID): String? = profileByUUID(uuid)?.name
override fun commandSenderFor(
user: String,
env: IBridgeCommand.CommandEnvironment,
op: Boolean
) = MatterLinkCommandSender(user, env, op)
override val mcVersion: String = MCVERSION
override val modVersion: String = MODVERSION
override val buildNumber = BUILD_NUMBER
override val forgeVersion = ForgeVersion.getVersion()
}

View File

@ -1,42 +0,0 @@
package matterlink.command
import matterlink.logger
import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString
class AuthCommand : CommandBase() {
override fun getName(): String {
return CommandCoreAuth.name
}
override fun getUsage(sender: ICommandSender): String {
return CommandCoreAuth.usage
}
override fun getAliases(): List<String> {
return CommandCoreAuth.aliases
}
override fun getRequiredPermissionLevel(): Int {
return 0
}
override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) {
if (args.isEmpty()) {
throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}")
}
val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()
val reply = CommandCoreAuth.execute(args, sender.name, uuid)
if (reply.isNotEmpty() && sender.sendCommandFeedback()) {
sender.sendMessage(TextComponentString(reply))
}
}
}

View File

@ -1,36 +0,0 @@
package matterlink.command
import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString
class MatterLinkCommand : CommandBase() {
override fun getName(): String {
return CommandCoreML.name
}
override fun getUsage(sender: ICommandSender): String {
return CommandCoreML.usage
}
override fun getAliases(): List<String> {
return CommandCoreML.aliases
}
override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) {
if (args.isEmpty()) {
throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}")
}
val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()
val reply = CommandCoreML.execute(args, sender.name, uuid)
if (reply.isNotEmpty() && sender.sendCommandFeedback()) {
sender.sendMessage(TextComponentString(reply))
}
}
}

View File

@ -1,66 +0,0 @@
package matterlink.command
import matterlink.bridge.command.IBridgeCommand
import matterlink.bridge.command.IMinecraftCommandSender
import net.minecraft.command.CommandResultStats
import net.minecraft.command.ICommandSender
import net.minecraft.entity.Entity
import net.minecraft.server.MinecraftServer
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
import net.minecraft.util.text.ITextComponent
import net.minecraft.util.text.TextComponentString
import net.minecraft.world.World
import net.minecraftforge.fml.common.FMLCommonHandler
import javax.annotation.Nonnull
class MatterLinkCommandSender(
user: String,
env: IBridgeCommand.CommandEnvironment,
op: Boolean) : IMinecraftCommandSender(user, env, op), ICommandSender {
override fun execute(cmdString: String): Boolean {
return 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand(
this,
cmdString
).apply {
sendReply(cmdString)
}
}
override fun getDisplayName(): ITextComponent {
return TextComponentString(displayName)
}
override fun getName() = accountName
override fun getEntityWorld(): World {
return FMLCommonHandler.instance().minecraftServerInstance.getWorld(0)
}
override fun canUseCommand(permLevel: Int, commandName: String): Boolean {
//we now do permissions checking on our end
return canExecute(commandName)
}
override fun getServer(): MinecraftServer? {
return FMLCommonHandler.instance().minecraftServerInstance
}
override fun sendMessage(@Nonnull component: ITextComponent?) {
appendReply(component!!.unformattedComponentText)
}
override fun sendCommandFeedback(): Boolean {
return true
}
//WtfMojangWhy
override fun getPosition(): BlockPos = BlockPos.ORIGIN
override fun setCommandStat(type: CommandResultStats.Type?, amount: Int) {}
override fun getPositionVector(): Vec3d = Vec3d.ZERO
override fun getCommandSenderEntity(): Entity? = null
}

View File

@ -14,38 +14,35 @@ buildscript {
} }
dependencies { dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '2.3-SNAPSHOT' classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '2.3-SNAPSHOT'
classpath group: 'com.github.jengelman.gradle.plugins', name: 'shadow', version: shadow_version classpath group: 'com.github.jengelman.gradle.plugins', name: 'shadow', version: shadowVersion
classpath group: 'gradle.plugin.com.matthewprenger', name: 'CurseGradle', version: cursegradle_version classpath group: 'gradle.plugin.com.matthewprenger', name: 'CurseGradle', version: cursegradleVersion
classpath group: 'com.vanniktech', name: 'gradle-dependency-graph-generator-plugin', version: '0.5.0'
} }
} }
apply plugin: 'net.minecraftforge.gradle.forge' apply plugin: 'net.minecraftforge.gradle.forge'
apply plugin: 'com.github.johnrengelman.shadow' apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'com.matthewprenger.cursegradle' apply plugin: 'com.matthewprenger.cursegradle'
apply plugin: "com.vanniktech.dependency.graph.generator"
version = project.mc_version + '-' + project.mod_version version = project.mc_version + '-' + project.modVersion
archivesBaseName = project.mod_name archivesBaseName = project.modName
sourceCompatibility = targetCompatibility = '1.8' sourceCompatibility = targetCompatibility = '1.8'
dependencies { dependencies {
compile project(':core') compile project(':core')
compile group: 'net.shadowfacts', name: 'Forgelin', version: project.forgelin_version shadow (project(path: ':core', configuration: 'shadow')) { transitive = false }
compile group: 'net.shadowfacts', name: 'Forgelin', version: project.forgelinVersion
} }
shadowJar { shadowJar {
classifier = '' classifier = ''
relocate 'blue.endless', 'matterlink.repack.blue.endless'
dependencies {
include project(':core')
include project(':api')
include project(':Jankson')
}
exclude 'dummyThing' exclude 'dummyThing'
configurations = [project.configurations.shadow]
} }
minecraft { minecraft {
@ -55,16 +52,16 @@ minecraft {
mappings = project.mcp_mappings mappings = project.mcp_mappings
replaceIn 'Constants.kt' replaceIn 'Constants.kt'
replace '@MODVERSION@', project.mod_version replace '@MODVERSION@', project.modVersion
replace '@MCVERSION@', project.mc_version replace '@MCVERSION@', project.mc_version
replace '@FORGELIN-VERSION@', project.forgelin_version replace '@FORGELIN-VERSION@', project.forgelinVersion
replace '@FORGE-VERSION@', project.forge_version replace '@FORGE-VERSION@', project.forge_version
replace '-1//@BUILD_NUMBER@', System.env.BUILD_NUMBER ?: -1 replace '-1//@BUILD_NUMBER@', System.env.BUILD_NUMBER ?: -1
} }
processResources { processResources {
// this will ensure that this task is redone when the versions change. // this will ensure that this task is redone when the versions change.
inputs.property "version", project.mod_version inputs.property "version", project.modVersion
inputs.property "mcversion", project.minecraft.version inputs.property "mcversion", project.minecraft.version
// replace stuff in mcmod.info, nothing else // replace stuff in mcmod.info, nothing else
@ -72,7 +69,7 @@ processResources {
include 'mcmod.info' include 'mcmod.info'
// replace version and mcversion // replace version and mcversion
expand 'version': project.mod_version, 'mcversion': project.minecraft.version expand 'version': project.modVersion, 'mcversion': project.minecraft.version
} }
// copy everything else except the mcmod.info // copy everything else except the mcmod.info
@ -96,7 +93,6 @@ sourceJar {
reobf { reobf {
shadowJar { mappingType = 'SEARGE' } shadowJar { mappingType = 'SEARGE' }
} }
tasks.shadowJar.finalizedBy reobfShadowJar tasks.shadowJar.finalizedBy reobfShadowJar
curseforge { curseforge {
@ -104,8 +100,8 @@ curseforge {
apiKey = CURSEFORGE_API_TOKEN apiKey = CURSEFORGE_API_TOKEN
} }
project { project {
id = project.curse_id id = project.curseId
releaseType = project.curse_release_type releaseType = project.curseReleaseType
if (project.hasProperty('changelog_file')) { if (project.hasProperty('changelog_file')) {
println("changelog = $changelog_file") println("changelog = $changelog_file")
changelogType = 'markdown' changelogType = 'markdown'
@ -119,3 +115,7 @@ curseforge {
} }
} }
} }
runServer {
outputs.upToDateWhen { false }
}

View File

@ -1,3 +1,3 @@
mc_version = 1.12.2 mc_version = 1.12.2
mcp_mappings = snapshot_20171003 mcp_mappings = stable_39
forge_version = 14.23.1.2599 forge_version = 14.23.1.2599

View File

@ -1,8 +1,13 @@
package matterlink package matterlink
import matterlink.api.ApiMessage.Companion.USER_ACTION import kotlinx.coroutines.runBlocking
import matterlink.config.cfg import matterlink.config.cfg
import matterlink.handlers.* import matterlink.handlers.ChatEvent
import matterlink.handlers.ChatProcessor
import matterlink.handlers.DeathHandler
import matterlink.handlers.JoinLeaveHandler
import matterlink.handlers.ProgressHandler
import matterlink.handlers.TickHandler
import net.minecraft.command.server.CommandBroadcast import net.minecraft.command.server.CommandBroadcast
import net.minecraft.command.server.CommandEmote import net.minecraft.command.server.CommandEmote
import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EntityPlayer
@ -23,55 +28,80 @@ object EventHandler {
//MC-VERSION & FORGE DEPENDENT //MC-VERSION & FORGE DEPENDENT
@SubscribeEvent @SubscribeEvent
@JvmStatic @JvmStatic
fun progressEvent(e: AdvancementEvent) { fun progressEvent(e: AdvancementEvent) = runBlocking {
if (e.advancement.display == null) return if (e.advancement.display == null) return@runBlocking
ProgressHandler.handleProgress( ProgressHandler.handleProgress(
name = e.entityPlayer.displayName.unformattedText, name = e.entityPlayer.displayName.unformattedText,
message = "has made the advancement", message = "has made the advancement",
display = e.advancement.displayText.unformattedText display = e.advancement.displayText.unformattedText,
x = e.entityPlayer.posX.toInt(),
y = e.entityPlayer.posY.toInt(),
z = e.entityPlayer.posZ.toInt(),
dimension = e.entityPlayer.dimension
) )
} }
//FORGE-DEPENDENT //FORGE-DEPENDENT
@SubscribeEvent @SubscribeEvent
@JvmStatic @JvmStatic
fun chatEvent(e: ServerChatEvent) { fun chatEvent(e: ServerChatEvent) = runBlocking {
if(e.isCanceled) return if (e.isCanceled) return@runBlocking
e.isCanceled = ChatProcessor.sendToBridge( e.isCanceled = ChatProcessor.sendToBridge(
user = e.player.displayName.unformattedText, user = e.player.displayName.unformattedText,
msg = e.message, msg = e.message,
event = "", x = e.player.posX.toInt(),
uuid = e.player.gameProfile.id y = e.player.posY.toInt(),
z = e.player.posZ.toInt(),
dimension = e.player.dimension,
event = ChatEvent.PLAIN,
uuid = e.player.gameProfile.id
) )
} }
//FORGE-DEPENDENT //FORGE-DEPENDENT
@SubscribeEvent @SubscribeEvent
@JvmStatic @JvmStatic
fun commandEvent(e: CommandEvent) { fun commandEvent(e: CommandEvent) = runBlocking {
val sender = when { val sender = when {
e.sender is DedicatedServer -> cfg.outgoing.systemUser e.sender is DedicatedServer -> cfg.outgoing.systemUser
else -> e.sender.displayName.unformattedText else -> e.sender.displayName.unformattedText
} }
val args = e.parameters.joinToString(" ") val args = e.parameters.joinToString(" ")
val type = when { val type = with(e.command) {
e.command is CommandEmote -> USER_ACTION when {
e.command.name.equals("me", true) -> USER_ACTION this is CommandEmote || name.equals("me", true) -> ChatEvent.ACTION
e.command is CommandBroadcast -> "" this is CommandBroadcast || name.equals("say", true) -> ChatEvent.BROADCAST
else -> return else -> return@runBlocking
}
} }
ChatProcessor.sendToBridge(user = sender, msg = args, event = type) ChatProcessor.sendToBridge(
user = sender,
msg = args,
event = type,
x = e.sender.position.x,
y = e.sender.position.y,
z = e.sender.position.z,
dimension = when {
e.sender is DedicatedServer -> null
else -> e.sender.commandSenderEntity?.dimension ?: e.sender.entityWorld.provider.dimension
}
)
} }
//FORGE-DEPENDENT //FORGE-DEPENDENT
@SubscribeEvent @SubscribeEvent
@JvmStatic @JvmStatic
fun deathEvent(e: LivingDeathEvent) { fun deathEvent(e: LivingDeathEvent) = runBlocking {
if (e.entityLiving is EntityPlayer) { if (e.entityLiving is EntityPlayer) {
DeathHandler.handleDeath( DeathHandler.handleDeath(
player = e.entityLiving.displayName.unformattedText, player = e.entityLiving.displayName.unformattedText,
deathMessage = e.entityLiving.combatTracker.deathMessage.unformattedText, deathMessage = e.entityLiving.combatTracker.deathMessage.unformattedText,
damageType = e.source.damageType damageType = e.source.damageType,
x = e.entityLiving.posX.toInt(),
y = e.entityLiving.posY.toInt(),
z = e.entityLiving.posZ.toInt(),
dimension = e.entityLiving.dimension
) )
} }
} }
@ -79,22 +109,35 @@ object EventHandler {
//FORGE-DEPENDENT //FORGE-DEPENDENT
@SubscribeEvent @SubscribeEvent
@JvmStatic @JvmStatic
fun joinEvent(e: PlayerEvent.PlayerLoggedInEvent) { fun joinEvent(e: PlayerEvent.PlayerLoggedInEvent) = runBlocking {
JoinLeaveHandler.handleJoin(e.player.displayName.unformattedText) JoinLeaveHandler.handleJoin(
player = e.player.displayName.unformattedText,
x = e.player.posX.toInt(),
y = e.player.posY.toInt(),
z = e.player.posZ.toInt(),
dimension = e.player.dimension
)
} }
//FORGE-DEPENDENT //FORGE-DEPENDENT
@SubscribeEvent @SubscribeEvent
@JvmStatic @JvmStatic
fun leaveEvent(e: PlayerEvent.PlayerLoggedOutEvent) { fun leaveEvent(e: PlayerEvent.PlayerLoggedOutEvent) = runBlocking {
JoinLeaveHandler.handleLeave(e.player.displayName.unformattedText) JoinLeaveHandler.handleLeave(
player = e.player.displayName.unformattedText,
x = e.player.posX.toInt(),
y = e.player.posY.toInt(),
z = e.player.posZ.toInt(),
dimension = e.player.dimension
)
} }
//FORGE-DEPENDENT //FORGE-DEPENDENT
@SubscribeEvent @SubscribeEvent
@JvmStatic @JvmStatic
fun serverTickEvent(e: TickEvent.ServerTickEvent) { fun serverTickEvent(e: TickEvent.ServerTickEvent) = runBlocking {
if (e.phase == TickEvent.Phase.END) if (e.phase == TickEvent.Phase.END)
TickHandler.handleTick() TickHandler.handleTick()
} }
} }

View File

@ -1,7 +1,7 @@
package matterlink package matterlink
import com.mojang.authlib.GameProfile import com.mojang.authlib.GameProfile
import jline.internal.Log.warn import kotlinx.coroutines.runBlocking
import matterlink.bridge.command.IBridgeCommand import matterlink.bridge.command.IBridgeCommand
import matterlink.command.AuthCommand import matterlink.command.AuthCommand
import matterlink.command.MatterLinkCommand import matterlink.command.MatterLinkCommand
@ -19,17 +19,17 @@ import net.minecraftforge.fml.common.event.FMLServerStartingEvent
import net.minecraftforge.fml.common.event.FMLServerStoppingEvent import net.minecraftforge.fml.common.event.FMLServerStoppingEvent
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
import org.apache.logging.log4j.core.config.Configurator import org.apache.logging.log4j.core.config.Configurator
import java.util.* import java.util.UUID
@Mod( @Mod(
modid = MODID, modid = MODID,
name = NAME, version = MODVERSION, name = NAME, version = MODVERSION,
serverSideOnly = true, serverSideOnly = true,
useMetadata = true, useMetadata = true,
acceptableRemoteVersions = "*", acceptableRemoteVersions = "*",
modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter", modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter",
dependencies = DEPENDENCIES dependencies = DEPENDENCIES
) )
object MatterLink : IMatterLink() { object MatterLink : IMatterLink() {
@ -40,7 +40,16 @@ object MatterLink : IMatterLink() {
@Mod.EventHandler @Mod.EventHandler
fun preInit(event: FMLPreInitializationEvent) { fun preInit(event: FMLPreInitializationEvent) {
logger = event.modLog as org.apache.logging.log4j.core.Logger logger = with(event.modLog) {
object : Logger {
override fun info(message: String) = this@with.info(message)
override fun debug(message: String) = this@with.debug(message)
override fun error(message: String) = this@with.error(message)
override fun warn(message: String) = this@with.warn(message)
override fun trace(message: String) = this@with.trace(message)
}
}
logger.info("Building bridge!") logger.info("Building bridge!")
cfg = BaseConfig(event.modConfigurationDirectory).load() cfg = BaseConfig(event.modConfigurationDirectory).load()
@ -48,19 +57,22 @@ object MatterLink : IMatterLink() {
@Mod.EventHandler @Mod.EventHandler
fun init(event: FMLInitializationEvent) { fun init(event: FMLInitializationEvent) {
logger.debug("Registering bridge commands")
this.registerBridgeCommands() this.registerBridgeCommands()
} }
@Mod.EventHandler @Mod.EventHandler
fun serverStarting(event: FMLServerStartingEvent) { fun serverStarting(event: FMLServerStartingEvent) {
logger.debug("Registering server commands") logger.debug("Registering server commands")
event.registerServerCommand(MatterLinkCommand()) event.registerServerCommand(MatterLinkCommand)
event.registerServerCommand(AuthCommand()) event.registerServerCommand(AuthCommand)
start() runBlocking {
start()
}
} }
@Mod.EventHandler @Mod.EventHandler
fun serverStopping(event: FMLServerStoppingEvent) { fun serverStopping(event: FMLServerStoppingEvent) = runBlocking {
stop() stop()
} }
@ -93,32 +105,42 @@ object MatterLink : IMatterLink() {
player.sendMessage(TextComponentString(msg)) player.sendMessage(TextComponentString(msg))
} }
override fun isOnline(username: String) = FMLCommonHandler.instance().minecraftServerInstance.onlinePlayerNames.contains(username) override fun isOnline(username: String) =
FMLCommonHandler.instance().minecraftServerInstance.onlinePlayerNames.contains(username)
private fun playerByProfile(gameProfile: GameProfile): EntityPlayerMP? = FMLCommonHandler.instance().minecraftServerInstance.playerList.getPlayerByUUID(gameProfile.id) private fun playerByProfile(gameProfile: GameProfile): EntityPlayerMP? =
FMLCommonHandler.instance().minecraftServerInstance.playerList.getPlayerByUUID(gameProfile.id)
private fun profileByUUID(uuid: UUID): GameProfile? = try { private fun profileByUUID(uuid: UUID): GameProfile? = try {
FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getProfileByUUID(uuid) FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getProfileByUUID(uuid)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
warn("cannot find profile by uuid $uuid") logger.warn("cannot find profile by uuid $uuid")
null null
} }
private fun profileByName(username: String): GameProfile? = try { private fun profileByName(username: String): GameProfile? = try {
FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getGameProfileForUsername(username) FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getGameProfileForUsername(username)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
warn("cannot find profile by username $username") logger.warn("cannot find profile by username $username")
null null
} }
override fun collectPlayers(area: Area): Set<UUID> {
val players = FMLCommonHandler.instance().minecraftServerInstance.playerList.players.filter {
(area.allDimensions || area.dimensions.contains(it.dimension))
&& area.testInBounds(it.posX.toInt(), it.posY.toInt(), it.posZ.toInt())
}
return players.map { it.uniqueID }.toSet()
}
override fun nameToUUID(username: String): UUID? = profileByName(username)?.id override fun nameToUUID(username: String): UUID? = profileByName(username)?.id
override fun uuidToName(uuid: UUID): String? = profileByUUID(uuid)?.name override fun uuidToName(uuid: UUID): String? = profileByUUID(uuid)?.name
override fun commandSenderFor( override fun commandSenderFor(
user: String, user: String,
env: IBridgeCommand.CommandEnvironment, env: IBridgeCommand.CommandEnvironment,
op: Boolean op: Boolean
) = MatterLinkCommandSender(user, env, op) ) = MatterLinkCommandSender(user, env, op)
override val mcVersion: String = MCVERSION override val mcVersion: String = MCVERSION

View File

@ -1,6 +1,5 @@
package matterlink.command package matterlink.command
import matterlink.logger
import net.minecraft.command.CommandBase import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException import net.minecraft.command.WrongUsageException
@ -9,7 +8,7 @@ import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString import net.minecraft.util.text.TextComponentString
class AuthCommand : CommandBase() { object AuthCommand : CommandBase() {
override fun getName(): String { override fun getName(): String {
return CommandCoreAuth.name return CommandCoreAuth.name
} }

View File

@ -1,6 +1,6 @@
package matterlink.command package matterlink.command
import matterlink.logger import kotlinx.coroutines.runBlocking
import net.minecraft.command.CommandBase import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException import net.minecraft.command.WrongUsageException
@ -9,7 +9,7 @@ import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString import net.minecraft.util.text.TextComponentString
class MatterLinkCommand : CommandBase() { object MatterLinkCommand : CommandBase() {
override fun getName(): String { override fun getName(): String {
return CommandCoreML.name return CommandCoreML.name
} }
@ -22,9 +22,9 @@ class MatterLinkCommand : CommandBase() {
return CommandCoreML.aliases return CommandCoreML.aliases
} }
override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) { override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) = runBlocking {
if (args.isEmpty()) { if (args.isEmpty()) {
throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}") throw WrongUsageException("Invalid command! Valid uses: ${getUsage(sender)}")
} }
val uuid = (sender as? EntityPlayer)?.uniqueID?.toString() val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()

View File

@ -1,5 +1,6 @@
package matterlink.command package matterlink.command
import kotlinx.coroutines.runBlocking
import matterlink.bridge.command.IBridgeCommand import matterlink.bridge.command.IBridgeCommand
import matterlink.bridge.command.IMinecraftCommandSender import matterlink.bridge.command.IMinecraftCommandSender
import net.minecraft.command.ICommandSender import net.minecraft.command.ICommandSender
@ -11,14 +12,15 @@ import net.minecraftforge.fml.common.FMLCommonHandler
import javax.annotation.Nonnull import javax.annotation.Nonnull
class MatterLinkCommandSender( class MatterLinkCommandSender(
user: String, user: String,
env: IBridgeCommand.CommandEnvironment, env: IBridgeCommand.CommandEnvironment,
op: Boolean) : IMinecraftCommandSender(user, env, op), ICommandSender { op: Boolean
) : IMinecraftCommandSender(user, env, op), ICommandSender {
override fun execute(cmdString: String): Boolean { override fun execute(cmdString: String): Boolean = runBlocking {
return 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand( 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand(
this, this@MatterLinkCommandSender,
cmdString cmdString
).apply { ).apply {
sendReply(cmdString) sendReply(cmdString)
} }

View File

@ -11,16 +11,16 @@ buildscript {
} }
dependencies { dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '1.2-SNAPSHOT' classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '1.2-SNAPSHOT'
classpath group: 'gradle.plugin.com.matthewprenger', name: 'CurseGradle', version: cursegradle_version classpath group: 'gradle.plugin.com.matthewprenger', name: 'CurseGradle', version: cursegradleVersion
} }
} }
apply plugin: 'forge' apply plugin: 'forge'
apply plugin: 'com.matthewprenger.cursegradle' apply plugin: 'com.matthewprenger.cursegradle'
version = project.mc_version + '-' + project.mod_version version = project.mc_version + '-' + project.modVersion
archivesBaseName = project.mod_name archivesBaseName = project.modName
sourceCompatibility = targetCompatibility = '1.8' sourceCompatibility = targetCompatibility = '1.8'
@ -30,21 +30,35 @@ configurations {
} }
dependencies { dependencies {
shade project(':core') shade (project(':core')) { transitive = true }
shade project(':api') shade (project(':Jankson')) { transitive = false }
shade project(':Jankson')
shade group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
shade(group: "org.jetbrains.kotlinx", name: "kotlinx-coroutines-core", version: coroutinesVersion)
shade(group: "org.jetbrains.kotlinx", name: "kotlinx-serialization-runtime", version: serializationVersion)
shade(group: "com.github.kittinunf.Fuel", name: "fuel", version: fuelVersion)
shade(group: "com.github.kittinunf.Fuel", name: "fuel-coroutines", version: fuelVersion)
shade(group: "com.github.kittinunf.Fuel", name: "fuel-kotlinx-serialization", version: fuelVersion)
shade(group: 'com.github.kittinunf.result', name: 'result', version: resultVersion)
// shade group: 'com.github.kittinunf.fuel', name: 'fuel', version: fuelVersion
// shade group: 'com.github.kittinunf.result', name: 'result', version: resultVersion
} }
minecraft { minecraft {
version = project.mc_version + '-' + project.forge_version + '-' + project.mc_version version = project.mc_version + '-' + project.forge_version + '-' + project.mc_version
runDir = 'run' runDir = 'run'
mappings = project.mcp_mappings mappings = project.mcp_mappings
srgExtra 'PK: kotlin matterlink/repack/kotlin' // srgExtra 'PK: kotlin matterlink/repack/kotlin'
srgExtra 'PK: org/jetbrains/annotations matterlink/repack/org/jetbrains/annotations' // srgExtra 'PK: org/jetbrains/annotations matterlink/repack/org/jetbrains/annotations'
srgExtra 'PK: org/intellij matterlink/repack/org/intellij' // srgExtra 'PK: org/intellij matterlink/repack/org/intellij'
srgExtra 'PK: blue/endless matterlink/repack/blue/endless' // srgExtra 'PK: blue/endless/ matterlink/repack/blue/endless/'
} }
compileKotlin.doFirst { compileKotlin.doFirst {
@ -53,13 +67,13 @@ compileKotlin.doFirst {
from('src/templates/kotlin/matterlink/Constants.kt') from('src/templates/kotlin/matterlink/Constants.kt')
into(target) into(target)
} }
ant.replaceregexp(match: '@MODVERSION@', replace: project.mod_version, flags: 'g', byline: true) { ant.replaceregexp(match: '@MODVERSION@', replace: project.modVersion, flags: 'g', byline: true) {
fileset(dir: target, includes: 'Constants.kt') fileset(dir: target, includes: 'Constants.kt')
} }
ant.replaceregexp(match: '@MCVERSION@', replace: project.mc_version, flags: 'g', byline: true) { ant.replaceregexp(match: '@MCVERSION@', replace: project.mc_version, flags: 'g', byline: true) {
fileset(dir: target, includes: 'Constants.kt') fileset(dir: target, includes: 'Constants.kt')
} }
ant.replaceregexp(match: '@FORGELIN-VERSION@', replace: project.forgelin_version, flags: 'g', byline: true) { ant.replaceregexp(match: '@FORGELIN-VERSION@', replace: project.forgelinVersion, flags: 'g', byline: true) {
fileset(dir: target, includes: 'Constants.kt') fileset(dir: target, includes: 'Constants.kt')
} }
ant.replaceregexp(match: '@FORGE-VERSION@', replace: project.forge_version, flags: 'g', byline: true) { ant.replaceregexp(match: '@FORGE-VERSION@', replace: project.forge_version, flags: 'g', byline: true) {
@ -72,7 +86,7 @@ compileKotlin.doFirst {
processResources { processResources {
// this will ensure that this task is redone when the versions change. // this will ensure that this task is redone when the versions change.
inputs.property 'version', project.mod_version inputs.property 'version', project.modVersion
inputs.property 'mcversion', project.minecraft.version inputs.property 'mcversion', project.minecraft.version
// replace stuff in mcmod.info, nothing else // replace stuff in mcmod.info, nothing else
@ -80,7 +94,7 @@ processResources {
include 'mcmod.info' include 'mcmod.info'
// replace version and mcversion // replace version and mcversion
expand 'version': project.mod_version, 'mcversion': project.minecraft.version expand 'version': project.modVersion, 'mcversion': project.minecraft.version
} }
// copy everything else except the mcmod.info // copy everything else except the mcmod.info
@ -93,7 +107,6 @@ jar {
configurations.shade.each { dep -> configurations.shade.each { dep ->
from(project.zipTree(dep)) { from(project.zipTree(dep)) {
exclude 'META-INF', 'META-INF/**' exclude 'META-INF', 'META-INF/**'
exclude 'com/google/gson', 'com/google/gson/**'
} }
} }
} }
@ -103,8 +116,8 @@ curseforge {
apiKey = CURSEFORGE_API_TOKEN apiKey = CURSEFORGE_API_TOKEN
} }
project { project {
id = project.curse_id id = project.curseId
releaseType = project.curse_release_type releaseType = project.curseReleaseType
if (project.hasProperty('changelog_file')) { if (project.hasProperty('changelog_file')) {
println("changelog = $changelog_file") println("changelog = $changelog_file")
changelogType = 'markdown' changelogType = 'markdown'

View File

@ -3,11 +3,17 @@ package matterlink
import cpw.mods.fml.common.eventhandler.SubscribeEvent import cpw.mods.fml.common.eventhandler.SubscribeEvent
import cpw.mods.fml.common.gameevent.PlayerEvent import cpw.mods.fml.common.gameevent.PlayerEvent
import cpw.mods.fml.common.gameevent.TickEvent import cpw.mods.fml.common.gameevent.TickEvent
import matterlink.api.ApiMessage.Companion.USER_ACTION import kotlinx.coroutines.runBlocking
import matterlink.config.cfg import matterlink.config.cfg
import matterlink.handlers.* import matterlink.handlers.ChatEvent
import matterlink.handlers.ChatProcessor
import matterlink.handlers.DeathHandler
import matterlink.handlers.JoinLeaveHandler
import matterlink.handlers.ProgressHandler
import matterlink.handlers.TickHandler
import net.minecraft.command.server.CommandBroadcast import net.minecraft.command.server.CommandBroadcast
import net.minecraft.command.server.CommandEmote import net.minecraft.command.server.CommandEmote
import net.minecraft.entity.Entity
import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EntityPlayer
import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.server.dedicated.DedicatedServer import net.minecraft.server.dedicated.DedicatedServer
@ -21,80 +27,121 @@ object EventHandler {
//MC-VERSION & FORGE DEPENDENT //MC-VERSION & FORGE DEPENDENT
@SubscribeEvent @SubscribeEvent
fun progressEvent(e: AchievementEvent) { fun progressEvent(e: AchievementEvent) = runBlocking {
val achievement = e.achievement val achievement = e.achievement
val entityPlayer = e.entityPlayer as? EntityPlayerMP ?: return val entityPlayer = e.entityPlayer as? EntityPlayerMP ?: return@runBlocking
val statFile = entityPlayer.statFile val statFile = entityPlayer.statFile
if (!statFile.canUnlockAchievement(achievement) || statFile.hasAchievementUnlocked(achievement)) { if (!statFile.canUnlockAchievement(achievement) || statFile.hasAchievementUnlocked(achievement)) {
return return@runBlocking
} }
ProgressHandler.handleProgress( ProgressHandler.handleProgress(
name = e.entityPlayer.displayName, name = e.entityPlayer.displayName,
message = "has earned the achievement", message = "has earned the achievement",
display = e.achievement.statName.unformattedText display = e.achievement.statName.unformattedText,
x = e.entityPlayer.posX.toInt(),
y = e.entityPlayer.posY.toInt(),
z = e.entityPlayer.posZ.toInt(),
dimension = e.entityPlayer.dimension
) )
} }
//FORGE-DEPENDENT //FORGE-DEPENDENT
@SubscribeEvent @SubscribeEvent
fun chatEvent(e: ServerChatEvent) { fun chatEvent(e: ServerChatEvent) = runBlocking {
if(e.isCanceled) return if (e.isCanceled) return@runBlocking
e.isCanceled = ChatProcessor.sendToBridge( e.isCanceled = ChatProcessor.sendToBridge(
user = e.player.displayName, user = e.player.displayName,
msg = e.message, msg = e.message,
event = "", x = e.player.posX.toInt(),
uuid = e.player.gameProfile.id y = e.player.posY.toInt(),
z = e.player.posZ.toInt(),
dimension = e.player.dimension,
event = ChatEvent.PLAIN,
uuid = e.player.gameProfile.id
) )
} }
//FORGE-DEPENDENT //FORGE-DEPENDENT
@SubscribeEvent @SubscribeEvent
fun commandEvent(e: CommandEvent) { fun commandEvent(e: CommandEvent) = runBlocking {
val sender = when { val sender = when {
e.sender is DedicatedServer -> cfg.outgoing.systemUser e.sender is DedicatedServer -> cfg.outgoing.systemUser
else -> e.sender.commandSenderName else -> e.sender.commandSenderName
} }
val args = e.parameters.joinToString(" ") val args = e.parameters.joinToString(" ")
val type = when { val type = with(e.command) {
e.command is CommandEmote -> USER_ACTION when {
e.command.commandName == "me" -> USER_ACTION this is CommandEmote || commandName.equals("me", true) -> ChatEvent.ACTION
e.command is CommandBroadcast -> "" this is CommandBroadcast || commandName.equals("say", true) -> ChatEvent.BROADCAST
else -> return else -> return@runBlocking
}
} }
ChatProcessor.sendToBridge(user = sender, msg = args, event = type) val s = e.sender
val (x, y, z) = when (s) {
is Entity -> Triple(s.posX.toInt(), s.posY.toInt(), s.posZ.toInt())
else -> with(s.commandSenderPosition) { Triple(posX, posY, posZ) }
}
ChatProcessor.sendToBridge(
user = sender,
msg = args,
event = type,
x = x,
y = y,
z = z,
dimension = when {
e.sender is DedicatedServer -> null
else -> e.sender.entityWorld.provider.dimensionId
}
)
} }
//FORGE-DEPENDENT //FORGE-DEPENDENT
@SubscribeEvent @SubscribeEvent
fun deathEvent(e: LivingDeathEvent) { fun deathEvent(e: LivingDeathEvent) = runBlocking {
if (e.entityLiving is EntityPlayer) { if (e.entityLiving is EntityPlayer) {
val player = e.entityLiving as EntityPlayer val player = e.entityLiving as EntityPlayer
DeathHandler.handleDeath( DeathHandler.handleDeath(
player = player.displayName, player = player.displayName,
deathMessage = e.entityLiving.combatTracker.func_151521_b().unformattedText, deathMessage = e.entityLiving.combatTracker.func_151521_b().unformattedText,
damageType = e.source.damageType damageType = e.source.damageType,
x = e.entityLiving.posX.toInt(),
y = e.entityLiving.posY.toInt(),
z = e.entityLiving.posZ.toInt(),
dimension = e.entityLiving.dimension
) )
} }
} }
//FORGE-DEPENDENT //FORGE-DEPENDENT
@SubscribeEvent @SubscribeEvent
fun joinEvent(e: PlayerEvent.PlayerLoggedInEvent) { fun joinEvent(e: PlayerEvent.PlayerLoggedInEvent) = runBlocking {
JoinLeaveHandler.handleJoin(e.player.displayName) JoinLeaveHandler.handleJoin(
player = e.player.displayName,
x = e.player.posX.toInt(),
y = e.player.posY.toInt(),
z = e.player.posZ.toInt(),
dimension = e.player.dimension
)
} }
//FORGE-DEPENDENT //FORGE-DEPENDENT
@SubscribeEvent @SubscribeEvent
fun leaveEvent(e: PlayerEvent.PlayerLoggedOutEvent) { fun leaveEvent(e: PlayerEvent.PlayerLoggedOutEvent) = runBlocking {
JoinLeaveHandler.handleLeave(e.player.displayName) JoinLeaveHandler.handleLeave(
player = e.player.displayName,
x = e.player.posX.toInt(),
y = e.player.posY.toInt(),
z = e.player.posZ.toInt(),
dimension = e.player.dimension
)
} }
//FORGE-DEPENDENT //FORGE-DEPENDENT
@SubscribeEvent @SubscribeEvent
fun serverTickEvent(e: TickEvent.ServerTickEvent) { fun serverTickEvent(e: TickEvent.ServerTickEvent) = runBlocking {
if (e.phase == TickEvent.Phase.END) if (e.phase == TickEvent.Phase.END)
TickHandler.handleTick() TickHandler.handleTick()
} }

View File

@ -7,6 +7,7 @@ import cpw.mods.fml.common.event.FMLInitializationEvent
import cpw.mods.fml.common.event.FMLPreInitializationEvent import cpw.mods.fml.common.event.FMLPreInitializationEvent
import cpw.mods.fml.common.event.FMLServerStartingEvent import cpw.mods.fml.common.event.FMLServerStartingEvent
import cpw.mods.fml.common.event.FMLServerStoppingEvent import cpw.mods.fml.common.event.FMLServerStoppingEvent
import kotlinx.coroutines.runBlocking
import matterlink.bridge.command.IBridgeCommand import matterlink.bridge.command.IBridgeCommand
import matterlink.command.AuthCommand import matterlink.command.AuthCommand
import matterlink.command.MatterLinkCommand import matterlink.command.MatterLinkCommand
@ -18,13 +19,13 @@ import net.minecraft.server.MinecraftServer
import net.minecraft.util.ChatComponentText import net.minecraft.util.ChatComponentText
import net.minecraftforge.common.ForgeVersion import net.minecraftforge.common.ForgeVersion
import net.minecraftforge.common.MinecraftForge import net.minecraftforge.common.MinecraftForge
import java.util.* import java.util.UUID
@Mod( @Mod(
modid = MODID, modid = MODID,
name = NAME, version = MODVERSION, name = NAME, version = MODVERSION,
useMetadata = true, useMetadata = true,
acceptableRemoteVersions = "*" acceptableRemoteVersions = "*"
) )
class MatterLink : IMatterLink() { class MatterLink : IMatterLink() {
init { init {
@ -36,7 +37,16 @@ class MatterLink : IMatterLink() {
MinecraftForge.EVENT_BUS.register(EventHandler) MinecraftForge.EVENT_BUS.register(EventHandler)
FMLCommonHandler.instance().bus().register(EventHandler) FMLCommonHandler.instance().bus().register(EventHandler)
logger = event.modLog as org.apache.logging.log4j.core.Logger logger = with(event.modLog) {
object : Logger {
override fun info(message: String) = this@with.info(message)
override fun debug(message: String) = this@with.debug(message)
override fun error(message: String) = this@with.error(message)
override fun warn(message: String) = this@with.warn(message)
override fun trace(message: String) = this@with.trace(message)
}
}
logger.info("Building bridge!") logger.info("Building bridge!")
cfg = BaseConfig(event.modConfigurationDirectory).load() cfg = BaseConfig(event.modConfigurationDirectory).load()
@ -48,15 +58,15 @@ class MatterLink : IMatterLink() {
} }
@Mod.EventHandler @Mod.EventHandler
fun serverStarting(event: FMLServerStartingEvent) { fun serverStarting(event: FMLServerStartingEvent) = runBlocking {
logger.debug("Registering server commands") logger.debug("Registering server commands")
event.registerServerCommand(MatterLinkCommand()) event.registerServerCommand(MatterLinkCommand)
event.registerServerCommand(AuthCommand()) event.registerServerCommand(AuthCommand)
start() start()
} }
@Mod.EventHandler @Mod.EventHandler
fun serverStopping(event: FMLServerStoppingEvent) { fun serverStopping(event: FMLServerStoppingEvent) = runBlocking {
stop() stop()
} }
@ -90,10 +100,11 @@ class MatterLink : IMatterLink() {
} }
override fun isOnline(username: String) = (FMLCommonHandler.instance() override fun isOnline(username: String) = (FMLCommonHandler.instance()
.minecraftServerInstance.configurationManager.getPlayerByUsername(username) ?: null) != null .minecraftServerInstance.configurationManager.getPlayerByUsername(username) ?: null) != null
private fun playerByProfile(gameProfile: GameProfile): EntityPlayerMP? { private fun playerByProfile(gameProfile: GameProfile): EntityPlayerMP? {
return FMLCommonHandler.instance().minecraftServerInstance.configurationManager.createPlayerForUser(gameProfile) return FMLCommonHandler.instance()
.minecraftServerInstance.configurationManager.getPlayerByUsername(gameProfile.name)
} }
private fun profileByUUID(uuid: UUID): GameProfile? = try { private fun profileByUUID(uuid: UUID): GameProfile? = try {
@ -110,14 +121,24 @@ class MatterLink : IMatterLink() {
null null
} }
override fun collectPlayers(area: Area): Set<UUID> {
val players = MinecraftServer.getServer().configurationManager.playerEntityList
.map { it as EntityPlayerMP }
.filter {
(area.allDimensions || area.dimensions.contains(it.dimension))
&& area.testInBounds(it.posX.toInt(), it.posY.toInt(), it.posZ.toInt())
}
return players.map { it.uniqueID }.toSet()
}
override fun nameToUUID(username: String): UUID? = profileByName(username)?.id override fun nameToUUID(username: String): UUID? = profileByName(username)?.id
override fun uuidToName(uuid: UUID): String? = profileByUUID(uuid)?.name override fun uuidToName(uuid: UUID): String? = profileByUUID(uuid)?.name
override fun commandSenderFor( override fun commandSenderFor(
user: String, user: String,
env: IBridgeCommand.CommandEnvironment, env: IBridgeCommand.CommandEnvironment,
op: Boolean op: Boolean
) = MatterLinkCommandSender(user, env, op) ) = MatterLinkCommandSender(user, env, op)
override val mcVersion: String = MCVERSION override val mcVersion: String = MCVERSION

View File

@ -7,7 +7,7 @@ import net.minecraft.entity.player.EntityPlayer
import net.minecraft.util.ChatComponentText import net.minecraft.util.ChatComponentText
class AuthCommand : CommandBase() { object AuthCommand : CommandBase() {
override fun getCommandName(): String { override fun getCommandName(): String {
return CommandCoreAuth.name return CommandCoreAuth.name
} }

View File

@ -1,5 +1,6 @@
package matterlink.command package matterlink.command
import kotlinx.coroutines.runBlocking
import net.minecraft.command.CommandBase import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException import net.minecraft.command.WrongUsageException
@ -7,7 +8,7 @@ import net.minecraft.entity.player.EntityPlayer
import net.minecraft.util.ChatComponentText import net.minecraft.util.ChatComponentText
class MatterLinkCommand : CommandBase() { object MatterLinkCommand : CommandBase() {
override fun getCommandName(): String { override fun getCommandName(): String {
return CommandCoreML.name return CommandCoreML.name
} }
@ -20,9 +21,9 @@ class MatterLinkCommand : CommandBase() {
return CommandCoreML.aliases return CommandCoreML.aliases
} }
override fun processCommand(sender: ICommandSender, args: Array<String>) { override fun processCommand(sender: ICommandSender, args: Array<String>) = runBlocking {
if (args.isEmpty()) { if (args.isEmpty()) {
throw WrongUsageException("Invalid command! Valid uses: ${this.getCommandUsage(sender)}") throw WrongUsageException("Invalid command! Valid uses: ${getCommandUsage(sender)}")
} }
val uuid = (sender as? EntityPlayer)?.uniqueID?.toString() val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()

View File

@ -1,5 +1,6 @@
package matterlink.command package matterlink.command
import kotlinx.coroutines.runBlocking
import matterlink.bridge.command.IBridgeCommand import matterlink.bridge.command.IBridgeCommand
import matterlink.bridge.command.IMinecraftCommandSender import matterlink.bridge.command.IMinecraftCommandSender
import net.minecraft.command.ICommandSender import net.minecraft.command.ICommandSender
@ -10,14 +11,15 @@ import net.minecraft.util.IChatComponent
import net.minecraft.world.World import net.minecraft.world.World
class MatterLinkCommandSender( class MatterLinkCommandSender(
user: String, user: String,
env: IBridgeCommand.CommandEnvironment, env: IBridgeCommand.CommandEnvironment,
op: Boolean) : IMinecraftCommandSender(user, env, op), ICommandSender { op: Boolean
) : IMinecraftCommandSender(user, env, op), ICommandSender {
override fun execute(cmdString: String): Boolean { override fun execute(cmdString: String): Boolean = runBlocking {
return 0 < MinecraftServer.getServer().commandManager.executeCommand( return@runBlocking 0 < MinecraftServer.getServer().commandManager.executeCommand(
this, this@MatterLinkCommandSender,
cmdString cmdString
).apply { ).apply {
sendReply(cmdString) sendReply(cmdString)
} }

View File

@ -11,8 +11,8 @@ buildscript {
} }
dependencies { dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '2.2-SNAPSHOT' classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '2.2-SNAPSHOT'
classpath group: 'com.github.jengelman.gradle.plugins', name: 'shadow', version: shadow_version classpath group: 'com.github.jengelman.gradle.plugins', name: 'shadow', version: shadowVersion
classpath group: 'gradle.plugin.com.matthewprenger', name: 'CurseGradle', version: cursegradle_version classpath group: 'gradle.plugin.com.matthewprenger', name: 'CurseGradle', version: cursegradleVersion
} }
} }
@ -20,32 +20,26 @@ apply plugin: 'net.minecraftforge.gradle.forge'
apply plugin: 'com.github.johnrengelman.shadow' apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'com.matthewprenger.cursegradle' apply plugin: 'com.matthewprenger.cursegradle'
version = project.mc_version + '-' + project.mod_version version = project.mc_version + '-' + project.modVersion
archivesBaseName = project.mod_name archivesBaseName = project.modName
sourceCompatibility = targetCompatibility = '1.8' sourceCompatibility = targetCompatibility = '1.8'
dependencies { dependencies {
compile project(':core') compile project(':core')
compile group: 'net.shadowfacts', name: 'Forgelin', version: project.forgelin_version shadow (project(path: ':core', configuration: 'shadow')) { transitive = false }
compile group: 'net.shadowfacts', name: 'Forgelin', version: project.forgelinVersion
} }
shadowJar { shadowJar {
classifier '' classifier = ''
relocate 'blue.endless', 'matterlink.repack.blue.endless'
dependencies {
include project(':core')
include project(':api')
include project(':Jankson')
}
exclude 'dummyThing' exclude 'dummyThing'
configurations = [project.configurations.shadow]
} }
import net.minecraftforge.gradle.user.TaskSourceCopy import net.minecraftforge.gradle.user.TaskSourceCopy
// Mad hacks to make source replacements work for Kotlin // Mad hacks to make source replacements work for Kotlin
@ -74,16 +68,16 @@ minecraft {
mappings = project.mcp_mappings mappings = project.mcp_mappings
replaceIn 'Constants.kt' replaceIn 'Constants.kt'
replace '@MODVERSION@', project.mod_version replace '@MODVERSION@', project.modVersion
replace '@MCVERSION@', project.mc_version replace '@MCVERSION@', project.mc_version
replace '@FORGELIN-VERSION@', project.forgelin_version replace '@FORGELIN-VERSION@', project.forgelinVersion
replace '@FORGE-VERSION@', project.forge_version replace '@FORGE-VERSION@', project.forge_version
replace '-1//@BUILD_NUMBER@', System.env.BUILD_NUMBER ?: -1 replace '-1//@BUILD_NUMBER@', System.env.BUILD_NUMBER ?: -1
} }
processResources { processResources {
// this will ensure that this task is redone when the versions change. // this will ensure that this task is redone when the versions change.
inputs.property 'version', project.mod_version inputs.property 'version', project.modVersion
inputs.property 'mcversion', project.minecraft.version inputs.property 'mcversion', project.minecraft.version
// replace stuff in mcmod.info, nothing else // replace stuff in mcmod.info, nothing else
@ -91,7 +85,7 @@ processResources {
include 'mcmod.info' include 'mcmod.info'
// replace version and mcversion // replace version and mcversion
expand 'version': project.mod_version, 'mcversion': project.minecraft.version expand 'version': project.modVersion, 'mcversion': project.minecraft.version
} }
// copy everything else except the mcmod.info // copy everything else except the mcmod.info
@ -116,7 +110,6 @@ sourceJar {
reobf { reobf {
shadowJar { mappingType = 'SEARGE' } shadowJar { mappingType = 'SEARGE' }
} }
tasks.shadowJar.finalizedBy reobfShadowJar tasks.shadowJar.finalizedBy reobfShadowJar
curseforge { curseforge {
@ -124,8 +117,9 @@ curseforge {
apiKey = CURSEFORGE_API_TOKEN apiKey = CURSEFORGE_API_TOKEN
} }
project { project {
id = project.curse_id id = project.curseId
releaseType = project.curse_release_type releaseType = project.curseReleaseType
addGameVersion '1.10'
if (project.hasProperty('changelog_file')) { if (project.hasProperty('changelog_file')) {
println("changelog = $changelog_file") println("changelog = $changelog_file")
changelogType = 'markdown' changelogType = 'markdown'

3
1.9.4/gradle.properties Normal file
View File

@ -0,0 +1,3 @@
mc_version = 1.9.4
mcp_mappings = stable_26
forge_version = 12.17.0.2051

View File

@ -0,0 +1,139 @@
package matterlink
import kotlinx.coroutines.runBlocking
import matterlink.config.cfg
import matterlink.handlers.ChatEvent
import matterlink.handlers.ChatProcessor
import matterlink.handlers.DeathHandler
import matterlink.handlers.JoinLeaveHandler
import matterlink.handlers.ProgressHandler
import matterlink.handlers.TickHandler
import net.minecraft.command.server.CommandBroadcast
import net.minecraft.command.server.CommandEmote
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.server.dedicated.DedicatedServer
import net.minecraftforge.event.CommandEvent
import net.minecraftforge.event.ServerChatEvent
import net.minecraftforge.event.entity.living.LivingDeathEvent
import net.minecraftforge.event.entity.player.AchievementEvent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.common.gameevent.PlayerEvent
import net.minecraftforge.fml.common.gameevent.TickEvent
//FORGE-DEPENDENT
object EventHandler {
//MC-VERSION & FORGE DEPENDENT
@SubscribeEvent
fun progressEvent(e: AchievementEvent) = runBlocking {
val achievement = e.achievement
val entityPlayer = e.entityPlayer as? EntityPlayerMP ?: return@runBlocking
val statFile = entityPlayer.statFile
if (!statFile.canUnlockAchievement(achievement) || statFile.hasAchievementUnlocked(achievement)) {
return@runBlocking
}
ProgressHandler.handleProgress(
name = e.entityPlayer.displayName.unformattedText,
message = "has earned the achievement",
display = e.achievement.statName.unformattedText,
x = e.entityPlayer.posX.toInt(),
y = e.entityPlayer.posY.toInt(),
z = e.entityPlayer.posZ.toInt(),
dimension = e.entityPlayer.dimension
)
}
//FORGE-DEPENDENT
@SubscribeEvent
fun chatEvent(e: ServerChatEvent) = runBlocking {
if (e.isCanceled) return@runBlocking
e.isCanceled = ChatProcessor.sendToBridge(
user = e.player.displayName.unformattedText,
msg = e.message,
x = e.player.posX.toInt(),
y = e.player.posY.toInt(),
z = e.player.posZ.toInt(),
dimension = e.player.dimension,
event = ChatEvent.PLAIN,
uuid = e.player.gameProfile.id
)
}
//FORGE-DEPENDENT
@SubscribeEvent
fun commandEvent(e: CommandEvent) = runBlocking {
val sender = when {
e.sender is DedicatedServer -> cfg.outgoing.systemUser
else -> e.sender.displayName.unformattedText
}
val args = e.parameters.joinToString(" ")
val type = with(e.command) {
when {
this is CommandEmote || commandName.equals("me", true) -> ChatEvent.ACTION
this is CommandBroadcast || commandName.equals("say", true) -> ChatEvent.BROADCAST
else -> return@runBlocking
}
}
ChatProcessor.sendToBridge(
user = sender,
msg = args,
event = type,
x = e.sender.position.x,
y = e.sender.position.y,
z = e.sender.position.z,
dimension = when {
e.sender is DedicatedServer -> null
else -> e.sender.commandSenderEntity?.dimension ?: e.sender.entityWorld.provider.dimension
}
)
}
//FORGE-DEPENDENT
@SubscribeEvent
fun deathEvent(e: LivingDeathEvent) = runBlocking {
if (e.entityLiving is EntityPlayer) {
DeathHandler.handleDeath(
player = e.entityLiving.displayName.unformattedText,
deathMessage = e.entityLiving.combatTracker.deathMessage.unformattedText,
damageType = e.source.damageType,
x = e.entityLiving.posX.toInt(),
y = e.entityLiving.posY.toInt(),
z = e.entityLiving.posZ.toInt(),
dimension = e.entityLiving.dimension
)
}
}
//FORGE-DEPENDENT
@SubscribeEvent
fun joinEvent(e: PlayerEvent.PlayerLoggedInEvent) = runBlocking {
JoinLeaveHandler.handleJoin(
player = e.player.displayName.unformattedText,
x = e.player.posX.toInt(),
y = e.player.posY.toInt(),
z = e.player.posZ.toInt(),
dimension = e.player.dimension
)
}
//FORGE-DEPENDENT
@SubscribeEvent
fun leaveEvent(e: PlayerEvent.PlayerLoggedOutEvent) = runBlocking {
JoinLeaveHandler.handleLeave(
player = e.player.displayName.unformattedText,
x = e.player.posX.toInt(),
y = e.player.posY.toInt(),
z = e.player.posZ.toInt(),
dimension = e.player.dimension
)
}
//FORGE-DEPENDENT
@SubscribeEvent
fun serverTickEvent(e: TickEvent.ServerTickEvent) = runBlocking {
if (e.phase == TickEvent.Phase.END)
TickHandler.handleTick()
}
}

View File

@ -1,7 +1,9 @@
package matterlink package matterlink
import com.mojang.authlib.GameProfile import com.mojang.authlib.GameProfile
import kotlinx.coroutines.runBlocking
import matterlink.bridge.command.IBridgeCommand import matterlink.bridge.command.IBridgeCommand
import matterlink.command.AuthCommand
import matterlink.command.MatterLinkCommand import matterlink.command.MatterLinkCommand
import matterlink.command.MatterLinkCommandSender import matterlink.command.MatterLinkCommandSender
import matterlink.config.BaseConfig import matterlink.config.BaseConfig
@ -9,24 +11,23 @@ import matterlink.config.cfg
import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.util.text.TextComponentString import net.minecraft.util.text.TextComponentString
import net.minecraftforge.common.ForgeVersion import net.minecraftforge.common.ForgeVersion
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.FMLCommonHandler import net.minecraftforge.fml.common.FMLCommonHandler
import net.minecraftforge.fml.common.Mod import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.common.event.FMLInitializationEvent import net.minecraftforge.fml.common.event.FMLInitializationEvent
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent import net.minecraftforge.fml.common.event.FMLPreInitializationEvent
import net.minecraftforge.fml.common.event.FMLServerStartingEvent import net.minecraftforge.fml.common.event.FMLServerStartingEvent
import net.minecraftforge.fml.common.event.FMLServerStoppingEvent import net.minecraftforge.fml.common.event.FMLServerStoppingEvent
import org.apache.logging.log4j.Level import java.util.UUID
import org.apache.logging.log4j.Logger
import java.util.*
@Mod( @Mod(
modid = MODID, modid = MODID,
name = NAME, version = MODVERSION, name = NAME, version = MODVERSION,
serverSideOnly = true, serverSideOnly = true,
useMetadata = true, useMetadata = true,
acceptableRemoteVersions = "*", acceptableRemoteVersions = "*",
modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter", modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter",
dependencies = DEPENDENCIES dependencies = DEPENDENCIES
) )
object MatterLink : IMatterLink() { object MatterLink : IMatterLink() {
init { init {
@ -35,7 +36,17 @@ object MatterLink : IMatterLink() {
@Mod.EventHandler @Mod.EventHandler
fun preInit(event: FMLPreInitializationEvent) { fun preInit(event: FMLPreInitializationEvent) {
logger = event.modLog as org.apache.logging.log4j.core.Logger MinecraftForge.EVENT_BUS.register(EventHandler)
logger = with(event.modLog) {
object : Logger {
override fun info(message: String) = this@with.info(message)
override fun debug(message: String) = this@with.debug(message)
override fun error(message: String) = this@with.error(message)
override fun warn(message: String) = this@with.warn(message)
override fun trace(message: String) = this@with.trace(message)
}
}
logger.info("Building bridge!") logger.info("Building bridge!")
cfg = BaseConfig(event.modConfigurationDirectory).load() cfg = BaseConfig(event.modConfigurationDirectory).load()
@ -47,14 +58,15 @@ object MatterLink : IMatterLink() {
} }
@Mod.EventHandler @Mod.EventHandler
fun serverStarting(event: FMLServerStartingEvent) { fun serverStarting(event: FMLServerStartingEvent) = runBlocking {
logger.debug("Registering server commands") logger.debug("Registering server commands")
event.registerServerCommand(MatterLinkCommand()) event.registerServerCommand(MatterLinkCommand)
event.registerServerCommand(AuthCommand)
start() start()
} }
@Mod.EventHandler @Mod.EventHandler
fun serverStopping(event: FMLServerStoppingEvent) { fun serverStopping(event: FMLServerStoppingEvent) = runBlocking {
stop() stop()
} }
@ -72,7 +84,7 @@ object MatterLink : IMatterLink() {
error("${profile.name} is not online") error("${profile.name} is not online")
return return
} }
player.sendMessage(TextComponentString(msg)) player.addChatMessage(TextComponentString(msg))
} }
override fun wrappedSendToPlayer(uuid: UUID, msg: String) { override fun wrappedSendToPlayer(uuid: UUID, msg: String) {
@ -84,12 +96,14 @@ object MatterLink : IMatterLink() {
logger.error("${profile.name} is not online") logger.error("${profile.name} is not online")
return return
} }
player.sendMessage(TextComponentString(msg)) player.addChatMessage(TextComponentString(msg))
} }
override fun isOnline(username: String) = FMLCommonHandler.instance().minecraftServerInstance.onlinePlayerNames.contains(username) override fun isOnline(username: String) =
FMLCommonHandler.instance().minecraftServerInstance.playerList.allUsernames.contains(username)
private fun playerByProfile(gameProfile: GameProfile): EntityPlayerMP? = FMLCommonHandler.instance().minecraftServerInstance.playerList.getPlayerByUUID(gameProfile.id) private fun playerByProfile(gameProfile: GameProfile): EntityPlayerMP? =
FMLCommonHandler.instance().minecraftServerInstance.playerList.getPlayerByUUID(gameProfile.id)
private fun profileByUUID(uuid: UUID): GameProfile? = try { private fun profileByUUID(uuid: UUID): GameProfile? = try {
@ -106,14 +120,25 @@ object MatterLink : IMatterLink() {
null null
} }
override fun collectPlayers(area: Area): Set<UUID> {
val playerList = FMLCommonHandler.instance().minecraftServerInstance.playerList
val players = playerList.allProfiles
.map { playerList.getPlayerByUUID(it.id) }
.filter {
(area.allDimensions || area.dimensions.contains(it.dimension))
&& area.testInBounds(it.posX.toInt(), it.posY.toInt(), it.posZ.toInt())
}
return players.map { it.uniqueID }.toSet()
}
override fun nameToUUID(username: String): UUID? = profileByName(username)?.id override fun nameToUUID(username: String): UUID? = profileByName(username)?.id
override fun uuidToName(uuid: UUID): String? = profileByUUID(uuid)?.name override fun uuidToName(uuid: UUID): String? = profileByUUID(uuid)?.name
override fun commandSenderFor( override fun commandSenderFor(
user: String, user: String,
env: IBridgeCommand.CommandEnvironment, env: IBridgeCommand.CommandEnvironment,
op: Boolean op: Boolean
) = MatterLinkCommandSender(user, env, op) ) = MatterLinkCommandSender(user, env, op)
override val mcVersion: String = MCVERSION override val mcVersion: String = MCVERSION

View File

@ -1,6 +1,5 @@
package matterlink.command package matterlink.command
import matterlink.logger
import net.minecraft.command.CommandBase import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException import net.minecraft.command.WrongUsageException
@ -9,16 +8,16 @@ import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString import net.minecraft.util.text.TextComponentString
class AuthCommand : CommandBase() { object AuthCommand : CommandBase() {
override fun getName(): String { override fun getCommandName(): String {
return CommandCoreAuth.name return CommandCoreAuth.name
} }
override fun getUsage(sender: ICommandSender): String { override fun getCommandUsage(sender: ICommandSender): String {
return CommandCoreAuth.usage return CommandCoreAuth.usage
} }
override fun getAliases(): List<String> { override fun getCommandAliases(): List<String> {
return CommandCoreAuth.aliases return CommandCoreAuth.aliases
} }
@ -28,14 +27,14 @@ class AuthCommand : CommandBase() {
override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) { override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}") throw WrongUsageException("Invalid command! Valid uses: ${this.getCommandUsage(sender)}")
} }
val uuid = (sender as? EntityPlayer)?.uniqueID?.toString() val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()
val reply = CommandCoreAuth.execute(args, sender.name, uuid) val reply = CommandCoreAuth.execute(args, sender.name, uuid)
if (reply.isNotEmpty() && sender.sendCommandFeedback()) { if (reply.isNotEmpty() && sender.sendCommandFeedback()) {
sender.sendMessage(TextComponentString(reply)) sender.addChatMessage(TextComponentString(reply))
} }
} }

View File

@ -1,5 +1,6 @@
package matterlink.command package matterlink.command
import kotlinx.coroutines.runBlocking
import net.minecraft.command.CommandBase import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException import net.minecraft.command.WrongUsageException
@ -8,29 +9,29 @@ import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString import net.minecraft.util.text.TextComponentString
class MatterLinkCommand : CommandBase() { object MatterLinkCommand : CommandBase() {
override fun getName(): String { override fun getCommandName(): String {
return CommandCoreML.name return CommandCoreML.name
} }
override fun getUsage(sender: ICommandSender): String { override fun getCommandUsage(sender: ICommandSender): String {
return CommandCoreML.usage return CommandCoreML.usage
} }
override fun getAliases(): List<String> { override fun getCommandAliases(): List<String> {
return CommandCoreML.aliases return CommandCoreML.aliases
} }
override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) { override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) = runBlocking {
if (args.isEmpty()) { if (args.isEmpty()) {
throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}") throw WrongUsageException("Invalid command! Valid uses: ${getCommandUsage(sender)}")
} }
val uuid = (sender as? EntityPlayer)?.uniqueID?.toString() val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()
val reply = CommandCoreML.execute(args, sender.name, uuid) val reply = CommandCoreML.execute(args, sender.name, uuid)
if (reply.isNotEmpty() && sender.sendCommandFeedback()) { if (reply.isNotEmpty() && sender.sendCommandFeedback()) {
sender.sendMessage(TextComponentString(reply)) sender.addChatMessage(TextComponentString(reply))
} }
} }
} }

View File

@ -1,5 +1,6 @@
package matterlink.command package matterlink.command
import kotlinx.coroutines.runBlocking
import matterlink.bridge.command.IBridgeCommand import matterlink.bridge.command.IBridgeCommand
import matterlink.bridge.command.IMinecraftCommandSender import matterlink.bridge.command.IMinecraftCommandSender
import net.minecraft.command.CommandResultStats import net.minecraft.command.CommandResultStats
@ -15,14 +16,14 @@ import net.minecraftforge.fml.common.FMLCommonHandler
import javax.annotation.Nonnull import javax.annotation.Nonnull
class MatterLinkCommandSender( class MatterLinkCommandSender(
user: String, user: String,
env: IBridgeCommand.CommandEnvironment, env: IBridgeCommand.CommandEnvironment,
op: Boolean) : IMinecraftCommandSender(user, env, op), ICommandSender { op: Boolean
) : IMinecraftCommandSender(user, env, op), ICommandSender {
override fun execute(cmdString: String): Boolean { override fun execute(cmdString: String): Boolean = runBlocking {
return 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand( return@runBlocking 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand(
this, this@MatterLinkCommandSender,
cmdString cmdString
).apply { ).apply {
sendReply(cmdString) sendReply(cmdString)
} }
@ -38,7 +39,7 @@ class MatterLinkCommandSender(
return FMLCommonHandler.instance().minecraftServerInstance.worldServerForDimension(0) return FMLCommonHandler.instance().minecraftServerInstance.worldServerForDimension(0)
} }
override fun canUseCommand(permLevel: Int, commandName: String): Boolean { override fun canCommandSenderUseCommand(permLevel: Int, commandName: String): Boolean {
//we check user on our end //we check user on our end
return canExecute(commandName) return canExecute(commandName)
} }
@ -47,7 +48,7 @@ class MatterLinkCommandSender(
return FMLCommonHandler.instance().minecraftServerInstance return FMLCommonHandler.instance().minecraftServerInstance
} }
override fun sendMessage(@Nonnull component: ITextComponent?) { override fun addChatMessage(@Nonnull component: ITextComponent?) {
appendReply(component!!.unformattedComponentText) appendReply(component!!.unformattedComponentText)
} }

@ -1 +1 @@
Subproject commit 93ab86ee821380584c22ac60d77737388976e531 Subproject commit 7d27c28784bacba17450faa9e723ca6b6eb39602

18
Jenkinsfile vendored
View File

@ -14,20 +14,12 @@ pipeline {
archiveArtifacts artifacts: '1.7.10/build/libs/*jar' archiveArtifacts artifacts: '1.7.10/build/libs/*jar'
} }
} }
stage("1.10.2") { stage("1.9.4") {
steps { steps {
sh './gradlew :1.10.2:setupCiWorkspace' sh './gradlew :1.9.4:setupCiWorkspace'
sh './gradlew :1.10.2:clean' sh './gradlew :1.9.4:clean'
sh './gradlew :1.10.2:build' sh './gradlew :1.9.4:build'
archiveArtifacts artifacts: '1.10.2/build/libs/*jar' archiveArtifacts artifacts: '1.9.4/build/libs/*jar'
}
}
stage("1.11.2") {
steps {
sh './gradlew :1.11.2:setupCiWorkspace'
sh './gradlew :1.11.2:clean'
sh './gradlew :1.11.2:build'
archiveArtifacts artifacts: '1.11.2/build/libs/*jar'
} }
} }
stage("1.12.2") { stage("1.12.2") {

View File

@ -28,11 +28,7 @@ Chat with us on IRC: [#matterlink @ irc.esper.net](irc://irc.esper.net/matterlin
[![Download 1.12.2](https://curse.nikky.moe/api/img/287323?logo&style=for-the-badge&version=1.12.2)](https://curse.nikky.moe/api/url/287323?version=1.12.2) [![Download 1.12.2](https://curse.nikky.moe/api/img/287323?logo&style=for-the-badge&version=1.12.2)](https://curse.nikky.moe/api/url/287323?version=1.12.2)
[![Download 1.11.2](https://curse.nikky.moe/api/img/287323?logo&style=for-the-badge&version=1.11.2)](https://curse.nikky.moe/api/url/287323?version=1.11.2) [![Download 1.9.4](https://curse.nikky.moe/api/img/287323?logo&style=for-the-badge&version=1.9.4)](https://curse.nikky.moe/api/url/287323?version=1.9.4)
[![Download 1.10.2](https://curse.nikky.moe/api/img/287323?logo&style=for-the-badge&version=1.10.2)](https://curse.nikky.moe/api/url/287323?version=1.10.2)
[![Download 1.7.10](https://curse.nikky.moe/api/img/287323?logo&style=for-the-badge&version=1.7.10)](https://curse.nikky.moe/api/url/287323?version=1.7.10)
## Dependencies ## Dependencies
@ -167,6 +163,13 @@ matterbridge
now start the server with matterlink (and forgelin) in the mods folder now start the server with matterlink (and forgelin) in the mods folder
and then [RTFM!!!](https://github.com/42wim/matterbridge#configuration) and configure all your needed gateways, endpoints etc and then [configure](https://github.com/42wim/matterbridge#configuration) all your needed gateways, endpoints etc
powered by wishful thinking ### Building
```bash
git clone --recursive https://git.lain.faith/sorceress/MatterLink.git
cd MatterLink/
./gradlew setupDecompWorkspace
./gradlew build
```

14
TODO.MD Normal file
View File

@ -0,0 +1,14 @@
# Replacement for guava
# using Fuel everywhere to simplify code
# Adittional player values
add optional feature:
GET json dictionary of player values like TEAM, NICK, TOWN
from url based on uuid / playername
config input: `http://rest.wurmcraft.com/user/find/{UUID}`
url: `http://rest.wurmcraft.com/user/find/148cf139-dd14-4bf4-97a2-08305dfef0a9`

1
api

@ -1 +0,0 @@
Subproject commit 75138cec00c66d479709f4b44d78ca3005993474

View File

@ -3,7 +3,8 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath group: "org.jetbrains.kotlin", name: "kotlin-gradle-plugin", version: kotlin_version classpath group: "org.jetbrains.kotlin", name: "kotlin-gradle-plugin", version: kotlinVersion
classpath group: "org.jetbrains.kotlin", name: "kotlin-serialization", version: kotlinVersion
} }
} }
@ -14,13 +15,14 @@ plugins {
subprojects { subprojects {
apply plugin: "kotlin" apply plugin: "kotlin"
apply plugin: "kotlinx-serialization"
apply plugin: "idea" apply plugin: "idea"
if (System.env.BUILD_NUMBER) { if (System.env.BUILD_NUMBER) {
mod_version += "-${System.env.BUILD_NUMBER}" modVersion += "-${System.env.BUILD_NUMBER}"
} else if (!project.hasProperty('release')) { } else if (!project.hasProperty('release')) {
// mod_version += "-dev" // modVersion += "-dev"
mod_version += "-dev" modVersion += "-dev"
} }
idea { idea {
@ -35,19 +37,28 @@ subprojects {
} }
repositories { repositories {
jcenter() jcenter()
mavenCentral()
maven { maven {
name = "unascribed" name = "bintray"
url = 'http://unascribed.com/maven/releases' url = "http://jcenter.bintray.com"
} }
maven {
name = "jitpack"
url = "https://jitpack.io"
}
maven {
name = "kotlinx"
url = "https://kotlin.bintray.com/kotlinx/"
}
// maven {
// name = "elytradev"
// // url = 'http://unascribed.com/maven/releases'
// url = "https://repo.elytradev.com"
// }
maven { maven {
name = "shadowfacts" name = "shadowfacts"
url = "http://maven.shadowfacts.net/" url = "http://maven.shadowfacts.net/"
} }
ivy {
//Resolves baubles and jankson
name = "endless.blue dependency mirror"
artifactPattern "https://endless.blue/files/ivy/[module]-[revision].[ext]"
}
} }
compileKotlin { compileKotlin {
kotlinOptions { kotlinOptions {

View File

@ -0,0 +1,11 @@
object MatterLink {
const val version = "1.6.5"
}
object Kotlin {
const val version = "1.3.0"
}
object Forgelin {
const val version = "1.8.0"
}

View File

@ -4,30 +4,40 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
// classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath group: "org.jetbrains.kotlin", name: "kotlin-gradle-plugin", version: kotlinVersion
classpath group: "com.github.jengelman.gradle.plugins", name: "shadow", version: shadow_version classpath group: "com.github.jengelman.gradle.plugins", name: "shadow", version: shadowVersion
classpath group: 'com.vanniktech', name: 'gradle-dependency-graph-generator-plugin', version: '0.5.0'
} }
} }
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: "com.github.johnrengelman.shadow" apply plugin: "com.github.johnrengelman.shadow"
apply plugin: "com.vanniktech.dependency.graph.generator"
sourceCompatibility = targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. sourceCompatibility = targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
dependencies { dependencies {
compile project(':api')
compile project(":Jankson") compile project(":Jankson")
shadow(project(':Jankson')) { transitive = false }
// compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '+'
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '+'
// compile group: 'commons-logging', name: 'commons-logging', version: '1.1.3'
compile group: 'com.google.code.gson', name: 'gson', version: '+'
compile group: 'com.google.guava', name: 'guava', version: '+' compile group: 'com.google.guava', name: 'guava', version: '+'
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '+' compile(group: "com.github.kittinunf.Fuel", name: "fuel", version: fuelVersion)
shadow(group: "com.github.kittinunf.Fuel", name: "fuel", version: fuelVersion) { transitive = true }
compile(group: "com.github.kittinunf.Fuel", name: "fuel-coroutines", version: fuelVersion)
shadow(group: "com.github.kittinunf.Fuel", name: "fuel-coroutines", version: fuelVersion) { transitive = true }
compile(group: "com.github.kittinunf.Fuel", name: "fuel-kotlinx-serialization", version: fuelVersion)
shadow(group: "com.github.kittinunf.Fuel", name: "fuel-kotlinx-serialization", version: fuelVersion) { transitive = true }
// compile(group: 'com.github.kittinunf.result', name: 'result', version: resultVersion)
// shadow(group: 'com.github.kittinunf.result', name: 'result', version: resultVersion) { transitive = false }
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: project.kotlin_version // compile(group: "org.jetbrains.kotlinx", name: "kotlinx-coroutines-core", version: coroutinesVersion)
// shadow(group: "org.jetbrains.kotlinx", name: "kotlinx-coroutines-core", version: coroutinesVersion) { transitive = false }
compile(group: "org.jetbrains.kotlinx", name: "kotlinx-serialization-runtime", version: serializationVersion)
shadow(group: "org.jetbrains.kotlinx", name: "kotlinx-serialization-runtime", version: serializationVersion) { transitive = false }
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
} }
compileKotlin { compileKotlin {
@ -37,10 +47,12 @@ compileKotlin {
} }
shadowJar { shadowJar {
classifier '' classifier = ''
dependencies { relocate 'blue.endless', 'matterlink.repack.blue.endless'
include project(":api") relocate 'com.github', 'matterlink.repack.com.github'
include project(":Jankson") relocate 'kotlinx', 'matterlink.repack.kotlinx'
} configurations = [project.configurations.shadow]
} }
//tasks.build.dependsOn shadowJar

View File

@ -0,0 +1,185 @@
package matterlink
import blue.endless.jankson.JsonObject
import blue.endless.jankson.impl.SyntaxError
import kotlin.math.sqrt
fun JsonObject.parseDimensions(): List<Int> = getOrPutList("dimensions", emptyList(), "list of dimension ids")
fun JsonObject.parseAllDimensions(): Boolean = getOrDefault("allDimensions", false, "ignores dimension list")
sealed class Area {
abstract val type: String
abstract val dimensions: List<Int>
abstract val allDimensions: Boolean
abstract fun testInBounds(x: Int, y: Int, z: Int): Boolean
fun testForDim(dimension: Int?): Boolean {
if (allDimensions) return true
if (dimension == null) return false
return dimensions.contains(dimension)
}
companion object {
fun parse(jsonObj: JsonObject): Area {
val type: String = jsonObj.getOrDefault("type", "INFINITE", "Area type identifier")
return when (type.toUpperCase()) {
"INFINITE" -> Infinite.parse(jsonObj)
"RADIUS" -> Radius.parse(jsonObj)
"SPHERE" -> Sphere.parse(jsonObj)
"BOX" -> Box.parse(jsonObj)
"SQUARE" -> Square.parse(jsonObj)
else -> throw SyntaxError("no Area type '$type' found")
}
}
}
data class Infinite(
override val dimensions: List<Int> = listOf(),
override val allDimensions: Boolean = false
) : Area() {
override val type = "INFINITE"
override fun testInBounds(x: Int, y: Int, z: Int): Boolean {
return true
}
companion object {
fun parse(jsonObj: JsonObject): Area {
return Infinite(
dimensions = jsonObj.parseDimensions(),
allDimensions = jsonObj.parseAllDimensions()
)
}
}
}
data class Radius(
override val dimensions: List<Int> = listOf(),
override val allDimensions: Boolean = false,
val x: Int,
val z: Int,
val radius: Int?
) : Area() {
override val type = "RADIUS"
override fun testInBounds(x: Int, y: Int, z: Int): Boolean {
if (radius == null) return true
return sqrt(((this.x - x) * (this.x - x)) + ((this.z - z) * (this.z - z)).toFloat()) < this.radius
}
companion object {
fun parse(jsonObj: JsonObject): Area {
return Radius(
dimensions = jsonObj.parseDimensions(),
allDimensions = jsonObj.parseAllDimensions(),
x = jsonObj.getOrDefault("x", 0),
z = jsonObj.getOrDefault("z", 0),
radius = jsonObj.getReified("radius")
)
}
}
}
class Sphere(
override val dimensions: List<Int> = listOf(),
override val allDimensions: Boolean = false,
val x: Int,
val y: Int,
val z: Int,
val radius: Int? = null
) : Area() {
override val type = "SPHERE"
override fun testInBounds(x: Int, y: Int, z: Int): Boolean {
if (radius == null) return true
return sqrt(((this.x - x) * (this.x - x)) + ((this.y - y) * (this.y - y)) + ((this.z - z) * (this.z - z)).toFloat()) < this.radius
}
companion object {
fun parse(jsonObj: JsonObject): Area {
return Sphere(
dimensions = jsonObj.parseDimensions(),
allDimensions = jsonObj.parseAllDimensions(),
x = jsonObj.getOrDefault("x", 0),
y = jsonObj.getOrDefault("y", 0),
z = jsonObj.getOrDefault("z", 0),
radius = jsonObj.getReified("radius")
)
}
}
}
class Box(
override val dimensions: List<Int> = listOf(),
override val allDimensions: Boolean = false,
val x1: Int,
val x2: Int,
val y1: Int,
val y2: Int,
val z1: Int,
val z2: Int
) : Area() {
override val type = "BOX"
override fun testInBounds(x: Int, y: Int, z: Int): Boolean {
return x in x1..x2 && y in y1..y2 && z in z1..z2
}
companion object {
fun parse(jsonObj: JsonObject): Area {
return Box(
dimensions = jsonObj.parseDimensions(),
allDimensions = jsonObj.parseAllDimensions(),
x1 = jsonObj.getOrDefault("x1", 0),
x2 = jsonObj.getOrDefault("x2", 0),
y1 = jsonObj.getOrDefault("y1", 0),
y2 = jsonObj.getOrDefault("y2", 0),
z1 = jsonObj.getOrDefault("z1", 0),
z2 = jsonObj.getOrDefault("z2", 0)
)
}
}
}
class Square(
override val dimensions: List<Int> = listOf(),
override val allDimensions: Boolean = false,
val x1: Int,
val x2: Int,
val z1: Int,
val z2: Int
) : Area() {
override val type = "SQUARE"
override fun testInBounds(x: Int, y: Int, z: Int): Boolean {
return x in x1..x2 && z in z1..z2
}
companion object {
fun parse(jsonObj: JsonObject): Area {
return Square(
dimensions = jsonObj.parseDimensions(),
allDimensions = jsonObj.parseAllDimensions(),
x1 = jsonObj.getOrDefault("x1", 0),
x2 = jsonObj.getOrDefault("x2", 0),
z1 = jsonObj.getOrDefault("z1", 0),
z2 = jsonObj.getOrDefault("z2", 0)
)
}
}
}
//
//
// class FakePlayer (
// val x: Int,
// val y: Int,
// val z: Int,
// val name: String
// ): Area()
}

View File

@ -1,42 +0,0 @@
package matterlink
import matterlink.HttpClientUtil.client
import org.apache.http.HttpResponse
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpRequestBase
import org.apache.http.impl.client.HttpClientBuilder
/**
* Created by nikky on 15/07/18.
* @author Nikky
*/
object HttpClientUtil {
val client = HttpClientBuilder.create().build()
}
fun String.httpGet(): HttpGet =
HttpGet(this)
fun HttpGet.header(pair: Pair<String, String>): HttpGet = this.apply {
addHeader(pair.first, pair.second)
}
fun HttpGet.responseString(): Triple<HttpRequestBase, HttpResponse, Result> {
val response = client.execute(this)
val result = response.entity.content.bufferedReader().use { it.readText() }
return Triple(this, response, Result.Success(result))
}
sealed class Result {
class Success(
val value: String
) : Result()
class Failure(
val error: Throwable
) : Result()
}

View File

@ -6,11 +6,11 @@ import matterlink.bridge.command.IBridgeCommand
import matterlink.bridge.command.IMinecraftCommandSender import matterlink.bridge.command.IMinecraftCommandSender
import matterlink.config.cfg import matterlink.config.cfg
import matterlink.update.UpdateChecker import matterlink.update.UpdateChecker
import org.apache.logging.log4j.Logger import java.util.UUID
import java.util.*
lateinit var logger: Logger
lateinit var instance: IMatterLink lateinit var instance: IMatterLink
lateinit var logger: Logger
abstract class IMatterLink { abstract class IMatterLink {
abstract val mcVersion: String abstract val mcVersion: String
@ -18,7 +18,11 @@ abstract class IMatterLink {
abstract val buildNumber: Int abstract val buildNumber: Int
abstract val forgeVersion: String abstract val forgeVersion: String
abstract fun commandSenderFor(user: String, env: IBridgeCommand.CommandEnvironment, op: Boolean): IMinecraftCommandSender abstract fun commandSenderFor(
user: String,
env: IBridgeCommand.CommandEnvironment,
op: Boolean
): IMinecraftCommandSender
abstract fun wrappedSendToPlayers(msg: String) abstract fun wrappedSendToPlayers(msg: String)
@ -28,46 +32,19 @@ abstract class IMatterLink {
abstract fun nameToUUID(username: String): UUID? abstract fun nameToUUID(username: String): UUID?
abstract fun uuidToName(uuid: UUID): String? abstract fun uuidToName(uuid: UUID): String?
fun start() { suspend fun start() {
// MessageHandlerInst.logger = { level, msg ->
// when (level) {
// "FATAL" -> logger.fatal(msg)
// "ERROR" -> logger.error(msg)
// "WARN" -> logger.warn(msg)
// "INFO" -> logger.info(msg)
// "DEBUG" -> logger.debug(msg)
// "TRACE" -> logger.trace(msg)
// }
// }
MessageHandlerInst.logger = logger MessageHandlerInst.logger = logger
serverStartTime = System.currentTimeMillis() serverStartTime = System.currentTimeMillis()
if (cfg.connect.autoConnect) if (cfg.connect.autoConnect)
MessageHandlerInst.start("Server started, connecting to matterbridge API", true) MessageHandlerInst.start("Server started, connecting to matterbridge API", true)
UpdateChecker.run() UpdateChecker.check()
} }
fun stop() { suspend fun stop() {
MessageHandlerInst.stop("Server shutting down, disconnecting from matterbridge API") MessageHandlerInst.stop("Server shutting down, disconnecting from matterbridge API")
} }
// abstract fun log(level: String, formatString: String, vararg data: Any)
// fun fatal(formatString: String, vararg data: Any) = log("FATAL", formatString, *data)
// fun error(formatString: String, vararg data: Any) = log("ERROR", formatString, *data)
// fun warn(formatString: String, vararg data: Any) = log("WARN", formatString, *data)
// fun info(formatString: String, vararg data: Any) = log("INFO", formatString, *data)
//
// fun debug(formatString: String, vararg data: Any) {
// if (cfg.debug.logLevel == "DEBUG" || cfg.debug.logLevel == "TRACE")
// log("INFO", "DEBUG: " + formatString.replace("\n", "\nDEBUG: "), *data)
// }
//
// fun trace(formatString: String, vararg data: Any) {
// if (cfg.debug.logLevel == "TRACE")
// log("INFO", "TRACE: " + formatString.replace("\n", "\nTRACE: "), *data)
// }
/** /**
* in milliseconds * in milliseconds
*/ */
@ -98,4 +75,6 @@ abstract class IMatterLink {
BridgeCommandRegistry.reloadCommands() BridgeCommandRegistry.reloadCommands()
} }
abstract fun collectPlayers(area: Area): Set<UUID>
} }

View File

@ -0,0 +1,9 @@
package matterlink
interface Logger {
fun info(message: String)
fun debug(message: String)
fun error(message: String)
fun warn(message: String)
fun trace(message: String)
}

View File

@ -1,8 +1,6 @@
package matterlink package matterlink
import blue.endless.jankson.Jankson import blue.endless.jankson.Jankson
import blue.endless.jankson.JsonObject
import blue.endless.jankson.impl.Marshaller
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
@ -13,34 +11,34 @@ import java.net.URL
*/ */
data class Paste( data class Paste(
val encrypted: Boolean = false, val encrypted: Boolean = false,
val description: String, val description: String,
val sections: List<PasteSection> val sections: List<PasteSection>
) )
data class PasteSection( data class PasteSection(
val name: String, val name: String,
val syntax: String = "text", val syntax: String = "text",
val contents: String val contents: String
) )
data class PasteResponse( data class PasteResponse(
val id: String, val id: String,
val link: String val link: String
) )
object PasteUtil { object PasteUtil {
private const val DEFAULT_KEY = "uKJoyicVJFnmpnrIZMklOURWxrCKXYaiBWOzPmvon" private const val DEFAULT_KEY = "uKJoyicVJFnmpnrIZMklOURWxrCKXYaiBWOzPmvon"
private val jankson = Jankson.builder() private val jankson = Jankson.builder()
.registerTypeAdapter { .registerTypeAdapter {
PasteResponse( PasteResponse(
id = it.getReified("id") ?: "", id = it.getReified("id") ?: "",
link = it.getReified<String>("link") link = it.getReified<String>("link")
?.replace("\\/", "/") ?.replace("\\/", "/")
?: "invalid" ?: "invalid"
) )
} }
// .registerSerializer { paste: Paste, marshaller: Marshaller -> // .registerSerializer { paste: Paste, marshaller: Marshaller ->
// JsonObject().apply { // JsonObject().apply {
// with(paste) { // with(paste) {
@ -62,7 +60,7 @@ object PasteUtil {
// } // }
// } // }
// } // }
.build() .build()
fun paste(paste: Paste, key: String = ""): PasteResponse { fun paste(paste: Paste, key: String = ""): PasteResponse {
val apiKey = key.takeIf { it.isNotBlank() } ?: DEFAULT_KEY val apiKey = key.takeIf { it.isNotBlank() } ?: DEFAULT_KEY
@ -73,8 +71,8 @@ object PasteUtil {
http.doOutput = true http.doOutput = true
val out = jankson.toJson(paste) val out = jankson.toJson(paste)
.toJson(false, false) .toJson(false, false)
.toByteArray() .toByteArray()
http.setFixedLengthStreamingMode(out.size) http.setFixedLengthStreamingMode(out.size)
http.setRequestProperty("Content-Type", "application/json; charset=UTF-8") http.setRequestProperty("Content-Type", "application/json; charset=UTF-8")

View File

@ -8,9 +8,7 @@ import blue.endless.jankson.impl.Marshaller
import matterlink.config.cfg import matterlink.config.cfg
import java.io.PrintWriter import java.io.PrintWriter
import java.io.StringWriter import java.io.StringWriter
import java.lang.Thread.yield
import java.util.* import java.util.*
import kotlin.streams.asSequence
private const val ZWSP: Char = '\u200b' private const val ZWSP: Char = '\u200b'
@ -64,27 +62,44 @@ val Exception.stackTraceString: String
} }
fun randomString(length: Int = 6): String = fun randomString(length: Int = 6): String =
java.util.UUID.randomUUID().toString().replace("-", "").take(length) java.util.UUID.randomUUID().toString().replace("-", "").take(length)
fun <T : Any> JsonObject.getOrDefault(key: String, default: T, comment: String? = null): T { fun <T : Any> JsonObject.getOrDefault(key: String, default: T, comment: String? = null): T {
// logger.info("type: ${default.javaClass.name} key: $key json: >>>${this.getObject(key)?.toJson()}<<< default: $default") logger.trace("type: ${default.javaClass.name} key: $key json: >>>${this.getObject(key)?.toJson()}<<< default: $default")
return putDefault(key, default, comment)!! return putDefault(key, default, comment)!!.also {
setComment(key, comment)
}
} }
inline fun <reified T : Any> Jankson.fromJson(obj: JsonObject): T = this.fromJson(obj, T::class.java) inline fun <reified T : Any> Jankson.fromJson(obj: JsonObject): T = this.fromJson(obj, T::class.java)
inline fun <reified T : Any> Jankson.fromJson(json: String): T = this.fromJson(json, T::class.java) inline fun <reified T : Any> Jankson.fromJson(json: String): T = this.fromJson(json, T::class.java)
inline fun <reified T : Any> Jankson.Builder.registerTypeAdapter(noinline adapter: (JsonObject) -> T) = this.registerTypeAdapter(T::class.java, adapter) inline fun <reified T : Any> Jankson.Builder.registerTypeAdapter(noinline adapter: (JsonObject) -> T) =
this.registerTypeAdapter(T::class.java, adapter)
inline fun <reified T : Any> Jankson.Builder.registerPrimitiveTypeAdapter(noinline adapter: (Any) -> T) = this.registerPrimitiveTypeAdapter(T::class.java, adapter) inline fun <reified T : Any> Jankson.Builder.registerPrimitiveTypeAdapter(noinline adapter: (Any) -> T) =
this.registerPrimitiveTypeAdapter(T::class.java, adapter)
inline fun <reified T : Any> Jankson.Builder.registerSerializer(noinline serializer: (T, Marshaller) -> JsonElement) = this.registerSerializer(T::class.java, serializer) inline fun <reified T : Any> Jankson.Builder.registerSerializer(noinline serializer: (T, Marshaller) -> JsonElement) =
this.registerSerializer(T::class.java, serializer)
inline fun <reified T : Any> Marshaller.registerSerializer(noinline serializer: (T) -> JsonElement) = this.registerSerializer(T::class.java, serializer) inline fun <reified T : Any> Marshaller.registerSerializer(noinline serializer: (T) -> JsonElement) =
this.registerSerializer(T::class.java, serializer)
inline fun <reified T : Any> Marshaller.registerSerializer(noinline serializer: (T, Marshaller) -> JsonElement) = this.registerSerializer(T::class.java, serializer) inline fun <reified T : Any> Marshaller.registerSerializer(noinline serializer: (T, Marshaller) -> JsonElement) =
this.registerSerializer(T::class.java, serializer)
inline fun <reified T : Any> JsonObject.getReified(key: String): T? = this.get(T::class.java, key) inline fun <reified T : Any> JsonObject.getReified(key: String, comment: String? = null): T? =
this.get(T::class.java, key)
?.also { setComment(key, comment) }
inline fun <reified T : Any> JsonObject.getReifiedOrDelete(key: String, comment: String? = null): T? =
this.get(T::class.java, key)
?.also { setComment(key, comment) }
?: run {
this.remove(key)
null
}
inline fun <reified T : Any> JsonObject.getList(key: String): List<T>? { inline fun <reified T : Any> JsonObject.getList(key: String): List<T>? {
return this[key]?.let { array -> return this[key]?.let { array ->
@ -98,3 +113,37 @@ inline fun <reified T : Any> JsonObject.getList(key: String): List<T>? {
} }
} }
} }
inline fun <reified T : Any> JsonObject.getOrPutList(key: String, default: List<T>, comment: String?): List<T> {
return this[key]?.let { array ->
when (array) {
is JsonArray -> {
array.indices.map { i ->
array.get(T::class.java, i) ?: throw NullPointerException("cannot parse ${array.get(i)}")
}
}
else -> null
}
}.also {
setComment(key, comment)
} ?: this.putDefault(key, default, comment) ?: default
}
inline fun <reified T : Any> JsonObject.getOrPutMap(
key: String,
default: Map<String, T>,
comment: String?
): Map<String, T> {
return this[key]?.let { map ->
when (map) {
is JsonObject -> {
map.mapValues { (key, element) ->
map.get(T::class.java, key) ?: throw NullPointerException("cannot parse $element")
}
}
else -> null
}
}.also {
setComment(key, comment)
} ?: this.putDefault(key, default, comment) ?: default
}

View File

@ -0,0 +1,88 @@
package matterlink.api
import kotlinx.serialization.Encoder
import kotlinx.serialization.Optional
import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
import kotlinx.serialization.json.JSON
/**
* Created by nikky on 07/05/18.
*
* @author Nikky
* @version 1.0
*/
@Serializable
data class ApiMessage(
@Optional var username: String = "",
@Optional var text: String = "",
@Optional var gateway: String = "",
@Optional var timestamp: String = "",
@Optional var channel: String = "",
@Optional var userid: String = "",
@Optional var avatar: String = "",
@Optional var account: String = "",
@Optional var protocol: String = "",
@Optional var event: String = "",
@Optional var id: String = "",
@Optional var Extra: Map<String, String>? = null
) {
fun encode(): String {
return JSON.nonstrict.stringify(ApiMessage.serializer(), this)
}
override fun toString(): String = encode()
@Serializer(forClass = ApiMessage::class)
companion object {
val USER_ACTION = "user_action"
val JOIN_LEAVE = "join_leave"
override fun serialize(output: Encoder, obj: ApiMessage) {
val elemOutput = output.beginStructure(descriptor)
obj.username.takeIf { it.isNotEmpty() }?.let {
elemOutput.encodeStringElement(descriptor, 0, it)
}
obj.text.takeIf { it.isNotEmpty() }?.let {
elemOutput.encodeStringElement(descriptor, 1, it)
}
obj.gateway.takeIf { it.isNotEmpty() }?.let {
elemOutput.encodeStringElement(descriptor, 2, it)
}
obj.timestamp.takeIf { it.isNotEmpty() }?.let {
elemOutput.encodeStringElement(descriptor, 3, it)
}
obj.channel.takeIf { it.isNotEmpty() }?.let {
elemOutput.encodeStringElement(descriptor, 4, it)
}
obj.userid.takeIf { it.isNotEmpty() }?.let {
elemOutput.encodeStringElement(descriptor, 5, it)
}
obj.avatar.takeIf { it.isNotEmpty() }?.let {
elemOutput.encodeStringElement(descriptor, 6, it)
}
obj.account.takeIf { it.isNotEmpty() }?.let {
elemOutput.encodeStringElement(descriptor, 7, it)
}
obj.protocol.takeIf { it.isNotEmpty() }?.let {
elemOutput.encodeStringElement(descriptor, 8, it)
}
obj.event.takeIf { it.isNotEmpty() }?.let {
elemOutput.encodeStringElement(descriptor, 9, it)
}
obj.id.takeIf { it.isNotEmpty() }?.let {
elemOutput.encodeStringElement(descriptor, 10, it)
}
// obj.Extra.takeIf { ! it.isNullOrEmpty() }?.let {
// elemOutput.encodeStringElement(descriptor, 11, it)
// }
elemOutput.endStructure(descriptor)
}
fun decode(input: String): ApiMessage {
return JSON.nonstrict.parse(ApiMessage.serializer(), input)
}
}
}

View File

@ -0,0 +1,10 @@
package matterlink.api
data class Config(
var url: String = "",
var token: String = "",
var announceConnect: Boolean = true,
var announceDisconnect: Boolean = true,
var reconnectWait: Long = 500,
var systemUser: String = "Server"
)

View File

@ -0,0 +1,240 @@
package matterlink.api
import com.github.kittinunf.fuel.core.FuelManager
import com.github.kittinunf.fuel.core.Method
import com.github.kittinunf.fuel.core.ResponseDeserializable
import com.github.kittinunf.fuel.core.extensions.cUrlString
import com.github.kittinunf.fuel.core.extensions.jsonBody
import com.github.kittinunf.fuel.coroutines.awaitStringResponseResult
import com.github.kittinunf.fuel.httpGet
import com.github.kittinunf.fuel.httpPost
import com.github.kittinunf.result.Result
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.channels.broadcast
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.JSON
import kotlinx.serialization.list
import matterlink.Logger
import java.io.Reader
import java.net.ConnectException
import kotlin.coroutines.CoroutineContext
/**
* Created by nikky on 07/05/18.
*
* @author Nikky
* @version 1.0
*/
open class MessageHandler : CoroutineScope {
override val coroutineContext: CoroutineContext = Job()
private var enabled = false
private var connectErrors = 0
private var reconnectCooldown = 0L
private var sendErrors = 0
private var sendChannel: SendChannel<ApiMessage> = senderActor()
private val messageStream = Channel<ApiMessage>(Channel.UNLIMITED)
@UseExperimental(ExperimentalCoroutinesApi::class)
var broadcast: BroadcastChannel<ApiMessage> = broadcast {
while (true) {
val msg = messageStream.receive()
send(msg)
}
}
private set
private val keepOpenManager = FuelManager().apply {
timeoutInMillisecond = 0
timeoutReadInMillisecond = 0
}
var config: Config = Config()
var logger = object : Logger {
override fun info(message: String) = println("INFO: $message")
override fun debug(message: String) = println("DEBUG: $message")
override fun error(message: String) = println("ERROR: $message")
override fun warn(message: String) = println("WARN: $message")
override fun trace(message: String) = println("TRACE: $message")
}
suspend fun stop(message: String? = null) {
if (message != null && config.announceDisconnect) {
sendStatusUpdate(message)
}
enabled = false
rcvJob?.cancel()
rcvJob = null
}
private var rcvJob: Job? = null
suspend fun start(message: String?, clear: Boolean) {
logger.debug("starting connection")
if (clear) {
clear()
}
enabled = true
rcvJob = messageBroadcast()
if (message != null && config.announceConnect) {
sendStatusUpdate(message)
}
}
private suspend fun clear() {
val url = "${config.url}/api/messages"
val (request, response, result) = url.httpGet()
.apply {
if (config.token.isNotEmpty()) {
headers["Authorization"] = "Bearer ${config.token}"
}
}
.awaitStringResponseResult()
when (result) {
is Result.Success -> {
val messages: List<ApiMessage> = JSON.nonstrict.parse(ApiMessage.list, result.value)
messages.forEach { msg ->
logger.trace("skipping $msg")
}
logger.debug("skipped ${messages.count()} messages")
}
is Result.Failure -> {
logger.error("failed to clear messages")
logger.error("url: $url")
logger.error("cUrl: ${request.cUrlString()}")
logger.error("response: $response")
logger.error(result.error.exception.localizedMessage)
result.error.exception.printStackTrace()
}
}
}
open suspend fun sendStatusUpdate(message: String) {
transmit(ApiMessage(text = message))
}
open suspend fun transmit(msg: ApiMessage) {
// if (streamConnection.isConnected || streamConnection.isConnecting) {
if (msg.username.isEmpty())
msg.username = config.systemUser
if (msg.gateway.isEmpty()) {
logger.error("missing gateway on message: $msg")
return
}
logger.debug("Transmitting: $msg")
sendChannel.send(msg)
// }
}
@Deprecated("use coroutine api", level = DeprecationLevel.ERROR)
fun checkConnection() {
}
@UseExperimental(ObsoleteCoroutinesApi::class)
private fun CoroutineScope.senderActor() = actor<ApiMessage>(context = Dispatchers.IO) {
consumeEach {
try {
logger.debug("sending $it")
val url = "${config.url}/api/message"
val (request, response, result) = url.httpPost()
.apply {
if (config.token.isNotEmpty()) {
headers["Authorization"] = "Bearer ${config.token}"
}
}
.jsonBody(it.encode())
.responseString()
when (result) {
is Result.Success -> {
logger.debug("sent $it")
sendErrors = 0
}
is Result.Failure -> {
sendErrors++
logger.error("failed to deliver: $it")
logger.error("url: $url")
logger.error("cUrl: ${request.cUrlString()}")
logger.error("response: $response")
logger.error(result.error.exception.localizedMessage)
result.error.exception.printStackTrace()
// close()
throw result.error.exception
}
}
} catch (connectError: ConnectException) {
connectError.printStackTrace()
sendErrors++
}
}
}
private fun CoroutineScope.messageBroadcast() = launch(context = Dispatchers.IO + CoroutineName("msgBroadcaster")) {
loop@ while (isActive) {
logger.info("opening connection")
val url = "${config.url}/api/stream"
val (request, response, result) = keepOpenManager.request(Method.GET, url)
.apply {
if (config.token.isNotEmpty()) {
headers["Authorization"] = "Bearer ${config.token}"
}
}
.responseObject(object : ResponseDeserializable<Unit> {
override fun deserialize(reader: Reader) =
runBlocking(Dispatchers.IO + CoroutineName("msgReceiver")) {
logger.info("connected successfully")
connectErrors = 0
reconnectCooldown = 0
reader.useLines { lines ->
lines.forEach { line ->
val msg = ApiMessage.decode(line)
logger.debug("received: $msg")
if (msg.event != "api_connect") {
messageStream.send(msg)
}
}
}
}
})
when (result) {
is Result.Success -> {
logger.info("connection closed")
}
is Result.Failure -> {
connectErrors++
reconnectCooldown = connectErrors * 1000L
logger.error("connectErrors: $connectErrors")
logger.error("connection error")
logger.error("curl: ${request.cUrlString()}")
logger.error(result.error.localizedMessage)
result.error.exception.printStackTrace()
if (connectErrors >= 10) {
logger.error("Caught too many errors, closing bridge")
stop("Interrupting connection to matterbridge API due to accumulated connection errors")
break@loop
}
}
}
delay(reconnectCooldown) // reconnect delay in ms
}
}
}

View File

@ -1,43 +1,63 @@
package matterlink.bridge package matterlink.bridge
import matterlink.* import matterlink.Paste
import matterlink.PasteSection
import matterlink.PasteUtil
import matterlink.antiping
import matterlink.api.ApiMessage import matterlink.api.ApiMessage
import matterlink.api.MessageHandler import matterlink.api.MessageHandler
import matterlink.config.cfg import matterlink.config.cfg
import matterlink.handlers.ChatEvent
import matterlink.handlers.LocationHandler
import matterlink.mapFormat
import matterlink.stackTraceString
object MessageHandlerInst : MessageHandler() { object MessageHandlerInst : MessageHandler() {
override fun transmit(msg: ApiMessage) { override suspend fun transmit(msg: ApiMessage) {
transmit(msg, cause = "") transmit(msg, cause = "")
} }
fun transmit(msg: ApiMessage, cause: String, maxLines: Int = cfg.outgoing.inlineLimit) { override suspend fun sendStatusUpdate(message: String) {
LocationHandler.sendToLocations(
msg = message,
x = 0, y = 0, z = 0, dimension = null,
systemuser = true,
event = ChatEvent.STATUS,
cause = "status update message"
)
}
suspend fun transmit(msg: ApiMessage, cause: String, maxLines: Int = cfg.outgoing.inlineLimit) {
if (msg.username.isEmpty()) { if (msg.username.isEmpty()) {
msg.username = cfg.outgoing.systemUser msg.username = cfg.outgoing.systemUser
if(msg.avatar.isEmpty() && cfg.outgoing.avatar.enable) { if (msg.avatar.isEmpty() && cfg.outgoing.avatar.enable) {
msg.avatar = cfg.outgoing.avatar.systemUserAvatar msg.avatar = cfg.outgoing.avatar.systemUserAvatar
} }
} }
if (msg.gateway.isEmpty()) if (msg.gateway.isEmpty()) {
msg.gateway = cfg.connect.gateway logger.error("dropped message '$msg' due to missing gateway")
return
}
if (msg.text.lines().count() >= maxLines) { if (msg.text.lines().count() >= maxLines) {
try { try {
val response = PasteUtil.paste( val response = PasteUtil.paste(
Paste( Paste(
description = cause, description = cause,
sections = listOf( sections = listOf(
PasteSection( PasteSection(
name = "log.txt", name = "log.txt",
syntax = "text", syntax = "text",
contents = msg.text contents = msg.text
) )
)
) )
)
) )
msg.text = msg.text.substringBefore('\n') msg.text = msg.text.substringBefore('\n')
.take(25) + "... " + response.link .take(25) + "... " + response.link
} catch(e: Exception) { } catch (e: Exception) {
logger.error(cause)
logger.error(e.stackTraceString) logger.error(e.stackTraceString)
} }
} }
@ -47,13 +67,13 @@ object MessageHandlerInst : MessageHandler() {
fun ApiMessage.format(fmt: String): String { fun ApiMessage.format(fmt: String): String {
return fmt.mapFormat( return fmt.mapFormat(
mapOf( mapOf(
"{username}" to username, "{username}" to username,
"{text}" to text, "{text}" to text,
"{gateway}" to gateway, "{gateway}" to gateway,
"{channel}" to channel, "{channel}" to channel,
"{protocol}" to protocol, "{protocol}" to protocol,
"{username:antiping}" to username.antiping "{username:antiping}" to username.antiping
) )
) )
} }

View File

@ -5,7 +5,7 @@ import matterlink.config.IdentitiesConfig
import matterlink.config.cfg import matterlink.config.cfg
import matterlink.instance import matterlink.instance
import matterlink.randomString import matterlink.randomString
import java.util.* import java.util.UUID
object AuthBridgeCommand : IBridgeCommand() { object AuthBridgeCommand : IBridgeCommand() {
val syntax = "Syntax: auth [username]" val syntax = "Syntax: auth [username]"
@ -13,7 +13,7 @@ object AuthBridgeCommand : IBridgeCommand() {
override val permLevel: Double override val permLevel: Double
get() = cfg.command.defaultPermUnauthenticated get() = cfg.command.defaultPermUnauthenticated
override fun execute(alias: String, user: String, env: CommandEnvironment, args: String): Boolean { override suspend fun execute(alias: String, user: String, env: CommandEnvironment, args: String): Boolean {
if (env !is CommandEnvironment.BridgeEnv) { if (env !is CommandEnvironment.BridgeEnv) {
env.respond("please initiate authentication from linked external chat") env.respond("please initiate authentication from linked external chat")
return true return true
@ -28,8 +28,10 @@ object AuthBridgeCommand : IBridgeCommand() {
val argList = args.split(' ', limit = 2) val argList = args.split(' ', limit = 2)
val target = argList.getOrNull(0) ?: run { val target = argList.getOrNull(0) ?: run {
env.respond("no username/uuid provided\n" + env.respond(
syntax) "no username/uuid provided\n" +
syntax
)
return true return true
} }
@ -62,7 +64,16 @@ object AuthBridgeCommand : IBridgeCommand() {
instance.wrappedSendToPlayer(targetUserName, "otherwise you may ignore this message") instance.wrappedSendToPlayer(targetUserName, "otherwise you may ignore this message")
IdentitiesConfig.authRequests.put(requestId, AuthRequest(username = targetUserName, uuid = targetUUid, nonce = nonce, platform = env.platform, userid = env.userId)) IdentitiesConfig.authRequests.put(
requestId,
AuthRequest(
username = targetUserName,
uuid = targetUUid,
nonce = nonce,
platform = env.platform,
userid = env.userId
)
)
env.respond("please accept the authentication request ingame, do not share the code") env.respond("please accept the authentication request ingame, do not share the code")
return true return true

View File

@ -1,15 +1,13 @@
package matterlink.bridge.command package matterlink.bridge.command
import matterlink.api.ApiMessage import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.CommandConfig import matterlink.config.CommandConfig
import matterlink.config.IdentitiesConfig import matterlink.config.IdentitiesConfig
import matterlink.config.PermissionConfig import matterlink.config.PermissionConfig
import matterlink.config.cfg import matterlink.config.cfg
import matterlink.instance
import matterlink.logger import matterlink.logger
import matterlink.stripColorOut import java.util.HashMap
import java.util.* import java.util.UUID
object BridgeCommandRegistry { object BridgeCommandRegistry {
@ -19,7 +17,7 @@ object BridgeCommandRegistry {
* *
* @return consume message flag * @return consume message flag
*/ */
fun handleCommand(input: ApiMessage): Boolean { suspend fun handleCommand(input: ApiMessage): Boolean {
if (!cfg.command.enable || input.text.isBlank()) return false if (!cfg.command.enable || input.text.isBlank()) return false
if (input.text[0] != cfg.command.prefix || input.text.length < 2) return false if (input.text[0] != cfg.command.prefix || input.text.length < 2) return false
@ -29,7 +27,13 @@ object BridgeCommandRegistry {
val uuid = IdentitiesConfig.getUUID(input.account, input.userid) val uuid = IdentitiesConfig.getUUID(input.account, input.userid)
val env = IBridgeCommand.CommandEnvironment.BridgeEnv(input.username, input.userid, input.account, uuid) val env = IBridgeCommand.CommandEnvironment.BridgeEnv(
input.username,
input.userid,
input.account,
input.gateway,
uuid
)
return commandMap[cmd[0]]?.let { return commandMap[cmd[0]]?.let {
if (!it.reachedTimeout()) { if (!it.reachedTimeout()) {
logger.debug("dropped command ${it.alias}") logger.debug("dropped command ${it.alias}")
@ -38,7 +42,7 @@ object BridgeCommandRegistry {
it.preExecute() // resets the tickCounter it.preExecute() // resets the tickCounter
if (!it.canExecute(uuid)) { if (!it.canExecute(uuid)) {
env.respond( env.respond(
text = "${input.username} is not permitted to perform command: ${cmd[0]}" text = "${input.username} is not permitted to perform command: ${cmd[0]}"
) )
return false return false
} }
@ -46,7 +50,7 @@ object BridgeCommandRegistry {
} ?: false } ?: false
} }
fun handleCommand(text: String, username: String, uuid: UUID): Boolean { suspend fun handleCommand(text: String, username: String, uuid: UUID): Boolean {
if (!cfg.command.enable || text.isBlank()) return false if (!cfg.command.enable || text.isBlank()) return false
if (text[0] != cfg.command.prefix || text.length < 2) return false if (text[0] != cfg.command.prefix || text.length < 2) return false
@ -64,7 +68,7 @@ object BridgeCommandRegistry {
it.preExecute() // resets the tickCounter it.preExecute() // resets the tickCounter
if (!it.canExecute(uuid)) { if (!it.canExecute(uuid)) {
env.respond( env.respond(
text = "$username is not permitted to perform command: ${cmd[0]}" text = "$username is not permitted to perform command: ${cmd[0]}"
) )
return false return false
} }
@ -98,11 +102,11 @@ object BridgeCommandRegistry {
fun getCommandList(permLvl: Double): String { fun getCommandList(permLvl: Double): String {
return commandMap return commandMap
.filterValues { .filterValues {
it.permLevel <= permLvl it.permLevel <= permLvl
} }
.keys .keys
.joinToString(" ") .joinToString(" ")
} }
fun reloadCommands() { fun reloadCommands() {

View File

@ -6,18 +6,18 @@ import matterlink.logger
import matterlink.stripColorIn import matterlink.stripColorIn
data class CustomCommand( data class CustomCommand(
val type: CommandType = CommandType.RESPONSE, val type: CommandType = CommandType.RESPONSE,
val execute: String? = null, val execute: String? = null,
val response: String? = null, val response: String? = null,
override val permLevel: Double = 0.0, override val permLevel: Double = 0.0,
override val help: String = "", override val help: String = "",
override val timeout: Int = 20, override val timeout: Int = 20,
val defaultCommand: Boolean? = null, val defaultCommand: Boolean? = null,
val execOp: Boolean? = null, val execOp: Boolean? = null,
val argumentsRegex: Regex? = null val argumentsRegex: Regex? = null
) : IBridgeCommand() { ) : IBridgeCommand() {
override fun execute(alias: String, user: String, env: CommandEnvironment, args: String): Boolean { override suspend fun execute(alias: String, user: String, env: CommandEnvironment, args: String): Boolean {
if (argumentsRegex != null) { if (argumentsRegex != null) {
logger.debug("testing '$args' against '${argumentsRegex.pattern}'") logger.debug("testing '$args' against '${argumentsRegex.pattern}'")
if (!argumentsRegex.matches(args)) { if (!argumentsRegex.matches(args)) {
@ -31,12 +31,14 @@ data class CustomCommand(
// uses a new commandsender for each use // uses a new commandsender for each use
val commandSender = instance.commandSenderFor(user, env, execOp ?: false) val commandSender = instance.commandSenderFor(user, env, execOp ?: false)
val cmd = execute?.lazyFormat(getReplacements(user, env, args))?.stripColorIn val cmd = execute?.lazyFormat(getReplacements(user, env, args))?.stripColorIn
?: return false ?: return false
commandSender.execute(cmd) || commandSender.reply.isNotEmpty() commandSender.execute(cmd) || commandSender.reply.isNotEmpty()
} }
CommandType.RESPONSE -> { CommandType.RESPONSE -> {
env.respond(response?.lazyFormat(getReplacements(user, env, args)) env.respond(
?: "", cause = "response to command: $alias") response?.lazyFormat(getReplacements(user, env, args))
?: "", cause = "response to command: $alias"
)
true true
} }
@ -60,34 +62,34 @@ data class CustomCommand(
val DEFAULT = CustomCommand() val DEFAULT = CustomCommand()
fun getReplacements(user: String, env: CommandEnvironment, args: String): Map<String, () -> String?> = mapOf( fun getReplacements(user: String, env: CommandEnvironment, args: String): Map<String, () -> String?> = mapOf(
"{uptime}" to instance::getUptimeAsString, "{uptime}" to instance::getUptimeAsString,
"{user}" to { user }, "{user}" to { user },
"{userid}" to { "{userid}" to {
when (env) { when (env) {
is CommandEnvironment.BridgeEnv -> env.userId is CommandEnvironment.BridgeEnv -> env.userId
else -> null else -> null
} }
}, },
"{uuid}" to { "{uuid}" to {
when (env) { when (env) {
is CommandEnvironment.BridgeEnv -> env.uuid.toString() is CommandEnvironment.BridgeEnv -> env.uuid.toString()
is CommandEnvironment.GameEnv -> env.uuid.toString() is CommandEnvironment.GameEnv -> env.uuid.toString()
} }
}, },
"{username}" to { "{username}" to {
when (env) { when (env) {
is CommandEnvironment.BridgeEnv -> env.uuid is CommandEnvironment.BridgeEnv -> env.uuid
is CommandEnvironment.GameEnv -> env.uuid is CommandEnvironment.GameEnv -> env.uuid
}?.let { instance.uuidToName(it) } }?.let { instance.uuidToName(it) }
}, },
"{platform}" to { "{platform}" to {
when (env) { when (env) {
is CommandEnvironment.BridgeEnv -> env.platform is CommandEnvironment.BridgeEnv -> env.platform
else -> null else -> null
} }
}, },
"{args}" to { args }, "{args}" to { args },
"{version}" to { instance.modVersion } "{version}" to { instance.modVersion }
) )
} }
} }

View File

@ -1,27 +1,24 @@
package matterlink.bridge.command package matterlink.bridge.command
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.cfg import matterlink.config.cfg
import matterlink.stripColorOut
object HelpCommand : IBridgeCommand() { object HelpCommand : IBridgeCommand() {
override val help: String = "Returns the help string for the given command. Syntax: help <command>" override val help: String = "Returns the help string for the given command. Syntax: help <command>"
override val permLevel: Double override val permLevel: Double
get() = cfg.command.defaultPermUnauthenticated get() = cfg.command.defaultPermUnauthenticated
override fun execute(alias: String, user: String, env: CommandEnvironment, args: String): Boolean { override suspend fun execute(alias: String, user: String, env: CommandEnvironment, args: String): Boolean {
val msg: String = when { val msg: String = when {
args.isEmpty() -> args.isEmpty() ->
"Available commands: ${BridgeCommandRegistry.getCommandList(IBridgeCommand.getPermLevel(env.uuid))}" "Available commands: ${BridgeCommandRegistry.getCommandList(IBridgeCommand.getPermLevel(env.uuid))}"
else -> args.split(" ", ignoreCase = false) else -> args.split(" ", ignoreCase = false)
.joinToString(separator = "\n") { .joinToString(separator = "\n") {
"$it: ${BridgeCommandRegistry.getHelpString(it)}" "$it: ${BridgeCommandRegistry.getHelpString(it)}"
} }
} }
env.respond( env.respond(
text = msg, text = msg,
cause = "Help Requested $args" cause = "Help Requested $args"
) )
return true return true
} }

View File

@ -8,7 +8,7 @@ import matterlink.handlers.TickHandler
import matterlink.instance import matterlink.instance
import matterlink.logger import matterlink.logger
import matterlink.stripColorOut import matterlink.stripColorOut
import java.util.* import java.util.UUID
abstract class IBridgeCommand { abstract class IBridgeCommand {
abstract val help: String abstract val help: String
@ -20,28 +20,30 @@ abstract class IBridgeCommand {
abstract val username: String? abstract val username: String?
data class BridgeEnv( data class BridgeEnv(
val name: String, val name: String,
val userId: String, val userId: String,
val platform: String, val platform: String,
override val uuid: UUID? val gateway: String,
override val uuid: UUID?
) : CommandEnvironment() { ) : CommandEnvironment() {
override val username: String? override val username: String?
get() = uuid?.let { instance.uuidToName(uuid) } get() = uuid?.let { instance.uuidToName(uuid) }
} }
data class GameEnv( data class GameEnv(
override val username: String, override val username: String,
override val uuid: UUID override val uuid: UUID
) : CommandEnvironment() ) : CommandEnvironment()
fun respond(text: String, cause: String = "") { suspend fun respond(text: String, cause: String = "") {
when (this) { when (this) {
is BridgeEnv -> { is BridgeEnv -> {
MessageHandlerInst.transmit( MessageHandlerInst.transmit(
ApiMessage( ApiMessage(
text = text.stripColorOut gateway = this.gateway,
), text = text.stripColorOut
cause = cause ),
cause = cause
) )
} }
is GameEnv -> { is GameEnv -> {
@ -70,7 +72,7 @@ abstract class IBridgeCommand {
* *
* @return consume message flag * @return consume message flag
*/ */
abstract fun execute(alias: String, user: String, env: CommandEnvironment, args: String): Boolean abstract suspend fun execute(alias: String, user: String, env: CommandEnvironment, args: String): Boolean
fun canExecute(uuid: UUID?): Boolean { fun canExecute(uuid: UUID?): Boolean {
logger.trace("canExecute this: $this uuid: $uuid permLevel: $permLevel") logger.trace("canExecute this: $this uuid: $uuid permLevel: $permLevel")

View File

@ -1,7 +1,5 @@
package matterlink.bridge.command package matterlink.bridge.command
import matterlink.stripColorOut
abstract class IMinecraftCommandSender(val user: String, val env: IBridgeCommand.CommandEnvironment, val op: Boolean) { abstract class IMinecraftCommandSender(val user: String, val env: IBridgeCommand.CommandEnvironment, val op: Boolean) {
/** /**
* @param cmdString The command to execute with its arguments * @param cmdString The command to execute with its arguments
@ -13,7 +11,7 @@ abstract class IMinecraftCommandSender(val user: String, val env: IBridgeCommand
val displayName = env.username ?: user val displayName = env.username ?: user
val accountName = when (env) { val accountName = when (env) {
is IBridgeCommand.CommandEnvironment.BridgeEnv -> "$user (id=${env.userId} platform=${env.platform}${env.uuid?.let { " uuid=$it" } is IBridgeCommand.CommandEnvironment.BridgeEnv -> "$user (id=${env.userId} platform=${env.platform}${env.uuid?.let { " uuid=$it" }
?: ""}${env.username?.let { " username=$it" } ?: ""})" ?: ""}${env.username?.let { " username=$it" } ?: ""})"
is IBridgeCommand.CommandEnvironment.GameEnv -> "$user (username=${env.username} uuid=${env.uuid})" is IBridgeCommand.CommandEnvironment.GameEnv -> "$user (username=${env.username} uuid=${env.uuid})"
} }
@ -37,10 +35,10 @@ abstract class IMinecraftCommandSender(val user: String, val env: IBridgeCommand
reply += text reply += text
} }
fun sendReply(cmdString: String) { suspend fun sendReply(cmdString: String) {
env.respond( env.respond(
text = reply.joinToString("\n"), text = reply.joinToString("\n"),
cause = "executed command: $cmdString" cause = "executed command: $cmdString"
) )
finished = true finished = true
} }

View File

@ -11,7 +11,7 @@ object RequestPermissionsCommand : IBridgeCommand() {
override val permLevel: Double override val permLevel: Double
get() = cfg.command.defaultPermAuthenticated get() = cfg.command.defaultPermAuthenticated
override fun execute(alias: String, user: String, env: CommandEnvironment, args: String): Boolean { override suspend fun execute(alias: String, user: String, env: CommandEnvironment, args: String): Boolean {
val uuid = env.uuid val uuid = env.uuid
if (uuid == null) { if (uuid == null) {
@ -23,8 +23,10 @@ object RequestPermissionsCommand : IBridgeCommand() {
val requestedLevelArg = argList.getOrNull(0) val requestedLevelArg = argList.getOrNull(0)
val requestedLevel = requestedLevelArg?.takeIf { it.isNotEmpty() }?.let { val requestedLevel = requestedLevelArg?.takeIf { it.isNotEmpty() }?.let {
it.toDoubleOrNull() ?: run { it.toDoubleOrNull() ?: run {
env.respond("cannot parse permlevel '$requestedLevelArg'\n" + env.respond(
syntax) "cannot parse permlevel '$requestedLevelArg'\n" +
syntax
)
return true return true
} }
} }
@ -33,7 +35,10 @@ object RequestPermissionsCommand : IBridgeCommand() {
val requestId = user.toLowerCase() val requestId = user.toLowerCase()
PermissionConfig.permissionRequests.put(requestId, PermissionRequest(uuid = uuid, user = user, nonce = nonce, powerlevel = requestedLevel)) PermissionConfig.permissionRequests.put(
requestId,
PermissionRequest(uuid = uuid, user = user, nonce = nonce, powerlevel = requestedLevel)
)
env.respond("please ask a op to accept your permission elevation with `/ml permAccept $requestId $nonce [permLevel]`") env.respond("please ask a op to accept your permission elevation with `/ml permAccept $requestId $nonce [permLevel]`")
return true return true

View File

@ -1,11 +1,6 @@
package matterlink.command package matterlink.command
import matterlink.bridge.MessageHandlerInst
import matterlink.bridge.command.BridgeCommandRegistry
import matterlink.config.IdentitiesConfig import matterlink.config.IdentitiesConfig
import matterlink.config.PermissionConfig
import matterlink.config.baseCfg
import matterlink.config.cfg
object CommandCoreAuth { object CommandCoreAuth {
val name = "auth" val name = "auth"
@ -28,17 +23,23 @@ object CommandCoreAuth {
val nonce = args.getOrNull(2)?.toUpperCase() ?: run { val nonce = args.getOrNull(2)?.toUpperCase() ?: run {
return "no code passed" return "no code passed"
} }
if(request.nonce != nonce) { if (request.nonce != nonce) {
return "nonce in request does not match" return "nonce in request does not match"
} }
if(request.username != user) { if (request.username != user) {
return "username in request does not match ${request.username} != $user" return "username in request does not match ${request.username} != $user"
} }
if(request.uuid != uuid) { if (request.uuid != uuid) {
return "uuid in request does not match ${request.uuid} != $uuid" return "uuid in request does not match ${request.uuid} != $uuid"
} }
IdentitiesConfig.add(request.uuid, request.username, request.platform, request.userid, "Accepted by $user") IdentitiesConfig.add(
request.uuid,
request.username,
request.platform,
request.userid,
"Accepted by $user"
)
IdentitiesConfig.authRequests.invalidate(requestId) IdentitiesConfig.authRequests.invalidate(requestId)
"${request.userid} on ${request.platform} is now identified as $user" "${request.userid} on ${request.platform} is now identified as $user"
@ -54,13 +55,13 @@ object CommandCoreAuth {
val nonce = args.getOrNull(2)?.toUpperCase() ?: run { val nonce = args.getOrNull(2)?.toUpperCase() ?: run {
return "no code passed" return "no code passed"
} }
if(request.nonce != nonce) { if (request.nonce != nonce) {
return "nonce in request does not match" return "nonce in request does not match"
} }
if(request.username != user) { if (request.username != user) {
return "username in request does not match ${request.username} != $user" return "username in request does not match ${request.username} != $user"
} }
if(request.uuid != uuid) { if (request.uuid != uuid) {
return "uuid in request does not match ${request.uuid} != $uuid" return "uuid in request does not match ${request.uuid} != $uuid"
} }

View File

@ -13,7 +13,7 @@ object CommandCoreML {
val usage = "ml <connect|disconnect|reload|permAccept>" val usage = "ml <connect|disconnect|reload|permAccept>"
fun execute(args: Array<String>, user: String, uuid: String?): String { suspend fun execute(args: Array<String>, user: String, uuid: String?): String {
val cmd = args[0].toLowerCase() val cmd = args[0].toLowerCase()
return when (cmd) { return when (cmd) {
@ -45,12 +45,12 @@ object CommandCoreML {
return "no code passed" return "no code passed"
} }
if (request.nonce != nonce) { if (request.nonce != nonce) {
return "nonce in request does not match" return "nonce in request does not match with $nonce"
} }
val powerLevelArg = args.getOrNull(2)?.toDoubleOrNull() val powerLevelArg = args.getOrNull(3)?.toDoubleOrNull()
val powerLevel = powerLevelArg ?: run { return "permLevel cannot be parsed" } val powerLevel = powerLevelArg ?: run { return "permLevel cannot be parsed: ${args.getOrNull(3)}" }
?: request.powerlevel ?: request.powerlevel
?: return "no permLevel provided" ?: return "no permLevel provided"
PermissionConfig.add(request.uuid, powerLevel, "${request.user} Authorized by $user") PermissionConfig.add(request.uuid, powerLevel, "${request.user} Authorized by $user")
PermissionConfig.permissionRequests.invalidate(requestId) PermissionConfig.permissionRequests.invalidate(requestId)
"added ${request.user} (uuid: ${request.uuid}) with power level: $powerLevel" "added ${request.user} (uuid: ${request.uuid}) with power level: $powerLevel"

View File

@ -4,9 +4,13 @@ import blue.endless.jankson.Jankson
import blue.endless.jankson.JsonObject import blue.endless.jankson.JsonObject
import blue.endless.jankson.impl.Marshaller import blue.endless.jankson.impl.Marshaller
import blue.endless.jankson.impl.SyntaxError import blue.endless.jankson.impl.SyntaxError
import matterlink.Area
import matterlink.bridge.MessageHandlerInst import matterlink.bridge.MessageHandlerInst
import matterlink.getOrDefault import matterlink.getOrDefault
import matterlink.getOrPutList
import matterlink.getReifiedOrDelete
import matterlink.logger import matterlink.logger
import matterlink.registerSerializer
import matterlink.registerTypeAdapter import matterlink.registerTypeAdapter
import matterlink.stackTraceString import matterlink.stackTraceString
import java.io.File import java.io.File
@ -20,376 +24,641 @@ data class BaseConfig(val rootDir: File) {
val configFile: File = cfgDirectory.resolve("matterlink.hjson") val configFile: File = cfgDirectory.resolve("matterlink.hjson")
init { init {
logger.info("Reading bridge blueprints... from {}", rootDir) logger.info("Reading bridge blueprints... from $rootDir")
baseCfg = this baseCfg = this
} }
data class MatterLinkConfig( data class MatterLinkConfig(
val connect: ConnectOptions = ConnectOptions(), val connect: ConnectOptions = ConnectOptions(),
val incoming: IncomingOptions = IncomingOptions(), val outgoingDefaults: DefaultSettingsOutgoing = DefaultSettingsOutgoing(),
val outgoing: OutgoingOptions = OutgoingOptions(), val incomingDefaults: DefaultSettingsIncoming = DefaultSettingsIncoming(),
val command: CommandOptions = CommandOptions(), val locations: List<Location> = listOf(
val update: UpdateOptions = UpdateOptions() Location(
label = "default",
gateway = "minecraft",
area = Area.Infinite(dimensions = listOf(-1, 0, 1), allDimensions = true),
outgoing = SettingsOutgoing(
plain = true,
action = true,
join = true,
leave = true,
advancement = true,
death = true,
broadcast = true,
status = true
),
incoming = SettingsIncoming(
plain = true,
action = true,
join_leave = true,
commands = true
)
)
),
val incoming: IncomingOptions = IncomingOptions(),
val outgoing: OutgoingOptions = OutgoingOptions(),
val command: CommandOptions = CommandOptions(),
val update: UpdateOptions = UpdateOptions()
) )
data class DefaultSettingsOutgoing(
val plain: Boolean = true,
val action: Boolean = true,
val join: Boolean = false,
val leave: Boolean = false,
val advancement: Boolean = false,
val death: Boolean = false,
val broadcast: Boolean = false,
val status: Boolean = false
)
data class SettingsOutgoing(
val plain: Boolean? = null,
val action: Boolean? = null,
val join: Boolean? = null,
val leave: Boolean? = null,
val advancement: Boolean? = null,
val death: Boolean? = null,
val broadcast: Boolean? = null,
val status: Boolean? = null,
val skip: List<String> = listOf()
)
data class DefaultSettingsIncoming(
val plain: Boolean = true,
val action: Boolean = true,
val join_leave: Boolean = true,
val commands: Boolean = true
)
data class SettingsIncoming(
val plain: Boolean? = null,
val action: Boolean? = null,
val join_leave: Boolean? = null,
val commands: Boolean? = null,
val skip: List<String> = listOf()
)
data class Location(
val label: String = "unlabeled",
val gateway: String = "",
val area: Area = Area.Infinite(),
val outgoing: SettingsOutgoing = SettingsOutgoing(),
val incoming: SettingsIncoming = SettingsIncoming()
)
data class CommandOptions( data class CommandOptions(
val prefix: Char = '!', val prefix: Char = '!',
val enable: Boolean = true, val enable: Boolean = true,
val authRequests: Boolean = true, val authRequests: Boolean = true,
val permisionRequests: Boolean = true, val permisionRequests: Boolean = true,
val defaultPermUnauthenticated: Double = 0.0, val defaultPermUnauthenticated: Double = 0.0,
val defaultPermAuthenticated: Double = 1.0 val defaultPermAuthenticated: Double = 1.0
) )
data class ConnectOptions( data class ConnectOptions(
val url: String = "http://localhost:4242", val url: String = "http://localhost:4242",
val authToken: String = "", val authToken: String = "",
val gateway: String = "minecraft", val autoConnect: Boolean = true,
val autoConnect: Boolean = true, val reconnectWait: Long = 500
val reconnectWait: Long = 500
) )
data class IncomingOptions( data class IncomingOptions(
val chat: String = "<{username}> {text}", val chat: String = "<{username}> {text}",
val joinPart: String = "§6-- {username} {text}", val joinPart: String = "§6-- {username} {text}",
val action: String = "§5* {username} {text}", val action: String = "§5* {username} {text}",
val stripColors: Boolean = true val stripColors: Boolean = true
) )
data class OutgoingOptions( data class OutgoingOptions(
val systemUser: String = "Server", val systemUser: String = "Server",
//outgoing toggles //outgoing toggles
val announceConnect: Boolean = true, val announceConnect: Boolean = true,
val announceDisconnect: Boolean = true, val announceDisconnect: Boolean = true,
val advancements: Boolean = true, val advancements: Boolean = true,
val stripColors: Boolean = true, val stripColors: Boolean = true,
val pasteEEKey: String = "", val pasteEEKey: String = "",
val inlineLimit: Int = 5, val inlineLimit: Int = 5,
val joinPart: JoinPartOptions = JoinPartOptions(), val joinPart: JoinPartOptions = JoinPartOptions(),
var avatar: AvatarOptions = AvatarOptions(), var avatar: AvatarOptions = AvatarOptions(),
val death: DeathOptions = DeathOptions() val death: DeathOptions = DeathOptions()
) )
data class DeathOptions( data class DeathOptions(
val enable: Boolean = true, val enable: Boolean = true,
val damageType: Boolean = true, val damageType: Boolean = true,
val damageTypeMapping: Map<String, Array<String>> = mapOf( val damageTypeMapping: Map<String, Array<String>> = mapOf(
"inFire" to arrayOf("\uD83D\uDD25"), //🔥 "inFire" to arrayOf("\uD83D\uDD25"), //🔥
"lightningBolt" to arrayOf("\uD83C\uDF29"), //🌩 "lightningBolt" to arrayOf("\uD83C\uDF29"), //🌩
"onFire" to arrayOf("\uD83D\uDD25"), //🔥 "onFire" to arrayOf("\uD83D\uDD25"), //🔥
"lava" to arrayOf("\uD83D\uDD25"), //🔥 "lava" to arrayOf("\uD83D\uDD25"), //🔥
"hotFloor" to arrayOf("♨️"), "hotFloor" to arrayOf("♨️"),
"inWall" to arrayOf(), "inWall" to arrayOf(),
"cramming" to arrayOf(), "cramming" to arrayOf(),
"drown" to arrayOf("\uD83C\uDF0A"), //🌊 "drown" to arrayOf("\uD83C\uDF0A"), //🌊
"starve" to arrayOf("\uD83D\uDC80"), //💀 "starve" to arrayOf("\uD83D\uDC80"), //💀
"cactus" to arrayOf("\uD83C\uDF35"), //🌵 "cactus" to arrayOf("\uD83C\uDF35"), //🌵
"fall" to arrayOf("\u2BEF"), //⯯️ "fall" to arrayOf("\u2BEF"), //⯯️
"flyIntoWall" to arrayOf("\uD83D\uDCA8"), //💨 "flyIntoWall" to arrayOf("\uD83D\uDCA8"), //💨
"outOfWorld" to arrayOf("\u2734"), //✴ "outOfWorld" to arrayOf("\u2734"), //✴
"generic" to arrayOf("\uD83D\uDC7B"), //👻 "generic" to arrayOf("\uD83D\uDC7B"), //👻
"magic" to arrayOf("", ""), "magic" to arrayOf("", ""),
"indirectMagic" to arrayOf("", ""), "indirectMagic" to arrayOf("", ""),
"wither" to arrayOf("\uD83D\uDD71"), //🕱 "wither" to arrayOf("\uD83D\uDD71"), //🕱
"anvil" to arrayOf(), "anvil" to arrayOf(),
"fallingBlock" to arrayOf(), "fallingBlock" to arrayOf(),
"dragonBreath" to arrayOf("\uD83D\uDC32"), //🐲 "dragonBreath" to arrayOf("\uD83D\uDC32"), //🐲
"fireworks" to arrayOf("\uD83C\uDF86"), //🎆 "fireworks" to arrayOf("\uD83C\uDF86"), //🎆
"mob" to arrayOf("\uD83D\uDC80"), //💀 "mob" to arrayOf("\uD83D\uDC80"), //💀
"player" to arrayOf("\uD83D\uDDE1"), //🗡 "player" to arrayOf("\uD83D\uDDE1"), //🗡
"arrow" to arrayOf("\uD83C\uDFF9"), //🏹 "arrow" to arrayOf("\uD83C\uDFF9"), //🏹
"thrown" to arrayOf("彡°"), "thrown" to arrayOf("彡°"),
"thorns" to arrayOf("\uD83C\uDF39"), //🌹 "thorns" to arrayOf("\uD83C\uDF39"), //🌹
"explosion" to arrayOf("\uD83D\uDCA3", "\uD83D\uDCA5"), //💣 💥 "explosion" to arrayOf("\uD83D\uDCA3", "\uD83D\uDCA5"), //💣 💥
"explosion.player" to arrayOf("\uD83D\uDCA3", "\uD83D\uDCA5") //💣 💥 "explosion.player" to arrayOf("\uD83D\uDCA3", "\uD83D\uDCA5"), //💣 💥
) "ieWireShock" to arrayOf("\uD83D\uDD0C", "\u26A1"), //🔌 ⚡
"immersiverailroading:hitByTrain" to arrayOf(
"\uD83D\uDE82",
"\uD83D\uDE83",
"\uD83D\uDE84",
"\uD83D\uDE85",
"\uD83D\uDE87",
"\uD83D\uDE88",
"\uD83D\uDE8A"
) //🚂 🚃 🚄 🚅 🚇 🚈 🚊
)
) )
data class AvatarOptions( data class AvatarOptions(
val enable: Boolean = true, val enable: Boolean = true,
val urlTemplate: String = "https://visage.surgeplay.com/head/512/{uuid}", val urlTemplate: String = "https://mc-heads.net/head/{uuid}",
// https://www.freepik.com/free-icon/right-arrow-angle-and-horizontal-down-line-code-signs_732795.htm val systemUserAvatar: String = ""
val systemUserAvatar: String = "https://image.freepik.com/free-icon/right-arrow-angle-and-horizontal-down-line-code-signs_318-53994.jpg"
) )
data class JoinPartOptions( data class JoinPartOptions(
val enable: Boolean = true, val enable: Boolean = true,
val joinServer: String = "{username:antiping} has connected to the platform", val joinServer: String = "{username:antiping} has connected to the server",
val partServer: String = "{username:antiping} has disconnected from the platform" val partServer: String = "{username:antiping} has disconnected from the server"
) )
data class UpdateOptions( data class UpdateOptions(
val enable: Boolean = true val enable: Boolean = true
) )
companion object { companion object {
val jankson = Jankson val jankson = Jankson
.builder() .builder()
.registerTypeAdapter { .registerTypeAdapter {
with(MatterLinkConfig()) {
MatterLinkConfig( MatterLinkConfig(
command = it.getOrDefault( command = it.getOrDefault(
"command", "command",
CommandOptions(), command,
"User commands" "User commands"
), ),
connect = it.getOrDefault( outgoingDefaults = it.getOrDefault(
"connect", "outgoingDefaults",
ConnectOptions(), outgoingDefaults,
"Connection Settings" "default settings for outgoing"
), ),
incoming = it.getOrDefault( incomingDefaults = it.getOrDefault(
"incoming", "incomingDefaults",
IncomingOptions(), incomingDefaults,
""" "default settings for incoming"
Gateway -> Server ),
Options all about receiving messages from the API locations = it.getOrPutList(
Formatting options: "locations",
Available variables: {username}, {text}, {gateway}, {channel}, {protocol}, {username:antiping} locations,
""".trimIndent() "list of fixed chat locations"
), ),
outgoing = it.getOrDefault( connect = it.getOrDefault(
"outgoing", "connect",
OutgoingOptions(), connect,
""" "Connection Settings"
Server -> Gateway ),
Options all about sending messages to the API incoming = it.getOrDefault(
""".trimIndent() "incoming",
), incoming,
update = it.getOrDefault( """
"update", Gateway -> Server
UpdateOptions(), Options all about receiving messages from the API
"Update Settings" Formatting options:
) Available variables: {username}, {text}, {gateway}, {channel}, {protocol}, {username:antiping}
""".trimIndent()
),
outgoing = it.getOrDefault(
"outgoing",
outgoing,
"""
Server -> Gateway
Options all about sending messages to the API
""".trimIndent()
),
update = it.getOrDefault(
"update",
update,
"Update Settings"
)
) )
} }
.registerTypeAdapter { }
with(CommandOptions()) { .registerTypeAdapter {
CommandOptions( with(DefaultSettingsOutgoing()) {
enable = it.getOrDefault( DefaultSettingsOutgoing(
"enable", plain = it.getOrDefault(
enable, "plain",
"Enable MC bridge commands" plain,
), "plain text messages"
prefix = it.getOrDefault( ),
"prefix", action = it.getOrDefault(
prefix, "action",
"Prefix for MC bridge commands. Accepts a single character (not alphanumeric or /)" action,
), "action messages"
authRequests = it.getOrDefault( ),
"authRequests", join = it.getOrDefault(
authRequests, "join",
"Enable the 'auth' command for linking chat accounts to uuid / ingame account" join,
), "handle join event"
permisionRequests = it.getOrDefault( ),
"permisionRequests", leave = it.getOrDefault(
authRequests, "leave",
"Enable the 'request' command for requestion permissions from chat" leave,
), "handle leave events"
defaultPermUnauthenticated = it.getOrDefault( ),
"defaultPermUnauthenticated", advancement = it.getOrDefault(
defaultPermUnauthenticated, "advancement",
"default permission level for unauthenticated players" advancement,
), "handle advancement events"
defaultPermAuthenticated = it.getOrDefault( ),
"defaultPermAuthenticated", death = it.getOrDefault(
defaultPermAuthenticated, "death",
"default permission level for players that hve linked their accounts" death,
) "handle death events"
),
broadcast = it.getOrDefault(
"broadcast",
broadcast,
"handle broadcast command"
),
status = it.getOrDefault(
"status",
status,
"handles tatus updates"
) )
} )
} }
.registerTypeAdapter { }
with(ConnectOptions()) { .registerTypeAdapter {
ConnectOptions( with(SettingsOutgoing()) {
url = it.getOrDefault( SettingsOutgoing(
"url", plain = it.getReifiedOrDelete("plain", "transmit join events"),
url, action = it.getReifiedOrDelete("action", "transmit join events"),
"The URL or IP address of the bridge platform" join = it.getReifiedOrDelete("join", "transmit join events"),
), leave = it.getReifiedOrDelete("leave", "transmit leave events"),
authToken = it.getOrDefault( advancement = it.getReifiedOrDelete("advancement", "transmit advancements"),
"authToken", death = it.getReifiedOrDelete("death", "transmit death messages"),
authToken, broadcast = it.getReifiedOrDelete("say", "transmit broadcasts"),
"Auth token used to connect to the bridge platform" status = it.getReifiedOrDelete("status", "transmit status updates"),
), skip = it.getOrPutList(
gateway = it.getOrDefault( "skip",
"gateway", skip,
gateway, "list of other locations to ignore after handling this"
"MatterBridge gateway"
),
autoConnect = it.getOrDefault(
"autoConnect",
autoConnect,
"Connect the relay on startup"
)
) )
} )
} }
.registerTypeAdapter { }
with(IncomingOptions()) {
IncomingOptions( .registerTypeAdapter {
chat = it.getOrDefault( with(DefaultSettingsIncoming()) {
"chat", DefaultSettingsIncoming(
chat, plain = it.getOrDefault(
"Generic chat event, just talking" "plain",
), plain,
joinPart = it.getOrDefault( "plain text messages"
"joinPart", ),
joinPart, action = it.getOrDefault(
"Join and part events from other gateways" "action",
), action,
action = it.getOrDefault( "action messages"
"action", ),
action, join_leave = it.getOrDefault(
"User actions (/me) sent by users from other gateways" "join_leave",
), join_leave,
stripColors = it.getOrDefault( "handle join/leave event"
"stripColors", ),
stripColors, commands = it.getOrDefault(
"strip colors from incoming text" "commands",
) join_leave,
"receive commands"
) )
} )
} }
.registerTypeAdapter { }
with(OutgoingOptions()) { .registerTypeAdapter {
OutgoingOptions( with(SettingsIncoming()) {
systemUser = it.getOrDefault( SettingsIncoming(
"systemUser", plain = it.getReifiedOrDelete("plain", "transmit join events"),
systemUser, action = it.getReifiedOrDelete("action", "transmit join events"),
"Name of the platform user (used by death and advancement messages and the /say command)" join_leave = it.getReifiedOrDelete("join_leave", "transmit join_leave events"),
), commands = it.getReifiedOrDelete("commands", "receive commands"),
advancements = it.getOrDefault( skip = it.getOrPutList(
"advancements", "skip",
advancements, skip,
"Relay player achievements / advancements" "list of other locations to ignore after handling this"
),
announceConnect = it.getOrDefault(
"announceConnect",
announceConnect,
"announce successful connection to the gateway"
),
announceDisconnect = it.getOrDefault(
"announceDisconnect",
announceConnect,
"announce intention to disconnect / reconnect"
),
stripColors = it.getOrDefault(
"stripColors",
stripColors,
"strip colors from nicknames and messages"
),
pasteEEKey = it.getOrDefault(
"pasteEEKey",
pasteEEKey,
"paste.ee api key, leave empty to use application default"
),
inlineLimit = it.getOrDefault(
"inlineLimit",
inlineLimit,
"messages with more lines than this will get shortened via paste.ee"
),
death = it.getOrDefault(
"death",
DeathOptions(),
"Death messages settings"
),
avatar = it.getOrDefault(
"avatar",
AvatarOptions(),
"Avatar options"
),
joinPart = it.getOrDefault(
"joinPart",
JoinPartOptions(),
"relay join and part messages to the gatway"
)
) )
} )
} }
.registerTypeAdapter { jsonObj -> }
with(DeathOptions()) { .registerTypeAdapter {
DeathOptions( with(Location()) {
enable = jsonObj.getOrDefault( Location(
"enable", label = it.getOrDefault(
enable, "label",
"Relay player death messages" label,
), "location label for identification"
damageType = jsonObj.getOrDefault( ),
"damageType", gateway = it.getOrDefault(
damageType, "gateway",
"Enable Damage type symbols on death messages" gateway,
), "matterbridge gateway identifier"
damageTypeMapping = (jsonObj.getObject("damageTypeMapping") ),
?: Marshaller.getFallback().serialize(damageTypeMapping) as JsonObject) area = Area.parse(it.getObject("area") ?: JsonObject()),
.let { outgoing = it.getOrDefault(
jsonObj.setComment( "outgoing",
"damageTypMapping", outgoing,
"Damage type mapping for death cause" "Location outgoing settings"
) ),
it.mapValues { (key, element) -> incoming = it.getOrDefault(
it.getOrDefault(key, damageTypeMapping[key] ?: emptyArray(), key) "incoming",
.apply { it[key] }.apply { incoming,
jsonObj["damageTypeMapping"] = it "incoming settings"
} )
} )
}
}
.registerTypeAdapter {
with(CommandOptions()) {
CommandOptions(
enable = it.getOrDefault(
"enable",
enable,
"Enable MC bridge commands"
),
prefix = it.getOrDefault(
"prefix",
prefix,
"Prefix for MC bridge commands. Accepts a single character (not alphanumeric or /)"
),
authRequests = it.getOrDefault(
"authRequests",
authRequests,
"Enable the 'auth' command for linking chat accounts to uuid / ingame account"
),
permisionRequests = it.getOrDefault(
"permisionRequests",
authRequests,
"Enable the 'request' command for requestion permissions from chat"
),
defaultPermUnauthenticated = it.getOrDefault(
"defaultPermUnauthenticated",
defaultPermUnauthenticated,
"default permission level for unauthenticated players"
),
defaultPermAuthenticated = it.getOrDefault(
"defaultPermAuthenticated",
defaultPermAuthenticated,
"default permission level for players that hve linked their accounts"
)
)
}
}
.registerTypeAdapter {
with(ConnectOptions()) {
ConnectOptions(
url = it.getOrDefault(
"url",
url,
"The URL or IP address of the bridge platform"
),
authToken = it.getOrDefault(
"authToken",
authToken,
"Auth token used to connect to the bridge platform"
),
autoConnect = it.getOrDefault(
"autoConnect",
autoConnect,
"Connect the relay on startup"
),
reconnectWait = it.getOrDefault(
"reconnectWait",
reconnectWait,
"base delay in milliseconds between attempting reconnects"
)
)
}
}
.registerTypeAdapter {
with(IncomingOptions()) {
IncomingOptions(
chat = it.getOrDefault(
"chat",
chat,
"Generic chat event, just talking"
),
joinPart = it.getOrDefault(
"joinPart",
joinPart,
"Join and part events from other gateways"
),
action = it.getOrDefault(
"action",
action,
"User actions (/me) sent by users from other gateways"
),
stripColors = it.getOrDefault(
"stripColors",
stripColors,
"strip colors from incoming text"
)
)
}
}
.registerTypeAdapter {
with(OutgoingOptions()) {
OutgoingOptions(
systemUser = it.getOrDefault(
"systemUser",
systemUser,
"Name of the platform user (used by death and advancement messages and the /say command)"
),
advancements = it.getOrDefault(
"advancements",
advancements,
"Relay player achievements / advancements"
),
announceConnect = it.getOrDefault(
"announceConnect",
announceConnect,
"announce successful connection to the gateway"
),
announceDisconnect = it.getOrDefault(
"announceDisconnect",
announceConnect,
"announce intention to disconnect / reconnect"
),
stripColors = it.getOrDefault(
"stripColors",
stripColors,
"strip colors from nicknames and messages"
),
pasteEEKey = it.getOrDefault(
"pasteEEKey",
pasteEEKey,
"paste.ee api key, leave empty to use application default"
),
inlineLimit = it.getOrDefault(
"inlineLimit",
inlineLimit,
"messages with more lines than this will get shortened via paste.ee"
),
death = it.getOrDefault(
"death",
DeathOptions(),
"Death messages settings"
),
avatar = it.getOrDefault(
"avatar",
AvatarOptions(),
"Avatar options"
),
joinPart = it.getOrDefault(
"joinPart",
JoinPartOptions(),
"relay join and part messages to the gatway"
)
)
}
}
.registerTypeAdapter { jsonObj ->
with(DeathOptions()) {
DeathOptions(
enable = jsonObj.getOrDefault(
"enable",
enable,
"Relay player death messages"
),
damageType = jsonObj.getOrDefault(
"damageType",
damageType,
"Enable Damage type symbols on death messages"
),
damageTypeMapping = (jsonObj.getObject("damageTypeMapping")
?: Marshaller.getFallback().serialize(damageTypeMapping) as JsonObject)
.let {
jsonObj.setComment(
"damageTypMapping",
"Damage type mapping for death cause"
)
it.mapValues { (key, _) ->
it.getOrDefault(key, damageTypeMapping[key] ?: emptyArray(), key)
.apply { it[key] }.apply {
jsonObj["damageTypeMapping"] = it
} }
) }
} }
)
} }
.registerTypeAdapter { }
with(AvatarOptions()) { .registerTypeAdapter {
AvatarOptions( with(AvatarOptions()) {
enable = it.getOrDefault( AvatarOptions(
"enable", enable = it.getOrDefault(
enable, "enable",
"enable ingame avatar" enable,
), "enable ingame avatar"
urlTemplate = it.getOrDefault( ),
"urlTemplate", urlTemplate = it.getOrDefault(
urlTemplate, "urlTemplate",
"template for constructing the user avatar url using the uuid" urlTemplate,
) "template for constructing the user avatar url using the uuid"
),
systemUserAvatar = it.getOrDefault(
"systemUserAvatar",
systemUserAvatar,
"avatar url for the system user"
) )
} )
} }
.registerTypeAdapter { }
with(JoinPartOptions()) { .registerTypeAdapter {
JoinPartOptions( with(JoinPartOptions()) {
enable = it.getOrDefault( JoinPartOptions(
"enable", enable = it.getOrDefault(
enable, "enable",
"Relay when a player joins / parts the game" + enable,
"\nany receiving end still needs to be configured with showJoinPart = true" + "Relay when a player joins / parts the game" +
"\nto display the messages" "\nany receiving end still needs to be configured with showJoinPart = true" +
), "\nto display the messages"
joinServer = it.getOrDefault( ),
"joinServer", joinServer = it.getOrDefault(
joinServer, "joinServer",
"user join message sent to other gateways, available variables: {username}, {username:antiping}" joinServer,
), "user join message sent to other gateways, available variables: {username}, {username:antiping}"
partServer = it.getOrDefault( ),
"partServer", partServer = it.getOrDefault(
partServer, "partServer",
"user part message sent to other gateways, available variables: {username}, {username:antiping}" partServer,
) "user part message sent to other gateways, available variables: {username}, {username:antiping}"
) )
} )
} }
.registerTypeAdapter { }
with(UpdateOptions()) { .registerTypeAdapter {
UpdateOptions( with(UpdateOptions()) {
enable = it.getOrDefault( UpdateOptions(
"enable", enable = it.getOrDefault(
enable, "enable",
"Enable Update checking" enable,
) "Enable Update checking"
) )
} )
} }
.build() }
.registerSerializer { locationSettings: SettingsOutgoing, marshaller: Marshaller ->
val jsonObject = JsonObject()
locationSettings.plain?.let {
jsonObject["plain"] = marshaller.serialize(it)
}
locationSettings.action?.let {
jsonObject["action"] = marshaller.serialize(it)
}
locationSettings.join?.let {
jsonObject["join"] = marshaller.serialize(it)
}
locationSettings.leave?.let {
jsonObject["leave"] = marshaller.serialize(it)
}
locationSettings.advancement?.let {
jsonObject["advancement"] = marshaller.serialize(it)
}
locationSettings.death?.let {
jsonObject["death"] = marshaller.serialize(it)
}
locationSettings.broadcast?.let {
jsonObject["broadcast"] = marshaller.serialize(it)
}
locationSettings.status?.let {
jsonObject["status"] = marshaller.serialize(it)
}
locationSettings.skip.let {
jsonObject["skip"] = marshaller.serialize(it)
}
jsonObject
}
.build()!!
} }
fun load(): MatterLinkConfig { fun load(): MatterLinkConfig {
@ -417,15 +686,15 @@ data class BaseConfig(val rootDir: File) {
logger.error("error parsing config: ${e.completeMessage} ") logger.error("error parsing config: ${e.completeMessage} ")
logger.error(e.stackTraceString) logger.error(e.stackTraceString)
cfgDirectory.resolve("error.matterlink.hjson").writeText(jsonObject.toJson(false, true)) cfgDirectory.resolve("error.matterlink.hjson").writeText(jsonObject.toJson(false, true))
MatterLinkConfig() if (::cfg.isInitialized) cfg else MatterLinkConfig()
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
logger.error(e.stackTraceString) logger.error(e.stackTraceString)
cfgDirectory.resolve("error.matterlink.hjson").writeText(jsonObject.toJson(false, true)) cfgDirectory.resolve("error.matterlink.hjson").writeText(jsonObject.toJson(false, true))
MatterLinkConfig() if (::cfg.isInitialized) cfg else MatterLinkConfig()
} catch (e: NullPointerException) { } catch (e: NullPointerException) {
logger.error("error loading config: ${e.stackTraceString}") logger.error("error loading config: ${e.stackTraceString}")
cfgDirectory.resolve("error.matterlink.hjson").writeText(jsonObject.toJson(false, true)) cfgDirectory.resolve("error.matterlink.hjson").writeText(jsonObject.toJson(false, true))
MatterLinkConfig() if (::cfg.isInitialized) cfg else MatterLinkConfig()
} }
// val defaultJsonObject = jankson.load("{}") // val defaultJsonObject = jankson.load("{}")
@ -434,7 +703,6 @@ data class BaseConfig(val rootDir: File) {
MessageHandlerInst.config.url = tmpCfg.connect.url MessageHandlerInst.config.url = tmpCfg.connect.url
MessageHandlerInst.config.token = tmpCfg.connect.authToken MessageHandlerInst.config.token = tmpCfg.connect.authToken
MessageHandlerInst.config.gateway = tmpCfg.connect.gateway
MessageHandlerInst.config.reconnectWait = tmpCfg.connect.reconnectWait MessageHandlerInst.config.reconnectWait = tmpCfg.connect.reconnectWait
MessageHandlerInst.config.systemUser = tmpCfg.outgoing.systemUser MessageHandlerInst.config.systemUser = tmpCfg.outgoing.systemUser

View File

@ -4,9 +4,12 @@ import blue.endless.jankson.Jankson
import blue.endless.jankson.JsonObject import blue.endless.jankson.JsonObject
import blue.endless.jankson.JsonPrimitive import blue.endless.jankson.JsonPrimitive
import blue.endless.jankson.impl.SyntaxError import blue.endless.jankson.impl.SyntaxError
import matterlink.*
import matterlink.bridge.command.CommandType import matterlink.bridge.command.CommandType
import matterlink.bridge.command.CustomCommand import matterlink.bridge.command.CustomCommand
import matterlink.getOrDefault
import matterlink.logger
import matterlink.registerPrimitiveTypeAdapter
import matterlink.registerTypeAdapter
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
@ -17,89 +20,89 @@ object CommandConfig {
private val configFile: File = baseCfg.cfgDirectory.resolve("commands.hjson") private val configFile: File = baseCfg.cfgDirectory.resolve("commands.hjson")
private val default: DefaultCommands = mapOf( private val default: DefaultCommands = mapOf(
"tps" to ("""Your run off the mill tps commands, change it to /sampler tps or /cofh tps if you like "tps" to ("""Your run off the mill tps commands, change it to /sampler tps or /cofh tps if you like
|make sure to disable defaultCommand if you want your edits to have any effect |make sure to disable defaultCommand if you want your edits to have any effect
""".trimMargin() """.trimMargin()
to CustomCommand( to CustomCommand(
type = CommandType.EXECUTE, type = CommandType.EXECUTE,
execute = "forge tps", execute = "forge tps",
help = "Print platform tps", help = "Print platform tps",
timeout = 200, timeout = 200,
defaultCommand = true defaultCommand = true
)), )),
"list" to ("lists all the players, this is just a straight pass-through" "list" to ("lists all the players, this is just a straight pass-through"
to CustomCommand( to CustomCommand(
type = CommandType.EXECUTE, type = CommandType.EXECUTE,
execute = "list", execute = "list",
help = "List online players", help = "List online players",
defaultCommand = true defaultCommand = true
)), )),
"seed" to ("another straight pass-through" "seed" to ("another straight pass-through"
to CustomCommand( to CustomCommand(
type = CommandType.EXECUTE, type = CommandType.EXECUTE,
execute = "seed", execute = "seed",
help = "Print platform world seed", help = "Print platform world seed",
defaultCommand = true defaultCommand = true
)), )),
"uptime" to ("this is a reponse command, it uses the uptime function, time since the mod was first loaded" "uptime" to ("this is a reponse command, it uses the uptime function, time since the mod was first loaded"
to CustomCommand( to CustomCommand(
type = CommandType.RESPONSE, type = CommandType.RESPONSE,
response = "{uptime}", response = "{uptime}",
help = "Print platform uptime", help = "Print platform uptime",
defaultCommand = true defaultCommand = true
)), )),
"whoami" to ("this shows you some of the other response macros" "whoami" to ("this shows you some of the other response macros"
to CustomCommand( to CustomCommand(
type = CommandType.RESPONSE, type = CommandType.RESPONSE,
response = "name: `{user}` userid: `{userid}` platform: `{platform}` username: `{username}` uuid: `{uuid}`", response = "name: `{user}` userid: `{userid}` platform: `{platform}` username: `{username}` uuid: `{uuid}`",
help = "Print debug user data", help = "Print debug user data",
timeout = 200, timeout = 200,
defaultCommand = true defaultCommand = true
)), )),
"version" to ("are you out of date huh ?" "version" to ("are you out of date huh ?"
to CustomCommand( to CustomCommand(
type = CommandType.RESPONSE, type = CommandType.RESPONSE,
response = "{version}", response = "{version}",
help = "are you out of date huh ?", help = "are you out of date huh ?",
timeout = 200, timeout = 200,
defaultCommand = true defaultCommand = true
)), )),
"exec" to ("this uses arguments in a passed-through command, you could restrict the arguments with a regex" "exec" to ("this uses arguments in a passed-through command, you could restrict the arguments with a regex"
to CustomCommand( to CustomCommand(
type = CommandType.EXECUTE, type = CommandType.EXECUTE,
execute = "{args}", execute = "{args}",
argumentsRegex = ".*".toRegex(), argumentsRegex = ".*".toRegex(),
permLevel = 50.0, permLevel = 50.0,
help = "Execute any command as OP, be careful with this one", help = "Execute any command as OP, be careful with this one",
execOp = true, execOp = true,
defaultCommand = true defaultCommand = true
)) ))
) )
val commands: CommandMap = hashMapOf() val commands: CommandMap = hashMapOf()
fun loadFile() { fun loadFile() {
val jankson = Jankson val jankson = Jankson
.builder() .builder()
.registerTypeAdapter { jsonObj -> .registerTypeAdapter { jsonObj ->
with(CustomCommand.DEFAULT) { with(CustomCommand.DEFAULT) {
CustomCommand( CustomCommand(
type = jsonObj.get(CommandType::class.java, "type") ?: type, type = jsonObj.get(CommandType::class.java, "type") ?: type,
execute = jsonObj.get(String::class.java, "execute") ?: execute, execute = jsonObj.get(String::class.java, "execute") ?: execute,
response = jsonObj.get(String::class.java, "response") ?: response, response = jsonObj.get(String::class.java, "response") ?: response,
permLevel = jsonObj.get(Double::class.java, "permLevel") ?: permLevel, permLevel = jsonObj.get(Double::class.java, "permLevel") ?: permLevel,
help = jsonObj.get(String::class.java, "help") ?: help, help = jsonObj.get(String::class.java, "help") ?: help,
timeout = jsonObj.get(Int::class.java, "timeout") ?: timeout, timeout = jsonObj.get(Int::class.java, "timeout") ?: timeout,
defaultCommand = jsonObj.get(Boolean::class.java, "defaultCommand") ?: defaultCommand, defaultCommand = jsonObj.get(Boolean::class.java, "defaultCommand") ?: defaultCommand,
execOp = jsonObj.get(Boolean::class.java, "execOp") ?: execOp, execOp = jsonObj.get(Boolean::class.java, "execOp") ?: execOp,
argumentsRegex = jsonObj.get(Regex::class.java, "argumentsRegex") ?: argumentsRegex argumentsRegex = jsonObj.get(Regex::class.java, "argumentsRegex") ?: argumentsRegex
) )
}
} }
.registerPrimitiveTypeAdapter { }
it.toString().toRegex() .registerPrimitiveTypeAdapter {
} it.toString().toRegex()
.build() }
.build()
jankson.marshaller.registerSerializer(Regex::class.java) { regex, _ -> jankson.marshaller.registerSerializer(Regex::class.java) { regex, _ ->
JsonPrimitive(regex.pattern) JsonPrimitive(regex.pattern)

View File

@ -11,34 +11,34 @@ import matterlink.logger
import matterlink.stackTraceString import matterlink.stackTraceString
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.util.* import java.util.UUID
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
typealias IdentMap = Map<String, Map<String, List<String>>> typealias IdentMap = Map<String, Map<String, List<String>>>
data class AuthRequest( data class AuthRequest(
val username: String, val username: String,
val uuid: String, val uuid: String,
val nonce: String, val nonce: String,
val platform: String, val platform: String,
val userid: String val userid: String
) )
object IdentitiesConfig { object IdentitiesConfig {
val authRequests: Cache<String, AuthRequest> = CacheBuilder.newBuilder() val authRequests: Cache<String, AuthRequest> = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) .expireAfterWrite(10, TimeUnit.MINUTES)
.build() .build()
private val jankson = Jankson private val jankson = Jankson
.builder() .builder()
.build() .build()
private val configFile: File = baseCfg.cfgDirectory.resolve("identities.hjson") private val configFile: File = baseCfg.cfgDirectory.resolve("identities.hjson")
private val default = mapOf( private val default = mapOf(
("edd31c45-b095-49c5-a9f5-59cec4cfed8c" to mapOf( ("edd31c45-b095-49c5-a9f5-59cec4cfed8c" to mapOf(
"discord.game" to (listOf("112228624366575616") to "discord id") "discord.game" to (listOf("112228624366575616") to "discord id")
) to "username: NikkyAi") ) to "username: NikkyAi")
) )
var idents: IdentMap = mapOf() var idents: IdentMap = mapOf()

View File

@ -6,34 +6,33 @@ import blue.endless.jankson.impl.SyntaxError
import com.google.common.cache.Cache import com.google.common.cache.Cache
import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheBuilder
import matterlink.getReified import matterlink.getReified
import matterlink.instance
import matterlink.logger import matterlink.logger
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.util.* import java.util.UUID
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
typealias PermissionMap = Map<String, Double> typealias PermissionMap = Map<String, Double>
data class PermissionRequest( data class PermissionRequest(
val uuid: UUID, val uuid: UUID,
val user: String, val user: String,
val nonce: String, val nonce: String,
val powerlevel: Double? = null val powerlevel: Double? = null
) )
object PermissionConfig { object PermissionConfig {
val permissionRequests: Cache<String, PermissionRequest> = CacheBuilder.newBuilder() val permissionRequests: Cache<String, PermissionRequest> = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) .expireAfterWrite(10, TimeUnit.MINUTES)
.build() .build()
private val jankson = Jankson private val jankson = Jankson
.builder() .builder()
.build() .build()
private val configFile: File = baseCfg.cfgDirectory.resolve("permissions.hjson") private val configFile: File = baseCfg.cfgDirectory.resolve("permissions.hjson")
private val default = mapOf( private val default = mapOf(
"edd31c45-b095-49c5-a9f5-59cec4cfed8c" to 9000.0 to "Superuser" "edd31c45-b095-49c5-a9f5-59cec4cfed8c" to 9000.0 to "Superuser"
) )
val perms: PermissionMap = mutableMapOf() val perms: PermissionMap = mutableMapOf()

View File

@ -1,35 +1,38 @@
package matterlink.handlers package matterlink.handlers
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.bridge.command.BridgeCommandRegistry import matterlink.bridge.command.BridgeCommandRegistry
import matterlink.config.cfg
import matterlink.instance
import matterlink.logger import matterlink.logger
import matterlink.stripColorOut import java.util.UUID
import java.util.*
object ChatProcessor { object ChatProcessor {
/** /**
* @return cancel message flag * @return cancel message flag
*/ */
fun sendToBridge(user: String, msg: String, event: String, uuid: UUID? = null): Boolean { suspend fun sendToBridge(
user: String,
msg: String,
x: Int,
y: Int,
z: Int,
dimension: Int?,
event: ChatEvent,
uuid: UUID? = null
): Boolean {
//TODO: pass message to Locations
logger.info("position: $x $y $z dimension: $dimension")
val message = msg.trim() val message = msg.trim()
if(uuid != null && BridgeCommandRegistry.handleCommand(message, user, uuid)) return true if (uuid != null && BridgeCommandRegistry.handleCommand(message, user, uuid)) return true
when { when {
message.isNotBlank() -> MessageHandlerInst.transmit( message.isNotBlank() -> LocationHandler.sendToLocations(
ApiMessage( user = user,
username = user.stripColorOut, msg = message,
text = message.stripColorOut, x = x, y = y, z = z, dimension = dimension,
event = event event = event,
).apply { cause = "Message from $user",
if(cfg.outgoing.avatar.enable) { uuid = uuid
if(uuid != null)
avatar = cfg.outgoing.avatar.urlTemplate.replace("{uuid}", uuid.toString())
}
},
cause = "Message from $user"
) )
else -> logger.warn("WARN: dropped blank message by '$user'") else -> logger.warn("WARN: dropped blank message by '$user'")
} }
return false return false

View File

@ -1,29 +1,35 @@
package matterlink.handlers package matterlink.handlers
import matterlink.antiping import matterlink.antiping
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.cfg import matterlink.config.cfg
import matterlink.stripColorOut import matterlink.stripColorOut
import java.util.* import java.util.Random
object DeathHandler { object DeathHandler {
private val random = Random() private val random = Random()
fun handleDeath( suspend fun handleDeath(
player: String, player: String,
deathMessage: String, deathMessage: String,
damageType: String damageType: String,
x: Int, y: Int, z: Int,
dimension: Int
) { ) {
if (cfg.outgoing.death.enable) { if (cfg.outgoing.death.enable) {
var msg = deathMessage.stripColorOut.replace(player, player.stripColorOut.antiping) var msg = deathMessage.stripColorOut.replace(player, player.stripColorOut.antiping)
if (cfg.outgoing.death.damageType) { if (cfg.outgoing.death.damageType) {
val emojis = cfg.outgoing.death.damageTypeMapping[damageType] val emojis = cfg.outgoing.death.damageTypeMapping[damageType]
?: arrayOf("\uD83D\uDC7B unknown type '$damageType'") ?: arrayOf("\uD83D\uDC7B unknown type '$damageType'")
val damageEmoji = emojis[random.nextInt(emojis.size)] val damageEmoji = emojis[random.nextInt(emojis.size)]
msg += " $damageEmoji" msg += " $damageEmoji"
} }
MessageHandlerInst.transmit(ApiMessage(text = msg), cause = "Death Event of $player") LocationHandler.sendToLocations(
msg = msg,
x = x, y = y, z = z, dimension = dimension,
event = ChatEvent.DEATH,
cause = "Death Event of $player",
systemuser = true
)
} }
} }
} }

View File

@ -1,46 +1,51 @@
package matterlink.handlers package matterlink.handlers
import matterlink.antiping import matterlink.antiping
import matterlink.api.ApiMessage
import matterlink.api.ApiMessage.Companion.JOIN_LEAVE
import matterlink.bridge.MessageHandlerInst
import matterlink.config.cfg import matterlink.config.cfg
import matterlink.mapFormat import matterlink.mapFormat
import matterlink.stripColorOut import matterlink.stripColorOut
object JoinLeaveHandler { object JoinLeaveHandler {
fun handleJoin(player: String) { suspend fun handleJoin(
player: String,
x: Int, y: Int, z: Int,
dimension: Int
) {
if (cfg.outgoing.joinPart.enable) { if (cfg.outgoing.joinPart.enable) {
val msg = cfg.outgoing.joinPart.joinServer.mapFormat( val msg = cfg.outgoing.joinPart.joinServer.mapFormat(
mapOf( mapOf(
"{username}" to player.stripColorOut, "{username}" to player.stripColorOut,
"{username:antiping}" to player.stripColorOut.antiping "{username:antiping}" to player.stripColorOut.antiping
) )
) )
MessageHandlerInst.transmit( LocationHandler.sendToLocations(
ApiMessage( msg = msg,
text = msg, x = x, y = y, z = z, dimension = dimension,
event = JOIN_LEAVE event = ChatEvent.JOIN,
), systemuser = true,
cause = "$player joined" cause = "$player joined"
) )
} }
} }
fun handleLeave(player: String) { suspend fun handleLeave(
player: String,
x: Int, y: Int, z: Int,
dimension: Int
) {
if (cfg.outgoing.joinPart.enable) { if (cfg.outgoing.joinPart.enable) {
val msg = cfg.outgoing.joinPart.partServer.mapFormat( val msg = cfg.outgoing.joinPart.partServer.mapFormat(
mapOf( mapOf(
"{username}" to player.stripColorOut, "{username}" to player.stripColorOut,
"{username:antiping}" to player.stripColorOut.antiping "{username:antiping}" to player.stripColorOut.antiping
) )
) )
MessageHandlerInst.transmit( LocationHandler.sendToLocations(
ApiMessage( msg = msg,
text = msg, x = x, y = y, z = z, dimension = dimension,
event = JOIN_LEAVE event = ChatEvent.JOIN,
), systemuser = true,
cause = "$player left" cause = "$player left"
) )
} }
} }

View File

@ -0,0 +1,110 @@
package matterlink.handlers
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.cfg
import matterlink.logger
import matterlink.stripColorOut
import java.util.UUID
enum class ChatEvent {
PLAIN, ACTION, DEATH, JOIN, LEAVE, ADVANCEMENT, BROADCAST, STATUS
}
object LocationHandler {
suspend fun sendToLocations(
user: String = cfg.outgoing.systemUser,
msg: String,
x: Int, y: Int, z: Int,
dimension: Int?,
event: ChatEvent,
systemuser: Boolean = false,
uuid: UUID? = null,
cause: String
): Boolean {
val defaults = cfg.outgoingDefaults
var handled = false
val skips = mutableSetOf<String>()
logger.debug("locations: ${cfg.locations.map { it.label }}")
for (location in cfg.locations) {
val label = location.label
if (skips.contains(label)) {
logger.debug("skipping $label (contained in in $skips)")
continue
}
if (!location.area.testForDim(dimension)) {
logger.debug("location: $label dropped message '$msg' from $user due to mismatched dimension")
continue
}
if (!location.area.testInBounds(x, y, z)) {
logger.debug("location: $label dropped message '$msg' from $user out of coordinate bounds")
continue
}
val matchesEvent = when (event) {
ChatEvent.PLAIN -> location.outgoing.plain ?: defaults.plain
ChatEvent.ACTION -> location.outgoing.action ?: defaults.action
ChatEvent.DEATH -> location.outgoing.death ?: defaults.death
ChatEvent.JOIN -> location.outgoing.join ?: defaults.join
ChatEvent.LEAVE -> location.outgoing.leave ?: defaults.leave
ChatEvent.ADVANCEMENT -> location.outgoing.advancement ?: defaults.advancement
ChatEvent.BROADCAST -> location.outgoing.broadcast ?: defaults.broadcast
ChatEvent.STATUS -> location.outgoing.status ?: defaults.status
}
if (!matchesEvent) {
logger.debug("location: $label dropped message '$msg' from user: '$user', event not enabled")
logger.debug("event: $event")
logger.debug("location.outgoing: ${location.outgoing}")
logger.debug("defaults: $defaults")
continue
}
skips.addAll(location.outgoing.skip)
val eventStr = when (event) {
ChatEvent.PLAIN -> ""
ChatEvent.ACTION -> ApiMessage.USER_ACTION
ChatEvent.DEATH -> ""
ChatEvent.JOIN -> ApiMessage.JOIN_LEAVE
ChatEvent.LEAVE -> ApiMessage.JOIN_LEAVE
ChatEvent.ADVANCEMENT -> ""
ChatEvent.BROADCAST -> ""
ChatEvent.STATUS -> ""
}
val username = when {
systemuser -> cfg.outgoing.systemUser
else -> user
}
val avatar = when {
systemuser ->
cfg.outgoing.avatar.systemUserAvatar
cfg.outgoing.avatar.enable && uuid != null ->
cfg.outgoing.avatar.urlTemplate.replace("{uuid}", uuid.toString())
else ->
null
}
when {
msg.isNotBlank() -> MessageHandlerInst.transmit(
ApiMessage(
username = username.stripColorOut,
text = msg.stripColorOut,
event = eventStr,
gateway = location.gateway
).apply {
avatar?.let {
this.avatar = it
}
},
cause = cause
)
else -> logger.warn("WARN: dropped blank message by '$user'")
}
logger.debug("sent message through location: $label, cause: $cause")
handled = true
}
return handled
}
}

View File

@ -1,21 +1,24 @@
package matterlink.handlers package matterlink.handlers
import matterlink.antiping import matterlink.antiping
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.cfg import matterlink.config.cfg
import matterlink.stripColorOut import matterlink.stripColorOut
object ProgressHandler { object ProgressHandler {
fun handleProgress(name: String, message: String, display: String) { suspend fun handleProgress(
name: String, message: String, display: String,
x: Int, y: Int, z: Int,
dimension: Int
) {
if (!cfg.outgoing.advancements) return if (!cfg.outgoing.advancements) return
val usr = name.stripColorOut.antiping val usr = name.stripColorOut.antiping
MessageHandlerInst.transmit( LocationHandler.sendToLocations(
ApiMessage( msg = "$usr $message $display".stripColorOut,
text = "$usr $message $display".stripColorOut x = x, y = y, z = z, dimension = dimension,
), event = ChatEvent.ADVANCEMENT,
cause = "Progress Event by $usr" cause = "Progress Event by $usr",
systemuser = true
) )
} }
} }

View File

@ -1,48 +1,132 @@
package matterlink.handlers package matterlink.handlers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst import matterlink.bridge.MessageHandlerInst
import matterlink.bridge.command.BridgeCommandRegistry import matterlink.bridge.command.BridgeCommandRegistry
import matterlink.bridge.format import matterlink.bridge.format
import matterlink.config.cfg import matterlink.config.cfg
import matterlink.instance import matterlink.instance
import matterlink.logger import matterlink.logger
import matterlink.stripColorIn import java.util.UUID
object ServerChatHandler { object ServerChatHandler {
@UseExperimental(ExperimentalCoroutinesApi::class)
val rcvChannel = MessageHandlerInst.broadcast.openSubscription()
/** /**
* This method must be called every server tick with no arguments. * This method must be called every server tick with no arguments.
*/ */
fun writeIncomingToChat() { suspend fun writeIncomingToChat() {
if (MessageHandlerInst.queue.isNotEmpty()) val nextMessage = rcvChannel.poll() ?: return
logger.debug("incoming: " + MessageHandlerInst.queue.toString())
val nextMessage = MessageHandlerInst.queue.poll() ?: null
if (nextMessage?.gateway == cfg.connect.gateway) { val defaults = cfg.incomingDefaults
if (!nextMessage.text.isBlank()) {
nextMessage.text = nextMessage.text.stripColorIn val sourceGateway = nextMessage.gateway
val message = when (nextMessage.event) {
"user_action" -> nextMessage.format(cfg.incoming.action) // find all areas to send to
"" -> {
// try to handle command and do not handle as a chat message val targetUUIDs = mutableSetOf<UUID>()
if (BridgeCommandRegistry.handleCommand(nextMessage)) return val skips = mutableSetOf<String>()
nextMessage.format(cfg.incoming.chat)
} val locations = cfg.locations.filter {
"join_leave" -> nextMessage.format(cfg.incoming.joinPart) it.gateway == sourceGateway
else -> { }
val user = nextMessage.username
val text = nextMessage.text if (nextMessage.event.isEmpty()) {
val json = nextMessage.encode() // filter command handlers
logger.debug("Threw out message with unhandled event: ${nextMessage.event}") val commandLocations = locations.filter {
logger.debug(" Message contents:") it.incoming.commands ?: cfg.incomingDefaults.commands
logger.debug(" User: $user") }
logger.debug(" Text: $text")
logger.debug(" JSON: $json") // process potential command
return for ((label, location) in commandLocations) {
} if (BridgeCommandRegistry.handleCommand(nextMessage)) return
}
instance.wrappedSendToPlayers(message)
} }
} }
for (location in locations) {
val label = location.label
if (skips.contains(label)) {
logger.debug("skipping $label")
continue
}
val matchesEvent = when (nextMessage.event) {
"" -> {
// if (location.incoming.commands ?: defaults.commands
// && BridgeCommandRegistry.handleCommand(nextMessage)) return
location.incoming.plain ?: defaults.plain
}
ApiMessage.JOIN_LEAVE -> location.incoming.join_leave ?: defaults.join_leave
ApiMessage.USER_ACTION -> location.incoming.action ?: defaults.action
else -> {
logger.error("unknown event type '${nextMessage.event}' on incoming message")
return
}
}
if (!matchesEvent) {
logger.info("location: $label dropped message '$nextMessage' event not enabled")
continue
}
targetUUIDs.addAll(instance.collectPlayers(location.area))
}
val message = when (nextMessage.event) {
"user_action" -> nextMessage.format(cfg.incoming.action)
"" -> {
// try to handle command and do not handle as a chat message
if (BridgeCommandRegistry.handleCommand(nextMessage)) return
nextMessage.format(cfg.incoming.chat)
}
"join_leave" -> nextMessage.format(cfg.incoming.joinPart)
else -> {
val user = nextMessage.username
val text = nextMessage.text
val json = nextMessage.encode()
logger.debug("Threw out message with unhandled event: ${nextMessage.event}")
logger.debug(" Message contents:")
logger.debug(" User: $user")
logger.debug(" Text: $text")
logger.debug(" JSON: $json")
return
}
}
targetUUIDs.forEach {
//TODO: optimize send to all at once
instance.wrappedSendToPlayer(it, message)
}
// if (nextMessage?.gateway == cfg.connect.gateway) {
// if (!nextMessage.text.isBlank()) {
// nextMessage.text = nextMessage.text.stripColorIn
// val message = when (nextMessage.event) {
// "user_action" -> nextMessage.format(cfg.incoming.action)
// "" -> {
// // try to handle command and do not handle as a chat message
// if (BridgeCommandRegistry.handleCommand(nextMessage)) return
// nextMessage.format(cfg.incoming.chat)
// }
// "join_leave" -> nextMessage.format(cfg.incoming.joinPart)
// else -> {
// val user = nextMessage.username
// val text = nextMessage.text
// val json = nextMessage.encode()
// logger.debug("Threw out message with unhandled event: ${nextMessage.event}")
// logger.debug(" Message contents:")
// logger.debug(" User: $user")
// logger.debug(" Text: $text")
// logger.debug(" JSON: $json")
// return
// }
// }
// instance.wrappedSendToPlayers(message)
// }
// }
} }
} }

View File

@ -1,6 +1,5 @@
package matterlink.handlers package matterlink.handlers
import matterlink.bridge.MessageHandlerInst
import matterlink.update.UpdateChecker import matterlink.update.UpdateChecker
/** /**
@ -13,17 +12,17 @@ object TickHandler {
private set private set
private var accumulator = 0 private var accumulator = 0
private const val updateInterval = 12 * 60 * 60 * 20 private const val updateInterval = 12 * 60 * 60 * 20
fun handleTick() { suspend fun handleTick() {
tickCounter++ tickCounter++
if (tickCounter % 100 == 0) { // if (tickCounter % 100 == 0) {
MessageHandlerInst.checkConnection() // MessageHandlerInst.checkConnection()
} // }
ServerChatHandler.writeIncomingToChat() ServerChatHandler.writeIncomingToChat()
if (accumulator++ > updateInterval) { if (accumulator++ > updateInterval) {
accumulator -= updateInterval accumulator -= updateInterval
UpdateChecker.run() UpdateChecker.check()
} }
} }
} }

View File

@ -1,15 +1,15 @@
package matterlink.jenkins package matterlink.jenkins
import com.google.gson.annotations.SerializedName import kotlinx.serialization.Serializable
/** /**
* Created by nikky on 03/02/18. * Created by nikky on 03/02/18.
* @author Nikky * @author Nikky
*/ */
//@JsonIgnoreProperties(ignoreUnknown = true) @Serializable
data class Artifact( data class Artifact(
val displayPath: String, val displayPath: String,
val fileName: String, val fileName: String,
val relativePath: String val relativePath: String
) )

View File

@ -1,11 +1,11 @@
package matterlink.jenkins package matterlink.jenkins
import com.google.gson.Gson import com.github.kittinunf.fuel.httpGet
import matterlink.* import com.github.kittinunf.fuel.serialization.kotlinxDeserializerOf
import matterlink.Result import com.github.kittinunf.result.Result
import matterlink.header import kotlinx.serialization.json.JSON
import matterlink.httpGet import matterlink.logger
import matterlink.responseString
/** /**
* Created by nikky on 03/02/18. * Created by nikky on 03/02/18.
@ -14,17 +14,17 @@ import matterlink.responseString
//@JsonIgnoreProperties(ignoreUnknown = true) //@JsonIgnoreProperties(ignoreUnknown = true)
data class Build( data class Build(
val number: Int, val number: Int,
val url: String val url: String
) { ) {
fun details(userAgent: String): BuildWithDetails? { fun details(userAgent: String): BuildWithDetails? {
val (request, response, result) = "$url/api/json" val (request, response, result) = "$url/api/json"
.httpGet() .httpGet()
.header("User-Agent" to userAgent) .header("User-Agent" to userAgent)
.responseString() .responseObject(kotlinxDeserializerOf(loader = BuildWithDetails.serializer(), json = JSON.nonstrict))
return when(result) { return when (result) {
is Result.Success -> { is Result.Success -> {
gson.fromJson(result.value, BuildWithDetails::class.java) result.value
} }
is Result.Failure -> { is Result.Failure -> {
logger.error(result.error.toString()) logger.error(result.error.toString())
@ -32,9 +32,5 @@ data class Build(
} }
} }
} }
companion object {
val gson = Gson()
}
} }

View File

@ -1,12 +1,12 @@
package matterlink.jenkins package matterlink.jenkins
import java.util.* import kotlinx.serialization.Serializable
import java.util.Date
//@JsonIgnoreProperties(ignoreUnknown = true) @Serializable
data class BuildWithDetails( data class BuildWithDetails(
val number: Int, val number: Int,
val url: String, val url: String,
val artifacts: List<Artifact>, val artifacts: List<Artifact>,
// @JsonFormat(shape=JsonFormat.Shape.NUMBER, pattern="s") val timestamp: Date
val timestamp: Date
) )

View File

@ -1,12 +1,10 @@
package voodoo.util.jenkins package matterlink.jenkins
import com.google.gson.Gson import com.github.kittinunf.fuel.httpGet
import matterlink.Result import com.github.kittinunf.fuel.serialization.kotlinxDeserializerOf
import matterlink.header import com.github.kittinunf.result.Result
import matterlink.httpGet import kotlinx.serialization.json.JSON
import matterlink.responseString
import matterlink.jenkins.Job
import matterlink.logger import matterlink.logger
/** /**
@ -22,12 +20,12 @@ class JenkinsServer(val url: String) {
fun getJob(job: String, userAgent: String): Job? { fun getJob(job: String, userAgent: String): Job? {
val requestURL = getUrl(job) + "/api/json" val requestURL = getUrl(job) + "/api/json"
val (_, _, result) = requestURL val (_, _, result) = requestURL
.httpGet() .httpGet()
.header("User-Agent" to userAgent) .header("User-Agent" to userAgent)
.responseString() .responseObject(kotlinxDeserializerOf(loader = Job.serializer(), json = JSON.nonstrict))
return when (result) { return when (result) {
is Result.Success -> { is Result.Success -> {
gson.fromJson(result.value, Job::class.java) result.value
} }
is Result.Failure -> { is Result.Failure -> {
logger.error(result.error.toString()) logger.error(result.error.toString())
@ -36,8 +34,4 @@ class JenkinsServer(val url: String) {
} }
} }
companion object {
val gson = Gson()
}
} }

View File

@ -1,20 +1,22 @@
package matterlink.jenkins; package matterlink.jenkins
import kotlinx.serialization.Serializable
/** /**
* Created by nikky on 03/02/18. * Created by nikky on 03/02/18.
* @author Nikky * @author Nikky
*/ */
//@JsonIgnoreProperties(ignoreUnknown = true) @Serializable
data class Job( data class Job(
val url: String, val url: String,
val name: String, val name: String,
val fullName: String, val fullName: String,
val displayName: String, val displayName: String,
val fullDisplayName: String, val fullDisplayName: String,
val builds: List<Build>?, val builds: List<Build>?,
val lastSuccessfulBuild: Build?, val lastSuccessfulBuild: Build?,
val lastStableBuild: Build? val lastStableBuild: Build?
) { ) {
fun getBuildByNumber(build: Int, userAgent: String): BuildWithDetails? { fun getBuildByNumber(build: Int, userAgent: String): BuildWithDetails? {
return builds?.find { it.number == build }?.details(userAgent) return builds?.find { it.number == build }?.details(userAgent)

View File

@ -1,9 +1,13 @@
package matterlink.update package matterlink.update
import kotlinx.serialization.Serializable
@Serializable
data class CurseFile( data class CurseFile(
val downloadURL: String, val downloadUrl: String,
val fileName: String, val fileName: String,
val gameVersion: List<String>, val fileNameOnDisk: String,
val releaseType: String, val gameVersion: List<String>,
val fileStatus: String val releaseType: String,
val fileStatus: String
) )

View File

@ -1,38 +1,40 @@
package matterlink.update package matterlink.update
import com.google.gson.FieldNamingPolicy import com.github.kittinunf.fuel.core.extensions.cUrlString
import com.google.gson.GsonBuilder import com.github.kittinunf.fuel.httpGet
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.serialization.list
import matterlink.api.ApiMessage import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst import matterlink.bridge.MessageHandlerInst
import matterlink.config.cfg import matterlink.config.cfg
import matterlink.handlers.ChatEvent
import matterlink.handlers.LocationHandler
import matterlink.instance import matterlink.instance
import matterlink.jenkins.JenkinsServer
import matterlink.logger import matterlink.logger
import voodoo.util.jenkins.JenkinsServer import com.github.kittinunf.fuel.serialization.kotlinxDeserializerOf
import java.io.BufferedReader import com.github.kittinunf.result.Result
import java.net.HttpURLConnection import kotlinx.serialization.json.JSON
import java.net.URL
class UpdateChecker : Thread() { object UpdateChecker : CoroutineScope {
companion object { override val coroutineContext = Job() + CoroutineName("UpdateChacker")
fun run() {
if (cfg.update.enable) { suspend fun check() {
UpdateChecker().start() if (cfg.update.enable) {
} run()
} }
} }
init { private suspend fun run() {
name = "UpdateCheckerThread"
}
override fun run() {
if (instance.buildNumber > 0) { if (instance.buildNumber > 0) {
val server = JenkinsServer("https://ci.elytradev.com") val server = JenkinsServer("https://ci.elytradev.com")
val job = server.getJob("elytra/MatterLink/master", "MatterLink/${instance.modVersion}") val job = server.getJob("elytra/MatterLink/master", "MatterLink/${instance.modVersion}")
?: run { ?: run {
logger.error("failed obtaining job: elytra/MatterLink/master") logger.error("failed obtaining job: elytra/MatterLink/master")
return return
} }
//TODO: add job name to constants at build time //TODO: add job name to constants at build time
val build = job.lastSuccessfulBuild ?: run { val build = job.lastSuccessfulBuild ?: run {
logger.error("no successful build found") logger.error("no successful build found")
@ -42,11 +44,12 @@ class UpdateChecker : Thread() {
when { when {
number > instance.buildNumber -> { number > instance.buildNumber -> {
logger.warn("Mod out of date! New build $number available at $url") logger.warn("Mod out of date! New build $number available at $url")
val difference = number - build.number val difference = number - instance.buildNumber
MessageHandlerInst.transmit( LocationHandler.sendToLocations(
ApiMessage( msg = "MatterLink out of date! You are $difference builds behind! Please download new version from $url",
text = "MatterLink out of date! You are $difference builds behind! Please download new version from $url" x = 0, y = 0, z = 0, dimension = null,
) event = ChatEvent.STATUS,
cause = "MatterLink update notice"
) )
} }
number < instance.buildNumber -> logger.error("lastSuccessfulBuild: $number is older than installed build: ${instance.buildNumber}") number < instance.buildNumber -> logger.error("lastSuccessfulBuild: $number is older than installed build: ${instance.buildNumber}")
@ -60,48 +63,38 @@ class UpdateChecker : Thread() {
return return
} }
val gson = GsonBuilder()
// .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.create()
logger.info("Checking for new versions...") logger.info("Checking for new versions...")
val (request, response, result) = with(instance) {
val url = URL("https://staging_cursemeta.dries007.net/api/v3/direct/addon/287323/files") val useragent =
val con = url.openConnection() as HttpURLConnection "MatterLink/$modVersion MinecraftForge/$mcVersion-$forgeVersion (https://github.com/elytra/MatterLink)"
with(instance) {
val useragent = "MatterLink/$modVersion MinecraftForge/$mcVersion-$forgeVersion (https://github.com/elytra/MatterLink)"
logger.debug("setting User-Agent: '$useragent'") logger.debug("setting User-Agent: '$useragent'")
con.setRequestProperty("User-Agent", useragent)
"https://curse.nikky.moe/api/addon/287323/files".httpGet()
.header("User-Agent" to useragent)
.responseObject(kotlinxDeserializerOf(loader = CurseFile.serializer().list, json = JSON.nonstrict))
} }
con.connect() val apiUpdateList = when(result) {
is Result.Success -> {
val apiUpdateList = if (200 == con.responseCode) { //HTTP 200 OK result.value
val buffer: BufferedReader = con.inputStream.bufferedReader() }
is Result.Failure -> {
//put all of the buffer content onto the string logger.error("Could not check for updates!")
val content = buffer.readText() logger.error("cUrl: ${request.cUrlString()}")
logger.trace("updateData: $content") logger.error("request: $request")
logger.error("response: $response")
gson.fromJson(content, Array<CurseFile>::class.java) return
.filter { }
it.fileStatus == "SemiNormal" && it.gameVersion.contains(instance.mcVersion)
}
.sortedByDescending { it.fileName.substringAfterLast(" ") }
} else {
logger.error("Could not check for updates!")
return
} }
.filter { it.fileStatus == "SemiNormal" && it.gameVersion.contains(instance.mcVersion) }
val modVersionChunks = instance.modVersion val modVersionChunks = instance.modVersion
.substringBefore("-dev") .substringBefore("-dev")
.substringBefore("-build") .substringBefore("-build")
.split('.') .split('.')
.map { .map {
it.toInt() it.toInt()
} }
val possibleUpdates = mutableListOf<CurseFile>() val possibleUpdates = mutableListOf<CurseFile>()
apiUpdateList.forEach { apiUpdateList.forEach {
@ -131,14 +124,14 @@ class UpdateChecker : Thread() {
logger.info("Matterlink out of date! You are $count $version behind") logger.info("Matterlink out of date! You are $count $version behind")
possibleUpdates.forEach { possibleUpdates.forEach {
logger.info("version: {} download: {}", it.fileName, it.downloadURL) logger.info("version: ${it.fileName} download: ${it.downloadUrl}")
} }
logger.warn("Mod out of date! New $version available at ${latest.downloadURL}") logger.warn("Mod out of date! New $version available at ${latest.downloadUrl}")
MessageHandlerInst.transmit( MessageHandlerInst.transmit(
ApiMessage( ApiMessage(
text = "MatterLink out of date! You are $count $version behind! Please download new version from ${latest.downloadURL}" text = "MatterLink out of date! You are $count $version behind! Please download new version from ${latest.downloadUrl}"
) )
) )
} }
} }

View File

@ -1,8 +1,12 @@
mod_name = MatterLink modName = MatterLink
mod_version = 1.6.3 modVersion = 1.6.4
forgelin_version = 1.6.0 forgelinVersion = 1.8.2
kotlin_version = 1.2.41 kotlinVersion = 1.3.10
shadow_version = 2.0.2 coroutinesVersion = 1.0.1
cursegradle_version = 1.0.10 serializationVersion = 0.9.1
curse_id = 287323 shadowVersion = 2.0.2
curse_release_type = beta fuelVersion = 8690665998
resultVersion = 2.0.0
cursegradleVersion = 1.1.2
curseId = 287323
curseReleaseType = beta

23
scripts/start.sh Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env bash
java -jar forge-installer.jar --installServer
FORGE_FILE=`grep "The server installed successfully, you should now be able to run the file " forge-installer.jar.log | tail -1`
FORGE_FILE=${FORGE_FILE#"The server installed successfully, you should now be able to run the file "}
echo $FORGE_FILE
cp -f "$FORGE_FILE" forge.jar
if [ ! $? -eq 0 ]; then
echo "Error installing forge"
exit 1
fi
echo "installed forge"
cp ../eula.txt .
mkdir -p config
rm -rf config/matterlink
cp -r ../matterlink_config config/matterlink
java -jar forge.jar

22
scripts/test12.sh Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )"
RUN="$PWD/run/1.12.2"
rm -rf "$RUN/mods"
mkdir -p "$RUN/mods"
"$PWD/gradlew" :1.12.2:clean :1.12.2:build && cp -f 1.12.2/build/libs/MatterLink-1.12.2-*-dev.jar "$RUN/mods"
if [ ! $? -eq 0 ]; then
echo "Error compiling matterlink"
exit 1
fi
cd "$RUN"
curl -o forge-installer.jar "https://files.minecraftforge.net/maven/net/minecraftforge/forge/1.12.2-14.23.5.2768/forge-1.12.2-14.23.5.2768-installer.jar"
curl -L -o "$RUN/mods/Forgelin.jar" "https://minecraft.curseforge.com/projects/shadowfacts-forgelin/files/2640952/download"
$DIR/scripts/start.sh

19
scripts/test7.sh Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )"
RUN="$PWD/run/1.7.10"
rm -rf "$RUN/mods"
mkdir -p "$RUN/mods"
"$PWD/gradlew" :1.7.10:clean :1.7.10:build && cp -f $DIR/1.7.10/build/libs/MatterLink-1.7.10-*-dev.jar "$RUN/mods"
if [ ! $? -eq 0 ]; then
echo "Error compiling matterlink"
exit 1
fi
cd "$RUN"
curl -o forge-installer.jar "https://files.minecraftforge.net/maven/net/minecraftforge/forge/1.7.10-10.13.4.1614-1.7.10/forge-1.7.10-10.13.4.1614-1.7.10-installer.jar"
$DIR/scripts/start.sh

22
scripts/test9.sh Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )"
RUN="$PWD/run/1.9.4"
rm -rf "$RUN/mods"
mkdir -p "$RUN/mods"
"$PWD/gradlew" :1.9.4:clean :1.9.4:build && cp -f 1.9.4/build/libs/MatterLink-1.9.4-*-dev.jar "$RUN/mods"
if [ ! $? -eq 0 ]; then
echo "Error compiling matterlink"
exit 1
fi
cd "$RUN"
curl -o forge-installer.jar "https://files.minecraftforge.net/maven/net/minecraftforge/forge/1.9.4-12.17.0.2051/forge-1.9.4-12.17.0.2051-installer.jar"
curl -L -o "$RUN/mods/Forgelin.jar" "https://minecraft.curseforge.com/projects/shadowfacts-forgelin/files/2640952/download" # "https://minecraft.curseforge.com/projects/shadowfacts-forgelin/files/2573311/download"
$DIR/scripts/start.sh

View File

@ -1,4 +1,4 @@
rootProject.name = 'MatterLink' rootProject.name = 'MatterLink'
include 'core', 'api' include 'core'
include 'Jankson' include 'Jankson'
include '1.12.2', '1.11.2', '1.10.2', '1.7.10' include '1.12.2', '1.9.4', '1.7.10'