package net.taehui.twilight import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.databind.ObjectMapper import com.google.protobuf.ByteString import net.dv8tion.jda.api.entities.Member import net.dv8tion.jda.api.events.message.MessageDeleteEvent import net.dv8tion.jda.api.events.message.MessageReceivedEvent import net.dv8tion.jda.api.events.message.MessageUpdateEvent import net.taehui.twilight.system.* import okhttp3.internal.toImmutableList import org.apache.commons.io.FileUtils import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.* import java.util.stream.Collectors import java.util.stream.Stream import kotlin.random.Random class Site : Logger { enum class AvatarConfigure(val value: Int) { DEFAULT(0), LEVYING(2), COMPILING(3), NET(4) } enum class SiteSituation { DEFAULT, COMPILING, NET; @JsonValue fun getValue(): Int { return ordinal } } enum class SiteMode { DEFAULT, FAVOR, NET; @JsonValue fun getValue(): Int { return ordinal } } private val isEssential: Boolean private val siteMode: SiteMode private val avatarsCeiling: Int private val siteID: UUID private val siteCipher: String private val isEditable: Boolean private val avatarsCSX = Any() private val avatars = mutableListOf<Avatar>() private val avatarConfigures = mutableMapOf<String, AvatarConfigure>() private val avatarGroups = mutableMapOf<String, Int>() private val lastAudioInputMillis = mutableMapOf<String, Long>() private val isAudioInputs = mutableMapOf<String, Boolean>() private val siteHandQueue = LinkedList<Avatar>() private val netSIteCommentItems = object : LinkedList<NetSiteCommentItems>() { override fun add(element: NetSiteCommentItems): Boolean { if (size >= 10) { remove() } return super.add(element) } } private val siteYells = LinkedList<SiteYell>() private val platformSiteYells = mutableMapOf<Long, MutableList<SiteYell>>() private val targetComputing = Computing("") private val isCallable: Boolean private val isGetNotify: Boolean private val isEnterQuitAware: Boolean private val hasAvatar: Boolean private val handler: ScheduledFuture<*>? private val netItems = ConcurrentHashMap<String, NetItem>() private var netDataMillis = 0L private var siteHand: Avatar? = null private var siteName = "" private var handlerID = "" private var siteNotify = "" private var isFavorNoteFile = true private var isFavorModeComponent = true private var isFavorAudioMultiplier = false private var isAutoSiteHand = false private var modeComponentData: Any? = null private var siteSituation = SiteSituation.DEFAULT private var validHunterMode = 0 private var validNetMode = 0 private var allowedPostableItems = emptyArray<Int>() private var noteID = "" private var noteIDs: Array<String>? = null private var bundleEntryPath = "" private var bundleName = "" constructor( siteID: UUID, isNetSite: Boolean, siteName: String, siteCipher: String, siteHand: Avatar?, isCallable: Boolean ) { siteMode = if (isNetSite) SiteMode.NET else SiteMode.FAVOR avatarsCeiling = if (isNetSite) 8 else Int.MAX_VALUE this.siteID = siteID this.siteName = siteName this.siteCipher = siteCipher isEditable = true this.siteHand = siteHand this.isCallable = isCallable isGetNotify = true isEnterQuitAware = true isEssential = false hasAvatar = true handler = ses.scheduleAtFixedRate({ var wasModified = false val millis = System.currentTimeMillis() synchronized(avatarsCSX) { lastAudioInputMillis.forEach { val isAudioInput = it.value + 500 >= millis if (isAudioInputs[it.key] != isAudioInput) { isAudioInputs[it.key] = isAudioInput wasModified = true } } } if (wasModified) { doCallSiteAvatar(false) } }, 0L, (1000 / 60).toLong(), TimeUnit.MILLISECONDS) } constructor( siteID: UUID, siteName: String, isEditable: Boolean, isGetNotify: Boolean, isEssential: Boolean, hasAvatar: Boolean ) { siteMode = SiteMode.DEFAULT avatarsCeiling = Int.MAX_VALUE this.siteID = siteID this.siteName = siteName siteCipher = "" this.isEditable = isEditable isCallable = true this.isGetNotify = isGetNotify isEnterQuitAware = false this.isEssential = isEssential this.hasAvatar = hasAvatar handler = null } fun onWipeSite() { if (siteMode == SiteMode.NET) { FileUtils.deleteQuietly(TwilightComponent.BUNDLE_ENTRY_PATH.resolve("$siteID.zip").toFile()) } handler?.cancel(false) } fun isAvatarJoined(avatar: Avatar): Boolean { return avatars.contains(avatar) } private fun tryQuitNet() { val netAvatars = avatars.filter { avatarConfigures[it.avatarID] == AvatarConfigure.NET } if (netAvatars.all { netItems[it.avatarID]?.avatarNetStatus != EventOuterClass.Event.AvatarNetStatus.Default }) { netAvatars.forEach { avatarConfigures[it.avatarID] = AvatarConfigure.DEFAULT } siteSituation = SiteSituation.DEFAULT setNetBundle("") if (isAutoSiteHand) { setAutoSiteHand() } doCallSiteAvatar(false) val netItemsHaveComment = netItems.entries.stream().filter { it.value.comment != null } .collect( Collectors.toMap({ it.key }, { it.value }) ) val validNetItems = setValidMap(netItemsHaveComment).toList() val text = object { val handlerID = this@Site.handlerID val quitNetItems = validNetItems.map { object { val avatarID = it.avatarID val avatarName = it.avatarName val title = it.title val artist = it.artist val genre = it.genre val level = it.level val levelText = it.levelText val wantLevelID = it.wantLevelID val autoMode = it.autoMode val noteSaltMode = it.noteSaltMode val audioMultiplier = it.audioMultiplier val faintNoteMode = it.faintNoteMode val hitPointsMode = it.hitPointsMode val judgmentMode = it.judgmentMode val noteMobilityMode = it.noteMobilityMode val inputFavorMode = it.inputFavorMode val longNoteMode = it.longNoteMode val noteModifyMode = it.noteModifyMode val bpmMode = it.bpmMode val waveMode = it.waveMode val setNoteMode = it.setNoteMode val lowestJudgmentConditionMode = it.lowestJudgmentConditionMode val totalNotes = it.totalNotes val judgmentStage = it.judgmentStage val hitPointsValue = it.hitPointsValue val highestInputCount = it.highestInputCount val length = it.length val bpm = it.bpm val multiplier = it.multiplier val inputMode = it.inputMode val stand = it.stand val highestBand = it.highestBand val point = it.point val hitPoints = it.hitPoints val highestJudgment0 = it.highestJudgment0 val higherJudgment0 = it.higherJudgment0 val highJudgment0 = it.highJudgment0 val lowJudgment0 = it.lowJudgment0 val lowerJudgment0 = it.lowerJudgment0 val lowestJudgment0 = it.lowestJudgment0 val highestJudgment1 = it.highestJudgment1 val higherJudgment1 = it.higherJudgment1 val highJudgment1 = it.highJudgment1 val lowJudgment1 = it.lowJudgment1 val lowerJudgment1 = it.lowerJudgment1 val lowestJudgment1 = it.lowestJudgment1 @JvmField val isF = it.avatarNetStatus == EventOuterClass.Event.AvatarNetStatus.Failed val netPosition = validNetItems.indexOf(it) } }.toList() } val comments = validNetItems.map { it.comment?.toByteString() }.toTypedArray() netAvatars.forEach { it.send( EventOuterClass.Event.EventID.QUIT_NET, text, *comments ) } if (netItemsHaveComment.isNotEmpty()) { netSIteCommentItems.add(NetSiteCommentItems(netItemsHaveComment, System.currentTimeMillis())) } } } private fun setValidMap(netItems: Map<String, NetItem>): Stream<NetItem> { fun getAvatarGroup(netItem: NetItem): String { val avatarID = netItem.avatarID val avatarGroup = avatarGroups[avatarID] ?: 0 return if (avatarGroup > 0) "#${avatarGroup}" else avatarID } fun getValue(netItem: NetItem): Double { return when (validHunterMode) { NetItem.STAND_MODE -> netItem.stand.toDouble() NetItem.POINT_MODE -> netItem.point NetItem.BAND_MODE -> netItem.band.toDouble() else -> 0.0 } } val avatarGroupValues = netItems.values.groupBy { getAvatarGroup(it) } .map { Pair(it.key, it.value.map { netItem -> getValue(netItem) }.average()) }.toMap() fun getAvatarGroupValue(avatarGroup: String): Double { return avatarGroupValues[avatarGroup] ?: 0.0 } return netItems.values.stream().sorted { o1, o2 -> val o1Failed = o1.avatarNetStatus == EventOuterClass.Event.AvatarNetStatus.Failed val o2Failed = o2.avatarNetStatus == EventOuterClass.Event.AvatarNetStatus.Failed if (o1Failed xor o2Failed) { o1Failed.compareTo(o2Failed) } else { val avatarGroup1 = getAvatarGroup(o1) val avatarGroup2 = getAvatarGroup(o2) if (avatarGroup1 != avatarGroup2) { getAvatarGroupValue(avatarGroup2).compareTo(getAvatarGroupValue(avatarGroup1)) } else { getValue(o2).compareTo(getValue(o1)) } } } } fun send(eventID: EventOuterClass.Event.EventID, text: Any?, vararg data: ByteString) { synchronized(avatarsCSX) { avatars.forEach { it.send( eventID, text, *data ) } } } fun send(event: EventOuterClass.Event) { synchronized(avatarsCSX) { avatars.forEach { it.send(event) } } } private fun sendSiteYell(siteID: UUID, siteYell: SiteYell, target: String) { val twilightSiteYell = JSON.TwilightSiteYell(siteID.toString(), siteYell, target) synchronized(avatarsCSX) { avatars.forEach { avatar -> avatar.send( EventOuterClass.Event.EventID.SITE_YELL, twilightSiteYell ) if (siteYell.translate) { Translator.translate(avatar.language, twilightSiteYell.siteYell, avatar).thenAccept { avatar.send( EventOuterClass.Event.EventID.MODIFY_SITE_YELL, object { val siteID = this@Site.siteID.toString() val siteYellID = twilightSiteYell.siteYellID val siteYell = it }) } } } } PlatformSystem.sendSiteYell(twilightSiteYell) } private fun doCallSiteAvatar(setNoteFile: Boolean) { if (hasAvatar) { synchronized(avatarsCSX) { send(EventOuterClass.Event.EventID.CALL_SITE_AVATAR, object { val siteID = this@Site.siteID.toString() val siteName = this@Site.siteName val siteHand = this@Site.siteHand?.avatarID ?: "" val situationValue = siteSituation val setNoteFile = setNoteFile val data = synchronized(avatarsCSX) { avatars.map { object { val avatarID = it.avatarID val avatarConfigure = (avatarConfigures[it.avatarID] ?: AvatarConfigure.DEFAULT).value val avatarName = it.avatarName val avatarGroup = avatarGroups[it.avatarID] ?: -1 @JvmField val isValve = it.isValve @JvmField val isAudioInput = isAudioInputs[it.avatarID] ?: false } } } }) } } } fun doCallSiteAvatar(avatar: Avatar?, avatars: List<Member>) { val data = object { val siteID = this@Site.siteID.toString() val siteName = this@Site.siteName val siteHand = "" val situationValue = SiteSituation.DEFAULT val setNoteFile = false val data = synchronized(avatarsCSX) { avatars.map { object { val avatarID = "$${it.id}" val avatarConfigure = AvatarConfigure.DEFAULT.value val avatarName = it.effectiveName @JvmField val isValve = false @JvmField val isAudioInput = false } } } } avatar?.send(EventOuterClass.Event.EventID.CALL_SITE_AVATAR, data) ?: send(EventOuterClass.Event.EventID.CALL_SITE_AVATAR, data) } private fun doCallSiteNet() { send(EventOuterClass.Event.EventID.CALL_SITE_NET, object { val siteID = this@Site.siteID.toString() val noteID = this@Site.noteID val noteIDs = this@Site.noteIDs val title = targetComputing.title val artist = targetComputing.artist val levelText = targetComputing.levelText val level = targetComputing.level val wantLevelID = targetComputing.wantLevelID val genre = targetComputing.genre val bpm = targetComputing.bpm val lowestBPM = targetComputing.lowestBPM val highestBPM = targetComputing.highestBPM 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 inputMode = targetComputing.inputMode @JvmField val isAutoLongNote = targetComputing.isAutoLongNote @JvmField val isFavorNoteFile = this@Site.isFavorNoteFile @JvmField val isFavorModeComponent = this@Site.isFavorModeComponent @JvmField val isFavorAudioMultiplier = this@Site.isFavorAudioMultiplier @JvmField val isAutoSiteHand = this@Site.isAutoSiteHand val validHunterMode = this@Site.validHunterMode val validNetMode = this@Site.validNetMode val allowedPostableItems = this@Site.allowedPostableItems val bundleEntryPath = this@Site.bundleEntryPath val bundleName = this@Site.bundleName }) } private fun doCallSiteModeComponent() { send(EventOuterClass.Event.EventID.CALL_SITE_MODE_COMPONENT, object { val siteID = this@Site.siteID.toString() val modeComponentData = this@Site.modeComponentData }) } fun setLevying(avatar: Avatar) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && isAvatarJoined(avatar) && siteSituation == SiteSituation.DEFAULT) { if (siteHand == avatar && validNetMode > 0 && allowedPostableItems.isEmpty()) { send(EventOuterClass.Event.EventID.WARNING, avatar.translateLanguage("wrongAllowedPostableItems")) } else { val avatarID = avatar.avatarID when (avatarConfigures[avatarID]) { AvatarConfigure.LEVYING -> { avatarConfigures.replace(avatarID, AvatarConfigure.DEFAULT) doCallSiteAvatar(false) } AvatarConfigure.DEFAULT -> { avatarConfigures.replace(avatarID, AvatarConfigure.LEVYING) doCallSiteAvatar(false) } else -> Unit } if (siteHand == avatar) { when (avatarConfigures[avatarID]) { AvatarConfigure.LEVYING, AvatarConfigure.DEFAULT -> { siteSituation = SiteSituation.COMPILING handlerID = UUID.randomUUID().toString() netItems.clear() val avatarsCount = avatars.count { avatarConfigures[it.avatarID] == AvatarConfigure.LEVYING } netItems.putAll(avatars.filter { if (avatarConfigures.replace( it.avatarID, AvatarConfigure.LEVYING, AvatarConfigure.COMPILING ) ) { it.send(EventOuterClass.Event.EventID.LEVY_NET, object { val siteID = this@Site.siteID.toString() val noteIDs = this@Site.noteIDs val handlerID = this@Site.handlerID @JvmField val isSiteHand = this@Site.siteHand == it @JvmField val isFavorModeComponent = this@Site.isFavorModeComponent @JvmField val isFavorAudioMultiplier = this@Site.isFavorAudioMultiplier val validHunterMode = this@Site.validHunterMode val validNetMode = this@Site.validNetMode val allowedPostableItems = this@Site.allowedPostableItems val modeComponentData = this@Site.modeComponentData val avatarsCount = avatarsCount }) true } else { false } }.map { Pair(it.avatarID, NetItem(it.avatarID, it.avatarName)) }) doCallSiteAvatar(false) } else -> Unit } } } } } } fun stopSiteNet(avatar: Avatar) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation != SiteSituation.DEFAULT) { synchronized(avatarsCSX) { avatars.filter { val avatarConfigure = avatarConfigures[it.avatarID] avatarConfigure == AvatarConfigure.COMPILING || avatarConfigure == AvatarConfigure.NET }.forEach { avatarConfigures[it.avatarID] = AvatarConfigure.DEFAULT it.send( EventOuterClass.Event.EventID.QUIT_NET, object { val handlerID = this@Site.handlerID } ) } } siteSituation = SiteSituation.DEFAULT doCallSiteAvatar(true) } } } fun setCompiled(avatar: Avatar, isCompiled: Boolean, handlerID: String) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && isAvatarJoined(avatar) && this.handlerID == handlerID && siteSituation != SiteSituation.DEFAULT) { val avatarID = avatar.avatarID if (isCompiled) { avatarConfigures.replace(avatarID, AvatarConfigure.COMPILING, AvatarConfigure.NET) } else { when (avatarConfigures[avatarID]) { AvatarConfigure.COMPILING, AvatarConfigure.NET -> { avatarConfigures[avatarID] = AvatarConfigure.DEFAULT } else -> Unit } netItems[avatarID]?.avatarNetStatus = EventOuterClass.Event.AvatarNetStatus.Failed } when (siteSituation) { SiteSituation.COMPILING -> { if (avatars.all { val avatarConfigure = avatarConfigures[it.avatarID] avatarConfigure == AvatarConfigure.DEFAULT || avatarConfigure == AvatarConfigure.LEVYING }) { siteSituation = SiteSituation.DEFAULT } else if (avatars.all { avatarConfigures[it.avatarID] != AvatarConfigure.COMPILING }) { siteSituation = SiteSituation.NET avatars.filter { avatarConfigures[it.avatarID] == AvatarConfigure.NET }.forEach { it.send( EventOuterClass.Event.EventID.COMPILED, this.handlerID ) } } } SiteSituation.NET -> { tryQuitNet() } else -> Unit } doCallSiteAvatar(false) } } } fun setNetData( avatar: Avatar, qwilightCallNet: EventOuterClass.Event.QwilightCallNet, comment: CommentOuterClass.Comment? ) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && isAvatarJoined(avatar) && handlerID == qwilightCallNet.handlerID && siteSituation == SiteSituation.NET) { val avatarID = avatar.avatarID if (avatarConfigures[avatarID] == AvatarConfigure.NET) { val targetNetItem = netItems[avatarID]!! if (targetNetItem.avatarNetStatus == EventOuterClass.Event.AvatarNetStatus.Default) { targetNetItem.setValues(qwilightCallNet, comment) val avatarNetStatus = qwilightCallNet.avatarNetStatus targetNetItem.avatarNetStatus = avatarNetStatus if (avatarNetStatus != EventOuterClass.Event.AvatarNetStatus.Default) { tryQuitNet() } else { val millis = System.currentTimeMillis() if (millis - netDataMillis > 1000.0 / 60) { netDataMillis = millis netItems.values.forEach { it.setFailed() } send(EventOuterClass.Event.newBuilder().apply { this.eventID = EventOuterClass.Event.EventID.CALL_NET this.twilightCallNet = EventOuterClass.Event.TwilightCallNet.newBuilder().apply { this.handlerID = this@Site.handlerID var targetPosition = 0 this.addAllData(setValidMap(netItems).map { EventOuterClass.Event.TwilightCallNet.CallNetItem.newBuilder().apply { this.avatarNetStatus = it.avatarNetStatus this.avatarID = it.avatarID this.avatarName = it.avatarName this.stand = it.stand this.band = it.band this.point = it.point this.hitPoints = it.hitPoints this.isFailed = it.failed this.lastJudged = it.lastJudged this.addAllDrawings(it.drawings) this.drawingComponent = it.drawingComponent this.hitPointsMode = it.hitPointsMode this.targetPosition = targetPosition++ }.build() }.toList()) }.build() }.build()) } } } } } } } fun getCallingData(): Any? { return if (isCallable) object { val siteID = this@Site.siteID.toString() val siteName = this@Site.siteName val siteSituation = this@Site.siteSituation val siteConfigure = siteMode val hasCipher = siteCipher.isNotEmpty() val avatarCount = (if (hasAvatar) avatars else PlatformSystem.platformAvatars).size } else null } fun isNetSiteHand(avatar: Avatar): Boolean { return synchronized(avatarsCSX) { siteMode == SiteMode.NET && avatar == siteHand } } fun quitAvatar(avatar: Avatar) { synchronized(avatarsCSX) { if (avatars.remove(avatar)) { val avatarID = avatar.avatarID lastAudioInputMillis.remove(avatarID) isAudioInputs.remove(avatarID) val avatarConfigure = avatarConfigures.remove(avatarID) if (avatarConfigure == AvatarConfigure.NET || avatarConfigure == AvatarConfigure.COMPILING) { netItems.remove(avatarID) avatar.send(EventOuterClass.Event.EventID.QUIT_NET, object { val handlerID = this@Site.handlerID }) tryQuitNet() } avatarGroups.remove(avatarID) if (siteHandQueue.remove(avatar)) { if (siteHand == avatar) { setAutoSiteHand() } } avatar.send(EventOuterClass.Event.EventID.QUIT_SITE, siteID.toString()) if (isEnterQuitAware && avatar.isEnterQuitAware) { putSiteYell("@Quit", avatarID, avatar.avatarName, System.currentTimeMillis()) } doCallSiteAvatar(true) } } } val isVoid: Boolean get() { return !isEssential && synchronized(avatarsCSX) { avatars.isEmpty() } } fun quitAvatars(): Boolean { synchronized(avatarsCSX) { avatars.toImmutableList().forEach { quitAvatar(it) } } return !isEssential } fun enterSite(avatar: Avatar) { synchronized(avatarsCSX) { avatars.add(avatar) val isNetSite = siteMode == SiteMode.NET val avatarID = avatar.avatarID avatarConfigures[avatarID] = AvatarConfigure.DEFAULT avatarGroups[avatarID] = if (isNetSite) 0 else -1 lastAudioInputMillis[avatarID] = Long.MIN_VALUE isAudioInputs[avatarID] = false siteHandQueue.addFirst(avatar) val data = synchronized(siteYells) { siteYells.stream().skip(0.coerceAtLeast(siteYells.size - SITE_YELL_UNIT).toLong()).map { val avatarName = it.avatarName val isWiped = it.isWiped object { val avatarID = it.avatarID val avatarName = if (isWiped) "@Wiped" else avatarName val date = it.date val siteYell = if (isWiped) avatarName else it.siteYell val siteYellID = it.siteYellID val translate = it.translate } }.toList() } avatar.send(EventOuterClass.Event.EventID.ENTER_SITE, object { val siteID = this@Site.siteID.toString() val siteNotify = this@Site.siteNotify @JvmField val isNetSite = isNetSite @JvmField val isGetNotify = this@Site.isGetNotify @JvmField val isEditable = this@Site.isEditable @JvmField val isAudioInput = siteMode != SiteMode.DEFAULT val data = data val noteID = this@Site.noteID val noteIDs = this@Site.noteIDs val bundleEntryPath = this@Site.bundleEntryPath val title = targetComputing.title val artist = targetComputing.artist val levelText = targetComputing.levelText val level = targetComputing.level val wantLevelID = targetComputing.wantLevelID val genre = targetComputing.genre val bpm = targetComputing.bpm val lowestBPM = targetComputing.lowestBPM val highestBPM = targetComputing.highestBPM 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 inputMode = targetComputing.inputMode @JvmField val isAutoLongNote = targetComputing.isAutoLongNote @JvmField val isFavorNoteFile = this@Site.isFavorNoteFile @JvmField val isFavorModeComponent = this@Site.isFavorModeComponent @JvmField val isFavorAudioMultiplier = this@Site.isFavorAudioMultiplier @JvmField val isAutoSiteHand = this@Site.isAutoSiteHand val validHunterMode = this@Site.validHunterMode val validNetMode = this@Site.validNetMode val allowedPostableItems = this@Site.allowedPostableItems val bundleName = this@Site.bundleName val modeComponentData = this@Site.modeComponentData }) data.filter { !it.avatarName.startsWith("@") }.filter { it.translate }.forEach { siteYell -> Translator.translate(avatar.language, siteYell.siteYell, avatar).thenAccept { avatar.send(EventOuterClass.Event.EventID.MODIFY_SITE_YELL, object { val siteID = this@Site.siteID.toString() val siteYellID = siteYell.siteYellID val siteYell = it }) } } if (isEnterQuitAware && avatar.isEnterQuitAware) { putSiteYell("@Enter", avatarID, avatar.avatarName, System.currentTimeMillis()) } if (hasAvatar) { doCallSiteAvatar(false) } else { doCallSiteAvatar(avatar, PlatformSystem.platformAvatars) } } } fun exileAvatar(avatar: Avatar, toExileAvatar: Avatar): Boolean { return synchronized(avatarsCSX) { if (siteHand == avatar) { if (toExileAvatar.isSU) { avatar.send( EventOuterClass.Event.EventID.NOTIFY_INFO, avatar.translateLanguage("failedExileCompetence") ) false } else { quitAvatar(toExileAvatar) true } } else { false } } } fun setSiteHand(avatar: Avatar, siteHand: Avatar) { synchronized(avatarsCSX) { if (this.siteHand == avatar) { avatarConfigures.replace(siteHand.avatarID, AvatarConfigure.LEVYING, AvatarConfigure.DEFAULT) this.siteHand = siteHand doCallSiteAvatar(true) } } } private fun setAutoSiteHand() { synchronized(avatarsCSX) { siteHandQueue.poll()?.let { siteHandQueue.add(it) if (it == siteHand) { setAutoSiteHand() } else { avatarConfigures.replace(it.avatarID, AvatarConfigure.LEVYING, AvatarConfigure.DEFAULT) siteHand = it doCallSiteAvatar(true) } } } } fun setSiteName(avatar: Avatar, siteName: String) { synchronized(avatarsCSX) { if (siteHand == avatar) { this.siteName = siteName doCallSiteAvatar(false) } } } fun enterSite(avatar: Avatar, siteCipher: String) { if (isCallable) { synchronized(avatarsCSX) { if (!avatar.isSU && avatars.size >= avatarsCeiling) { avatar.send(EventOuterClass.Event.EventID.WARNING, avatar.translateLanguage("siteHasNotSeat")) } else if (isAvatarJoined(avatar)) { avatar.send(EventOuterClass.Event.EventID.WARNING, avatar.translateLanguage("alreadyEnteredSite")) } else { if (avatar.isSU || this.siteCipher.isEmpty() || this.siteCipher == siteCipher) { enterSite(avatar) } else { avatar.send(EventOuterClass.Event.EventID.WARNING, avatar.translateLanguage("wrongSiteCipher")) } } } } } fun putSiteYell(siteID: UUID, siteName: String, avatarID: String, avatarName: String, millis: Long) { val target = getTarget(avatarID) sendSiteYell(SiteHandler.defaultSiteID, synchronized(siteYells) { val siteYell = SiteYell(avatarID, "@Invite", millis, ObjectMapper().writeValueAsString(object { val avatarName = avatarName val siteID = siteID.toString() val siteName = siteName }), siteYells.size, -1, false) putSiteYell(siteYell) siteYell }, target) } fun putSiteYell(tvItem: TVSystem.TVItem) { val target = getTarget("") val siteYell = synchronized(siteYells) { val siteYell = SiteYell( "", "@TV", System.currentTimeMillis(), ObjectMapper().writeValueAsString(tvItem), siteYells.size, -1, false ) putSiteYell(siteYell) siteYell } sendSiteYell(SiteHandler.defaultSiteID, siteYell, target) DB.saveSiteYell(siteID.toString(), siteYell) } fun putSiteYell(situation: String, avatarID: String, avatarName: String, millis: Long) { val target = getTarget(avatarID) val siteYell = synchronized(siteYells) { val siteYell = SiteYell(avatarID, situation, millis, avatarName, siteYells.size, -1, false) putSiteYell(siteYell) siteYell } sendSiteYell(siteID, siteYell, target) if (isEssential) { DB.saveSiteYell(siteID.toString(), siteYell) } } fun putSiteYell(avatar: Avatar, qwilightSiteYell: JSON.QwilightSiteYell, translate: Boolean) { synchronized(avatarsCSX) { if (isAvatarJoined(avatar)) { if (isEditable) { val avatarID = avatar.avatarID val avatarName = avatar.avatarName val millis = System.currentTimeMillis() if (avatar.isMillisSuitable(millis)) { if (qwilightSiteYell.target.isEmpty()) { qwilightSiteYell.target = getTarget(avatarID) } val siteYell = synchronized(siteYells) { val siteYell = SiteYell( avatarID, avatarName, millis, qwilightSiteYell.siteYell, siteYells.size, -1, translate ) putSiteYell(siteYell) siteYell } sendSiteYell( siteID, siteYell, qwilightSiteYell.target ) if (isEssential) { DB.saveSiteYell(siteID.toString(), siteYell) } if (!avatar.isAwilight) { logInfo("$avatarName ($avatarID): ${siteYell.siteYell}") } } else { avatar.send( EventOuterClass.Event.EventID.SITE_YELL, JSON.TwilightSiteYell( siteID.toString(), SiteYell( avatarID, "", millis, avatar.translateLanguage("millisNotSuitable"), -1, -1, false ), "" ) ) } } } } } fun putSiteYell(avatar: Member, event: MessageReceivedEvent) { val avatarID = "$${avatar.id}" PlatformSystem.putDrawing(avatarID, avatar.effectiveAvatarUrl).thenAccept { if (event.message.contentDisplay.isNotEmpty()) { val target = getTarget(avatarID) val siteYell = synchronized(siteYells) { val siteYell = SiteYell( avatarID, avatar.effectiveName, System.currentTimeMillis(), event.message.contentDisplay, siteYells.size, event.messageIdLong, true ) putSiteYell(siteYell) siteYell } sendSiteYell(siteID, siteYell, target) if (isEssential) { DB.saveSiteYell(siteID.toString(), siteYell) } } event.message.stickers.forEach { val target = getTarget(avatarID) val siteYell = synchronized(siteYells) { val siteYell = SiteYell( avatarID, avatar.effectiveName, System.currentTimeMillis(), it.iconUrl, siteYells.size, event.messageIdLong, false ) putSiteYell(siteYell) siteYell } sendSiteYell(siteID, siteYell, target) if (isEssential) { DB.saveSiteYell(siteID.toString(), siteYell) } } event.message.attachments.forEach { val target = getTarget(avatarID) val siteYell = synchronized(siteYells) { val siteYell = SiteYell( avatarID, avatar.effectiveName, System.currentTimeMillis(), it.url, siteYells.size, event.messageIdLong, false ) putSiteYell(siteYell) siteYell } sendSiteYell(siteID, siteYell, target) if (isEssential) { DB.saveSiteYell(siteID.toString(), siteYell) } } } } fun doModifySiteYell(avatar: Member, event: MessageUpdateEvent) { PlatformSystem.putDrawing("$${avatar.id}", avatar.effectiveAvatarUrl).thenAccept { synchronized(siteYells) { platformSiteYells.computeIfPresent(event.messageIdLong) { _, platformSiteYell -> platformSiteYell.forEach { it.siteYell = event.message.contentDisplay DB.doModifySiteYell(it) send(EventOuterClass.Event.EventID.MODIFY_SITE_YELL, object { val siteID = SiteHandler.platformSiteID val siteYellID = it.siteYellID val siteYell = it.siteYell }) } platformSiteYell } } } } fun wipeSiteYell(event: MessageDeleteEvent) { synchronized(siteYells) { platformSiteYells.computeIfPresent(event.messageIdLong) { _, platformSiteYell -> platformSiteYell.forEach { it.isWiped = true DB.wipeSiteYell(it) send(EventOuterClass.Event.EventID.WIPE_SITE_YELL, object { val siteID = SiteHandler.platformSiteID val siteYellID = it.siteYellID }) } null } } } fun setNetBundle(bundleName: String) { this.bundleName = bundleName doCallSiteNet() } fun getSiteYells(avatar: Avatar, qwilightGetSiteYells: JSON.QwilightGetSiteYells) { synchronized(avatarsCSX) { if (isAvatarJoined(avatar)) { val siteYellID = qwilightGetSiteYells.siteYellID val data = synchronized(siteYells) { siteYells.stream().skip( 0.coerceAtLeast(siteYellID - SITE_YELL_UNIT).toLong() ).limit(siteYellID.coerceAtMost(SITE_YELL_UNIT).toLong()).map { val avatarName = it.avatarName val isWiped = it.isWiped object { val avatarID = it.avatarID val avatarName = if (isWiped) "@Wiped" else avatarName val date = it.date val siteYell = if (isWiped) avatarName else it.siteYell val siteYellID = it.siteYellID val translate = it.translate } }.toList() } avatar.send(EventOuterClass.Event.EventID.GET_SITE_YELLS, object { val siteID = this@Site.siteID.toString() val data = data }) data.filter { !it.avatarName.startsWith("@") }.filter { it.translate }.forEach { Translator.translate(avatar.language, it.siteYell, avatar).thenAccept { siteYell -> avatar.send(EventOuterClass.Event.EventID.MODIFY_SITE_YELL, object { val siteID = this@Site.siteID val siteYellID = it.siteYellID val siteYell = siteYell }) } } } } } private fun getTarget(avatarID: String): String { return synchronized(avatarsCSX) { val avatars = avatars.filter { it.isAwilight && it.avatarID != avatarID } if (avatars.isEmpty()) { "" } else { avatars[(Math.random() * avatars.size).toInt()].avatarID } } } fun setSiteNotify(siteNotify: String) { this.siteNotify = siteNotify val siteYell = synchronized(siteYells) { val siteYell = SiteYell("", "@Notify", System.currentTimeMillis(), siteNotify, siteYells.size, -1, true) putSiteYell(siteYell) siteYell } sendSiteYell(siteID, siteYell, "") if (isEssential) { DB.saveSiteYell(siteID.toString(), siteYell) } } fun putSIteYell(twilightCommentSiteYell: JSON.TwilightCommentSiteYell) { val siteYell = synchronized(siteYells) { val siteYell = SiteYell( twilightCommentSiteYell.avatarID, "@Comment", System.currentTimeMillis(), ObjectMapper().writeValueAsString(twilightCommentSiteYell), siteYells.size, -1, false ) putSiteYell(siteYell) siteYell } sendSiteYell(siteID, siteYell, "") DB.saveSiteYell(siteID.toString(), siteYell) } fun putSIteYell(twilightAbilitySiteYell: JSON.TwilightAbilitySiteYell) { val siteYell = synchronized(siteYells) { val siteYell = SiteYell( twilightAbilitySiteYell.avatarID, "@Ability", System.currentTimeMillis(), ObjectMapper().writeValueAsString(twilightAbilitySiteYell), siteYells.size, -1, false ) putSiteYell(siteYell) siteYell } sendSiteYell(siteID, siteYell, "") DB.saveSiteYell(siteID.toString(), siteYell) } fun putSIteYell(twilightLevelSiteYell: JSON.TwilightLevelSiteYell) { val siteYell = synchronized(siteYells) { val siteYell = SiteYell( twilightLevelSiteYell.avatarID, "@Level", System.currentTimeMillis(), ObjectMapper().writeValueAsString(twilightLevelSiteYell), siteYells.size, -1, false ) putSiteYell(siteYell) siteYell } sendSiteYell(siteID, siteYell, "") DB.saveSiteYell(siteID.toString(), siteYell) } fun setModeComponent(avatar: Avatar, modeComponentData: Any) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) { setModeComponent(modeComponentData) } } } fun setModeComponent(modeComponentData: Any) { this.modeComponentData = modeComponentData doCallSiteModeComponent() } fun setFavorNoteFile(avatar: Avatar) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) { isFavorNoteFile = !isFavorNoteFile doCallSiteNet() doCallSiteAvatar(true) } } } fun setFavorModeComponent(avatar: Avatar) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) { isFavorModeComponent = !isFavorModeComponent doCallSiteNet() doCallSiteModeComponent() } } } fun setFavorAudioMultiplier(avatar: Avatar) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) { isFavorAudioMultiplier = !isFavorAudioMultiplier if (isFavorAudioMultiplier) { validNetMode = 0 } doCallSiteNet() } } } fun setAvatarGroup(avatar: Avatar, qwilightSetAvatarGroup: JSON.QwilightSetAvatarGroup) { val avatarGroup = qwilightSetAvatarGroup.avatarGroup if (avatarGroup in 0..4) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && siteSituation == SiteSituation.DEFAULT) { avatarGroups[avatar.avatarID] = qwilightSetAvatarGroup.avatarGroup doCallSiteAvatar(false) } } } } fun setAutoSiteHand(avatar: Avatar) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && siteHand == avatar) { isAutoSiteHand = !isAutoSiteHand doCallSiteNet() } } } fun setValidHunterMode(avatar: Avatar) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) { validHunterMode = (validHunterMode + 1) % 3 doCallSiteNet() } } } fun setValidNetMode(avatar: Avatar, qwilightSetValidNetMode: JSON.QwilightSetValidNetMode) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) { validNetMode = qwilightSetValidNetMode.validNetMode if (validNetMode > 0) { isFavorAudioMultiplier = false } doCallSiteNet() } } } fun setAllowedPostableItems(allowedPostableItems: Array<Int>) { this.allowedPostableItems = allowedPostableItems doCallSiteNet() } fun setAllowedPostableItems( avatar: Avatar, qwilightSetAllowedPostableItems: JSON.QwilightSetAllowedPostableItems ) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) { setAllowedPostableItems(qwilightSetAllowedPostableItems.allowedPostableItems) } } } fun doCallNetSiteComments(avatar: Avatar) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && isAvatarJoined(avatar)) { if (netSIteCommentItems.isEmpty()) { avatar.send( EventOuterClass.Event.EventID.WARNING, avatar.translateLanguage("netSiteCommentNotAvailable") ) } else { avatar.send( EventOuterClass.Event.EventID.CALL_NET_SITE_COMMENTS, netSIteCommentItems.stream().map { object { val date = it.date val data = setValidMap(it.netItems).map { netItem -> object { val avatarNetStatus = netItem.avatarNetStatus.number val avatarID = netItem.avatarID val avatarName = netItem.avatarName val stand = netItem.stand val band = netItem.band val point = netItem.point val highestJudgment = netItem.comment?.highestJudgment ?: 0 val higherJudgment = netItem.comment?.higherJudgment ?: 0 val highJudgment = netItem.comment?.highJudgment ?: 0 val lowJudgment = netItem.comment?.lowJudgment ?: 0 val lowerJudgment = netItem.comment?.lowerJudgment ?: 0 val lowestJudgment = netItem.comment?.lowestJudgment ?: 0 } }.toArray() } }.toArray() ) } } } } fun setNoteFileContents(avatar: Avatar, noteFileData: JSON.QwilightSetNoteFile) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) { if (Arrays.compare(noteFileData.noteIDs, noteIDs) != 0) { synchronized(avatarsCSX) { avatars.forEach { avatarConfigures[it.avatarID] = AvatarConfigure.DEFAULT } } doCallSiteAvatar(false) } setNoteFileContents(noteFileData) } } } fun setNoteFileContents(noteFileData: JSON.QwilightSetNoteFile) { noteID = noteFileData.noteID noteIDs = noteFileData.noteIDs bundleEntryPath = noteFileData.bundleEntryPath targetComputing.title = noteFileData.title targetComputing.artist = noteFileData.artist targetComputing.genre = noteFileData.genre targetComputing.levelText = noteFileData.levelText targetComputing.level = noteFileData.level targetComputing.wantLevelID = noteFileData.wantLevelID targetComputing.bpm = noteFileData.bpm targetComputing.lowestBPM = noteFileData.lowestBPM targetComputing.highestBPM = noteFileData.highestBPM targetComputing.judgmentStage = noteFileData.judgmentStage targetComputing.hitPointsValue = noteFileData.hitPointsValue targetComputing.totalNotes = noteFileData.totalNotes targetComputing.longNotes = noteFileData.longNotes targetComputing.autoableNotes = noteFileData.autoableNotes targetComputing.trapNotes = noteFileData.trapNotes targetComputing.highestInputCount = noteFileData.highestInputCount targetComputing.length = noteFileData.length targetComputing.isAutoLongNote = noteFileData.isAutoLongNote targetComputing.inputMode = noteFileData.inputMode doCallSiteNet() } fun setSiteYells(siteYells: Collection<SiteYell>) { synchronized(siteYells) { siteYells.forEach { putSiteYell(it) } } } private fun putSiteYell(siteYell: SiteYell) { this.siteYells.add(siteYell) val platformID = siteYell.platformID if (platformID != -1L) { platformSiteYells.computeIfAbsent(platformID) { mutableListOf() }.add(siteYell) } } fun silentSite() { synchronized(siteYells) { siteYells.clear() } } fun audioInput(avatar: Avatar, data: ByteString) { synchronized(avatarsCSX) { if (siteMode != SiteMode.DEFAULT && isAvatarJoined(avatar)) { val avatarID = avatar.avatarID val event = EventOuterClass.Event.newBuilder().apply { eventID = EventOuterClass.Event.EventID.AUDIO_INPUT twilightAudioInput = EventOuterClass.Event.TwilightAudioInput.newBuilder().apply { siteID = this@Site.siteID.toString() this.avatarID = avatarID }.build() addData(data) }.build() avatars.filter { it.avatarID != avatarID }.forEach { it.send(event) } lastAudioInputMillis[avatarID] = System.currentTimeMillis() } } } fun postItem(avatar: Avatar, qwilightPostItem: EventOuterClass.Event.QwilightPostItem) { synchronized(avatarsCSX) { if (siteMode == SiteMode.NET && isAvatarJoined(avatar) && handlerID == qwilightPostItem.handlerID && siteSituation == SiteSituation.NET && netItems[avatar.avatarID]?.avatarNetStatus == EventOuterClass.Event.AvatarNetStatus.Default) { val avatarGroup = avatarGroups[avatar.avatarID] val wait = if (qwilightPostItem.lowestWait < qwilightPostItem.highestWait) Random.nextDouble( qwilightPostItem.lowestWait, qwilightPostItem.highestWait ) else qwilightPostItem.highestWait avatars.filter { val isPositive = qwilightPostItem.isPositive if (isPositive == -1) { true } else { if (avatar == it) { isPositive > 0 } else { (isPositive > 0) xor (avatarGroups[it.avatarID] == 0 || avatarGroups[it.avatarID] != avatarGroup) } } }.forEach { it.send(EventOuterClass.Event.newBuilder().apply { eventID = EventOuterClass.Event.EventID.POST_ITEM twilightPostItem = EventOuterClass.Event.TwilightPostItem.newBuilder().apply { handlerID = this@Site.handlerID avatarName = avatar.avatarName postedItem = qwilightPostItem.postedItem this.wait = wait }.build() }.build()) } } } } override fun toString(): String { return when (siteSituation) { SiteSituation.DEFAULT -> "[$siteID] $siteName [${avatars.size}] Idle" SiteSituation.COMPILING -> "[$siteID] $siteName [${avatars.size}] Compiling Note File" SiteSituation.NET -> "[$siteID] $siteName [${avatars.size}] Net Computing" } } override fun logInfo(toNotify: String) { LoggerFactory.getLogger(javaClass).info("[{}] {}", siteName, toNotify) } override fun logFault(e: Throwable) { LoggerFactory.getLogger(javaClass).error("[{}] {}", siteName, Utility.getFaultText(e)) } companion object { const val SITE_YELL_UNIT = 16 private val ses: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor { Executors.defaultThreadFactory().newThread(it).apply { isDaemon = true } } } }