package net.taehui.twilight.awilight import com.fasterxml.jackson.databind.ObjectMapper import com.google.protobuf.ByteString import io.netty.channel.ChannelHandlerContext import io.netty.channel.SimpleChannelInboundHandler import net.taehui.twilight.* import net.taehui.twilight.system.* import org.apache.hc.client5.http.classic.methods.HttpGet import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler import org.apache.hc.client5.http.impl.classic.HttpClients import org.python.core.Py import org.python.core.PyFunction import org.python.core.PyObject import org.python.util.PythonInterpreter import org.slf4j.LoggerFactory import java.nio.file.Files import java.time.LocalDateTime import java.util.* import kotlin.io.path.pathString class AwilightAvatar : SimpleChannelInboundHandler<EventOuterClass.Event>(), Logger { private lateinit var handler: ChannelHandlerContext private val enteredSites = mutableMapOf<String, AwilightSite>() private val defaultComputerCSX = Any() private val w = Math.random().coerceIn(0.3, 0.7) private val awilightCaller = PythonInterpreter() val qwilightName = QwilightNamesSystem.qwilightName var defaultComputer: DefaultCompute? = null var avatarID = "" init { loadAwilight() } val isBeginner: Boolean get() { return w < 0.4 } fun loadAwilight() { awilightCaller.cleanup() awilightCaller["awilight"] = this awilightCaller.execfile(TwilightComponent.AWILIGHT_FILE_PATH.pathString) } fun dispose() { WitSystem.wipeWit(this) synchronized(defaultComputerCSX) { defaultComputer?.stop() } } private fun on(function: String, params: Array<PyObject>) { awilightCaller.get(function, PyFunction::class.java)?.__call__(params) } fun send(eventID: EventOuterClass.Event.EventID, text: Any?, vararg data: ByteString) { handler.writeAndFlush(EventOuterClass.Event.newBuilder().apply { this.millis = System.currentTimeMillis() this.avatarID = this@AwilightAvatar.avatarID this.eventID = eventID if (text != null) { this.text = if (text is String) text else ObjectMapper().writeValueAsString(text) } this.addAllData(data.toList()) }.build()) } fun send(event: EventOuterClass.Event.Builder) { event.millis = System.currentTimeMillis() event.avatarID = avatarID handler.writeAndFlush(event.build()) } fun yell(siteID: String, siteYell: String) { send(EventOuterClass.Event.EventID.SITE_YELL, object { val siteID = siteID val siteYell = siteYell }) } fun setSiteHand(siteID: String, avatarID: String) { synchronized(enteredSites) { enteredSites[siteID]?.let { if (it.isSiteHand) { send(EventOuterClass.Event.EventID.SET_SITE_HAND, object { val siteID = siteID val avatarID = avatarID }) } } } } fun stopSiteNet(siteID: String) { synchronized(enteredSites) { enteredSites[siteID]?.let { if (it.isSiteHand) { send(EventOuterClass.Event.EventID.QUIT_NET, siteID) } } } } fun handleSiteNet(siteID: String) { synchronized(enteredSites) { enteredSites[siteID]?.let { if (it.isSiteHand && it.avatarConfigure == AwilightSite.AVATAR_CONFIGURE_DEFAULT) { send(EventOuterClass.Event.EventID.LEVY_NET, siteID) } } } } fun quitSite(siteID: String) { send(EventOuterClass.Event.EventID.QUIT_SITE, siteID) } fun setNoteFile(siteID: String) { synchronized(enteredSites) { enteredSites[siteID]?.let { if (it.isSiteHand) { NoteFilesSystem.noteFile?.let { (noteID, noteFilePath) -> val targetComputing = BaseCompiler.handleCompile(Files.readAllBytes(noteFilePath), -1).random() send( EventOuterClass.Event.EventID.SET_NOTE_FILE, object { val siteID = siteID val noteID = noteID val noteIDs = arrayOf(noteID) val title = targetComputing.title val artist = targetComputing.artist val genre = targetComputing.genre val levelText = targetComputing.levelText val level = targetComputing.level val wantLevelID = targetComputing.wantLevelID val judgmentStage = targetComputing.judgmentStage val hitPointsValue = targetComputing.hitPointsValue val totalNotes = targetComputing.totalNotes val longNotes = targetComputing.longNotes val autoableNotes = targetComputing.autoableNotes val trapNotes = targetComputing.trapNotes val highestInputCount = targetComputing.highestInputCount val length = targetComputing.length val bpm = targetComputing.bpm val lowestBPM = targetComputing.lowestBPM val highestBPM = targetComputing.highestBPM val inputMode = targetComputing.inputMode } ) send( EventOuterClass.Event.EventID.SET_SITE_NAME, object { val siteID = siteID val siteName = targetComputing.title } ) } } } } } override fun logInfo(toNotify: String) { LoggerFactory.getLogger(javaClass).info("[{}] {}", this.qwilightName, toNotify) } override fun logFault(e: Throwable) { LoggerFactory.getLogger(javaClass).error("[{}] {}", this.qwilightName, Utility.getFaultText(e)) } override fun channelActive(ctx: ChannelHandlerContext) { handler = ctx AwilightHandler.putAvatar(ctx, this) } override fun channelInactive(ctx: ChannelHandlerContext) { AwilightHandler.quitAvatar(ctx) } public override fun channelRead0(ctx: ChannelHandlerContext, msg: EventOuterClass.Event) { val eventText = msg.text val jm = ObjectMapper() when (msg.eventID) { EventOuterClass.Event.EventID.ESTABLISH -> { val text = jm.readValue(eventText, JSON.TwilightEstablish::class.java) this.avatarID = text.avatarID on("onEstablished", emptyArray()) send( EventOuterClass.Event.EventID.ENTER_SITE, object { val siteID = "00000000-0000-0000-0000-000000000000" val siteCipher = "" } ) send( EventOuterClass.Event.EventID.ENTER_SITE, object { val siteID = "00000000-0000-0000-0000-000000000001" val siteCipher = "" } ) WitSystem.handleLoop(this, LocalDateTime.now(), -1) { synchronized(enteredSites) { val sites = enteredSites.values.filter { it.isNetSite } if (sites.isNotEmpty()) { sites.stream() .filter { it.siteSituation == AwilightSite.SITE_SITUATION_DEFAULT } .findAny().ifPresent { if (Arrays.stream(it.noteIDs) .anyMatch { noteID -> NoteFilesSystem.hasNoteFile(noteID) } ) { if (it.isNetAllowed) { send(EventOuterClass.Event.EventID.LEVY_NET, it.siteID) } } else { setNoteFile(it.siteID) } } } else { logValueFuture { HttpClients.createDefault().use { val dataGet = HttpGet(Configure.www.qwilight + "/sites") val siteIDs = ObjectMapper().readValue( it.execute( dataGet, BasicHttpClientResponseHandler() ), Array<JSON.TwilightWwwSite>::class.java ) .filter { siteData -> !siteData.hasCipher && siteData.siteConfigure == 2 && siteData.avatarCount < 7 } .map { siteData -> siteData.siteID } if (siteIDs.isNotEmpty()) { send( EventOuterClass.Event.EventID.ENTER_SITE, object { val siteID = siteIDs.random() val siteCipher = "" } ) } else { NoteFilesSystem.noteFile?.let { noteFile -> val targetComputing = BaseCompiler.handleCompile(Files.readAllBytes(noteFile.value), -1) .random() val modeComponentValue = ModeComponent() send( EventOuterClass.Event.EventID.NEW_SITE, object { val siteName = targetComputing.title val siteCipher = "" @JvmField val isNetSite = true val data = object { val salt = modeComponentValue.salt val valueMultiplier = modeComponentValue.multiplierValue val autoMode = modeComponentValue.autoMode val noteSaltMode = modeComponentValue.noteSaltMode val faintNoteMode = modeComponentValue.faintNoteMode val judgmentMode = modeComponentValue.judgmentMode val hitPointsMode = modeComponentValue.hitPointsMode val noteMobilityMode = modeComponentValue.noteMobilityMode val longNoteMode = modeComponentValue.longNoteMode val inputFavorMode = modeComponentValue.inputFavorMode val noteModifyMode = modeComponentValue.noteModifyMode val bpmMode = modeComponentValue.bpmMode val waveMode = modeComponentValue.waveMode val setNoteMode = modeComponentValue.setNoteMode val lowestJudgmentConditionMode = modeComponentValue.lowestJudgmentConditionMode val audioMultiplier = modeComponentValue.audioMultiplier val highestJudgment0 = -1.0 val higherJudgment0 = -1.0 val highJudgment0 = -1.0 val lowJudgment0 = -1.0 val lowerJudgment0 = -1.0 val lowestJudgment0 = -1.0 val highestJudgment1 = 1.0 val higherJudgment1 = 1.0 val highJudgment1 = 1.0 val lowJudgment1 = 1.0 val lowerJudgment1 = 1.0 val lowestJudgment1 = 1.0 val lowestLongNoteModify = 100.0 val highestLongNoteModify = 100.0 val putNoteSet = 25.0 val putNoteSetMillis = 100.0 } val noteID = targetComputing.noteID val noteIDs = arrayOf(targetComputing.noteID) val title = targetComputing.title val artist = targetComputing.artist val genre = targetComputing.genre val levelText = targetComputing.levelText val level = targetComputing.level val wantLevelID = targetComputing.wantLevelID val judgmentStage = targetComputing.judgmentStage val hitPointsValue = targetComputing.hitPointsValue val totalNotes = targetComputing.totalNotes val longNotes = targetComputing.longNotes val autoableNotes = targetComputing.autoableNotes val trapNotes = targetComputing.trapNotes val highestInputCount = targetComputing.highestInputCount val length = targetComputing.length val bpm = targetComputing.bpm val inputMode = targetComputing.inputMode @JvmField val isAutoLongNote = targetComputing.isAutoLongNote val bundleEntryPath = "" } ) } } } } } } } } EventOuterClass.Event.EventID.ENTER_SITE -> { val text = jm.readValue(eventText, JSON.TwilightEnterSite::class.java) synchronized(enteredSites) { enteredSites.put( text.siteID, AwilightSite( text.siteID, text.isNetSite, text.noteID, text.noteIDs ) ) } } EventOuterClass.Event.EventID.QUIT_SITE -> { synchronized(enteredSites) { enteredSites.remove(eventText) } } EventOuterClass.Event.EventID.LEVY_NET -> { val text = jm.readValue(eventText, JSON.TwilightLevyNet::class.java) synchronized(enteredSites) { enteredSites[text.siteID]?.let { synchronized(defaultComputerCSX) { defaultComputer?.stop() defaultComputer = DefaultCompute( if (NoteFilesSystem.hasNoteFile(it.noteID)) { it.noteID } else { Arrays.stream(text.noteIDs) .filter { NoteFilesSystem.hasNoteFile(it) }.findAny().orElse("") }, this, text.handlerID, it ).also { tmpComputer -> Thread { var isOK = false try { if (tmpComputer.noteID.isNotEmpty()) { NoteFilesSystem.getNoteFile(tmpComputer.noteID)?.let { tmpComputer.handleCompile(it) isOK = true } } } finally { if (!isOK) { tmpComputer.stop() setNoteFile(it.siteID) } } }.apply { isDaemon = true }.start() } } } } } EventOuterClass.Event.EventID.COMPILED -> { synchronized(defaultComputerCSX) { if (eventText == defaultComputer?.handlerID) { Thread { try { defaultComputer?.handleNotes(w) } finally { defaultComputer?.stop() } }.apply { isDaemon = true }.start() } } } EventOuterClass.Event.EventID.QUIT_NET -> { val text = jm.readValue(eventText, JSON.TwilightQuitNet::class.java) synchronized(defaultComputerCSX) { defaultComputer?.let { tmpComputer -> if (tmpComputer.handlerID == text.handlerID) { tmpComputer.stop() text.quitNetItems?.let { on( "onHandled", arrayOf( Py.newBoolean( tmpComputer.isF ), Py.newUnicode(tmpComputer.title), Py.newInteger(tmpComputer.targetStand) ) ) } setNoteFile(tmpComputer.site.siteID) } } } } EventOuterClass.Event.EventID.CALL_SITE_AVATAR -> { val text = jm.readValue( eventText, JSON.TwilightCallSiteAvatar::class.java ) synchronized(enteredSites) { enteredSites[text.siteID]?.let { site -> Arrays.stream(text.data) .filter { it.avatarID == avatarID } .findAny().ifPresent { site.setAvatarConfigure( avatarID == text.siteHand, it.avatarConfigure ) } site.setSIteSituation(text.situationValue) site.setNetAllowed(text.data, avatarID) } } } EventOuterClass.Event.EventID.CALL_SITE_NET -> { val text = jm.readValue(eventText, JSON.TwilightCallSiteNet::class.java) synchronized(enteredSites) { enteredSites[text.siteID]?.setSiteNetData(text) } } EventOuterClass.Event.EventID.SITE_YELL -> { val text = jm.readValue(eventText, JSON.TwilightSiteYell::class.java) on( "onSiteYell", arrayOf( Py.newBoolean( avatarID == text.target ), Py.newUnicode(text.siteID), Py.newUnicode(text.avatarID), Py.newUnicode(text.avatarName), Py.newUnicode(text.siteYell) ) ) } EventOuterClass.Event.EventID.CALL_IO -> { val text = jm.readValue(eventText, JSON.TwilightCallIO::class.java) synchronized(defaultComputerCSX) { defaultComputer?.let { tmpComputer -> if (tmpComputer.setStop) { send(EventOuterClass.Event.EventID.IO_NOT, object { val avatarID = text.avatarID val handlerID = text.handlerID }) } else { val modeComponentValue = tmpComputer.modeComponentValue send( EventOuterClass.Event.EventID.CALL_IO_COMPONENT, object { val noteID = tmpComputer.noteID val handlerID = tmpComputer.handlerID val avatarID = text.avatarID val data = object { val salt = modeComponentValue.salt val valueMultiplier = modeComponentValue.multiplierValue val autoMode = modeComponentValue.autoMode val noteSaltMode = modeComponentValue.noteSaltMode val faintNoteMode = modeComponentValue.faintNoteMode val judgmentMode = modeComponentValue.judgmentMode val hitPointsMode = modeComponentValue.hitPointsMode val noteMobilityMode = modeComponentValue.noteMobilityMode val longNoteMode = modeComponentValue.longNoteMode val inputFavorMode = modeComponentValue.inputFavorMode val noteModifyMode = modeComponentValue.noteModifyMode val bpmMode = modeComponentValue.bpmMode val waveMode = modeComponentValue.waveMode val setNoteMode = modeComponentValue.setNoteMode val lowestJudgmentConditionMode = modeComponentValue.lowestJudgmentConditionMode val audioMultiplier = modeComponentValue.audioMultiplier val highestJudgment0 = -1.0 val higherJudgment0 = -1.0 val highJudgment0 = -1.0 val lowJudgment0 = -1.0 val lowerJudgment0 = -1.0 val lowestJudgment0 = -1.0 val highestJudgment1 = 1.0 val higherJudgment1 = 1.0 val highJudgment1 = 1.0 val lowJudgment1 = 1.0 val lowerJudgment1 = 1.0 val lowestJudgment1 = 1.0 val lowestLongNoteModify = 100.0 val highestLongNoteModify = 100.0 val putNoteSet = 25.0 val putNoteSetMillis = 100.0 } val ioHandlerID = text.handlerID @JvmField val isFailMode = true val ioMillis = text.ioMillis val targetIOMillis = System.currentTimeMillis() } ) } } } } EventOuterClass.Event.EventID.COMPILED_IO -> { val text = jm.readValue(eventText, JSON.TwilightCompiledIO::class.java) synchronized(defaultComputerCSX) { defaultComputer?.let { tmpComputer -> if (text.handlerID == tmpComputer.handlerID) { if (tmpComputer.setStop) { send( EventOuterClass.Event.EventID.IO_QUIT, object { val handlerID = text.handlerID val avatarIDs = arrayOf(text.avatarID) @JvmField val isBanned = true } ) } else { tmpComputer.twilightCompiledIOQueue.offer(text) } } } } } else -> Unit } } override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { logFault(cause) } }