fix: 修复图片上传、郊狼模块

This commit is contained in:
叁玖领域 2026-04-20 23:59:28 +08:00
parent 81e1237635
commit a36c44a63e
12 changed files with 728 additions and 170 deletions

View File

@ -3,5 +3,5 @@ org.gradle.downloadSources=false
org.gradle.parallel=true
org.gradle.degree_of_parallelism=16
project_group=top.r3944realms.ltdmanager
project_version=1.16-SNAPSHOT
dg_lab_version=4.4.14.18
project_version=1.17-SNAPSHOT
dg_lab_version=4.4.14.19

View File

@ -5,6 +5,7 @@ import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
@ -104,15 +105,60 @@ class CheveretoClient private constructor() :
method = request.method()
// 设置请求头
headers {
request.headers().invoke(this)
header("X-API-Key", apiKey)
}
when (request) {
is CheveretoUploadRequest -> {
// 使用 multipart/form-data 格式上传文件
setBody(MultiPartFormDataContent(
formData {
// API Key
append("key", apiKey)
// 对于非GET请求设置请求体
if (request.method() != HttpMethod.Get) {
setBody(request.toJSON())
// 处理 source
when (val source = request.source) {
is CheveretoSource.ByteArraySource -> {
LoggerUtil.logger.debug("上传文件: ${source.fileName}, 大小: ${source.bytes.size} bytes")
append("source", source.bytes, Headers.build {
append(HttpHeaders.ContentType, "image/png")
append(HttpHeaders.ContentDisposition, "form-data; name=\"source\"; filename=\"${source.fileName}\"")
})
}
is CheveretoSource.UrlSource -> {
append("source", source.url)
}
}
// 添加所有参数
request.title?.let { append("title", it) }
request.description?.let { append("description", it) }
request.tags?.let { append("tags", it) }
request.albumId?.let { append("album_id", it) }
request.categoryId?.let { append("category_id", it) }
request.width?.let { append("width", it.toString()) }
request.expiration?.let { append("expiration", it) }
request.nsfw?.let { append("nsfw", it.toString()) }
append("format", request.format)
request.useFileDate?.let { append("use_file_date", it.toString()) }
}
))
// 设置请求头
headers {
append(HttpHeaders.Accept, "application/json")
append("X-API-Key", apiKey)
}
}
else -> {
// 其他请求使用 JSON 格式
headers {
request.headers().invoke(this)
header("X-API-Key", apiKey)
}
if (request.method() != HttpMethod.Get) {
setBody(request.toJSON())
}
}
}
}
val responseText: String = response.body()
@ -173,7 +219,7 @@ class CheveretoClient private constructor() :
maxRetries: Int = 3
): CheveretoResponse {
upload(CheveretoUploadRequest(
return upload(CheveretoUploadRequest(
source = CheveretoSource.ByteArraySource(file.readBytes(), file.name),
format = format,
title = title,
@ -186,7 +232,6 @@ class CheveretoClient private constructor() :
nsfw = nsfw,
useFileDate = useFileDate
), priority, maxRetries).getRetResponse()
throw Exception("Never Reach")
}
@ -209,8 +254,24 @@ class CheveretoClient private constructor() :
priority: Int = 5,
maxRetries: Int = 3
): CheveretoResponse {
upload(CheveretoUploadRequest(
source = CheveretoSource.ByteArraySource(inputStream.readBytes(), fileName),
val bytes = if (inputStream.markSupported()) {
inputStream.mark(Integer.MAX_VALUE)
val b = inputStream.readBytes()
inputStream.reset()
b
} else {
// 如果不支持 mark直接读取
inputStream.readBytes()
}
if (bytes.isEmpty()) {
throw IllegalStateException("InputStream is empty for file: $fileName")
}
LoggerUtil.logger.debug("Uploading ${bytes.size} bytes for $fileName")
return upload(CheveretoUploadRequest(
source = CheveretoSource.ByteArraySource(bytes, fileName),
format = format,
title = title,
description = description,
@ -222,7 +283,6 @@ class CheveretoClient private constructor() :
nsfw = nsfw,
useFileDate = useFileDate
), priority, maxRetries).getRetResponse()
throw Exception("Never Reach")
}
/**
@ -243,7 +303,7 @@ class CheveretoClient private constructor() :
priority: Int = 5,
maxRetries: Int = 3
): CheveretoResponse {
upload(CheveretoUploadRequest(
return upload(CheveretoUploadRequest(
source = CheveretoSource.UrlSource(url),
format = format,
title = title,
@ -256,7 +316,6 @@ class CheveretoClient private constructor() :
nsfw = nsfw,
useFileDate = useFileDate
), priority, maxRetries).getRetResponse()
throw Exception("Never Reach")
}
suspend fun upload(

View File

@ -19,7 +19,10 @@ data class CheveretoImage(
val nsfw: Int,
@SerialName("storage_mode")
val storageMode: String,
val md5: String,
val checksum: String? = null,
@SerialName("source_checksum")
val sourceChecksum: String? = null,
val md5: String? = null,
@SerialName("source_md5")
val sourceMd5: String? = null,
@SerialName("original_filename")

View File

@ -13,20 +13,17 @@ import top.r3944realms.ltdmanager.core.client.response.ResponseResult
@Serializable
data class CheveretoUploadRequest(
private val source: CheveretoSource,
private val title: String? = null,
private val description: String? = null,
private val tags: String? = null,
@SerialName("album_id")
private val albumId: String? = null,
@SerialName("category_id")
private val categoryId: String? = null,
private val width: Int? = null,
private val expiration: String? = null,
private val nsfw: Int? = null,
private val format: String = "json",
@SerialName("use_file_date")
private val useFileDate: Int? = null
val source: CheveretoSource,
val title: String? = null,
val description: String? = null,
val tags: String? = null,
@SerialName("album_id") val albumId: String? = null,
@SerialName("category_id") val categoryId: String? = null,
val width: Int? = null,
val expiration: String? = null,
val nsfw: Int? = null,
val format: String = "json",
@SerialName("use_file_date") val useFileDate: Int? = null
) : CheveretoRequest() {
override fun path(): String = "api/1/upload"

View File

@ -6,6 +6,7 @@ import top.r3944realms.ltdmanager.utils.YamlUpdater
data class DgLabConfig(
var wsServer: WsServerConfig = WsServerConfig(),
var imgAlbumsId: String = "",
var dgLabClient: DgLabClientConfig = DgLabClientConfig(),
var pulseData: PulseDataConfig = PulseDataConfig(),
var commandText: CommandTextConfig = CommandTextConfig(),

View File

@ -160,14 +160,22 @@ data class ModuleConfig(
fun float(paramName: String): Float = get<Float>(paramName)
// 可选值方法
inline fun <reified T> getOrNull(paramName: String): T? =
config[paramName] as? T ?: run {
val value = config[paramName]
if (value == null) null
else if (value is T) value
else null
}
inline fun <reified T> getOrNull(paramName: String): T? {
val value = config[paramName] ?: return null
return when {
value is T -> value
T::class == Long::class && value is Int -> value.toLong() as T
T::class == Long::class && value is String -> value.toLongOrNull() as? T
T::class == Int::class && value is Long -> value.toInt() as T
T::class == Int::class && value is String -> value.toIntOrNull() as? T
T::class == String::class -> value.toString() as T
T::class == Boolean::class && value is String -> value.toBoolean() as T
T::class == Double::class && value is Number -> value.toDouble() as T
T::class == Float::class && value is Number -> value.toFloat() as T
else -> null
}
}
inline fun <reified T> getOrDefault(paramName: String, defaultValue: T): T =
getOrNull<T>(paramName) ?: defaultValue

View File

@ -121,31 +121,29 @@ class DgLab {
fun getClient(key: String): DGPBClientManager? {
return clientManager?.getClient(key)
}
/**
* 获取 & 创建 客户端管理类
*/
fun getClientOrCreate(key: String, operation: ClientOperation): DGPBClientManager {
val client = getClient(key)
if (client == null) {
val loadDgLabConfig = YamlConfigLoader.loadDgLabConfig()
val boxWSClient = PowerBoxWSClient.Builder.getBuilder()
.address(loadDgLabConfig.wsServer.localServerUrl)
.port(loadDgLabConfig.wsServer.localServerPort)
.role(WebSocketClientRole("QQ-$key"))
.operation(operation)
.sharedData(ClientPowerBoxSharedData())
.useRoleMsgMode(true)
.build()
if (loadDgLabConfig.wsServer.localServerSecure) {
boxWSClient.enableSSL()
}
val clientManager = DGPBClientManager(
boxWSClient
)
this.clientManager?.addClient(key, clientManager)
return clientManager
/**
* 创建 客户端管理类
*/
fun createClient(key: String, operation: ClientOperation): DGPBClientManager {
getClient(key)?.stop()
val loadDgLabConfig = YamlConfigLoader.loadDgLabConfig()
val boxWSClient = PowerBoxWSClient.Builder.getBuilder()
.address(loadDgLabConfig.wsServer.localServerUrl)
.port(loadDgLabConfig.wsServer.localServerPort)
.role(WebSocketClientRole("QQ-$key"))
.operation(operation)
.sharedData(ClientPowerBoxSharedData())
.useRoleMsgMode(true)
.build()
if (loadDgLabConfig.wsServer.localServerSecure) {
boxWSClient.enableSSL()
}
return client
val clientManager = DGPBClientManager(
boxWSClient
)
addClient(key, clientManager)
return clientManager
}
}

View File

@ -6,6 +6,51 @@ class ClientManager(
private val clients: MutableMap<String, DGPBClientManager> = mutableMapOf(),
) : IManager<MutableMap<String, DGPBClientManager>> {
/**
* 强度信息数据类
*/
data class ClientStrengthInfo(
val aValue: Int,
val aMax: Int,
val bValue: Int,
val bMax: Int,
val timestamp: Long = System.currentTimeMillis()
) {
fun getAStrength(): Int = aValue
fun getBStrength(): Int = bValue
fun getAStrengthPercent(): Double = if (aMax > 0) (aValue.toDouble() / aMax) * 100 else 0.0
fun getBStrengthPercent(): Double = if (bMax > 0) (bValue.toDouble() / bMax) * 100 else 0.0
override fun toString(): String {
return "A: $aValue/$aMax (${String.format("%.1f", getAStrengthPercent())}%), B: $bValue/$bMax (${String.format("%.1f", getBStrengthPercent())}%)"
}
}
// 存储每个客户端的强度信息
private val clientStrengths: MutableMap<String, ClientStrengthInfo> = mutableMapOf()
// 存储每个客户端最后更新时间
private val lastUpdateTime: MutableMap<String, Long> = mutableMapOf()
/**
* 获取客户端的强度信息
* @param key 客户端标识
* @return 强度信息如果不存在则返回 null
*/
fun getClientStrength(key: String): ClientStrengthInfo? {
return clientStrengths[key]
}
/**
* 更新客户端的强度信息
* @param key 客户端标识
* @param strengthInfo 强度信息
*/
fun updateClientStrength(key: String, strengthInfo: ClientStrengthInfo) {
clientStrengths[key] = strengthInfo
lastUpdateTime[key] = System.currentTimeMillis()
}
/**
* 添加单例客户端管理示例
* @param key 唯一标识客户端管理的 key比如 ID name
@ -19,6 +64,8 @@ class ClientManager(
*/
fun removeClient(key: String) {
clients.remove(key)?.stop()
clientStrengths.remove(key)
lastUpdateTime.remove(key)
}
/**
@ -40,6 +87,7 @@ class ClientManager(
*/
override fun stopAll() {
clients.values.forEach { it.stop() }
clients.clear()
}
/**

View File

@ -1,16 +1,26 @@
package top.r3944realms.ltdmanager.dglab.model.game
import com.r3944realms.dg_lab.api.dataType.PowerBoxMsgType
import com.r3944realms.dg_lab.api.exception.NoMatchDataTypeException
import com.r3944realms.dg_lab.api.message.IPowerBoxMsg
import com.r3944realms.dg_lab.api.message.argType.ChangePolicy
import com.r3944realms.dg_lab.api.operation.ClientOperation
import com.r3944realms.dg_lab.api.websocket.message.MessageDirection
import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData
import com.r3944realms.dg_lab.api.websocket.message.data.type.PowerBoxDataType
import com.r3944realms.dg_lab.manager.DGPBClientManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.apache.logging.log4j.core.util.UuidUtil
import top.r3944realms.ltdmanager.GlobalManager
import top.r3944realms.ltdmanager.chevereto.response.FailedCheveretoResponse
import top.r3944realms.ltdmanager.chevereto.response.v1.CheveretoUploadResponse
import top.r3944realms.ltdmanager.core.config.DgLabConfig
import top.r3944realms.ltdmanager.core.config.YamlConfigLoader
import top.r3944realms.ltdmanager.dglab.DgLab
import top.r3944realms.ltdmanager.dglab.manager.ClientManager
import top.r3944realms.ltdmanager.napcat.NapCatClient
import top.r3944realms.ltdmanager.napcat.data.ID
import top.r3944realms.ltdmanager.napcat.data.MessageElement
@ -19,6 +29,7 @@ import top.r3944realms.ltdmanager.napcat.request.other.SendPrivateMsgRequest
import top.r3944realms.ltdmanager.utils.LoggerUtil
import top.r3944realms.ltdmanager.utils.QRCodeUtil
import java.io.ByteArrayInputStream
import java.util.UUID
class GameClientOperation(
@ -28,10 +39,11 @@ class GameClientOperation(
private val playerId: Long
) : ClientOperation {
private val scope = CoroutineScope(Dispatchers.IO)
private var qrcode:ByteArrayInputStream? = null;
private var qrcode:ByteArrayInputStream? = null
var clientSelf: DGPBClientManager? = null
private var hasBinding = false
private var bindingTimeoutJob: kotlinx.coroutines.Job? = null // 保存倒计时任务
override fun ClientStartingHandler() {
LoggerUtil.logger.debug("Player $playerId is starting the client...")
scope.launch {
@ -124,34 +136,48 @@ class GameClientOperation(
scope.launch {
// 上传二维码图片
val response = GlobalManager.cheveretoClient.uploadStream(
qrcode!!,
"$playerId-Qrcode-${System.currentTimeMillis()}.png",
"Qrcode-$playerId-${System.currentTimeMillis()}",
"5min后将会自动删除",
albumId = "BFx",
expiration = "PT5M"
)
if (response is CheveretoUploadResponse){
try {
val response = GlobalManager.cheveretoClient.uploadStream(
qrcode!!,
"$playerId-Qrcode-${System.currentTimeMillis()}.png",
"Qrcode-$playerId-${System.currentTimeMillis()}",
"5min后将会自动删除",
albumId = YamlConfigLoader.loadDgLabConfig().imgAlbumsId,
expiration = "PT5M"
)
if (response is CheveretoUploadResponse){
napCatClient.sendUnit(
SendPrivateMsgRequest(
listOf(
MessageElement.text("请在60s内绑定APP否则将自动断开连接"),
MessageElement.image(response.image.url, "二维码")
),
ID.long(playerId)
)
)
} else if (response is FailedCheveretoResponse.Default){
napCatClient.sendUnit(
SendPrivateMsgRequest(
listOf(
MessageElement.text("无法上传图片,请联系管理员:${response.httpStatusCode} , ${response.failedMessage}"),
),
ID.long(playerId)
)
)
clientSelf?.stop()
}
} catch (e: Exception) {
napCatClient.sendUnit(
SendPrivateMsgRequest(
listOf(
MessageElement.text("请在60s内绑定APP否则将自动断开连接"),
MessageElement.image(response.image.url, "二维码")
),
ID.long(playerId)
)
)
} else if (response is FailedCheveretoResponse.Default){
napCatClient.sendUnit(
SendPrivateMsgRequest(
listOf(
MessageElement.text("无法上传图片,请联系管理员:${response.httpStatusCode} , ${response.failedMessage}"),
MessageElement.text("无法上传图片,请联系管理员:${e.message}"),
),
ID.long(playerId)
)
)
clientSelf?.stop()
}
// 启动 60 秒倒计时任务
bindingTimeoutJob = launch {
kotlinx.coroutines.delay(60_000)
@ -218,11 +244,35 @@ class GameClientOperation(
// napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("连接断开, $p0")), ID.long(playerId)))
// }
when (p0?.commandType) {
PowerBoxDataType.STRENGTH -> TODO()
PowerBoxDataType.PULSE -> TODO()
PowerBoxDataType.CLEAR -> TODO()
PowerBoxDataType.FEEDBACK -> TODO()
PowerBoxDataType.STRENGTH -> scope.launch {
// val strengthInfo : IPowerBoxMsg.StrengthInfo
//
// strengthInfo = IPowerBoxMsg.StrengthInfo.read(
// PowerBoxMessage.createPowerBoxMessage(
// p0,
// MessageDirection.of(
// MessageDirection.DirectType.PLACEHOLDER_TO_PLACEHOLDER,
// ROM_UUID,
// ROM_UUID
// )
// )
// )
// napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("强度信息:\n A:${strengthInfo.aValue}/${strengthInfo.aMax} \n B:${strengthInfo.bValue}/${strengthInfo.bMax}")), ID.long(playerId)))
}
PowerBoxDataType.PULSE -> scope.launch {
}
PowerBoxDataType.CLEAR -> scope.launch {
}
PowerBoxDataType.FEEDBACK -> scope.launch {
}
else -> return
}
}
companion object {
val ROM_UUID = "00001101-0000-1000-8000-00805f9b34fb"
}
}

View File

@ -4,6 +4,31 @@ import com.r3944realms.dg_lab.api.message.data.PulseWave
import com.r3944realms.dg_lab.api.message.data.PulseWaveList
object DefaultPulseData {
/**
* 循环次数配置可以根据需要调整
*/
private const val DEFAULT_CYCLES = 3
/**
* 创建带循环的 PulseWaveList
*/
private fun createCyclicWaveList(
baseSegments: List<Array<IntArray>>,
cycles: Int = DEFAULT_CYCLES,
name: String = ""
): PulseWaveList {
val list = PulseWaveList()
list.name = name
repeat(cycles) {
baseSegments.forEach { seg ->
list.add(createWaveSegment(seg[0], seg[1]))
}
}
return list
}
/**
* 将频率转换为 Dg-Lab 格式
*
@ -77,12 +102,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0))
)
// 转成 PulseWave 并加入列表
for (seg in segments) {
list.add(createWaveSegment(seg[0], seg[1]))
}
list
createCyclicWaveList(segments)
}
val Tide: PulseWaveList by lazy {
val list = PulseWaveList()
@ -100,8 +120,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(84, 82, 80, 76)),
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(68, 68, 68, 68))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val Combo: PulseWaveList by lazy {
@ -117,8 +136,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 1)),
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(2, 2, 2, 2))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val FastPinch: PulseWaveList by lazy {
val list = PulseWaveList()
@ -128,8 +146,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val PinchGradual: PulseWaveList by lazy {
val list = PulseWaveList()
@ -147,8 +164,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val Heartbeat: PulseWaveList by lazy {
@ -172,8 +188,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val Compress: PulseWaveList by lazy {
val list = PulseWaveList()
@ -201,8 +216,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val RhythmStep: PulseWaveList by lazy {
val list = PulseWaveList()
@ -235,8 +249,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val GranularFriction: PulseWaveList by lazy {
@ -248,8 +261,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val GradualBounce: PulseWaveList by lazy {
@ -263,8 +275,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0)),
arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val WaveRipple: PulseWaveList by lazy {
val list = PulseWaveList()
@ -278,8 +289,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(50, 50, 50, 50)),
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val RainWash: PulseWaveList by lazy {
@ -291,8 +301,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(80, 90, 100, 100)),
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val SpeedHit: PulseWaveList by lazy {
@ -305,8 +314,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(20, 20, 20, 20), intArrayOf(50, 50, 50, 50)),
arrayOf(intArrayOf(15, 15, 15, 15), intArrayOf(0, 0, 0, 0))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val SignalLight: PulseWaveList by lazy {
val list = PulseWaveList()
@ -317,8 +325,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val Tease1: PulseWaveList by lazy {
@ -328,8 +335,7 @@ object DefaultPulseData {
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 30, 60, 100)),
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 70, 40, 0))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
val Tease2: PulseWaveList by lazy {
@ -339,7 +345,6 @@ object DefaultPulseData {
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 50, 100, 100)),
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 50, 0, 0))
)
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
list
createCyclicWaveList(segments)
}
}

View File

@ -54,74 +54,296 @@ class DGLabModule(
var dgLabManager: DgLab? = null
private var scope: CoroutineScope? = null
private var dglabCommandDispatcher: CommandDispatcher<Player> = CommandDispatcher<Player>().apply {
/*
dglab (命令头)
server (管理员)
start - 启动 DG-LAB 服务器
stop - 停止 DG-LAB 服务器
clients
stopAll - 停止所有客户端
client
start - 启动自己的客户端
stop - 停止自己的客户端
strength
self - 对自己的操作
a
add <value> - A通道增加强度 (-200~200)
set <value> - A通道设置强度 (0~200)
b
add <value> - B通道增加强度 (-200~200)
set <value> - B通道设置强度 (0~200)
ab
add <value> - 双通道增加强度 (-200~200)
set <value> - 双通道设置强度 (0~200)
target - 对他人的操作
<player> (QQ号)
a
add <value> - A通道增加强度 (-200~200)
set <value> - A通道设置强度 (0~200)
b
add <value> - B通道增加强度 (-200~200)
set <value> - B通道设置强度 (0~200)
ab
add <value> - 双通道增加强度 (-200~200)
set <value> - 双通道设置强度 (0~200)
pulse
self - 对自己的操作
a
clear - 清除A通道脉冲
set <pulseName> <timer> - 设置A通道脉冲
b
clear - 清除B通道脉冲
set <pulseName> <timer> - 设置B通道脉冲
ab
clear - 清除双通道脉冲
set <pulseName> <timer> - 设置双通道脉冲
target - 对他人的操作
<player> (QQ号)
a
clear - 清除A通道脉冲
set <pulseName> <timer> - 设置A通道脉冲
b
clear - 清除B通道脉冲
set <pulseName> <timer> - 设置B通道脉冲
ab
clear - 清除双通道脉冲
set <pulseName> <timer> - 设置双通道脉冲
add - 快捷增加强度
a <value> - A通道增加强度
b <value> - B通道增加强度
ab <value> - 双通道增加强度
set - 快捷设置强度
a <value> - A通道设置强度
b <value> - B通道设置强度
ab <value> - 双通道设置强度
clear - 快捷清除脉冲
a - 清除A通道脉冲
b - 清除B通道脉冲
ab - 清除双通道脉冲
*/
for (command in commandHead) register(
literal<Player>(command)
// ========== 服务器管理(管理员) ==========
.then(literal<Player?>("server").requires { adminIds.contains(it.id) }
.then(literal<Player?>("start").executes { startDgLab() })
.then(literal<Player?>("stop").executes { stopDgLab() })
.then(literal<Player?>("stopAllClient").executes { stopAllDgLabClient() })
.then(literal<Player?>("clients")
.then(literal<Player?>("stopAll").executes { stopAllDgLabClient() })
)
)
// ========== 客户端管理 ==========
.then(literal<Player?>("client")
.then(literal<Player?>("start").executes { startClient(it.source.id) })
.then(literal<Player?>("stop").executes { stopClient(it.source.id) })
)
// ========== 强度控制(统一结构) ==========
.then(literal<Player?>("strength")
.then(argument<Player?, String>("channel", StringArgumentType.string())
.then(literal<Player?>("add")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(-200, 200))
.executes { strengthAdd(it.source.id, StringArgumentType.getString(it, "channel"), IntegerArgumentType.getInteger(it, "value")) }
)
)
.then(literal<Player?>("set")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(0, 200))
.executes { strengthSet(it.source.id, StringArgumentType.getString(it, "channel"), IntegerArgumentType.getInteger(it, "value")) }
)
)
)
.then(argument<Player?, Long>("player", LongArgumentType.longArg())
.then(argument<Player?, String>("channel", StringArgumentType.string())
// 对自己的操作
.then(literal<Player?>("self")
.then(literal<Player?>("a")
.then(literal<Player?>("add")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(-200, 200))
.executes { strengthAdd(LongArgumentType.getLong(it, "player"), StringArgumentType.getString(it, "channel"), IntegerArgumentType.getInteger(it, "value")) }
.executes { strengthAdd(it.source.id, "a", IntegerArgumentType.getInteger(it, "value")) }
)
)
.then(literal<Player?>("set")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(0, 200))
.executes { strengthSet(LongArgumentType.getLong(it, "player"), StringArgumentType.getString(it, "channel"), IntegerArgumentType.getInteger(it, "value")) }
.executes { strengthSet(it.source.id, "a", IntegerArgumentType.getInteger(it, "value")) }
)
)
)
)
)
.then(literal<Player?>("pulse")
.then(argument<Player?, String>("channel", StringArgumentType.string())
.then(literal<Player?>("clear").executes { pulseClear(it.source.id, StringArgumentType.getString(it, "channel")) })
.then(literal<Player?>("set")
.then(argument<Player?, String>("pulseName", StringArgumentType.string())
.then(argument<Player?, Int>("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE))
.executes { pulseSet(it.source.id, StringArgumentType.getString(it, "channel"), StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) }
.then(literal<Player?>("b")
.then(literal<Player?>("add")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(-200, 200))
.executes { strengthAdd(it.source.id, "b", IntegerArgumentType.getInteger(it, "value")) }
)
)
)
)
.then(argument<Player?, Long>("player", LongArgumentType.longArg())
.then(argument<Player?, String>("channel", StringArgumentType.string())
.then(literal<Player?>("clear").executes { pulseClear(LongArgumentType.getLong(it, "player"), StringArgumentType.getString(it, "channel")) })
.then(literal<Player?>("set")
.then(argument<Player?, String>("pulseName", StringArgumentType.string())
.then(argument<Player?, Int>("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE))
.executes { pulseSet(LongArgumentType.getLong(it, "player"), StringArgumentType.getString(it, "channel"), StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) }
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(0, 200))
.executes { strengthSet(it.source.id, "b", IntegerArgumentType.getInteger(it, "value")) }
)
)
)
.then(literal<Player?>("ab")
.then(literal<Player?>("add")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(-200, 200))
.executes { strengthAdd(it.source.id, "ab", IntegerArgumentType.getInteger(it, "value")) }
)
)
.then(literal<Player?>("set")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(0, 200))
.executes { strengthSet(it.source.id, "ab", IntegerArgumentType.getInteger(it, "value")) }
)
)
)
)
// 对他人的操作
.then(literal<Player?>("target")
.then(argument<Player?, Long>("player", LongArgumentType.longArg())
.then(literal<Player?>("a")
.then(literal<Player?>("add")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(-200, 200))
.executes { strengthAdd(LongArgumentType.getLong(it, "player"), "a", IntegerArgumentType.getInteger(it, "value")) }
)
)
.then(literal<Player?>("set")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(0, 200))
.executes { strengthSet(LongArgumentType.getLong(it, "player"), "a", IntegerArgumentType.getInteger(it, "value")) }
)
)
)
.then(literal<Player?>("b")
.then(literal<Player?>("add")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(-200, 200))
.executes { strengthAdd(LongArgumentType.getLong(it, "player"), "b", IntegerArgumentType.getInteger(it, "value")) }
)
)
.then(literal<Player?>("set")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(0, 200))
.executes { strengthSet(LongArgumentType.getLong(it, "player"), "b", IntegerArgumentType.getInteger(it, "value")) }
)
)
)
.then(literal<Player?>("ab")
.then(literal<Player?>("add")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(-200, 200))
.executes { strengthAdd(LongArgumentType.getLong(it, "player"), "ab", IntegerArgumentType.getInteger(it, "value")) }
)
)
.then(literal<Player?>("set")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(0, 200))
.executes { strengthSet(LongArgumentType.getLong(it, "player"), "ab", IntegerArgumentType.getInteger(it, "value")) }
)
)
)
)
)
)
)
// .then(literal<Player?>("info").executes {}
// .then(argument<Player?, String>("player", StringArgumentType.string()).executes {})
// )
// ========== 脉冲控制(统一结构) ==========
.then(literal<Player?>("pulse")
// 对自己的操作
.then(literal<Player?>("self")
.then(literal<Player?>("a")
.then(literal<Player?>("clear").executes { pulseClear(it.source.id, "a") })
.then(literal<Player?>("set")
.then(argument<Player?, String>("pulseName", StringArgumentType.string())
.then(argument<Player?, Int>("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE))
.executes { pulseSet(it.source.id, "a", StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) }
)
)
)
)
.then(literal<Player?>("b")
.then(literal<Player?>("clear").executes { pulseClear(it.source.id, "b") })
.then(literal<Player?>("set")
.then(argument<Player?, String>("pulseName", StringArgumentType.string())
.then(argument<Player?, Int>("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE))
.executes { pulseSet(it.source.id, "b", StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) }
)
)
)
)
.then(literal<Player?>("ab")
.then(literal<Player?>("clear").executes { pulseClear(it.source.id, "ab") })
.then(literal<Player?>("set")
.then(argument<Player?, String>("pulseName", StringArgumentType.string())
.then(argument<Player?, Int>("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE))
.executes { pulseSet(it.source.id, "ab", StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) }
)
)
)
)
)
// 对他人的操作
.then(literal<Player?>("target")
.then(argument<Player?, Long>("player", LongArgumentType.longArg())
.then(literal<Player?>("a")
.then(literal<Player?>("clear").executes { pulseClear(LongArgumentType.getLong(it, "player"), "a") })
.then(literal<Player?>("set")
.then(argument<Player?, String>("pulseName", StringArgumentType.string())
.then(argument<Player?, Int>("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE))
.executes { pulseSet(LongArgumentType.getLong(it, "player"), "a", StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) }
)
)
)
)
.then(literal<Player?>("b")
.then(literal<Player?>("clear").executes { pulseClear(LongArgumentType.getLong(it, "player"), "b") })
.then(literal<Player?>("set")
.then(argument<Player?, String>("pulseName", StringArgumentType.string())
.then(argument<Player?, Int>("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE))
.executes { pulseSet(LongArgumentType.getLong(it, "player"), "b", StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) }
)
)
)
)
.then(literal<Player?>("ab")
.then(literal<Player?>("clear").executes { pulseClear(LongArgumentType.getLong(it, "player"), "ab") })
.then(literal<Player?>("set")
.then(argument<Player?, String>("pulseName", StringArgumentType.string())
.then(argument<Player?, Int>("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE))
.executes { pulseSet(LongArgumentType.getLong(it, "player"), "ab", StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) }
)
)
)
)
)
)
)
// ========== 快捷操作(简化版) ==========
.then(literal<Player?>("add")
.then(literal<Player?>("a")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(-200, 200))
.executes { strengthAdd(it.source.id, "a", IntegerArgumentType.getInteger(it, "value")) }
)
)
.then(literal<Player?>("b")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(-200, 200))
.executes { strengthAdd(it.source.id, "b", IntegerArgumentType.getInteger(it, "value")) }
)
)
.then(literal<Player?>("ab")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(-200, 200))
.executes { strengthAdd(it.source.id, "ab", IntegerArgumentType.getInteger(it, "value")) }
)
)
)
.then(literal<Player?>("set")
.then(literal<Player?>("a")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(0, 200))
.executes { strengthSet(it.source.id, "a", IntegerArgumentType.getInteger(it, "value")) }
)
)
.then(literal<Player?>("b")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(0, 200))
.executes { strengthSet(it.source.id, "b", IntegerArgumentType.getInteger(it, "value")) }
)
)
.then(literal<Player?>("ab")
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(0, 200))
.executes { strengthSet(it.source.id, "ab", IntegerArgumentType.getInteger(it, "value")) }
)
)
)
.then(literal<Player?>("clear")
.then(literal<Player?>("a").executes { pulseClear(it.source.id, "a") })
.then(literal<Player?>("b").executes { pulseClear(it.source.id, "b") })
.then(literal<Player?>("ab").executes { pulseClear(it.source.id, "ab") })
)
)
}
private val stateFile: File = getStateFileInternal("dg_lab_state.json", name)
private val stateBackupFile: File = getStateFileInternal("dg_lab_state.json.bak", name)
@ -184,11 +406,22 @@ class DGLabModule(
triggerMsgs.forEach { (msg, userId, processedText) ->
refMsg = msg
LoggerUtil.logger.info("[$name] 原始消息用户: $userId")
LoggerUtil.logger.info("[$name] 处理后的命令: $processedText")
var result = processedText.replace(Regex("\\s+"), " ")
result = result.replace("", "\"")
.replace("", "\"")
.replace("", "'")
.replace("", "'")
.replace("", "\"")
.replace("", "\"")
.replace("", "[")
.replace("", "]")
.replace("", "(")
.replace("", ")")
LoggerUtil.logger.info("[$name] 处理后的命令: $result")
refPlayer = dgLabManager?.getPlayerManager()?.getPlayer(userId)
dgLabState = dgLabState.updateOrCreate(userId, msg.realId, msg.time)
val execute = dglabCommandDispatcher.execute(processedText, refPlayer)
val execute = dglabCommandDispatcher.execute(result, refPlayer)
scope?.launch {
GlobalManager.napCatClient.sendUnit(
SetMsgEmojiLikeRequest(
@ -300,7 +533,7 @@ class DGLabModule(
dgLabManager!!.getPlayerManager(),
qq
)
val dgpbClientManager = dgLabManager?.getClientOrCreate(
val dgpbClientManager = dgLabManager?.createClient(
qq.toString(),
operation
)
@ -310,7 +543,7 @@ class DGLabModule(
return 1
}
private fun stopClient(qq: Long): Int {
dgLabManager?.getClient(qq.toString())?.stop()
dgLabManager?.removeClient(qq.toString())
return 1
}
private fun strengthAdd(qq: Long, channel: String, value: Int): Int {

View File

@ -1,9 +1,165 @@
package top.r394realms.ltdmanagertest
import kotlin.random.Random
fun main() {
for(item in 1..100){
println(Random.nextInt(100))
val startTime = System.currentTimeMillis()
// 生成1~9的所有排列并检查是否是幻方
val numbers = arrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
val magicSquares = mutableListOf<Array<Array<Int>>>()
val used = BooleanArray(9)
val current = Array(3) { Array(3) { 0 } }
fun backtrack(index: Int) {
if (index == 9) {
if (isMagicSquare(current)) {
// 深拷贝当前矩阵
val copy = Array(3) { i -> Array(3) { j -> current[i][j] } }
magicSquares.add(copy)
}
return
}
for (i in numbers.indices) {
if (!used[i]) {
used[i] = true
current[index / 3][index % 3] = numbers[i]
backtrack(index + 1)
used[i] = false
}
}
}
backtrack(0)
// 去重(排除旋转和镜像对称的重复解)
val uniqueSquares = removeDuplicates(magicSquares)
println("找到 ${uniqueSquares.size} 个独特的3阶幻方")
println("=".repeat(30))
uniqueSquares.forEachIndexed { idx, square ->
println("幻方 ${idx + 1}:")
printMagicSquare(square)
println("-".repeat(20))
}
val endTime = System.currentTimeMillis()
println("总排列数: ${factorial(9)}")
println("有效幻方数: ${magicSquares.size}")
println("独特幻方数: ${uniqueSquares.size}")
println("运行时间: ${endTime - startTime}ms")
}
fun isMagicSquare(square: Array<Array<Int>>): Boolean {
// 检查所有行、列、对角线之和是否为15
return (square[0][0] + square[0][1] + square[0][2] == 15) &&
(square[1][0] + square[1][1] + square[1][2] == 15) &&
(square[2][0] + square[2][1] + square[2][2] == 15) &&
(square[0][0] + square[1][0] + square[2][0] == 15) &&
(square[0][1] + square[1][1] + square[2][1] == 15) &&
(square[0][2] + square[1][2] + square[2][2] == 15) &&
(square[0][0] + square[1][1] + square[2][2] == 15) &&
(square[0][2] + square[1][1] + square[2][0] == 15)
}
fun removeDuplicates(squares: List<Array<Array<Int>>>): List<Array<Array<Int>>> {
val unique = mutableListOf<Array<Array<Int>>>()
val seen = mutableSetOf<String>()
for (square in squares) {
// 获取所有旋转和镜像变换
val transformations = getAllTransformations(square)
var isNew = true
for (transform in transformations) {
val key = transformToString(transform)
if (key in seen) {
isNew = false
break
}
}
if (isNew) {
val key = transformToString(square)
seen.add(key)
unique.add(square)
}
}
return unique
}
fun getAllTransformations(square: Array<Array<Int>>): List<Array<Array<Int>>> {
val transformations = mutableListOf<Array<Array<Int>>>()
var current = square
// 4个旋转方向
repeat(4) {
transformations.add(current)
// 添加镜像
transformations.add(mirrorHorizontal(current))
transformations.add(mirrorVertical(current))
transformations.add(mirrorDiagonal(current))
current = rotate90(current)
}
return transformations.distinctBy { transformToString(it) }
}
fun rotate90(square: Array<Array<Int>>): Array<Array<Int>> {
val result = Array(3) { Array(3) { 0 } }
for (i in 0..2) {
for (j in 0..2) {
result[j][2 - i] = square[i][j]
}
}
return result
}
fun mirrorHorizontal(square: Array<Array<Int>>): Array<Array<Int>> {
val result = Array(3) { Array(3) { 0 } }
for (i in 0..2) {
for (j in 0..2) {
result[i][2 - j] = square[i][j]
}
}
return result
}
fun mirrorVertical(square: Array<Array<Int>>): Array<Array<Int>> {
val result = Array(3) { Array(3) { 0 } }
for (i in 0..2) {
for (j in 0..2) {
result[2 - i][j] = square[i][j]
}
}
return result
}
fun mirrorDiagonal(square: Array<Array<Int>>): Array<Array<Int>> {
val result = Array(3) { Array(3) { 0 } }
for (i in 0..2) {
for (j in 0..2) {
result[j][i] = square[i][j]
}
}
return result
}
fun transformToString(square: Array<Array<Int>>): String {
return square.joinToString("") { row -> row.joinToString("") }
}
fun printMagicSquare(square: Array<Array<Int>>) {
for (row in square) {
println(row.joinToString(" ") { "%d".format(it) })
}
}
fun factorial(n: Long): Long {
return if (n <= 1) 1
else n * factorial(n - 1)
}