rename
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("com.github.johnrengelman.shadow")
|
||||
}
|
||||
|
||||
group = "cc.maxmc.msm.controlled"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://repo.papermc.io/repository/maven-public/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-Beta")
|
||||
implementation(project(":common"))
|
||||
compileOnly("io.netty:netty-all:4.1.90.Final")
|
||||
@Suppress("VulnerableLibrariesLocal") compileOnly("io.github.waterfallmc:waterfall-api:1.19-R0.1-SNAPSHOT")
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
archiveClassifier.set(null as? String?)
|
||||
relocate("kotlin", "cc.maxmc.msm.lib.kotlin")
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
dependsOn(tasks.shadowJar)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cc.maxmc.msm.controlled
|
||||
|
||||
import cc.maxmc.msm.api.MultiServerManAPIProvider
|
||||
import cc.maxmc.msm.controlled.api.APIImpl
|
||||
import cc.maxmc.msm.controlled.listener.APIPacketListener
|
||||
import cc.maxmc.msm.controlled.command.Api
|
||||
import cc.maxmc.msm.controlled.command.Send
|
||||
import cc.maxmc.msm.controlled.listener.ProtocolListener
|
||||
import cc.maxmc.msm.controlled.netty.NetClient
|
||||
import cc.maxmc.msm.controlled.settings.Settings
|
||||
import cc.maxmc.msm.controlled.settings.SettingsReader
|
||||
import cc.maxmc.msm.common.network.netty.NetworkRegistry
|
||||
import net.md_5.bungee.api.ProxyServer
|
||||
import net.md_5.bungee.api.plugin.Plugin
|
||||
|
||||
class MultiServerMan : Plugin() {
|
||||
init {
|
||||
instance = this
|
||||
}
|
||||
|
||||
override fun onEnable() {
|
||||
ProxyServer.getInstance().pluginManager.registerCommand(this, Send)
|
||||
ProxyServer.getInstance().pluginManager.registerCommand(this, Api)
|
||||
ProxyServer.getInstance().pluginManager.registerListener(this, APIPacketListener)
|
||||
ProxyServer.getInstance().pluginManager.registerListener(this, ProtocolListener)
|
||||
SettingsReader
|
||||
NetworkRegistry
|
||||
NetClient.init(Settings.Parent.address, Settings.Parent.port)
|
||||
MultiServerManAPIProvider.register(APIImpl)
|
||||
}
|
||||
|
||||
override fun onDisable() {
|
||||
NetClient.shutdown()
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var instance: MultiServerMan
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package cc.maxmc.msm.controlled.api
|
||||
|
||||
import cc.maxmc.msm.api.MultiServerManAPI
|
||||
import cc.maxmc.msm.api.misc.MatchInfo
|
||||
import cc.maxmc.msm.controlled.netty.NetClient
|
||||
import cc.maxmc.msm.common.network.packet.PPacketAPICall
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
object APIImpl : MultiServerManAPI {
|
||||
val apiCallCache = HashMap<UUID, CompletableFuture<in Any>>()
|
||||
private const val TIMEOUT = 3L
|
||||
|
||||
override fun getServer(type: String, players: MutableList<String>): MatchInfo {
|
||||
val packet = PPacketAPICall.PPacketCallGetServer(type, players)
|
||||
NetClient.sendPacket(packet)
|
||||
val future = CompletableFuture<MatchInfo>()
|
||||
apiCallCache[packet.uid] = future as CompletableFuture<Any>
|
||||
return future.get(TIMEOUT, TimeUnit.SECONDS)
|
||||
}
|
||||
|
||||
override fun informEnd(id: Int) {
|
||||
val packet = PPacketAPICall.PPacketCallInformEnd(id)
|
||||
NetClient.sendPacket(packet)
|
||||
val future = CompletableFuture<Unit>()
|
||||
apiCallCache[packet.uid] = future as CompletableFuture<Any>
|
||||
future.get(TIMEOUT, TimeUnit.SECONDS)
|
||||
return
|
||||
}
|
||||
|
||||
override fun getPlayerServer(player: String): MatchInfo {
|
||||
val packet = PPacketAPICall.PPacketCallGetPlayerServer(player)
|
||||
NetClient.sendPacket(packet)
|
||||
val future = CompletableFuture<MatchInfo>()
|
||||
apiCallCache[packet.uid] = future as CompletableFuture<Any>
|
||||
return future.get(TIMEOUT, TimeUnit.SECONDS)
|
||||
}
|
||||
|
||||
override fun containPlayer(player: String): Boolean {
|
||||
val packet = PPacketAPICall.PPacketCallContainPlayer(player)
|
||||
NetClient.sendPacket(packet)
|
||||
val future = CompletableFuture<Boolean>()
|
||||
apiCallCache[packet.uid] = future as CompletableFuture<Any>
|
||||
return future.get(TIMEOUT, TimeUnit.SECONDS)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cc.maxmc.msm.controlled.command
|
||||
|
||||
import cc.maxmc.msm.api.MultiServerManAPIProvider
|
||||
import net.md_5.bungee.api.CommandSender
|
||||
import net.md_5.bungee.api.chat.TextComponent
|
||||
import net.md_5.bungee.api.plugin.Command
|
||||
|
||||
object Api : Command("api") {
|
||||
override fun execute(sender: CommandSender, args: Array<out String>) {
|
||||
sender.sendMessage(TextComponent("Calling API"))
|
||||
val api = args[0]
|
||||
val apiInst = MultiServerManAPIProvider.getAPI()
|
||||
@Suppress("IMPLICIT_CAST_TO_ANY") val ret = when (api) {
|
||||
"gs" -> {
|
||||
apiInst.getServer("def", listOf("abc", "def"))
|
||||
}
|
||||
|
||||
"ie" -> {
|
||||
apiInst.informEnd(100)
|
||||
}
|
||||
|
||||
"gps" -> {
|
||||
apiInst.getPlayerServer("def")
|
||||
}
|
||||
|
||||
"cp" -> {
|
||||
apiInst.containPlayer("awa")
|
||||
}
|
||||
|
||||
else -> {
|
||||
"non api exist"
|
||||
}
|
||||
}.toString()
|
||||
sender.sendMessage(TextComponent("Returning $ret"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cc.maxmc.msm.controlled.command
|
||||
|
||||
import cc.maxmc.msm.controlled.netty.NetClient
|
||||
import cc.maxmc.msm.common.network.packet.PPacketDebug
|
||||
import net.md_5.bungee.api.CommandSender
|
||||
import net.md_5.bungee.api.chat.TextComponent
|
||||
import net.md_5.bungee.api.plugin.Command
|
||||
|
||||
object Send : Command("debugP") {
|
||||
override fun execute(sender: CommandSender, args: Array<out String>) {
|
||||
val content = args.joinToString(" ")
|
||||
val packet = PPacketDebug(content)
|
||||
NetClient.sendPacket(packet)
|
||||
sender.sendMessage(TextComponent("Success."))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cc.maxmc.msm.controlled.listener
|
||||
|
||||
import cc.maxmc.msm.controlled.api.APIImpl
|
||||
import cc.maxmc.msm.common.event.PacketReceiveEvent
|
||||
import cc.maxmc.msm.common.network.packet.CPacketAPICallback
|
||||
import net.md_5.bungee.api.plugin.Listener
|
||||
import net.md_5.bungee.event.EventHandler
|
||||
|
||||
object APIPacketListener : Listener {
|
||||
@EventHandler
|
||||
fun onPacket(packetEvent: PacketReceiveEvent) {
|
||||
val packet = packetEvent.packet
|
||||
if (packet !is CPacketAPICallback) {
|
||||
return
|
||||
}
|
||||
val future = APIImpl.apiCallCache[packet.uid]
|
||||
when (packet) {
|
||||
is CPacketAPICallback.CPacketCallbackContainPlayer -> {
|
||||
future?.complete(packet.value)
|
||||
?: throw IllegalStateException("Packet callback received, however no request")
|
||||
}
|
||||
|
||||
is CPacketAPICallback.CPacketCallbackGetPlayerServer -> {
|
||||
future?.complete(packet.matchInfo)
|
||||
?: throw IllegalStateException("Packet callback received, however no request")
|
||||
}
|
||||
|
||||
is CPacketAPICallback.CPacketCallbackGetServer -> {
|
||||
future?.complete(packet.matchInfo)
|
||||
?: throw IllegalStateException("Packet callback received, however no request")
|
||||
}
|
||||
|
||||
is CPacketAPICallback.CPacketCallbackInformEnd -> {
|
||||
future?.complete(Unit)
|
||||
?: throw IllegalStateException("Packet callback received, however no request")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package cc.maxmc.msm.controlled.listener
|
||||
|
||||
import cc.maxmc.msm.controlled.MultiServerMan
|
||||
import cc.maxmc.msm.controlled.misc.SubServer
|
||||
import cc.maxmc.msm.controlled.netty.NetClient
|
||||
import cc.maxmc.msm.controlled.settings.Settings
|
||||
import cc.maxmc.msm.common.event.ChannelInactiveEvent
|
||||
import cc.maxmc.msm.common.event.PacketReceiveEvent
|
||||
import cc.maxmc.msm.common.network.packet.*
|
||||
import cc.maxmc.msm.common.utils.log
|
||||
import cc.maxmc.msm.common.utils.pluginScope
|
||||
import kotlinx.coroutines.launch
|
||||
import net.md_5.bungee.api.plugin.Listener
|
||||
import net.md_5.bungee.event.EventHandler
|
||||
import java.util.*
|
||||
import kotlin.io.path.isDirectory
|
||||
import kotlin.io.path.listDirectoryEntries
|
||||
import kotlin.io.path.name
|
||||
|
||||
object ProtocolListener : Listener {
|
||||
private val serverCache = HashMap<UUID, SubServer>()
|
||||
|
||||
@EventHandler
|
||||
fun onGetInfo(packetE: PacketReceiveEvent) {
|
||||
if (packetE.packet !is CPacketGetInfo) return
|
||||
val types = MultiServerMan.instance.dataFolder.toPath().resolve("patterns").listDirectoryEntries()
|
||||
.filter { it.isDirectory() }.map { it.name }.toMutableSet()
|
||||
val packetCallBack = PPacketChildInfo(Settings.portRange.toHashSet(), types)
|
||||
NetClient.sendPacket(packetCallBack)
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onRequestServer(packetE: PacketReceiveEvent) {
|
||||
val packet = packetE.packet as? CPacketRequestServer ?: return
|
||||
val serverInfo = packet.serverInfo
|
||||
log("§a| §7正在启动服务器 ${serverInfo.server}")
|
||||
val subServer = SubServer(serverInfo.uid, packet.type, serverInfo.server!!.split(":")[1].toInt())
|
||||
serverCache[serverInfo.uid] = subServer
|
||||
pluginScope.launch {
|
||||
try {
|
||||
subServer.initServer()
|
||||
subServer.startServer()
|
||||
} catch (e: Exception) {
|
||||
NetClient.sendPacket(PPacketServerStarted())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onDisconnect(evt: ChannelInactiveEvent) {
|
||||
log("§c| §7与远程服务器断开连接.")
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onServerEnd(evt: PacketReceiveEvent) {
|
||||
val packet = evt.packet as? CPacketEndServer ?: return
|
||||
val serverInfo = packet.server
|
||||
val server =
|
||||
serverCache[serverInfo.uid] ?: throw IllegalAccessError("Server ($serverInfo) is not in this sub bungee.")
|
||||
pluginScope.launch {
|
||||
server.cleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cc.maxmc.msm.controlled.misc
|
||||
|
||||
import cc.maxmc.msm.controlled.MultiServerMan
|
||||
import cc.maxmc.msm.controlled.settings.Settings
|
||||
import cc.maxmc.msm.controlled.utils.ScriptRunner
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.io.path.copyToRecursively
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.deleteRecursively
|
||||
|
||||
@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate") // API
|
||||
@OptIn(kotlin.io.path.ExperimentalPathApi::class)
|
||||
class SubServer(
|
||||
val uid: UUID, val type: String, val port: Int, baseFolder: Path = MultiServerMan.instance.dataFolder.toPath()
|
||||
) {
|
||||
private val serverFolder =
|
||||
baseFolder.resolve("cache").resolve(uid.toString().substring(0..7)).also { it.createDirectories() }
|
||||
private val patternFolder = baseFolder.resolve("patterns").resolve(type).also { it.createDirectories() }
|
||||
private val runner = ScriptRunner(serverFolder.resolve("start.sh"))
|
||||
|
||||
suspend fun initServer() = withContext(Dispatchers.IO) {
|
||||
patternFolder.copyToRecursively(serverFolder, followLinks = false, overwrite = true)
|
||||
}
|
||||
|
||||
suspend fun startServer() = suspendCoroutine {
|
||||
runner.launch(Settings.Patterns.arguments(type).joinToString(" ").replace("\${port}", port.toString()))
|
||||
runner.onOutput("For help, type \"help\" or \"?\"") {
|
||||
it.resume(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun cleanup() {
|
||||
runner.exec("stop")
|
||||
runner.exit()
|
||||
serverFolder.deleteRecursively()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package cc.maxmc.msm.controlled.netty
|
||||
|
||||
import cc.maxmc.msm.common.network.BungeePacket
|
||||
import cc.maxmc.msm.common.network.netty.NetworkRegistry
|
||||
import cc.maxmc.msm.common.utils.log
|
||||
import cc.maxmc.msm.common.utils.pipelineInit
|
||||
import io.netty.bootstrap.Bootstrap
|
||||
import io.netty.channel.Channel
|
||||
import io.netty.channel.ChannelOption
|
||||
import io.netty.channel.nio.NioEventLoopGroup
|
||||
import io.netty.channel.socket.nio.NioSocketChannel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
object NetClient {
|
||||
private val loop = NioEventLoopGroup()
|
||||
private lateinit var channel: Channel
|
||||
private lateinit var boot: Bootstrap
|
||||
|
||||
fun init(address: String, port: Int) {
|
||||
boot = Bootstrap().channel(NioSocketChannel::class.java).group(loop)
|
||||
.handler(pipelineInit(NetworkRegistry.PacketDirection.CHILD_BOUND)).option(ChannelOption.TCP_NODELAY, true)
|
||||
.remoteAddress(address, port)
|
||||
runBlocking {
|
||||
connect(0)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun connect(delay: Long = 3000) {
|
||||
delay(delay)
|
||||
log("§b| §7正在尝试连接主BC")
|
||||
val future = boot.connect().await()
|
||||
val result = future.cause()
|
||||
if (result == null) {
|
||||
channel = future.channel()
|
||||
log("§a| §7成功连接到集群的主节点. (${future.channel().remoteAddress()})")
|
||||
return
|
||||
}
|
||||
log("§c| §7未能连接到主BC,将在 §c3s §7后重新连接。(原因: ${result.message})")
|
||||
withContext(Dispatchers.IO) {
|
||||
connect()
|
||||
}
|
||||
}
|
||||
|
||||
fun sendPacket(packet: BungeePacket) {
|
||||
channel.writeAndFlush(packet)
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
loop.shutdownGracefully().sync()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cc.maxmc.msm.controlled.settings
|
||||
|
||||
import cc.maxmc.msm.controlled.settings.SettingsReader.config
|
||||
|
||||
object Settings {
|
||||
val name
|
||||
get() = config
|
||||
|
||||
val portRange
|
||||
get() = config.getStringList("ports").flatMap {
|
||||
it.toIntOrNull()?.run { listOf(this) } ?: it.split("..").run { this[0].toInt()..this[1].toInt() }
|
||||
}
|
||||
|
||||
object Parent {
|
||||
val address: String
|
||||
get() = config.getString("parent.address", "localhost")
|
||||
val port: Int
|
||||
get() = config.getInt("parent.port", 23333)
|
||||
}
|
||||
|
||||
object Patterns {
|
||||
fun arguments(type: String): List<String> {
|
||||
if (!config.contains("patterns.$type.args")) throw IllegalArgumentException("服务端样板 $type 的配置不存在,请检查settings.yml")
|
||||
return config.getStringList("patterns.$type.args")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cc.maxmc.msm.controlled.settings
|
||||
|
||||
import cc.maxmc.msm.controlled.MultiServerMan
|
||||
import net.md_5.bungee.api.ProxyServer
|
||||
import net.md_5.bungee.api.chat.TextComponent
|
||||
import net.md_5.bungee.config.Configuration
|
||||
import net.md_5.bungee.config.ConfigurationProvider
|
||||
import net.md_5.bungee.config.YamlConfiguration
|
||||
import kotlin.io.path.*
|
||||
|
||||
object SettingsReader {
|
||||
private val file = MultiServerMan.instance.dataFolder.toPath().resolve("settings.yml")
|
||||
val config: Configuration
|
||||
private val basePatternFolder = MultiServerMan.instance.dataFolder.toPath().resolve("patterns")
|
||||
|
||||
init {
|
||||
if (!basePatternFolder.exists()) {
|
||||
basePatternFolder.createDirectories()
|
||||
}
|
||||
if (!file.exists()) {
|
||||
MultiServerMan.instance.dataFolder.toPath().createDirectories()
|
||||
val stream = MultiServerMan.instance.getResourceAsStream("settings.yml")
|
||||
file.createFile()
|
||||
stream.copyTo(file.outputStream())
|
||||
ProxyServer.getInstance().console.sendMessage(TextComponent("§b| §7配置文件不存在,正在创建配置文件。"))
|
||||
}
|
||||
config = ConfigurationProvider.getProvider(YamlConfiguration::class.java).load(file.inputStream())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package cc.maxmc.msm.controlled.utils
|
||||
|
||||
import cc.maxmc.msm.common.utils.log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.io.path.extension
|
||||
import kotlin.io.path.name
|
||||
|
||||
class ScriptRunner(private val scriptPath: Path) {
|
||||
private lateinit var process: Process
|
||||
lateinit var thread: Thread
|
||||
private val listeners = CopyOnWriteArrayList<Pair<String, () -> Unit>>()
|
||||
private val writer by lazy { process.outputStream.bufferedWriter() }
|
||||
private val reader by lazy { process.inputStream.bufferedReader() }
|
||||
|
||||
fun launch(vararg args: String) {
|
||||
val builder = ProcessBuilder().directory(scriptPath.parent.toFile())
|
||||
if (scriptPath.extension == "sh") {
|
||||
builder.command("bash", scriptPath.name, *args)
|
||||
} else {
|
||||
builder.command("powershell.exe", scriptPath.name, *args)
|
||||
}
|
||||
process = builder.start()
|
||||
log("§c§lProcess Started")
|
||||
thread = thread {
|
||||
while (true) {
|
||||
if (listeners.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
var line = ""
|
||||
while (reader.readLine()?.also { line = it } != null) {
|
||||
listeners.forEach {
|
||||
if (line.contains(it.first)) it.second()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun exec(command: String) {
|
||||
writer.appendLine(command)
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
fun onOutput(string: String, func: () -> Unit) {
|
||||
listeners.add(string to func)
|
||||
}
|
||||
|
||||
suspend fun exit(): Int = withContext(Dispatchers.IO) {
|
||||
process.waitFor()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
name: Controlled
|
||||
main: cc.maxmc.msm.controlled.MultiServerMan
|
||||
author: MistyRain
|
||||
@@ -0,0 +1,14 @@
|
||||
# 主BC的地址及端口
|
||||
parent:
|
||||
address: 127.0.0.1
|
||||
port: 23333
|
||||
|
||||
# 该子BC所在机器所能开放的端口
|
||||
ports:
|
||||
- 30000
|
||||
- 30001..30019
|
||||
|
||||
# 该子BC所支持的服务端模版类型及模版类型启动所需参数
|
||||
patterns:
|
||||
main:
|
||||
args: [ "-ListenPort=${port}", "-usr=usr", "-pwd=pwd" ]
|
||||
Reference in New Issue
Block a user