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
.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
### MatterLink ###
@ -102,3 +98,5 @@ run/
\.floo
\.flooignore
bugreport/

3
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "api"]
path = api
url = https://github.com/NikkyAI/MatterLinkApi.git
[submodule "Jankson"]
path = Jankson
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 {
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: 'gradle.plugin.com.matthewprenger', name: 'CurseGradle', version: cursegradle_version
classpath group: 'com.github.jengelman.gradle.plugins', name: 'shadow', version: shadowVersion
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: 'com.github.johnrengelman.shadow'
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'
dependencies {
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 {
classifier = ''
relocate 'blue.endless', 'matterlink.repack.blue.endless'
dependencies {
include project(':core')
include project(':api')
include project(':Jankson')
}
exclude 'dummyThing'
configurations = [project.configurations.shadow]
}
minecraft {
@ -55,16 +52,16 @@ minecraft {
mappings = project.mcp_mappings
replaceIn 'Constants.kt'
replace '@MODVERSION@', project.mod_version
replace '@MODVERSION@', project.modVersion
replace '@MCVERSION@', project.mc_version
replace '@FORGELIN-VERSION@', project.forgelin_version
replace '@FORGELIN-VERSION@', project.forgelinVersion
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 "version", project.modVersion
inputs.property "mcversion", project.minecraft.version
// replace stuff in mcmod.info, nothing else
@ -72,7 +69,7 @@ processResources {
include 'mcmod.info'
// 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
@ -96,7 +93,6 @@ sourceJar {
reobf {
shadowJar { mappingType = 'SEARGE' }
}
tasks.shadowJar.finalizedBy reobfShadowJar
curseforge {
@ -104,8 +100,8 @@ curseforge {
apiKey = CURSEFORGE_API_TOKEN
}
project {
id = project.curse_id
releaseType = project.curse_release_type
id = project.curseId
releaseType = project.curseReleaseType
if (project.hasProperty('changelog_file')) {
println("changelog = $changelog_file")
changelogType = 'markdown'
@ -119,3 +115,7 @@ curseforge {
}
}
}
runServer {
outputs.upToDateWhen { false }
}

View File

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

View File

@ -1,8 +1,13 @@
package matterlink
import matterlink.api.ApiMessage.Companion.USER_ACTION
import kotlinx.coroutines.runBlocking
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.CommandEmote
import net.minecraft.entity.player.EntityPlayer
@ -23,24 +28,32 @@ object EventHandler {
//MC-VERSION & FORGE DEPENDENT
@SubscribeEvent
@JvmStatic
fun progressEvent(e: AdvancementEvent) {
if (e.advancement.display == null) return
fun progressEvent(e: AdvancementEvent) = runBlocking {
if (e.advancement.display == null) return@runBlocking
ProgressHandler.handleProgress(
name = e.entityPlayer.displayName.unformattedText,
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
@SubscribeEvent
@JvmStatic
fun chatEvent(e: ServerChatEvent) {
if(e.isCanceled) return
fun chatEvent(e: ServerChatEvent) = runBlocking {
if (e.isCanceled) return@runBlocking
e.isCanceled = ChatProcessor.sendToBridge(
user = e.player.displayName.unformattedText,
msg = e.message,
event = "",
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
)
}
@ -48,30 +61,47 @@ object EventHandler {
//FORGE-DEPENDENT
@SubscribeEvent
@JvmStatic
fun commandEvent(e: CommandEvent) {
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 = when {
e.command is CommandEmote -> USER_ACTION
e.command.name.equals("me", true) -> USER_ACTION
e.command is CommandBroadcast -> ""
else -> return
val type = with(e.command) {
when {
this is CommandEmote || name.equals("me", true) -> ChatEvent.ACTION
this is CommandBroadcast || name.equals("say", true) -> ChatEvent.BROADCAST
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
@SubscribeEvent
@JvmStatic
fun deathEvent(e: LivingDeathEvent) {
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
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
@SubscribeEvent
@JvmStatic
fun joinEvent(e: PlayerEvent.PlayerLoggedInEvent) {
JoinLeaveHandler.handleJoin(e.player.displayName.unformattedText)
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
@JvmStatic
fun leaveEvent(e: PlayerEvent.PlayerLoggedOutEvent) {
JoinLeaveHandler.handleLeave(e.player.displayName.unformattedText)
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
@JvmStatic
fun serverTickEvent(e: TickEvent.ServerTickEvent) {
fun serverTickEvent(e: TickEvent.ServerTickEvent) = runBlocking {
if (e.phase == TickEvent.Phase.END)
TickHandler.handleTick()
}
}

View File

@ -1,7 +1,7 @@
package matterlink
import com.mojang.authlib.GameProfile
import jline.internal.Log.warn
import kotlinx.coroutines.runBlocking
import matterlink.bridge.command.IBridgeCommand
import matterlink.command.AuthCommand
import matterlink.command.MatterLinkCommand
@ -19,7 +19,7 @@ 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.core.config.Configurator
import java.util.*
import java.util.UUID
@Mod(
@ -40,7 +40,16 @@ object MatterLink : IMatterLink() {
@Mod.EventHandler
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!")
cfg = BaseConfig(event.modConfigurationDirectory).load()
@ -48,19 +57,22 @@ object MatterLink : IMatterLink() {
@Mod.EventHandler
fun init(event: FMLInitializationEvent) {
logger.debug("Registering bridge commands")
this.registerBridgeCommands()
}
@Mod.EventHandler
fun serverStarting(event: FMLServerStartingEvent) {
logger.debug("Registering server commands")
event.registerServerCommand(MatterLinkCommand())
event.registerServerCommand(AuthCommand())
event.registerServerCommand(MatterLinkCommand)
event.registerServerCommand(AuthCommand)
runBlocking {
start()
}
}
@Mod.EventHandler
fun serverStopping(event: FMLServerStoppingEvent) {
fun serverStopping(event: FMLServerStoppingEvent) = runBlocking {
stop()
}
@ -93,24 +105,34 @@ object MatterLink : IMatterLink() {
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 {
FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getProfileByUUID(uuid)
} catch (e: IllegalArgumentException) {
warn("cannot find profile by uuid $uuid")
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) {
warn("cannot find profile by username $username")
logger.warn("cannot find profile by username $username")
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 uuidToName(uuid: UUID): String? = profileByUUID(uuid)?.name

View File

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

View File

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

View File

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

View File

@ -11,16 +11,16 @@ buildscript {
}
dependencies {
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: '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'
@ -30,21 +30,35 @@ configurations {
}
dependencies {
shade project(':core')
shade project(':api')
shade project(':Jankson')
shade (project(':core')) { transitive = true }
shade (project(':Jankson')) { transitive = false }
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 {
version = project.mc_version + '-' + project.forge_version + '-' + project.mc_version
runDir = 'run'
mappings = project.mcp_mappings
srgExtra 'PK: kotlin matterlink/repack/kotlin'
srgExtra 'PK: org/jetbrains/annotations matterlink/repack/org/jetbrains/annotations'
srgExtra 'PK: org/intellij matterlink/repack/org/intellij'
srgExtra 'PK: blue/endless matterlink/repack/blue/endless'
// srgExtra 'PK: kotlin matterlink/repack/kotlin'
// srgExtra 'PK: org/jetbrains/annotations matterlink/repack/org/jetbrains/annotations'
// srgExtra 'PK: org/intellij matterlink/repack/org/intellij'
// srgExtra 'PK: blue/endless/ matterlink/repack/blue/endless/'
}
compileKotlin.doFirst {
@ -53,13 +67,13 @@ compileKotlin.doFirst {
from('src/templates/kotlin/matterlink/Constants.kt')
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')
}
ant.replaceregexp(match: '@MCVERSION@', replace: project.mc_version, flags: 'g', byline: true) {
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')
}
ant.replaceregexp(match: '@FORGE-VERSION@', replace: project.forge_version, flags: 'g', byline: true) {
@ -72,7 +86,7 @@ compileKotlin.doFirst {
processResources {
// 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
// replace stuff in mcmod.info, nothing else
@ -80,7 +94,7 @@ processResources {
include 'mcmod.info'
// 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
@ -93,7 +107,6 @@ jar {
configurations.shade.each { dep ->
from(project.zipTree(dep)) {
exclude 'META-INF', 'META-INF/**'
exclude 'com/google/gson', 'com/google/gson/**'
}
}
}
@ -103,8 +116,8 @@ curseforge {
apiKey = CURSEFORGE_API_TOKEN
}
project {
id = project.curse_id
releaseType = project.curse_release_type
id = project.curseId
releaseType = project.curseReleaseType
if (project.hasProperty('changelog_file')) {
println("changelog = $changelog_file")
changelogType = 'markdown'

View File

@ -3,11 +3,17 @@ package matterlink
import cpw.mods.fml.common.eventhandler.SubscribeEvent
import cpw.mods.fml.common.gameevent.PlayerEvent
import cpw.mods.fml.common.gameevent.TickEvent
import matterlink.api.ApiMessage.Companion.USER_ACTION
import kotlinx.coroutines.runBlocking
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.CommandEmote
import net.minecraft.entity.Entity
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.server.dedicated.DedicatedServer
@ -21,80 +27,121 @@ object EventHandler {
//MC-VERSION & FORGE DEPENDENT
@SubscribeEvent
fun progressEvent(e: AchievementEvent) {
fun progressEvent(e: AchievementEvent) = runBlocking {
val achievement = e.achievement
val entityPlayer = e.entityPlayer as? EntityPlayerMP ?: return
val entityPlayer = e.entityPlayer as? EntityPlayerMP ?: return@runBlocking
val statFile = entityPlayer.statFile
if (!statFile.canUnlockAchievement(achievement) || statFile.hasAchievementUnlocked(achievement)) {
return
return@runBlocking
}
ProgressHandler.handleProgress(
name = e.entityPlayer.displayName,
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
@SubscribeEvent
fun chatEvent(e: ServerChatEvent) {
if(e.isCanceled) return
fun chatEvent(e: ServerChatEvent) = runBlocking {
if (e.isCanceled) return@runBlocking
e.isCanceled = ChatProcessor.sendToBridge(
user = e.player.displayName,
msg = e.message,
event = "",
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) {
fun commandEvent(e: CommandEvent) = runBlocking {
val sender = when {
e.sender is DedicatedServer -> cfg.outgoing.systemUser
else -> e.sender.commandSenderName
}
val args = e.parameters.joinToString(" ")
val type = when {
e.command is CommandEmote -> USER_ACTION
e.command.commandName == "me" -> USER_ACTION
e.command is CommandBroadcast -> ""
else -> return
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)
}
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
@SubscribeEvent
fun deathEvent(e: LivingDeathEvent) {
fun deathEvent(e: LivingDeathEvent) = runBlocking {
if (e.entityLiving is EntityPlayer) {
val player = e.entityLiving as EntityPlayer
DeathHandler.handleDeath(
player = player.displayName,
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
@SubscribeEvent
fun joinEvent(e: PlayerEvent.PlayerLoggedInEvent) {
JoinLeaveHandler.handleJoin(e.player.displayName)
fun joinEvent(e: PlayerEvent.PlayerLoggedInEvent) = runBlocking {
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
@SubscribeEvent
fun leaveEvent(e: PlayerEvent.PlayerLoggedOutEvent) {
JoinLeaveHandler.handleLeave(e.player.displayName)
fun leaveEvent(e: PlayerEvent.PlayerLoggedOutEvent) = runBlocking {
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
@SubscribeEvent
fun serverTickEvent(e: TickEvent.ServerTickEvent) {
fun serverTickEvent(e: TickEvent.ServerTickEvent) = runBlocking {
if (e.phase == TickEvent.Phase.END)
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.FMLServerStartingEvent
import cpw.mods.fml.common.event.FMLServerStoppingEvent
import kotlinx.coroutines.runBlocking
import matterlink.bridge.command.IBridgeCommand
import matterlink.command.AuthCommand
import matterlink.command.MatterLinkCommand
@ -18,7 +19,7 @@ import net.minecraft.server.MinecraftServer
import net.minecraft.util.ChatComponentText
import net.minecraftforge.common.ForgeVersion
import net.minecraftforge.common.MinecraftForge
import java.util.*
import java.util.UUID
@Mod(
modid = MODID,
@ -36,7 +37,16 @@ class MatterLink : IMatterLink() {
MinecraftForge.EVENT_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!")
cfg = BaseConfig(event.modConfigurationDirectory).load()
@ -48,15 +58,15 @@ class MatterLink : IMatterLink() {
}
@Mod.EventHandler
fun serverStarting(event: FMLServerStartingEvent) {
fun serverStarting(event: FMLServerStartingEvent) = runBlocking {
logger.debug("Registering server commands")
event.registerServerCommand(MatterLinkCommand())
event.registerServerCommand(AuthCommand())
event.registerServerCommand(MatterLinkCommand)
event.registerServerCommand(AuthCommand)
start()
}
@Mod.EventHandler
fun serverStopping(event: FMLServerStoppingEvent) {
fun serverStopping(event: FMLServerStoppingEvent) = runBlocking {
stop()
}
@ -93,7 +103,8 @@ class MatterLink : IMatterLink() {
.minecraftServerInstance.configurationManager.getPlayerByUsername(username) ?: null) != null
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 {
@ -110,6 +121,16 @@ class MatterLink : IMatterLink() {
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 uuidToName(uuid: UUID): String? = profileByUUID(uuid)?.name

View File

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

View File

@ -1,5 +1,6 @@
package matterlink.command
import kotlinx.coroutines.runBlocking
import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException
@ -7,7 +8,7 @@ import net.minecraft.entity.player.EntityPlayer
import net.minecraft.util.ChatComponentText
class MatterLinkCommand : CommandBase() {
object MatterLinkCommand : CommandBase() {
override fun getCommandName(): String {
return CommandCoreML.name
}
@ -20,9 +21,9 @@ class MatterLinkCommand : CommandBase() {
return CommandCoreML.aliases
}
override fun processCommand(sender: ICommandSender, args: Array<String>) {
override fun processCommand(sender: ICommandSender, args: Array<String>) = runBlocking {
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()

View File

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

View File

@ -11,8 +11,8 @@ buildscript {
}
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
classpath group: 'com.github.jengelman.gradle.plugins', name: 'shadow', version: shadowVersion
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.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'
dependencies {
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 {
classifier ''
relocate 'blue.endless', 'matterlink.repack.blue.endless'
dependencies {
include project(':core')
include project(':api')
include project(':Jankson')
}
classifier = ''
exclude 'dummyThing'
configurations = [project.configurations.shadow]
}
import net.minecraftforge.gradle.user.TaskSourceCopy
// Mad hacks to make source replacements work for Kotlin
@ -74,16 +68,16 @@ minecraft {
mappings = project.mcp_mappings
replaceIn 'Constants.kt'
replace '@MODVERSION@', project.mod_version
replace '@MODVERSION@', project.modVersion
replace '@MCVERSION@', project.mc_version
replace '@FORGELIN-VERSION@', project.forgelin_version
replace '@FORGELIN-VERSION@', project.forgelinVersion
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 'version', project.modVersion
inputs.property 'mcversion', project.minecraft.version
// replace stuff in mcmod.info, nothing else
@ -91,7 +85,7 @@ processResources {
include 'mcmod.info'
// 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
@ -116,7 +110,6 @@ sourceJar {
reobf {
shadowJar { mappingType = 'SEARGE' }
}
tasks.shadowJar.finalizedBy reobfShadowJar
curseforge {
@ -124,8 +117,9 @@ curseforge {
apiKey = CURSEFORGE_API_TOKEN
}
project {
id = project.curse_id
releaseType = project.curse_release_type
id = project.curseId
releaseType = project.curseReleaseType
addGameVersion '1.10'
if (project.hasProperty('changelog_file')) {
println("changelog = $changelog_file")
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
import com.mojang.authlib.GameProfile
import kotlinx.coroutines.runBlocking
import matterlink.bridge.command.IBridgeCommand
import matterlink.command.AuthCommand
import matterlink.command.MatterLinkCommand
import matterlink.command.MatterLinkCommandSender
import matterlink.config.BaseConfig
@ -9,15 +11,14 @@ import matterlink.config.cfg
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.util.text.TextComponentString
import net.minecraftforge.common.ForgeVersion
import net.minecraftforge.common.MinecraftForge
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.*
import java.util.UUID
@Mod(
modid = MODID,
@ -35,7 +36,17 @@ object MatterLink : IMatterLink() {
@Mod.EventHandler
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!")
cfg = BaseConfig(event.modConfigurationDirectory).load()
@ -47,14 +58,15 @@ object MatterLink : IMatterLink() {
}
@Mod.EventHandler
fun serverStarting(event: FMLServerStartingEvent) {
fun serverStarting(event: FMLServerStartingEvent) = runBlocking {
logger.debug("Registering server commands")
event.registerServerCommand(MatterLinkCommand())
event.registerServerCommand(MatterLinkCommand)
event.registerServerCommand(AuthCommand)
start()
}
@Mod.EventHandler
fun serverStopping(event: FMLServerStoppingEvent) {
fun serverStopping(event: FMLServerStoppingEvent) = runBlocking {
stop()
}
@ -72,7 +84,7 @@ object MatterLink : IMatterLink() {
error("${profile.name} is not online")
return
}
player.sendMessage(TextComponentString(msg))
player.addChatMessage(TextComponentString(msg))
}
override fun wrappedSendToPlayer(uuid: UUID, msg: String) {
@ -84,12 +96,14 @@ object MatterLink : IMatterLink() {
logger.error("${profile.name} is not online")
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 {
@ -106,6 +120,17 @@ object MatterLink : IMatterLink() {
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 uuidToName(uuid: UUID): String? = profileByUUID(uuid)?.name

View File

@ -1,6 +1,5 @@
package matterlink.command
import matterlink.logger
import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException
@ -9,16 +8,16 @@ import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString
class AuthCommand : CommandBase() {
override fun getName(): String {
object AuthCommand : CommandBase() {
override fun getCommandName(): String {
return CommandCoreAuth.name
}
override fun getUsage(sender: ICommandSender): String {
override fun getCommandUsage(sender: ICommandSender): String {
return CommandCoreAuth.usage
}
override fun getAliases(): List<String> {
override fun getCommandAliases(): List<String> {
return CommandCoreAuth.aliases
}
@ -28,14 +27,14 @@ class AuthCommand : CommandBase() {
override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) {
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 reply = CommandCoreAuth.execute(args, sender.name, uuid)
if (reply.isNotEmpty() && sender.sendCommandFeedback()) {
sender.sendMessage(TextComponentString(reply))
sender.addChatMessage(TextComponentString(reply))
}
}

View File

@ -1,5 +1,6 @@
package matterlink.command
import kotlinx.coroutines.runBlocking
import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException
@ -8,29 +9,29 @@ import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString
class MatterLinkCommand : CommandBase() {
override fun getName(): String {
object MatterLinkCommand : CommandBase() {
override fun getCommandName(): String {
return CommandCoreML.name
}
override fun getUsage(sender: ICommandSender): String {
override fun getCommandUsage(sender: ICommandSender): String {
return CommandCoreML.usage
}
override fun getAliases(): List<String> {
override fun getCommandAliases(): List<String> {
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()) {
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 reply = CommandCoreML.execute(args, sender.name, uuid)
if (reply.isNotEmpty() && sender.sendCommandFeedback()) {
sender.sendMessage(TextComponentString(reply))
sender.addChatMessage(TextComponentString(reply))
}
}
}

View File

@ -1,5 +1,6 @@
package matterlink.command
import kotlinx.coroutines.runBlocking
import matterlink.bridge.command.IBridgeCommand
import matterlink.bridge.command.IMinecraftCommandSender
import net.minecraft.command.CommandResultStats
@ -17,11 +18,11 @@ 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,
op: Boolean
) : IMinecraftCommandSender(user, env, op), ICommandSender {
override fun execute(cmdString: String): Boolean = runBlocking {
return@runBlocking 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand(
this@MatterLinkCommandSender,
cmdString
).apply {
sendReply(cmdString)
@ -38,7 +39,7 @@ class MatterLinkCommandSender(
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
return canExecute(commandName)
}
@ -47,7 +48,7 @@ class MatterLinkCommandSender(
return FMLCommonHandler.instance().minecraftServerInstance
}
override fun sendMessage(@Nonnull component: ITextComponent?) {
override fun addChatMessage(@Nonnull component: ITextComponent?) {
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'
}
}
stage("1.10.2") {
stage("1.9.4") {
steps {
sh './gradlew :1.10.2:setupCiWorkspace'
sh './gradlew :1.10.2:clean'
sh './gradlew :1.10.2:build'
archiveArtifacts artifacts: '1.10.2/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'
sh './gradlew :1.9.4:setupCiWorkspace'
sh './gradlew :1.9.4:clean'
sh './gradlew :1.9.4:build'
archiveArtifacts artifacts: '1.9.4/build/libs/*jar'
}
}
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.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.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)
[![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)
## Dependencies
@ -167,6 +163,13 @@ matterbridge
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()
}
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 {
apply plugin: "kotlin"
apply plugin: "kotlinx-serialization"
apply plugin: "idea"
if (System.env.BUILD_NUMBER) {
mod_version += "-${System.env.BUILD_NUMBER}"
modVersion += "-${System.env.BUILD_NUMBER}"
} else if (!project.hasProperty('release')) {
// mod_version += "-dev"
mod_version += "-dev"
// modVersion += "-dev"
modVersion += "-dev"
}
idea {
@ -35,19 +37,28 @@ subprojects {
}
repositories {
jcenter()
mavenCentral()
maven {
name = "unascribed"
url = 'http://unascribed.com/maven/releases'
name = "bintray"
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 {
name = "shadowfacts"
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 {
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()
}
dependencies {
// classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath group: "com.github.jengelman.gradle.plugins", name: "shadow", version: shadow_version
classpath group: "org.jetbrains.kotlin", name: "kotlin-gradle-plugin", version: kotlinVersion
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: "com.github.johnrengelman.shadow"
apply plugin: "com.vanniktech.dependency.graph.generator"
sourceCompatibility = targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
dependencies {
compile project(':api')
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: '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 {
@ -37,10 +47,12 @@ compileKotlin {
}
shadowJar {
classifier ''
classifier = ''
dependencies {
include project(":api")
include project(":Jankson")
}
relocate 'blue.endless', 'matterlink.repack.blue.endless'
relocate 'com.github', 'matterlink.repack.com.github'
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.config.cfg
import matterlink.update.UpdateChecker
import org.apache.logging.log4j.Logger
import java.util.*
import java.util.UUID
lateinit var logger: Logger
lateinit var instance: IMatterLink
lateinit var logger: Logger
abstract class IMatterLink {
abstract val mcVersion: String
@ -18,7 +18,11 @@ abstract class IMatterLink {
abstract val buildNumber: Int
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)
@ -28,46 +32,19 @@ abstract class IMatterLink {
abstract fun nameToUUID(username: String): UUID?
abstract fun uuidToName(uuid: UUID): String?
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)
// }
// }
suspend fun start() {
MessageHandlerInst.logger = logger
serverStartTime = System.currentTimeMillis()
if (cfg.connect.autoConnect)
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")
}
// 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
*/
@ -98,4 +75,6 @@ abstract class IMatterLink {
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
import blue.endless.jankson.Jankson
import blue.endless.jankson.JsonObject
import blue.endless.jankson.impl.Marshaller
import java.net.HttpURLConnection
import java.net.URL

View File

@ -8,9 +8,7 @@ import blue.endless.jankson.impl.Marshaller
import matterlink.config.cfg
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.Thread.yield
import java.util.*
import kotlin.streams.asSequence
private const val ZWSP: Char = '\u200b'
@ -67,24 +65,41 @@ fun randomString(length: Int = 6): String =
java.util.UUID.randomUUID().toString().replace("-", "").take(length)
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")
return putDefault(key, default, comment)!!
logger.trace("type: ${default.javaClass.name} key: $key json: >>>${this.getObject(key)?.toJson()}<<< default: $default")
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(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>? {
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,16 +1,33 @@
package matterlink.bridge
import matterlink.*
import matterlink.Paste
import matterlink.PasteSection
import matterlink.PasteUtil
import matterlink.antiping
import matterlink.api.ApiMessage
import matterlink.api.MessageHandler
import matterlink.config.cfg
import matterlink.handlers.ChatEvent
import matterlink.handlers.LocationHandler
import matterlink.mapFormat
import matterlink.stackTraceString
object MessageHandlerInst : MessageHandler() {
override fun transmit(msg: ApiMessage) {
override suspend fun transmit(msg: ApiMessage) {
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()) {
msg.username = cfg.outgoing.systemUser
@ -18,8 +35,10 @@ object MessageHandlerInst : MessageHandler() {
msg.avatar = cfg.outgoing.avatar.systemUserAvatar
}
}
if (msg.gateway.isEmpty())
msg.gateway = cfg.connect.gateway
if (msg.gateway.isEmpty()) {
logger.error("dropped message '$msg' due to missing gateway")
return
}
if (msg.text.lines().count() >= maxLines) {
try {
@ -38,6 +57,7 @@ object MessageHandlerInst : MessageHandler() {
msg.text = msg.text.substringBefore('\n')
.take(25) + "... " + response.link
} catch (e: Exception) {
logger.error(cause)
logger.error(e.stackTraceString)
}
}

View File

@ -5,7 +5,7 @@ import matterlink.config.IdentitiesConfig
import matterlink.config.cfg
import matterlink.instance
import matterlink.randomString
import java.util.*
import java.util.UUID
object AuthBridgeCommand : IBridgeCommand() {
val syntax = "Syntax: auth [username]"
@ -13,7 +13,7 @@ object AuthBridgeCommand : IBridgeCommand() {
override val permLevel: Double
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) {
env.respond("please initiate authentication from linked external chat")
return true
@ -28,8 +28,10 @@ object AuthBridgeCommand : IBridgeCommand() {
val argList = args.split(' ', limit = 2)
val target = argList.getOrNull(0) ?: run {
env.respond("no username/uuid provided\n" +
syntax)
env.respond(
"no username/uuid provided\n" +
syntax
)
return true
}
@ -62,7 +64,16 @@ object AuthBridgeCommand : IBridgeCommand() {
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")
return true

View File

@ -1,15 +1,13 @@
package matterlink.bridge.command
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.CommandConfig
import matterlink.config.IdentitiesConfig
import matterlink.config.PermissionConfig
import matterlink.config.cfg
import matterlink.instance
import matterlink.logger
import matterlink.stripColorOut
import java.util.*
import java.util.HashMap
import java.util.UUID
object BridgeCommandRegistry {
@ -19,7 +17,7 @@ object BridgeCommandRegistry {
*
* @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 (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 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 {
if (!it.reachedTimeout()) {
logger.debug("dropped command ${it.alias}")
@ -46,7 +50,7 @@ object BridgeCommandRegistry {
} ?: 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 (text[0] != cfg.command.prefix || text.length < 2) return false

View File

@ -17,7 +17,7 @@ data class CustomCommand(
val argumentsRegex: Regex? = null
) : 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) {
logger.debug("testing '$args' against '${argumentsRegex.pattern}'")
if (!argumentsRegex.matches(args)) {
@ -35,8 +35,10 @@ data class CustomCommand(
commandSender.execute(cmd) || commandSender.reply.isNotEmpty()
}
CommandType.RESPONSE -> {
env.respond(response?.lazyFormat(getReplacements(user, env, args))
?: "", cause = "response to command: $alias")
env.respond(
response?.lazyFormat(getReplacements(user, env, args))
?: "", cause = "response to command: $alias"
)
true
}

View File

@ -1,16 +1,13 @@
package matterlink.bridge.command
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.cfg
import matterlink.stripColorOut
object HelpCommand : IBridgeCommand() {
override val help: String = "Returns the help string for the given command. Syntax: help <command>"
override val permLevel: Double
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 {
args.isEmpty() ->
"Available commands: ${BridgeCommandRegistry.getCommandList(IBridgeCommand.getPermLevel(env.uuid))}"

View File

@ -8,7 +8,7 @@ import matterlink.handlers.TickHandler
import matterlink.instance
import matterlink.logger
import matterlink.stripColorOut
import java.util.*
import java.util.UUID
abstract class IBridgeCommand {
abstract val help: String
@ -23,6 +23,7 @@ abstract class IBridgeCommand {
val name: String,
val userId: String,
val platform: String,
val gateway: String,
override val uuid: UUID?
) : CommandEnvironment() {
override val username: String?
@ -34,11 +35,12 @@ abstract class IBridgeCommand {
override val uuid: UUID
) : CommandEnvironment()
fun respond(text: String, cause: String = "") {
suspend fun respond(text: String, cause: String = "") {
when (this) {
is BridgeEnv -> {
MessageHandlerInst.transmit(
ApiMessage(
gateway = this.gateway,
text = text.stripColorOut
),
cause = cause
@ -70,7 +72,7 @@ abstract class IBridgeCommand {
*
* @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 {
logger.trace("canExecute this: $this uuid: $uuid permLevel: $permLevel")

View File

@ -1,7 +1,5 @@
package matterlink.bridge.command
import matterlink.stripColorOut
abstract class IMinecraftCommandSender(val user: String, val env: IBridgeCommand.CommandEnvironment, val op: Boolean) {
/**
* @param cmdString The command to execute with its arguments
@ -37,7 +35,7 @@ abstract class IMinecraftCommandSender(val user: String, val env: IBridgeCommand
reply += text
}
fun sendReply(cmdString: String) {
suspend fun sendReply(cmdString: String) {
env.respond(
text = reply.joinToString("\n"),
cause = "executed command: $cmdString"

View File

@ -11,7 +11,7 @@ object RequestPermissionsCommand : IBridgeCommand() {
override val permLevel: Double
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
if (uuid == null) {
@ -23,8 +23,10 @@ object RequestPermissionsCommand : IBridgeCommand() {
val requestedLevelArg = argList.getOrNull(0)
val requestedLevel = requestedLevelArg?.takeIf { it.isNotEmpty() }?.let {
it.toDoubleOrNull() ?: run {
env.respond("cannot parse permlevel '$requestedLevelArg'\n" +
syntax)
env.respond(
"cannot parse permlevel '$requestedLevelArg'\n" +
syntax
)
return true
}
}
@ -33,7 +35,10 @@ object RequestPermissionsCommand : IBridgeCommand() {
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]`")
return true

View File

@ -1,11 +1,6 @@
package matterlink.command
import matterlink.bridge.MessageHandlerInst
import matterlink.bridge.command.BridgeCommandRegistry
import matterlink.config.IdentitiesConfig
import matterlink.config.PermissionConfig
import matterlink.config.baseCfg
import matterlink.config.cfg
object CommandCoreAuth {
val name = "auth"
@ -38,7 +33,13 @@ object CommandCoreAuth {
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)
"${request.userid} on ${request.platform} is now identified as $user"

View File

@ -13,7 +13,7 @@ object CommandCoreML {
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()
return when (cmd) {
@ -45,10 +45,10 @@ object CommandCoreML {
return "no code passed"
}
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 powerLevel = powerLevelArg ?: run { return "permLevel cannot be parsed" }
val powerLevelArg = args.getOrNull(3)?.toDoubleOrNull()
val powerLevel = powerLevelArg ?: run { return "permLevel cannot be parsed: ${args.getOrNull(3)}" }
?: request.powerlevel
?: return "no permLevel provided"
PermissionConfig.add(request.uuid, powerLevel, "${request.user} Authorized by $user")

View File

@ -4,9 +4,13 @@ import blue.endless.jankson.Jankson
import blue.endless.jankson.JsonObject
import blue.endless.jankson.impl.Marshaller
import blue.endless.jankson.impl.SyntaxError
import matterlink.Area
import matterlink.bridge.MessageHandlerInst
import matterlink.getOrDefault
import matterlink.getOrPutList
import matterlink.getReifiedOrDelete
import matterlink.logger
import matterlink.registerSerializer
import matterlink.registerTypeAdapter
import matterlink.stackTraceString
import java.io.File
@ -20,18 +24,88 @@ data class BaseConfig(val rootDir: File) {
val configFile: File = cfgDirectory.resolve("matterlink.hjson")
init {
logger.info("Reading bridge blueprints... from {}", rootDir)
logger.info("Reading bridge blueprints... from $rootDir")
baseCfg = this
}
data class MatterLinkConfig(
val connect: ConnectOptions = ConnectOptions(),
val outgoingDefaults: DefaultSettingsOutgoing = DefaultSettingsOutgoing(),
val incomingDefaults: DefaultSettingsIncoming = DefaultSettingsIncoming(),
val locations: List<Location> = listOf(
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(
val prefix: Char = '!',
@ -45,7 +119,6 @@ data class BaseConfig(val rootDir: File) {
data class ConnectOptions(
val url: String = "http://localhost:4242",
val authToken: String = "",
val gateway: String = "minecraft",
val autoConnect: Boolean = true,
val reconnectWait: Long = 500
)
@ -104,21 +177,30 @@ data class BaseConfig(val rootDir: File) {
"thrown" to arrayOf("彡°"),
"thorns" to arrayOf("\uD83C\uDF39"), //🌹
"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(
val enable: Boolean = true,
val urlTemplate: String = "https://visage.surgeplay.com/head/512/{uuid}",
// https://www.freepik.com/free-icon/right-arrow-angle-and-horizontal-down-line-code-signs_732795.htm
val systemUserAvatar: String = "https://image.freepik.com/free-icon/right-arrow-angle-and-horizontal-down-line-code-signs_318-53994.jpg"
val urlTemplate: String = "https://mc-heads.net/head/{uuid}",
val systemUserAvatar: String = ""
)
data class JoinPartOptions(
val enable: Boolean = true,
val joinServer: String = "{username:antiping} has connected to the platform",
val partServer: String = "{username:antiping} has disconnected from the platform"
val joinServer: String = "{username:antiping} has connected to the server",
val partServer: String = "{username:antiping} has disconnected from the server"
)
data class UpdateOptions(
@ -129,20 +211,36 @@ data class BaseConfig(val rootDir: File) {
val jankson = Jankson
.builder()
.registerTypeAdapter {
with(MatterLinkConfig()) {
MatterLinkConfig(
command = it.getOrDefault(
"command",
CommandOptions(),
command,
"User commands"
),
outgoingDefaults = it.getOrDefault(
"outgoingDefaults",
outgoingDefaults,
"default settings for outgoing"
),
incomingDefaults = it.getOrDefault(
"incomingDefaults",
incomingDefaults,
"default settings for incoming"
),
locations = it.getOrPutList(
"locations",
locations,
"list of fixed chat locations"
),
connect = it.getOrDefault(
"connect",
ConnectOptions(),
connect,
"Connection Settings"
),
incoming = it.getOrDefault(
"incoming",
IncomingOptions(),
incoming,
"""
Gateway -> Server
Options all about receiving messages from the API
@ -152,7 +250,7 @@ data class BaseConfig(val rootDir: File) {
),
outgoing = it.getOrDefault(
"outgoing",
OutgoingOptions(),
outgoing,
"""
Server -> Gateway
Options all about sending messages to the API
@ -160,11 +258,146 @@ data class BaseConfig(val rootDir: File) {
),
update = it.getOrDefault(
"update",
UpdateOptions(),
update,
"Update Settings"
)
)
}
}
.registerTypeAdapter {
with(DefaultSettingsOutgoing()) {
DefaultSettingsOutgoing(
plain = it.getOrDefault(
"plain",
plain,
"plain text messages"
),
action = it.getOrDefault(
"action",
action,
"action messages"
),
join = it.getOrDefault(
"join",
join,
"handle join event"
),
leave = it.getOrDefault(
"leave",
leave,
"handle leave events"
),
advancement = it.getOrDefault(
"advancement",
advancement,
"handle advancement events"
),
death = it.getOrDefault(
"death",
death,
"handle death events"
),
broadcast = it.getOrDefault(
"broadcast",
broadcast,
"handle broadcast command"
),
status = it.getOrDefault(
"status",
status,
"handles tatus updates"
)
)
}
}
.registerTypeAdapter {
with(SettingsOutgoing()) {
SettingsOutgoing(
plain = it.getReifiedOrDelete("plain", "transmit join events"),
action = it.getReifiedOrDelete("action", "transmit join events"),
join = it.getReifiedOrDelete("join", "transmit join events"),
leave = it.getReifiedOrDelete("leave", "transmit leave events"),
advancement = it.getReifiedOrDelete("advancement", "transmit advancements"),
death = it.getReifiedOrDelete("death", "transmit death messages"),
broadcast = it.getReifiedOrDelete("say", "transmit broadcasts"),
status = it.getReifiedOrDelete("status", "transmit status updates"),
skip = it.getOrPutList(
"skip",
skip,
"list of other locations to ignore after handling this"
)
)
}
}
.registerTypeAdapter {
with(DefaultSettingsIncoming()) {
DefaultSettingsIncoming(
plain = it.getOrDefault(
"plain",
plain,
"plain text messages"
),
action = it.getOrDefault(
"action",
action,
"action messages"
),
join_leave = it.getOrDefault(
"join_leave",
join_leave,
"handle join/leave event"
),
commands = it.getOrDefault(
"commands",
join_leave,
"receive commands"
)
)
}
}
.registerTypeAdapter {
with(SettingsIncoming()) {
SettingsIncoming(
plain = it.getReifiedOrDelete("plain", "transmit join events"),
action = it.getReifiedOrDelete("action", "transmit join events"),
join_leave = it.getReifiedOrDelete("join_leave", "transmit join_leave events"),
commands = it.getReifiedOrDelete("commands", "receive commands"),
skip = it.getOrPutList(
"skip",
skip,
"list of other locations to ignore after handling this"
)
)
}
}
.registerTypeAdapter {
with(Location()) {
Location(
label = it.getOrDefault(
"label",
label,
"location label for identification"
),
gateway = it.getOrDefault(
"gateway",
gateway,
"matterbridge gateway identifier"
),
area = Area.parse(it.getObject("area") ?: JsonObject()),
outgoing = it.getOrDefault(
"outgoing",
outgoing,
"Location outgoing settings"
),
incoming = it.getOrDefault(
"incoming",
incoming,
"incoming settings"
)
)
}
}
.registerTypeAdapter {
with(CommandOptions()) {
CommandOptions(
@ -214,15 +447,15 @@ data class BaseConfig(val rootDir: File) {
authToken,
"Auth token used to connect to the bridge platform"
),
gateway = it.getOrDefault(
"gateway",
gateway,
"MatterBridge gateway"
),
autoConnect = it.getOrDefault(
"autoConnect",
autoConnect,
"Connect the relay on startup"
),
reconnectWait = it.getOrDefault(
"reconnectWait",
reconnectWait,
"base delay in milliseconds between attempting reconnects"
)
)
}
@ -329,7 +562,7 @@ data class BaseConfig(val rootDir: File) {
"damageTypMapping",
"Damage type mapping for death cause"
)
it.mapValues { (key, element) ->
it.mapValues { (key, _) ->
it.getOrDefault(key, damageTypeMapping[key] ?: emptyArray(), key)
.apply { it[key] }.apply {
jsonObj["damageTypeMapping"] = it
@ -351,6 +584,11 @@ data class BaseConfig(val rootDir: File) {
"urlTemplate",
urlTemplate,
"template for constructing the user avatar url using the uuid"
),
systemUserAvatar = it.getOrDefault(
"systemUserAvatar",
systemUserAvatar,
"avatar url for the system user"
)
)
}
@ -389,7 +627,38 @@ data class BaseConfig(val rootDir: File) {
)
}
}
.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 {
@ -417,15 +686,15 @@ data class BaseConfig(val rootDir: File) {
logger.error("error parsing config: ${e.completeMessage} ")
logger.error(e.stackTraceString)
cfgDirectory.resolve("error.matterlink.hjson").writeText(jsonObject.toJson(false, true))
MatterLinkConfig()
if (::cfg.isInitialized) cfg else MatterLinkConfig()
} catch (e: IllegalStateException) {
logger.error(e.stackTraceString)
cfgDirectory.resolve("error.matterlink.hjson").writeText(jsonObject.toJson(false, true))
MatterLinkConfig()
if (::cfg.isInitialized) cfg else MatterLinkConfig()
} catch (e: NullPointerException) {
logger.error("error loading config: ${e.stackTraceString}")
cfgDirectory.resolve("error.matterlink.hjson").writeText(jsonObject.toJson(false, true))
MatterLinkConfig()
if (::cfg.isInitialized) cfg else MatterLinkConfig()
}
// val defaultJsonObject = jankson.load("{}")
@ -434,7 +703,6 @@ data class BaseConfig(val rootDir: File) {
MessageHandlerInst.config.url = tmpCfg.connect.url
MessageHandlerInst.config.token = tmpCfg.connect.authToken
MessageHandlerInst.config.gateway = tmpCfg.connect.gateway
MessageHandlerInst.config.reconnectWait = tmpCfg.connect.reconnectWait
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.JsonPrimitive
import blue.endless.jankson.impl.SyntaxError
import matterlink.*
import matterlink.bridge.command.CommandType
import matterlink.bridge.command.CustomCommand
import matterlink.getOrDefault
import matterlink.logger
import matterlink.registerPrimitiveTypeAdapter
import matterlink.registerTypeAdapter
import java.io.File
import java.io.FileNotFoundException

View File

@ -11,7 +11,7 @@ import matterlink.logger
import matterlink.stackTraceString
import java.io.File
import java.io.FileNotFoundException
import java.util.*
import java.util.UUID
import java.util.concurrent.TimeUnit
typealias IdentMap = Map<String, Map<String, List<String>>>

View File

@ -6,11 +6,10 @@ import blue.endless.jankson.impl.SyntaxError
import com.google.common.cache.Cache
import com.google.common.cache.CacheBuilder
import matterlink.getReified
import matterlink.instance
import matterlink.logger
import java.io.File
import java.io.FileNotFoundException
import java.util.*
import java.util.UUID
import java.util.concurrent.TimeUnit
typealias PermissionMap = Map<String, Double>

View File

@ -1,35 +1,38 @@
package matterlink.handlers
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.bridge.command.BridgeCommandRegistry
import matterlink.config.cfg
import matterlink.instance
import matterlink.logger
import matterlink.stripColorOut
import java.util.*
import java.util.UUID
object ChatProcessor {
/**
* @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()
if (uuid != null && BridgeCommandRegistry.handleCommand(message, user, uuid)) return true
when {
message.isNotBlank() -> MessageHandlerInst.transmit(
ApiMessage(
username = user.stripColorOut,
text = message.stripColorOut,
event = event
).apply {
if(cfg.outgoing.avatar.enable) {
if(uuid != null)
avatar = cfg.outgoing.avatar.urlTemplate.replace("{uuid}", uuid.toString())
}
},
cause = "Message from $user"
message.isNotBlank() -> LocationHandler.sendToLocations(
user = user,
msg = message,
x = x, y = y, z = z, dimension = dimension,
event = event,
cause = "Message from $user",
uuid = uuid
)
else -> logger.warn("WARN: dropped blank message by '$user'")
}
return false

View File

@ -1,19 +1,19 @@
package matterlink.handlers
import matterlink.antiping
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.cfg
import matterlink.stripColorOut
import java.util.*
import java.util.Random
object DeathHandler {
private val random = Random()
fun handleDeath(
suspend fun handleDeath(
player: String,
deathMessage: String,
damageType: String
damageType: String,
x: Int, y: Int, z: Int,
dimension: Int
) {
if (cfg.outgoing.death.enable) {
var msg = deathMessage.stripColorOut.replace(player, player.stripColorOut.antiping)
@ -23,7 +23,13 @@ object DeathHandler {
val damageEmoji = emojis[random.nextInt(emojis.size)]
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,15 +1,16 @@
package matterlink.handlers
import matterlink.antiping
import matterlink.api.ApiMessage
import matterlink.api.ApiMessage.Companion.JOIN_LEAVE
import matterlink.bridge.MessageHandlerInst
import matterlink.config.cfg
import matterlink.mapFormat
import matterlink.stripColorOut
object JoinLeaveHandler {
fun handleJoin(player: String) {
suspend fun handleJoin(
player: String,
x: Int, y: Int, z: Int,
dimension: Int
) {
if (cfg.outgoing.joinPart.enable) {
val msg = cfg.outgoing.joinPart.joinServer.mapFormat(
mapOf(
@ -17,17 +18,21 @@ object JoinLeaveHandler {
"{username:antiping}" to player.stripColorOut.antiping
)
)
MessageHandlerInst.transmit(
ApiMessage(
text = msg,
event = JOIN_LEAVE
),
LocationHandler.sendToLocations(
msg = msg,
x = x, y = y, z = z, dimension = dimension,
event = ChatEvent.JOIN,
systemuser = true,
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) {
val msg = cfg.outgoing.joinPart.partServer.mapFormat(
mapOf(
@ -35,11 +40,11 @@ object JoinLeaveHandler {
"{username:antiping}" to player.stripColorOut.antiping
)
)
MessageHandlerInst.transmit(
ApiMessage(
text = msg,
event = JOIN_LEAVE
),
LocationHandler.sendToLocations(
msg = msg,
x = x, y = y, z = z, dimension = dimension,
event = ChatEvent.JOIN,
systemuser = true,
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
import matterlink.antiping
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.cfg
import matterlink.stripColorOut
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
val usr = name.stripColorOut.antiping
MessageHandlerInst.transmit(
ApiMessage(
text = "$usr $message $display".stripColorOut
),
cause = "Progress Event by $usr"
LocationHandler.sendToLocations(
msg = "$usr $message $display".stripColorOut,
x = x, y = y, z = z, dimension = dimension,
event = ChatEvent.ADVANCEMENT,
cause = "Progress Event by $usr",
systemuser = true
)
}
}

View File

@ -1,26 +1,80 @@
package matterlink.handlers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.bridge.command.BridgeCommandRegistry
import matterlink.bridge.format
import matterlink.config.cfg
import matterlink.instance
import matterlink.logger
import matterlink.stripColorIn
import java.util.UUID
object ServerChatHandler {
@UseExperimental(ExperimentalCoroutinesApi::class)
val rcvChannel = MessageHandlerInst.broadcast.openSubscription()
/**
* This method must be called every server tick with no arguments.
*/
fun writeIncomingToChat() {
if (MessageHandlerInst.queue.isNotEmpty())
logger.debug("incoming: " + MessageHandlerInst.queue.toString())
val nextMessage = MessageHandlerInst.queue.poll() ?: null
suspend fun writeIncomingToChat() {
val nextMessage = rcvChannel.poll() ?: return
val defaults = cfg.incomingDefaults
val sourceGateway = nextMessage.gateway
// find all areas to send to
val targetUUIDs = mutableSetOf<UUID>()
val skips = mutableSetOf<String>()
val locations = cfg.locations.filter {
it.gateway == sourceGateway
}
if (nextMessage.event.isEmpty()) {
// filter command handlers
val commandLocations = locations.filter {
it.incoming.commands ?: cfg.incomingDefaults.commands
}
// process potential command
for ((label, location) in commandLocations) {
if (BridgeCommandRegistry.handleCommand(nextMessage)) return
}
}
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))
}
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)
"" -> {
@ -41,8 +95,38 @@ object ServerChatHandler {
return
}
}
instance.wrappedSendToPlayers(message)
}
}
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
import matterlink.bridge.MessageHandlerInst
import matterlink.update.UpdateChecker
/**
@ -13,17 +12,17 @@ object TickHandler {
private set
private var accumulator = 0
private const val updateInterval = 12 * 60 * 60 * 20
fun handleTick() {
suspend fun handleTick() {
tickCounter++
if (tickCounter % 100 == 0) {
MessageHandlerInst.checkConnection()
}
// if (tickCounter % 100 == 0) {
// MessageHandlerInst.checkConnection()
// }
ServerChatHandler.writeIncomingToChat()
if (accumulator++ > updateInterval) {
accumulator -= updateInterval
UpdateChecker.run()
UpdateChecker.check()
}
}
}

View File

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

View File

@ -1,11 +1,11 @@
package matterlink.jenkins
import com.google.gson.Gson
import matterlink.*
import matterlink.Result
import matterlink.header
import matterlink.httpGet
import matterlink.responseString
import com.github.kittinunf.fuel.httpGet
import com.github.kittinunf.fuel.serialization.kotlinxDeserializerOf
import com.github.kittinunf.result.Result
import kotlinx.serialization.json.JSON
import matterlink.logger
/**
* Created by nikky on 03/02/18.
@ -21,10 +21,10 @@ data class Build(
val (request, response, result) = "$url/api/json"
.httpGet()
.header("User-Agent" to userAgent)
.responseString()
.responseObject(kotlinxDeserializerOf(loader = BuildWithDetails.serializer(), json = JSON.nonstrict))
return when (result) {
is Result.Success -> {
gson.fromJson(result.value, BuildWithDetails::class.java)
result.value
}
is Result.Failure -> {
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
import java.util.*
import kotlinx.serialization.Serializable
import java.util.Date
//@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class BuildWithDetails(
val number: Int,
val url: String,
val artifacts: List<Artifact>,
// @JsonFormat(shape=JsonFormat.Shape.NUMBER, pattern="s")
val timestamp: Date
)

View File

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

View File

@ -1,11 +1,13 @@
package matterlink.jenkins;
package matterlink.jenkins
import kotlinx.serialization.Serializable
/**
* Created by nikky on 03/02/18.
* @author Nikky
*/
//@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Job(
val url: String,
val name: String,

View File

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

View File

@ -1,31 +1,33 @@
package matterlink.update
import com.google.gson.FieldNamingPolicy
import com.google.gson.GsonBuilder
import com.github.kittinunf.fuel.core.extensions.cUrlString
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.bridge.MessageHandlerInst
import matterlink.config.cfg
import matterlink.handlers.ChatEvent
import matterlink.handlers.LocationHandler
import matterlink.instance
import matterlink.jenkins.JenkinsServer
import matterlink.logger
import voodoo.util.jenkins.JenkinsServer
import java.io.BufferedReader
import java.net.HttpURLConnection
import java.net.URL
import com.github.kittinunf.fuel.serialization.kotlinxDeserializerOf
import com.github.kittinunf.result.Result
import kotlinx.serialization.json.JSON
class UpdateChecker : Thread() {
companion object {
fun run() {
object UpdateChecker : CoroutineScope {
override val coroutineContext = Job() + CoroutineName("UpdateChacker")
suspend fun check() {
if (cfg.update.enable) {
UpdateChecker().start()
}
run()
}
}
init {
name = "UpdateCheckerThread"
}
override fun run() {
private suspend fun run() {
if (instance.buildNumber > 0) {
val server = JenkinsServer("https://ci.elytradev.com")
val job = server.getJob("elytra/MatterLink/master", "MatterLink/${instance.modVersion}")
@ -42,11 +44,12 @@ class UpdateChecker : Thread() {
when {
number > instance.buildNumber -> {
logger.warn("Mod out of date! New build $number available at $url")
val difference = number - build.number
MessageHandlerInst.transmit(
ApiMessage(
text = "MatterLink out of date! You are $difference builds behind! Please download new version from $url"
)
val difference = number - instance.buildNumber
LocationHandler.sendToLocations(
msg = "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}")
@ -60,40 +63,30 @@ class UpdateChecker : Thread() {
return
}
val gson = GsonBuilder()
// .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.create()
logger.info("Checking for new versions...")
val url = URL("https://staging_cursemeta.dries007.net/api/v3/direct/addon/287323/files")
val con = url.openConnection() as HttpURLConnection
with(instance) {
val useragent = "MatterLink/$modVersion MinecraftForge/$mcVersion-$forgeVersion (https://github.com/elytra/MatterLink)"
val (request, response, result) = with(instance) {
val useragent =
"MatterLink/$modVersion MinecraftForge/$mcVersion-$forgeVersion (https://github.com/elytra/MatterLink)"
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 = if (200 == con.responseCode) { //HTTP 200 OK
val buffer: BufferedReader = con.inputStream.bufferedReader()
//put all of the buffer content onto the string
val content = buffer.readText()
logger.trace("updateData: $content")
gson.fromJson(content, Array<CurseFile>::class.java)
.filter {
it.fileStatus == "SemiNormal" && it.gameVersion.contains(instance.mcVersion)
val apiUpdateList = when(result) {
is Result.Success -> {
result.value
}
.sortedByDescending { it.fileName.substringAfterLast(" ") }
} else {
is Result.Failure -> {
logger.error("Could not check for updates!")
logger.error("cUrl: ${request.cUrlString()}")
logger.error("request: $request")
logger.error("response: $response")
return
}
}
.filter { it.fileStatus == "SemiNormal" && it.gameVersion.contains(instance.mcVersion) }
val modVersionChunks = instance.modVersion
.substringBefore("-dev")
@ -131,13 +124,13 @@ class UpdateChecker : Thread() {
logger.info("Matterlink out of date! You are $count $version behind")
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(
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
mod_version = 1.6.3
forgelin_version = 1.6.0
kotlin_version = 1.2.41
shadow_version = 2.0.2
cursegradle_version = 1.0.10
curse_id = 287323
curse_release_type = beta
modName = MatterLink
modVersion = 1.6.4
forgelinVersion = 1.8.2
kotlinVersion = 1.3.10
coroutinesVersion = 1.0.1
serializationVersion = 0.9.1
shadowVersion = 2.0.2
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'
include 'core', 'api'
include 'core'
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'