From bb960b4478f1b84b853e6881e8edf5a54bda3e14 Mon Sep 17 00:00:00 2001 From: Jonn Date: Sat, 17 Aug 2024 16:26:11 +0900 Subject: [PATCH 1/4] Refactor game chat commands. --- .../controller/v086/action/GameChatAction.kt | 453 +------------ .../controller/v086/commands/Commands.kt | 635 ++++++++++++++++++ .../org/emulinker/kaillera/pico/AppModule.kt | 7 + 3 files changed, 664 insertions(+), 431 deletions(-) create mode 100644 emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt diff --git a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt index d5f72c3ad..f8321f397 100644 --- a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt +++ b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt @@ -4,24 +4,22 @@ import com.google.common.flogger.FluentLogger import java.util.* import javax.inject.Inject import javax.inject.Singleton -import kotlin.time.Duration.Companion.milliseconds import org.emulinker.kaillera.access.AccessManager import org.emulinker.kaillera.controller.messaging.MessageFormatException import org.emulinker.kaillera.controller.v086.V086ClientHandler +import org.emulinker.kaillera.controller.v086.commands.GameChatCommand import org.emulinker.kaillera.controller.v086.protocol.* -import org.emulinker.kaillera.lookingforgame.TwitterBroadcaster import org.emulinker.kaillera.model.event.GameChatEvent import org.emulinker.kaillera.model.exception.ActionException import org.emulinker.kaillera.model.exception.GameChatException -import org.emulinker.util.EmuLang -import org.emulinker.util.EmuUtil.threadSleep +import org.emulinker.kaillera.model.impl.KailleraGameImpl @Singleton class GameChatAction @Inject internal constructor( private val gameOwnerCommandAction: GameOwnerCommandAction, - private val lookingForGameReporter: TwitterBroadcaster + private val gameChatCommands: @JvmSuppressWildcards List, ) : V086Action, V086GameEventHandler { override var actionPerformedCount = 0 private set @@ -40,7 +38,9 @@ internal constructor( if (gameOwnerCommandAction.isValidCommand((message as GameChat).message)) { gameOwnerCommandAction.performAction(message, clientHandler) if ((message as GameChat).message == "/help") checkCommands(message, clientHandler) - } else checkCommands(message, clientHandler) + } else { + checkCommands(message, clientHandler) + } return } catch (e: FatalActionException) { logger.atWarning().withCause(e).log("GameOwner command failed") @@ -57,440 +57,31 @@ internal constructor( } @Throws(FatalActionException::class) - private fun checkCommands(message: V086Message, clientHandler: V086ClientHandler?) { + private fun checkCommands(message: GameChat, clientHandler: V086ClientHandler?) { var doCommand = true if (clientHandler!!.user.accessLevel < AccessManager.ACCESS_ELEVATED) { try { - clientHandler.user.chat(":USER_COMMAND") + clientHandler.user.chat(":USER_COMMAND") // TODO(nue): What is this?? } catch (e: ActionException) { doCommand = false } } + val game: KailleraGameImpl = clientHandler.user.game ?: return if (doCommand) { - if ((message as GameChat).message == "/msgon") { - try { - clientHandler.user.isAcceptingDirectMessages = true - clientHandler.user.game!!.announce("Private messages are now on.", clientHandler.user) - } catch (e: Exception) {} - return - } else if (message.message == "/msgoff") { - try { - clientHandler.user.isAcceptingDirectMessages = false - clientHandler.user.game!!.announce("Private messages are now off.", clientHandler.user) - } catch (e: Exception) {} - return - } else if (message.message.startsWith("/p2p")) { - if (message.message == "/p2pon") { - if (clientHandler.user.game!!.owner == clientHandler.user) { - clientHandler.user.game!!.ignoringUnnecessaryServerActivity = true - for (u in clientHandler.user.game!!.players) { - u.ignoringUnnecessaryServerActivity = true - if (u.loggedIn) { - u.game!!.announce( - "This game will NOT receive any server activity during gameplay!", - u - ) - } - } - } else { - clientHandler.user.ignoringUnnecessaryServerActivity = true - for (u in clientHandler.user.game!!.players) { - if (u.loggedIn) { - u.game!!.announce( - "${clientHandler.user.name} will NOT receive any server activity during gameplay!", - u - ) - } - } - } - } else if (message.message == "/p2poff") { - if (clientHandler.user.game!!.owner == clientHandler.user) { - clientHandler.user.game!!.ignoringUnnecessaryServerActivity = false - for (u in clientHandler.user.game!!.players) { - u.ignoringUnnecessaryServerActivity = false - if (u.loggedIn) { - u.game!!.announce( - "This game will NOW receive ALL server activity during gameplay!", - u - ) - } - } - } else { - clientHandler.user.ignoringUnnecessaryServerActivity = false - for (u in clientHandler.user.game!!.players) { - if (u.loggedIn) { - u.game!!.announce( - clientHandler.user.name + - " will NOW receive ALL server activity during gameplay!", - u - ) - } - } - } - } else { - clientHandler.user.game!!.announce("Failed P2P: /p2pon or /p2poff", clientHandler.user) - } - return - } else if (message.message.startsWith("/msg")) { - val scanner = Scanner(message.message).useDelimiter(" ") - val access = - clientHandler.user.server.accessManager.getAccess( - clientHandler.user.socketAddress!!.address - ) - if ( - access < AccessManager.ACCESS_SUPERADMIN && - clientHandler.user.server.accessManager.isSilenced( - clientHandler.user.socketAddress!!.address - ) - ) { - clientHandler.user.game!!.announce("You are silenced!", clientHandler.user) - return - } - try { - scanner.next() - val userID = scanner.nextInt() - val user = clientHandler.user.server.getUser(userID) - val sb = StringBuilder() - while (scanner.hasNext()) { - sb.append(scanner.next()) - sb.append(" ") - } - if (user == null) { - clientHandler.user.game!!.announce("User not found!", clientHandler.user) - return - } - if (user.game != clientHandler.user.game) { - clientHandler.user.game!!.announce("User not in this game!", clientHandler.user) - return - } - if (user === clientHandler.user) { - clientHandler.user.game!!.announce( - "You can't private message yourself!", - clientHandler.user - ) - return - } - if ( - !user.isAcceptingDirectMessages || - user.searchIgnoredUsers(clientHandler.user.connectSocketAddress.address.hostAddress) - ) { - clientHandler.user.game!!.announce( - "<" + user.name + "> Is not accepting private messages!", - clientHandler.user - ) - return - } - var m = sb.toString() - m = m.trim { it <= ' ' } - if (m.isBlank() || m.startsWith("�") || m.startsWith("�")) return - if (access == AccessManager.ACCESS_NORMAL) { - val chars = m.toCharArray() - for (i in chars.indices) { - if (chars[i].code < 32) { - logger.atWarning().log("%s /msg denied: Illegal characters in message", user) - clientHandler.user.game!!.announce( - "Private Message Denied: Illegal characters in message", - clientHandler.user - ) - return - } - } - if (m.length > 320) { - logger.atWarning().log("%s /msg denied: Message Length > 320", user) - clientHandler.user.game!!.announce( - "Private Message Denied: Message Too Long", - clientHandler.user - ) - return - } - } - clientHandler.user.lastMsgID = user.id - user.lastMsgID = clientHandler.user.id - - // user1.getServer().announce("TO: <" + user.getName() + ">(" + user.getID() + ") <" + - // clientHandler.getUser().getName() + "> (" + clientHandler.getUser().getID() + "): " + - // m, false, user1); - // user.getServer().announce("<" + clientHandler.getUser().getName() + "> (" + - // clientHandler.getUser().getID() + "): " + m, false, user); - clientHandler.user.game?.announce( - "TO: <${user.name}>(${user.id}) <${clientHandler.user.name}> (${clientHandler.user.id}): $m", - clientHandler.user - ) - user.game?.announce("<${clientHandler.user.name}> (${clientHandler.user.id}): $m", user) - return - } catch (e: NoSuchElementException) { - if (clientHandler.user.lastMsgID != -1) { - try { - val user = clientHandler.user.server.getUser(clientHandler.user.lastMsgID) - val sb = StringBuilder() - while (scanner.hasNext()) { - sb.append(scanner.next()) - sb.append(" ") - } - if (user == null) { - clientHandler.user.game!!.announce("User not found!", clientHandler.user) - return - } - if (user.game != clientHandler.user.game) { - clientHandler.user.game!!.announce("User not in this game!", clientHandler.user) - return - } - if (user === clientHandler.user) { - clientHandler.user.game!!.announce( - "You can't private message yourself!", - clientHandler.user - ) - return - } - var m = sb.toString() - m = m.trim { it <= ' ' } - if (m.isBlank() || m.startsWith("�") || m.startsWith("�")) return - if (access == AccessManager.ACCESS_NORMAL) { - val chars = m.toCharArray() - var i = 0 - while (i < chars.size) { - if (chars[i].code < 32) { - logger.atWarning().log("%s /msg denied: Illegal characters in message", user) - clientHandler.user.game!!.announce( - "Private Message Denied: Illegal characters in message", - clientHandler.user - ) - return - } - i++ - } - if (m.length > 320) { - logger.atWarning().log("%s /msg denied: Message Length > 320", user) - clientHandler.user.game!!.announce( - "Private Message Denied: Message Too Long", - clientHandler.user - ) - return - } - } - - // user1.getServer().announce("TO: <" + user.getName() + ">(" + user.getID() + ") <" + - // clientHandler.getUser().getName() + "> (" + clientHandler.getUser().getID() + "): " - // + m, false, user1); - // user.getServer().announce("<" + clientHandler.getUser().getName() + "> (" + - // clientHandler.getUser().getID() + "): " + m, false, user); - clientHandler.user.game?.announce( - "TO: <${user.name}>(${user.id}) <${clientHandler.user.name}> (${clientHandler.user.id}): $m", - clientHandler.user - ) - user.game?.announce( - "<${clientHandler.user.name}> (${clientHandler.user.id}): $m", - user - ) - return - } catch (e1: Exception) { - clientHandler.user.game!!.announce( - "Private Message Error: /msg ", - clientHandler.user - ) - return - } - } else { - clientHandler.user.game!!.announce( - "Private Message Error: /msg ", - clientHandler.user - ) - return - } - } - } else if (message.message == "/ignoreall") { - try { - clientHandler.user.ignoreAll = true - clientHandler.user.server.announce( - clientHandler.user.name + " is now ignoring everyone!", - false, - null - ) - } catch (e: Exception) {} - return - } else if (message.message == "/unignoreall") { - try { - clientHandler.user.ignoreAll = false - clientHandler.user.server.announce( - clientHandler.user.name + " is now unignoring everyone!", - false, - null - ) - } catch (e: Exception) {} - return - } else if (message.message.startsWith("/ignore")) { - val scanner = Scanner(message.message).useDelimiter(" ") - try { - scanner.next() - val userID = scanner.nextInt() - val user = clientHandler.user.server.getUser(userID) - if (user == null) { - clientHandler.user.game!!.announce("User not found!", clientHandler.user) - return - } - if (user === clientHandler.user) { - clientHandler.user.game!!.announce("You can't ignore yourself!", clientHandler.user) - return - } - if (clientHandler.user.findIgnoredUser(user.connectSocketAddress.address.hostAddress)) { - clientHandler.user.game!!.announce( - "You can't ignore a user that is already ignored!", - clientHandler.user - ) - return - } - if (user.accessLevel >= AccessManager.ACCESS_MODERATOR) { - clientHandler.user.game!!.announce( - "You cannot ignore a moderator or admin!", - clientHandler.user - ) - return - } - clientHandler.user.addIgnoredUser(user.connectSocketAddress.address.hostAddress) - user.server.announce( - "${clientHandler.user.name} is now ignoring <${user.name}> ID: ${user.id}", - false, - null - ) - return - } catch (e: NoSuchElementException) { - clientHandler.user.game!!.announce( - "Ignore User Error: /ignore ", - clientHandler.user - ) - logger - .atInfo() - .withCause(e) - .log( - "IGNORE USER ERROR: %s: %s", - clientHandler.user.name, - clientHandler.remoteSocketAddress!!.hostName - ) - return - } - } else if (message.message.startsWith("/unignore")) { - val scanner = Scanner(message.message).useDelimiter(" ") - try { - scanner.next() - val userID = scanner.nextInt() - val user = clientHandler.user.server.getUser(userID) - if (user == null) { - clientHandler.user.game!!.announce("User Not Found!", clientHandler.user) - return - } - if (!clientHandler.user.findIgnoredUser(user.connectSocketAddress.address.hostAddress)) { - clientHandler.user.game!!.announce( - "You can't unignore a user that isn't ignored", - clientHandler.user - ) - return - } - if ( - clientHandler.user.removeIgnoredUser( - user.connectSocketAddress.address.hostAddress, - false - ) - ) - user.server.announce( - "${clientHandler.user.name} is now unignoring <${user.name}> ID: ${user.id}", - false, - null - ) - else - try { - clientHandler.send( - InformationMessage(clientHandler.nextMessageNumber, "server", "User Not Found!") - ) - } catch (e: Exception) {} - return - } catch (e: NoSuchElementException) { - clientHandler.user.game!!.announce( - "Unignore User Error: /ignore ", - clientHandler.user - ) - logger - .atInfo() - .withCause(e) - .log( - "UNIGNORE USER ERROR: %s: %s", - clientHandler.user.name, - clientHandler.remoteSocketAddress!!.hostName - ) - return - } - } else if (message.message.startsWith("/me")) { - val space = message.message.indexOf(' ') - if (space < 0) { - clientHandler.user.game!!.announce("Invalid # of Fields!", clientHandler.user) - return - } - var announcement = message.message.substring(space + 1) - if (announcement.startsWith(":")) - announcement = - announcement.substring( - 1 - ) // this protects against people screwing up the emulinker supraclient - val access = - clientHandler.user.server.accessManager.getAccess( - clientHandler.user.socketAddress!!.address - ) - if ( - access < AccessManager.ACCESS_SUPERADMIN && - clientHandler.user.server.accessManager.isSilenced( - clientHandler.user.socketAddress!!.address - ) - ) { - clientHandler.user.game!!.announce("You are silenced!", clientHandler.user) - return - } - if (clientHandler.user.server.checkMe(clientHandler.user, announcement)) { - val m = announcement - announcement = "*" + clientHandler.user.name + " " + m - for (user in clientHandler.user.game!!.players) { - user.game!!.announce(announcement, user) - } - return - } - } else if (message.message == "/help") { - clientHandler.user.game!!.announce( - "/me to make personal message eg. /me is bored ...SupraFast is bored.", - clientHandler.user - ) - threadSleep(20.milliseconds) - clientHandler.user.game!!.announce( - "/msg to PM somebody. /msgoff or /msgon to turn pm off | on.", - clientHandler.user - ) - threadSleep(20.milliseconds) - clientHandler.user.game!!.announce( - "/ignore or /unignore or /ignoreall or /unignoreall to ignore users.", - clientHandler.user - ) - threadSleep(20.milliseconds) - clientHandler.user.game!!.announce( - "/p2pon or /p2poff this option ignores all server activity during gameplay.", - clientHandler.user - ) - threadSleep(20.milliseconds) - } else if (message.message == "/stop") { - if (lookingForGameReporter.cancelActionsForUser(clientHandler.user.id)) { - clientHandler.user.game!!.announce( - EmuLang.getStringOrDefault( - "KailleraServerImpl.CanceledPendingTweet", - default = "Canceled pending tweet." - ), - clientHandler.user - ) - } else { - clientHandler.user.game!!.announce("No pending tweets.", clientHandler.user) - } - } else - clientHandler.user.game!!.announce( - "Unknown Command: " + message.message, - clientHandler.user - ) + val commandName = + message.message + .trim() + .removePrefix(GameChatCommand.COMMAND_PREFIX) + .split(" ", limit = 1) + .first() + val matchedCommand = gameChatCommands.firstOrNull { it.prefix == commandName } + if (matchedCommand == null) { + game.announce("Unknown Command: " + message.message, clientHandler.user) + } else { + matchedCommand.handle(message.message.trim(), game, clientHandler) + } } else { - clientHandler.user.game!!.announce("Denied: Flood Control", clientHandler.user) + game.announce("Denied: Flood Control", clientHandler.user) } } diff --git a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt new file mode 100644 index 000000000..3adf81615 --- /dev/null +++ b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt @@ -0,0 +1,635 @@ +package org.emulinker.kaillera.controller.v086.commands + +import com.google.common.flogger.FluentLogger +import kotlin.time.Duration.Companion.milliseconds +import org.emulinker.kaillera.access.AccessManager +import org.emulinker.kaillera.controller.v086.V086ClientHandler +import org.emulinker.kaillera.lookingforgame.TwitterBroadcaster +import org.emulinker.kaillera.model.impl.KailleraGameImpl +import org.emulinker.util.EmuUtil.threadSleep + +private val logger = FluentLogger.forEnclosingClass() + +sealed interface ParseResult { + data object Success : ParseResult + + @JvmInline value class Failure(val messageToUser: String) : ParseResult +} + +enum class CommandUsageLocation { + ANYWHERE, + ROOM_CHAT, + SERVER_CHAT, +} + +fun provideGameChatCommands(twitterBroadcaster: TwitterBroadcaster): List = + listOf( + object : + GameChatCommand( + prefix = "msgon", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + clientHandler.user.isAcceptingDirectMessages = true + game.announce("Private messages are now on.", clientHandler.user) + } + + override fun matches(command: String): ParseResult { + TODO("Not yet implemented") + } + }, + object : + GameChatCommand( + prefix = "msgoff", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + clientHandler.user.isAcceptingDirectMessages = false + game.announce("Private messages are now off.", clientHandler.user) + } + + override fun matches(command: String): ParseResult { + TODO("Not yet implemented") + } + }, + object : + GameChatCommand( + prefix = "p2pon", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + if (game.owner == clientHandler.user) { + game.ignoringUnnecessaryServerActivity = true + for (u in game.players) { + u.ignoringUnnecessaryServerActivity = true + if (u.loggedIn) { + game.announce("This game will NOT receive any server activity during gameplay!", u) + } + } + } else { + clientHandler.user.ignoringUnnecessaryServerActivity = true + for (u in game.players) { + if (u.loggedIn) { + game.announce( + "${clientHandler.user.name} will NOT receive any server activity during gameplay!", + u + ) + } + } + } + } + + override fun matches(command: String): ParseResult { + TODO("Not yet implemented") + } + }, + object : + GameChatCommand( + prefix = "p2poff", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + if (game.owner == clientHandler.user) { + game.ignoringUnnecessaryServerActivity = false + for (u in game.players) { + u.ignoringUnnecessaryServerActivity = false + if (u.loggedIn) { + game.announce("This game will NOW receive ALL server activity during gameplay!", u) + } + } + } else { + clientHandler.user.ignoringUnnecessaryServerActivity = false + for (u in game.players) { + if (u.loggedIn) { + game.announce( + clientHandler.user.name + " will NOW receive ALL server activity during gameplay!", + u + ) + } + } + } + } + + override fun matches(command: String): ParseResult { + TODO("Not yet implemented") + } + }, + object : + GameChatCommand( + prefix = "msg", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val scanner = java.util.Scanner(message).useDelimiter(" ") + val access = + clientHandler.user.server.accessManager.getAccess( + clientHandler.user.socketAddress!!.address + ) + if ( + access < AccessManager.ACCESS_SUPERADMIN && + clientHandler.user.server.accessManager.isSilenced( + clientHandler.user.socketAddress!!.address + ) + ) { + game.announce("You are silenced!", clientHandler.user) + return + } + try { + scanner.next() + val userID = scanner.nextInt() + val user = clientHandler.user.server.getUser(userID) + val sb = StringBuilder() + while (scanner.hasNext()) { + sb.append(scanner.next()) + sb.append(" ") + } + if (user == null) { + game.announce("User not found!", clientHandler.user) + return + } + if (user.game != game) { + game.announce("User not in this game!", clientHandler.user) + return + } + if (user === clientHandler.user) { + game.announce("You can't private message yourself!", clientHandler.user) + return + } + if ( + !user.isAcceptingDirectMessages || + user.searchIgnoredUsers(clientHandler.user.connectSocketAddress.address.hostAddress) + ) { + game.announce( + "<" + user.name + "> Is not accepting private messages!", + clientHandler.user + ) + return + } + var m = sb.toString() + m = m.trim { it <= ' ' } + if (m.isBlank() || m.startsWith("�") || m.startsWith("�")) return + if (access == AccessManager.ACCESS_NORMAL) { + val chars = m.toCharArray() + for (i in chars.indices) { + if (chars[i].code < 32) { + logger.atWarning().log("%s /msg denied: Illegal characters in message", user) + game.announce( + "Private Message Denied: Illegal characters in message", + clientHandler.user + ) + return + } + } + if (m.length > 320) { + logger.atWarning().log("%s /msg denied: Message Length > 320", user) + game.announce("Private Message Denied: Message Too Long", clientHandler.user) + return + } + } + clientHandler.user.lastMsgID = user.id + user.lastMsgID = clientHandler.user.id + + // user1.getServer().announce("TO: <" + user.getName() + ">(" + user.getID() + ") <" + + // clientHandler.getUser().getName() + "> (" + clientHandler.getUser().getID() + "): " + // + + // m, false, user1); + // user.getServer().announce("<" + clientHandler.getUser().getName() + "> (" + + // clientHandler.getUser().getID() + "): " + m, false, user); + game.announce( + "TO: <${user.name}>(${user.id}) <${clientHandler.user.name}> (${clientHandler.user.id}): $m", + clientHandler.user + ) + user.game?.announce("<${clientHandler.user.name}> (${clientHandler.user.id}): $m", user) + return + } catch (e: java.util.NoSuchElementException) { + if (clientHandler.user.lastMsgID != -1) { + try { + val user = clientHandler.user.server.getUser(clientHandler.user.lastMsgID) + val sb = StringBuilder() + while (scanner.hasNext()) { + sb.append(scanner.next()) + sb.append(" ") + } + if (user == null) { + game.announce("User not found!", clientHandler.user) + return + } + if (user.game != game) { + game.announce("User not in this game!", clientHandler.user) + return + } + if (user === clientHandler.user) { + game.announce("You can't private message yourself!", clientHandler.user) + return + } + var m = sb.toString() + m = m.trim { it <= ' ' } + if (m.isBlank() || m.startsWith("�") || m.startsWith("�")) return + if (access == AccessManager.ACCESS_NORMAL) { + val chars = m.toCharArray() + var i = 0 + while (i < chars.size) { + if (chars[i].code < 32) { + logger.atWarning().log("%s /msg denied: Illegal characters in message", user) + game.announce( + "Private Message Denied: Illegal characters in message", + clientHandler.user + ) + return + } + i++ + } + if (m.length > 320) { + logger.atWarning().log("%s /msg denied: Message Length > 320", user) + game.announce("Private Message Denied: Message Too Long", clientHandler.user) + return + } + } + + // user1.getServer().announce("TO: <" + user.getName() + ">(" + user.getID() + ") + // <" + // + + // clientHandler.getUser().getName() + "> (" + clientHandler.getUser().getID() + + // "): + // " + // + m, false, user1); + // user.getServer().announce("<" + clientHandler.getUser().getName() + "> (" + + // clientHandler.getUser().getID() + "): " + m, false, user); + game.announce( + "TO: <${user.name}>(${user.id}) <${clientHandler.user.name}> (${clientHandler.user.id}): $m", + clientHandler.user + ) + user.game?.announce( + "<${clientHandler.user.name}> (${clientHandler.user.id}): $m", + user + ) + return + } catch (e1: Exception) { + game.announce("Private Message Error: /msg ", clientHandler.user) + return + } + } else { + game.announce("Private Message Error: /msg ", clientHandler.user) + return + } + } + } + + override fun matches(command: String): ParseResult { + TODO("Not yet implemented") + } + }, + object : + GameChatCommand( + prefix = "ignoreall", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + clientHandler.user.ignoreAll = true + clientHandler.user.server.announce( + clientHandler.user.name + " is now ignoring everyone!", + false, + null + ) + } + + override fun matches(command: String): ParseResult { + TODO("Not yet implemented") + } + }, + object : + GameChatCommand( + prefix = "unignoreall", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + clientHandler.user.ignoreAll = false + clientHandler.user.server.announce( + clientHandler.user.name + " is now unignoring everyone!", + gamesAlso = false, + targetUser = null + ) + } + + override fun matches(command: String): ParseResult { + TODO("Not yet implemented") + } + }, + object : + GameChatCommand( + prefix = "ignore", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val scanner = java.util.Scanner(message).useDelimiter(" ") + try { + scanner.next() + val userID = scanner.nextInt() + val user = clientHandler.user.server.getUser(userID) + if (user == null) { + game.announce("User not found!", clientHandler.user) + return + } + if (user === clientHandler.user) { + game.announce("You can't ignore yourself!", clientHandler.user) + return + } + if (clientHandler.user.findIgnoredUser(user.connectSocketAddress.address.hostAddress)) { + game.announce("You can't ignore a user that is already ignored!", clientHandler.user) + return + } + if (user.accessLevel >= AccessManager.ACCESS_MODERATOR) { + game.announce("You cannot ignore a moderator or admin!", clientHandler.user) + return + } + clientHandler.user.addIgnoredUser(user.connectSocketAddress.address.hostAddress) + user.server.announce( + "${clientHandler.user.name} is now ignoring <${user.name}> ID: ${user.id}", + false, + null + ) + return + } catch (e: java.util.NoSuchElementException) { + game.announce("Ignore User Error: /ignore ", clientHandler.user) + logger + .atInfo() + .withCause(e) + .log( + "IGNORE USER ERROR: %s: %s", + clientHandler.user.name, + clientHandler.remoteSocketAddress!!.hostName + ) + return + } + } + + override fun matches(command: String): ParseResult { + TODO("Not yet implemented") + } + }, + object : + GameChatCommand( + prefix = "unignore", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val scanner = java.util.Scanner(message).useDelimiter(" ") + try { + scanner.next() + val userID = scanner.nextInt() + val user = clientHandler.user.server.getUser(userID) + if (user == null) { + game.announce("User Not Found!", clientHandler.user) + return + } + if (!clientHandler.user.findIgnoredUser(user.connectSocketAddress.address.hostAddress)) { + game.announce("You can't unignore a user that isn't ignored", clientHandler.user) + return + } + if ( + clientHandler.user.removeIgnoredUser( + user.connectSocketAddress.address.hostAddress, + false + ) + ) + user.server.announce( + "${clientHandler.user.name} is now unignoring <${user.name}> ID: ${user.id}", + false, + null + ) + else + clientHandler.send( + org.emulinker.kaillera.controller.v086.protocol.InformationMessage( + clientHandler.nextMessageNumber, + "server", + "User Not Found!" + ) + ) + } catch (e: java.util.NoSuchElementException) { + game.announce("Unignore User Error: /ignore ", clientHandler.user) + logger + .atInfo() + .withCause(e) + .log( + "UNIGNORE USER ERROR: %s: %s", + clientHandler.user.name, + clientHandler.remoteSocketAddress!!.hostName + ) + } + } + + override fun matches(command: String): ParseResult { + TODO("Not yet implemented") + } + }, + object : + GameChatCommand( + prefix = "me", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val space = message.indexOf(' ') + if (space < 0) { + game.announce("Invalid # of Fields!", clientHandler.user) + return + } + var announcement = message.substring(space + 1) + if (announcement.startsWith(":")) + announcement = + announcement.substring( + 1 + ) // this protects against people screwing up the emulinker supraclient + val access = + clientHandler.user.server.accessManager.getAccess( + clientHandler.user.socketAddress!!.address + ) + if ( + access < AccessManager.ACCESS_SUPERADMIN && + clientHandler.user.server.accessManager.isSilenced( + clientHandler.user.socketAddress!!.address + ) + ) { + game.announce("You are silenced!", clientHandler.user) + return + } + if (clientHandler.user.server.checkMe(clientHandler.user, announcement)) { + val m = announcement + announcement = "*" + clientHandler.user.name + " " + m + for (user in game.players) { + game.announce(announcement, user) + } + } + } + + override fun matches(command: String): ParseResult { + TODO("Not yet implemented") + } + }, + object : + GameChatCommand( + prefix = "", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + game.announce( + "/me to make personal message eg. /me is bored ...SupraFast is bored.", + clientHandler.user + ) + threadSleep(20.milliseconds) + game.announce( + "/msg to PM somebody. /msgoff or /msgon to turn pm off | on.", + clientHandler.user + ) + threadSleep(20.milliseconds) + game.announce( + "/ignore or /unignore or /ignoreall or /unignoreall to ignore users.", + clientHandler.user + ) + threadSleep(20.milliseconds) + game.announce( + "/p2pon or /p2poff this option ignores all server activity during gameplay.", + clientHandler.user + ) + threadSleep(20.milliseconds) + } + + override fun matches(command: String): ParseResult { + TODO("Not yet implemented") + } + }, + object : + GameChatCommand( + prefix = "stop", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + if (twitterBroadcaster.cancelActionsForUser(clientHandler.user.id)) { + game.announce( + org.emulinker.util.EmuLang.getStringOrDefault( + "KailleraServerImpl.CanceledPendingTweet", + default = "Canceled pending tweet." + ), + clientHandler.user + ) + } else { + game.announce("No pending tweets.", clientHandler.user) + } + } + + override fun matches(command: String): ParseResult { + TODO("Not yet implemented") + } + }, + // TODO(nue): Handle the "else" case. + object : + GameChatCommand( + prefix = "UNKNOWN", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + TODO("Not yet implemented") + } + + override fun matches(command: String): ParseResult { + TODO("Not yet implemented") + } + } + ) + +class CommandPermissions( + val allowedLocations: CommandUsageLocation = CommandUsageLocation.ANYWHERE, + val gameOwnerOnly: Boolean = false, + val minimumAccessRequired: Int = AccessManager.ACCESS_NORMAL, +) { + companion object { + val LENIENT = + CommandPermissions( + allowedLocations = CommandUsageLocation.ANYWHERE, + gameOwnerOnly = false, + minimumAccessRequired = AccessManager.ACCESS_NORMAL + ) + } +} + +abstract class GameChatCommand( + /** For the command `/maxusers 42`, this would be `"maxusers"`. */ + val prefix: String, + val commandPermissions: CommandPermissions, +) { + protected abstract fun performAction( + message: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) + + /** Validates whether the command is valid. */ + protected abstract fun matches(command: String): ParseResult + + fun handle(message: String, game: KailleraGameImpl, handler: V086ClientHandler) { + when (val parseResult = matches(message.trim())) { + ParseResult.Success -> { + performAction(message, game, handler) + } + is ParseResult.Failure -> { + parseResult.messageToUser + // TODO(nue): Send messageToUser to user. + } + } + } + + companion object { + const val COMMAND_PREFIX = "/" + } +} diff --git a/emulinker/src/main/java/org/emulinker/kaillera/pico/AppModule.kt b/emulinker/src/main/java/org/emulinker/kaillera/pico/AppModule.kt index 7d246f066..922c7510e 100644 --- a/emulinker/src/main/java/org/emulinker/kaillera/pico/AppModule.kt +++ b/emulinker/src/main/java/org/emulinker/kaillera/pico/AppModule.kt @@ -21,6 +21,8 @@ import org.emulinker.kaillera.access.AccessManager import org.emulinker.kaillera.access.AccessManager2 import org.emulinker.kaillera.controller.KailleraServerController import org.emulinker.kaillera.controller.v086.V086Controller +import org.emulinker.kaillera.controller.v086.commands.GameChatCommand +import org.emulinker.kaillera.lookingforgame.TwitterBroadcaster import org.emulinker.kaillera.master.MasterListStatsCollector import org.emulinker.kaillera.master.StatsCollector import org.emulinker.kaillera.model.impl.AutoFireDetectorFactory @@ -129,5 +131,10 @@ abstract class AppModule { i++ } } + + @Provides + @Singleton + fun provideGameChatCommands(twitterBroadcaster: TwitterBroadcaster): List = + provideGameChatCommands(twitterBroadcaster) } } From 09d347fadac96612db4da6a9af4f1f89df61bfd6 Mon Sep 17 00:00:00 2001 From: Jonn Date: Sat, 17 Aug 2024 17:13:38 +0900 Subject: [PATCH 2/4] It works! --- .../controller/v086/action/GameChatAction.kt | 2 +- .../controller/v086/commands/Commands.kt | 135 +++++++----------- .../org/emulinker/kaillera/pico/AppModule.kt | 3 +- 3 files changed, 56 insertions(+), 84 deletions(-) diff --git a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt index f8321f397..6251da1e2 100644 --- a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt +++ b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt @@ -72,7 +72,7 @@ internal constructor( message.message .trim() .removePrefix(GameChatCommand.COMMAND_PREFIX) - .split(" ", limit = 1) + .split(" ", limit = 2) .first() val matchedCommand = gameChatCommands.firstOrNull { it.prefix == commandName } if (matchedCommand == null) { diff --git a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt index 3adf81615..9f3da149b 100644 --- a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt +++ b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt @@ -22,7 +22,7 @@ enum class CommandUsageLocation { SERVER_CHAT, } -fun provideGameChatCommands(twitterBroadcaster: TwitterBroadcaster): List = +fun buildGameChatCommands(twitterBroadcaster: TwitterBroadcaster): List = listOf( object : GameChatCommand( @@ -30,7 +30,7 @@ fun provideGameChatCommands(twitterBroadcaster: TwitterBroadcaster): List { - performAction(message, game, handler) + performAction(args, game, handler) } is ParseResult.Failure -> { parseResult.messageToUser diff --git a/emulinker/src/main/java/org/emulinker/kaillera/pico/AppModule.kt b/emulinker/src/main/java/org/emulinker/kaillera/pico/AppModule.kt index 922c7510e..832b0e68a 100644 --- a/emulinker/src/main/java/org/emulinker/kaillera/pico/AppModule.kt +++ b/emulinker/src/main/java/org/emulinker/kaillera/pico/AppModule.kt @@ -22,6 +22,7 @@ import org.emulinker.kaillera.access.AccessManager2 import org.emulinker.kaillera.controller.KailleraServerController import org.emulinker.kaillera.controller.v086.V086Controller import org.emulinker.kaillera.controller.v086.commands.GameChatCommand +import org.emulinker.kaillera.controller.v086.commands.buildGameChatCommands import org.emulinker.kaillera.lookingforgame.TwitterBroadcaster import org.emulinker.kaillera.master.MasterListStatsCollector import org.emulinker.kaillera.master.StatsCollector @@ -135,6 +136,6 @@ abstract class AppModule { @Provides @Singleton fun provideGameChatCommands(twitterBroadcaster: TwitterBroadcaster): List = - provideGameChatCommands(twitterBroadcaster) + buildGameChatCommands(twitterBroadcaster) } } From c165c58d0a6b5d2c55bdf115e70a87c80462dcf7 Mon Sep 17 00:00:00 2001 From: Jonn Date: Sat, 17 Aug 2024 19:04:33 +0900 Subject: [PATCH 3/4] More stuff! --- .../controller/v086/action/GameChatAction.kt | 22 +- .../v086/action/GameOwnerCommandAction.kt | 583 ------ .../controller/v086/commands/Commands.kt | 1796 ++++++++++++----- .../org/emulinker/kaillera/pico/AppModule.kt | 8 - 4 files changed, 1300 insertions(+), 1109 deletions(-) delete mode 100644 emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameOwnerCommandAction.kt diff --git a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt index 6251da1e2..365438369 100644 --- a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt +++ b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt @@ -8,6 +8,7 @@ import org.emulinker.kaillera.access.AccessManager import org.emulinker.kaillera.controller.messaging.MessageFormatException import org.emulinker.kaillera.controller.v086.V086ClientHandler import org.emulinker.kaillera.controller.v086.commands.GameChatCommand +import org.emulinker.kaillera.controller.v086.commands.GameChatCommandHandler import org.emulinker.kaillera.controller.v086.protocol.* import org.emulinker.kaillera.model.event.GameChatEvent import org.emulinker.kaillera.model.exception.ActionException @@ -18,8 +19,7 @@ import org.emulinker.kaillera.model.impl.KailleraGameImpl class GameChatAction @Inject internal constructor( - private val gameOwnerCommandAction: GameOwnerCommandAction, - private val gameChatCommands: @JvmSuppressWildcards List, + private val gameChatCommandHandler: GameChatCommandHandler, ) : V086Action, V086GameEventHandler { override var actionPerformedCount = 0 private set @@ -32,21 +32,7 @@ internal constructor( override fun performAction(message: GameChatRequest, clientHandler: V086ClientHandler) { if (clientHandler.user.game == null) return if (message.message.startsWith(ADMIN_COMMAND_ESCAPE_STRING)) { - // if(clientHandler.getUser().getAccess() >= AccessManager.ACCESS_ADMIN || - // clientHandler.getUser().equals(clientHandler.getUser().getGame().getOwner())){ - try { - if (gameOwnerCommandAction.isValidCommand((message as GameChat).message)) { - gameOwnerCommandAction.performAction(message, clientHandler) - if ((message as GameChat).message == "/help") checkCommands(message, clientHandler) - } else { - checkCommands(message, clientHandler) - } - return - } catch (e: FatalActionException) { - logger.atWarning().withCause(e).log("GameOwner command failed") - } - - // } + checkCommands(message, clientHandler) } actionPerformedCount++ try { @@ -74,7 +60,7 @@ internal constructor( .removePrefix(GameChatCommand.COMMAND_PREFIX) .split(" ", limit = 2) .first() - val matchedCommand = gameChatCommands.firstOrNull { it.prefix == commandName } + val matchedCommand = gameChatCommandHandler.commands.firstOrNull { it.prefix == commandName } if (matchedCommand == null) { game.announce("Unknown Command: " + message.message, clientHandler.user) } else { diff --git a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameOwnerCommandAction.kt b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameOwnerCommandAction.kt deleted file mode 100644 index aa7717795..000000000 --- a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameOwnerCommandAction.kt +++ /dev/null @@ -1,583 +0,0 @@ -package org.emulinker.kaillera.controller.v086.action - -import com.google.common.flogger.FluentLogger -import java.util.* -import javax.inject.Inject -import javax.inject.Singleton -import kotlin.time.Duration.Companion.milliseconds -import org.emulinker.config.RuntimeFlags -import org.emulinker.kaillera.access.AccessManager -import org.emulinker.kaillera.controller.messaging.MessageFormatException -import org.emulinker.kaillera.controller.v086.V086ClientHandler -import org.emulinker.kaillera.controller.v086.protocol.GameChat -import org.emulinker.kaillera.model.GameStatus -import org.emulinker.kaillera.model.KailleraUser -import org.emulinker.kaillera.model.exception.ActionException -import org.emulinker.kaillera.model.impl.KailleraGameImpl -import org.emulinker.util.EmuLang -import org.emulinker.util.EmuUtil.threadSleep - -@Singleton -class GameOwnerCommandAction @Inject internal constructor(private val flags: RuntimeFlags) : - V086Action { - override val actionPerformedCount = 0 - override fun toString() = "GameOwnerCommandAction" - - fun isValidCommand(chat: String): Boolean { - return when { - chat.startsWith(COMMAND_HELP) || - chat.startsWith(COMMAND_DETECTAUTOFIRE) || - chat.startsWith(COMMAND_MAXUSERS) || - chat.startsWith(COMMAND_MAXPING) || - chat == COMMAND_START || - chat.startsWith(COMMAND_STARTN) || - chat.startsWith(COMMAND_MUTE) || - chat.startsWith(COMMAND_EMU) || - chat.startsWith(COMMAND_CONN) || - chat.startsWith(COMMAND_UNMUTE) || - chat.startsWith(COMMAND_SWAP) || - chat.startsWith(COMMAND_KICK) || - chat.startsWith(COMMAND_SAMEDELAY) || - chat.startsWith(COMMAND_LAGSTAT) || - chat.startsWith(COMMAND_NUM) -> true - else -> false - } - } - - @Throws(FatalActionException::class) - override fun performAction(message: GameChat, clientHandler: V086ClientHandler) { - val chat = message.message - val user = clientHandler.user - val game = - user.game ?: throw FatalActionException("GameOwner Command Failed: Not in a game: $chat") - if (user != game.owner && user.accessLevel < AccessManager.ACCESS_SUPERADMIN) { - if (!chat.startsWith(COMMAND_HELP)) { - logger - .atWarning() - .log("GameOwner Command Denied: Not game owner: %s: %s: %s", game, user, chat) - game.announce("GameOwner Command Error: You are not an owner!", user) - return - } - } - try { - when { - chat.startsWith(COMMAND_HELP) -> processHelp(game, user) - chat.startsWith(COMMAND_DETECTAUTOFIRE) -> processDetectAutoFire(chat, game, user) - chat.startsWith(COMMAND_MAXUSERS) -> processMaxUsers(chat, game, user) - chat.startsWith(COMMAND_MAXPING) -> processMaxPing(chat, game, user) - chat == COMMAND_START -> processStart(game, user) - chat.startsWith(COMMAND_STARTN) -> processStartN(chat, game, user, clientHandler) - chat.startsWith(COMMAND_MUTE) -> processMute(chat, game, user, clientHandler) - chat.startsWith(COMMAND_EMU) -> processEmu(chat, game, user, clientHandler) - chat.startsWith(COMMAND_CONN) -> processConn(chat, game, user) - chat.startsWith(COMMAND_UNMUTE) -> processUnmute(chat, game, user, clientHandler) - chat.startsWith(COMMAND_SWAP) -> processSwap(chat, game, user) - chat.startsWith(COMMAND_KICK) -> processKick(chat, game, user, clientHandler) - chat.startsWith(COMMAND_LAGSTAT) -> processLagstat(chat, game) - chat.startsWith(COMMAND_SAMEDELAY) -> processSameDelay(chat, game, user) - chat.startsWith(COMMAND_NUM) -> processNum(game, user) - else -> { - game.announce("Unknown Command: $chat", user) - logger.atInfo().log("Unknown GameOwner Command: %s: %s: %s", game, user, chat) - } - } - } catch (e: ActionException) { - logger.atInfo().withCause(e).log("GameOwner Command Failed: %s: %s: %s", game, user, chat) - game.announce(EmuLang.getString("GameOwnerCommandAction.CommandFailed", e.message), user) - } catch (e: MessageFormatException) { - logger.atSevere().withCause(e).log("Failed to construct message") - } - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processHelp(game: KailleraGameImpl, admin: KailleraUser) { - if (admin != game.owner && admin.accessLevel < AccessManager.ACCESS_SUPERADMIN) return - // game.setIndividualGameAnnounce(admin.getPlayerNumber()); - // game.announce(EmuLang.getString("GameOwnerCommandAction.AvailableCommands")); - // try { Thread.sleep(20); } catch(Exception e) {} - game.announce(EmuLang.getString("GameOwnerCommandAction.SetAutofireDetection"), admin) - threadSleep(20.milliseconds) - game.announce("/maxusers <#> to set capacity of room", admin) - threadSleep(20.milliseconds) - game.announce("/maxping <#> to set maximum ping for room", admin) - threadSleep(20.milliseconds) - game.announce("/start or /startn <#> start game when n players are joined.", admin) - threadSleep(20.milliseconds) - game.announce("/mute /unmute or /muteall or /unmuteall to mute player(s).", admin) - threadSleep(20.milliseconds) - game.announce( - "/swap eg. 123..n {n = total # of players; Each slot = new player#}", - admin - ) - threadSleep(20.milliseconds) - game.announce("/kick or /kickall to kick a player(s).", admin) - threadSleep(20.milliseconds) - game.announce("/setemu To restrict the gameroom to this emulator!", admin) - threadSleep(20.milliseconds) - game.announce("/setconn To restrict the gameroom to this connection type!", admin) - threadSleep(20.milliseconds) - game.announce( - "/lagstat To check who has the most lag spikes or /lagreset to reset lagstat!", - admin - ) - threadSleep(20.milliseconds) - game.announce( - "/samedelay {true | false} to play at the same delay as player with highest ping. Default is false.", - admin - ) - threadSleep(20.milliseconds) - } - - private fun autoFireHelp(game: KailleraGameImpl, admin: KailleraUser) { - val cur = game.autoFireDetector!!.sensitivity - game.announce(EmuLang.getString("GameOwnerCommandAction.HelpSensitivity"), admin) - threadSleep(20.milliseconds) - game.announce(EmuLang.getString("GameOwnerCommandAction.HelpDisable"), admin) - threadSleep(20.milliseconds) - game.announce( - EmuLang.getString("GameOwnerCommandAction.HelpCurrentSensitivity", cur) + - if (cur == 0) EmuLang.getString("GameOwnerCommandAction.HelpDisabled") else "", - admin - ) - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processDetectAutoFire(message: String, game: KailleraGameImpl, admin: KailleraUser) { - if (game.status != GameStatus.WAITING) { - game.announce(EmuLang.getString("GameOwnerCommandAction.AutoFireChangeDeniedInGame"), admin) - return - } - val st = StringTokenizer(message, " ") - if (st.countTokens() != 2) { - autoFireHelp(game, admin) - return - } - val unusedCommand = st.nextToken() - val sensitivityStr = st.nextToken() - var sensitivity = -1 - try { - sensitivity = sensitivityStr.toInt() - } catch (e: NumberFormatException) {} - if (sensitivity > 5 || sensitivity < 0) { - autoFireHelp(game, admin) - return - } - game.autoFireDetector!!.sensitivity = sensitivity - game.announce( - EmuLang.getString("GameOwnerCommandAction.HelpCurrentSensitivity", sensitivity) + - if (sensitivity == 0) EmuLang.getString("GameOwnerCommandAction.HelpDisabled") else "", - ) - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processEmu( - message: String, - game: KailleraGameImpl, - admin: KailleraUser, - clientHandler: V086ClientHandler - ) { - var emu = game.owner.clientType - if (message == "/setemu any") { - emu = "any" - } - admin.game!!.aEmulator = emu!! - admin.game!!.announce( - "Owner has restricted the emulator to: $emu", - ) - return - } - - // new gameowner command /setconn - @Throws(ActionException::class, MessageFormatException::class) - private fun processConn(message: String, game: KailleraGameImpl, admin: KailleraUser) { - var conn = game.owner.connectionType.readableName - if (message == "/setconn any") { - conn = "any" - } - admin.game!!.aConnection = conn - admin.game!!.announce( - "Owner has restricted the connection type to: $conn", - ) - return - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processNum(game: KailleraGameImpl, admin: KailleraUser) { - admin.game!!.announce("${game.players.size} in the room!", admin) - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processLagstat( - message: String, - game: KailleraGameImpl, - ) { - if (message == "/lagstat") { - game.announce("Lagged frames per player:") - game.players - .asSequence() - .filter { !it.inStealthMode } - .forEach { - game.announce( - "P${it.playerNumber}: ${it.smallLagSpikesCausedByUser} (tiny), ${it.bigLagSpikesCausedByUser} (big)" - ) - } - } else if (message == "/lagreset") { - for (player in game.players) { - player.timeouts = 0 - player.smallLagSpikesCausedByUser = 0 - player.bigLagSpikesCausedByUser = 0 - } - game.announce( - "LagStat has been reset!", - ) - } - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processSameDelay( - message: String, - game: KailleraGameImpl, - admin: KailleraUser, - ) { - if (message == "/samedelay true") { - game.sameDelay = true - admin.game!!.announce( - "Players will have the same delay when game starts (restarts)!", - ) - } else { - game.sameDelay = false - admin.game!!.announce( - "Players will have independent delays when game starts (restarts)!", - ) - } - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processMute( - message: String, - game: KailleraGameImpl, - admin: KailleraUser, - clientHandler: V086ClientHandler - ) { - val scanner = Scanner(message).useDelimiter(" ") - try { - val str = scanner.next() - if (str == "/muteall") { - for (w in 1..game.players.size) { - // do not mute owner or admin - if ( - game.getPlayer(w)!!.accessLevel < AccessManager.ACCESS_ADMIN && - game.getPlayer(w) != game.owner - ) { - game.getPlayer(w)!!.isMuted = true - game.mutedUsers.add(game.getPlayer(w)!!.connectSocketAddress.address.hostAddress) - } - } - admin.game!!.announce( - "All players have been muted!", - ) - return - } - val userID = scanner.nextInt() - val user = clientHandler.user.server.getUser(userID) - if (user == null) { - admin.game!!.announce("Player doesn't exist!", admin) - return - } - if (user === clientHandler.user) { - user.game!!.announce("You can't mute yourself!", admin) - return - } - if ( - user.accessLevel >= AccessManager.ACCESS_ADMIN && - admin.accessLevel != AccessManager.ACCESS_SUPERADMIN - ) { - user.game!!.announce("You can't mute an Admin", admin) - return - } - - // mute by IP - game.mutedUsers.add(user.connectSocketAddress.address.hostAddress) - user.isMuted = true - val user1 = clientHandler.user - user1.game!!.announce( - user.name + " has been muted!", - ) - } catch (e: NoSuchElementException) { - val user = clientHandler.user - user.game!!.announce("Mute Player Error: /mute ", admin) - } - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processUnmute( - message: String, - game: KailleraGameImpl, - admin: KailleraUser, - clientHandler: V086ClientHandler - ) { - val scanner = Scanner(message).useDelimiter(" ") - try { - val str = scanner.next() - if (str == "/unmuteall") { - game.players.forEach { kailleraUser -> - kailleraUser.isMuted = false - game.mutedUsers.remove(kailleraUser.connectSocketAddress.address.hostAddress) - } - admin.game!!.announce( - "All players have been unmuted!", - ) - return - } - val userID = scanner.nextInt() - val user = clientHandler.user.server.getUser(userID) - if (user == null) { - admin.game!!.announce("Player doesn't exist!", admin) - return - } - if (user === clientHandler.user) { - user.game!!.announce("You can't unmute yourself!", admin) - return - } - if ( - user.accessLevel >= AccessManager.ACCESS_ADMIN && - admin.accessLevel != AccessManager.ACCESS_SUPERADMIN - ) { - user.game!!.announce("You can't unmute an Admin", admin) - return - } - game.mutedUsers.remove(user.connectSocketAddress.address.hostAddress) - user.isMuted = false - val user1 = clientHandler.user - user1.game!!.announce( - user.name + " has been unmuted!", - ) - } catch (e: NoSuchElementException) { - val user = clientHandler.user - user.game!!.announce("Unmute Player Error: /unmute ", admin) - } - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processStartN( - message: String, - game: KailleraGameImpl, - admin: KailleraUser, - clientHandler: V086ClientHandler - ) { - val scanner = Scanner(message).useDelimiter(" ") - try { - scanner.next() - val num = scanner.nextInt() - if (num in 1..100) { - game.startN = num.toByte().toInt() - game.announce( - "This game will start when $num players have joined.", - ) - } else { - game.announce("StartN Error: Enter value between 1 and 100.", admin) - } - } catch (e: NoSuchElementException) { - game.announce("Failed: /startn <#>", admin) - } - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processSwap( - message: String, - game: KailleraGameImpl, - admin: KailleraUser, - ) { - /*if(game.getStatus() != KailleraGame.STATUS_PLAYING){ - game.announce("Failed: wap Players can only be used during gameplay!", admin); - return; - }*/ - val scanner = Scanner(message).useDelimiter(" ") - try { - var i: Int - val str: String - scanner.next() - val test = scanner.nextInt() - str = test.toString() - if (game.players.size < str.length) { - game.announce("Failed: You can't swap more than the # of players in the room.", admin) - return - } - if (test > 0) { - var numCount = 0 - val num = IntArray(str.length) - // before swap check numbers to prevent errors due to incorrectly entered numbers - i = 0 - while (i < num.size) { - num[i] = str[i].toString().toInt() - numCount = 1 - if (num[i] == 0 || num[i] > game.players.size) break - for (j in num.indices) { - if (num[i] != num[j]) numCount++ - } - i++ - } - if (numCount == game.players.size) { - game.swap = true - // PlayerActionQueue temp = game.getPlayerActionQueue()[0]; - i = 0 - while (i < str.length) { - val player = game.players[i] - player.playerNumber = num[i] - /*if(num[i] == 1){ - game.getPlayerActionQueue()[i] = temp; - } - else{ - game.getPlayerActionQueue()[i] = game.getPlayerActionQueue()[num[i]-1]; - }*/ game.announce( - player.name + " is now Player#: " + player.playerNumber, - ) - i++ - } - } else - game.announce( - "Swap Player Error: /swap eg. 123..n {n = total # of players; Each slot = new player#}", - admin - ) - } - } catch (e: NoSuchElementException) { - game.announce( - "Swap Player Error: /swap eg. 123..n {n = total # of players; Each slot = new player#}", - admin - ) - } - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processStart(game: KailleraGameImpl, admin: KailleraUser) { - game.start(admin) - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processKick( - message: String, - game: KailleraGameImpl, - admin: KailleraUser, - clientHandler: V086ClientHandler - ) { - val scanner = Scanner(message).useDelimiter(" ") - try { - val str = scanner.next() - if (str == "/kickall") { - // start kick players from last to first and don't kick owner or admin - for (w in game.players.size downTo 1) { - if ( - game.getPlayer(w)!!.accessLevel < AccessManager.ACCESS_ADMIN && - game.getPlayer(w) != game.owner - ) - game.kick(admin, game.getPlayer(w)!!.id) - } - admin.game!!.announce( - "All players have been kicked!", - ) - return - } - val playerNumber = scanner.nextInt() - if (playerNumber in 1..100) { - if (game.getPlayer(playerNumber) != null) - game.kick(admin, game.getPlayer(playerNumber)!!.id) - else { - game.announce("Player doesn't exisit!", admin) - } - } else { - game.announce("Kick Player Error: Enter value between 1 and 100", admin) - } - } catch (e: NoSuchElementException) { - game.announce("Failed: /kick or /kickall to kick all players.", admin) - } - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processMaxUsers( - message: String, - game: KailleraGameImpl, - admin: KailleraUser, - ) { - if (System.currentTimeMillis() - lastMaxUserChange <= 3000) { - game.announce("Max User Command Spam Detection...Please Wait!", admin) - lastMaxUserChange = System.currentTimeMillis() - return - } else { - lastMaxUserChange = System.currentTimeMillis() - } - val scanner = Scanner(message).useDelimiter(" ") - try { - scanner.next() - val num = scanner.nextInt() - if (num in 1..100) { - game.maxUsers = num - game.announce( - "Max Users has been set to $num", - ) - } else { - game.announce("Max Users Error: Enter value between 1 and 100", admin) - } - } catch (e: NoSuchElementException) { - game.announce("Failed: /maxusers <#>", admin) - } - } - - @Throws(ActionException::class, MessageFormatException::class) - private fun processMaxPing( - message: String, - game: KailleraGameImpl, - admin: KailleraUser, - ) { - val scanner = Scanner(message).useDelimiter(" ") - try { - scanner.next() - val num = scanner.nextInt() - if (num in 1..1000) { - game.maxPing = num - game.announce( - "Max Ping has been set to $num", - ) - } else { - game.announce("Max Ping Error: Enter value between 1 and 1000", admin) - } - } catch (e: NoSuchElementException) { - game.announce("Failed: /maxping <#>", admin) - } - } - - companion object { - private var lastMaxUserChange: Long = 0 - private val logger = FluentLogger.forEnclosingClass() - - private const val COMMAND_HELP = "/help" - - private const val COMMAND_DETECTAUTOFIRE = "/detectautofire" - - // SF MOD - private const val COMMAND_LAGSTAT = "/lag" - - private const val COMMAND_MAXUSERS = "/maxusers" - - private const val COMMAND_MAXPING = "/maxping" - - private const val COMMAND_START = "/start" - - private const val COMMAND_STARTN = "/startn" - - private const val COMMAND_MUTE = "/mute" - - private const val COMMAND_UNMUTE = "/unmute" - - private const val COMMAND_SWAP = "/swap" - - private const val COMMAND_KICK = "/kick" - - private const val COMMAND_EMU = "/setemu" - - private const val COMMAND_CONN = "/setconn" - - private const val COMMAND_SAMEDELAY = "/samedelay" - - private const val COMMAND_NUM = "/num" - } -} diff --git a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt index 9f3da149b..fe3d4e425 100644 --- a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt +++ b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt @@ -1,11 +1,19 @@ package org.emulinker.kaillera.controller.v086.commands import com.google.common.flogger.FluentLogger +import java.util.NoSuchElementException +import java.util.Scanner +import java.util.StringTokenizer +import javax.inject.Inject +import javax.inject.Singleton import kotlin.time.Duration.Companion.milliseconds import org.emulinker.kaillera.access.AccessManager import org.emulinker.kaillera.controller.v086.V086ClientHandler import org.emulinker.kaillera.lookingforgame.TwitterBroadcaster +import org.emulinker.kaillera.model.GameStatus +import org.emulinker.kaillera.model.KailleraUser import org.emulinker.kaillera.model.impl.KailleraGameImpl +import org.emulinker.util.EmuLang import org.emulinker.util.EmuUtil.threadSleep private val logger = FluentLogger.forEnclosingClass() @@ -18,589 +26,1377 @@ sealed interface ParseResult { enum class CommandUsageLocation { ANYWHERE, - ROOM_CHAT, - SERVER_CHAT, + GAME_CHAT_ONLY, + SERVER_CHAT_ONLY, } -fun buildGameChatCommands(twitterBroadcaster: TwitterBroadcaster): List = - listOf( - object : - GameChatCommand( - prefix = "msgon", - commandPermissions = CommandPermissions.LENIENT, - ) { - override fun performAction( - args: String, - game: KailleraGameImpl, - clientHandler: V086ClientHandler - ) { - clientHandler.user.isAcceptingDirectMessages = true - game.announce("Private messages are now on.", clientHandler.user) - } +class CommandPermissions( + val allowedLocations: CommandUsageLocation, + val gameOwnerOnly: Boolean, + val minimumAccessRequired: Int = AccessManager.ACCESS_NORMAL, +) { + companion object { + val LENIENT = + CommandPermissions( + allowedLocations = CommandUsageLocation.ANYWHERE, + gameOwnerOnly = false, + minimumAccessRequired = AccessManager.ACCESS_NORMAL + ) + } +} - override fun verifyArgs(args: String): ParseResult = - if (args.isEmpty()) ParseResult.Success else TODO("Write a message ARGS IS " + args) - }, - object : - GameChatCommand( - prefix = "msgoff", - commandPermissions = CommandPermissions.LENIENT, - ) { - override fun performAction( - args: String, - game: KailleraGameImpl, - clientHandler: V086ClientHandler - ) { - clientHandler.user.isAcceptingDirectMessages = false - game.announce("Private messages are now off.", clientHandler.user) +abstract class GameChatCommand( + /** For the command `/maxusers 42`, this would be `"maxusers"`. */ + val prefix: String, + // TODO(nue): Use this. + val commandPermissions: CommandPermissions, +) { + protected abstract fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) + + /** Validates whether the command is valid. */ + protected abstract fun verifyArgs(args: String): ParseResult + + /** @param message Does not start with "/". */ + fun handle(message: String, game: KailleraGameImpl, handler: V086ClientHandler) { + if (commandPermissions.allowedLocations == CommandUsageLocation.SERVER_CHAT_ONLY) { + TODO("Can only do this in server chat!") + } + + if (commandPermissions.gameOwnerOnly && game.owner.id != handler.user.id) { + TODO("Not the owner!") + } + + if (commandPermissions.minimumAccessRequired > handler.user.accessLevel) { + TODO("Insufficient access level!") + } + + val args = message.removePrefix(COMMAND_PREFIX + prefix).trim() + when (val parseResult = verifyArgs(args)) { + ParseResult.Success -> { + performAction(args, game, handler) } + is ParseResult.Failure -> { + parseResult.messageToUser + // TODO(nue): Send messageToUser to user. + } + } + } - override fun verifyArgs(args: String): ParseResult = - if (args.isEmpty()) ParseResult.Success else TODO("Write a message") - }, - object : - GameChatCommand( - prefix = "p2pon", - commandPermissions = CommandPermissions.LENIENT, - ) { - override fun performAction( - args: String, - game: KailleraGameImpl, - clientHandler: V086ClientHandler - ) { - if (game.owner == clientHandler.user) { - game.ignoringUnnecessaryServerActivity = true - for (u in game.players) { - u.ignoringUnnecessaryServerActivity = true - if (u.loggedIn) { - game.announce("This game will NOT receive any server activity during gameplay!", u) + companion object { + const val COMMAND_PREFIX = "/" + } +} + +@Singleton +class GameChatCommandHandler @Inject constructor(twitterBroadcaster: TwitterBroadcaster) { + + val commands = + listOf( + object : + GameChatCommand( + prefix = "msgon", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + clientHandler.user.isAcceptingDirectMessages = true + game.announce("Private messages are now on.", clientHandler.user) + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "msgoff", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + clientHandler.user.isAcceptingDirectMessages = false + game.announce("Private messages are now off.", clientHandler.user) + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "p2pon", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + if (game.owner == clientHandler.user) { + game.ignoringUnnecessaryServerActivity = true + for (u in game.players) { + u.ignoringUnnecessaryServerActivity = true + if (u.loggedIn) { + game.announce("This game will NOT receive any server activity during gameplay!", u) + } } - } - } else { - clientHandler.user.ignoringUnnecessaryServerActivity = true - for (u in game.players) { - if (u.loggedIn) { - game.announce( - "${clientHandler.user.name} will NOT receive any server activity during gameplay!", - u - ) + } else { + clientHandler.user.ignoringUnnecessaryServerActivity = true + for (u in game.players) { + if (u.loggedIn) { + game.announce( + "${clientHandler.user.name} will NOT receive any server activity during gameplay!", + u + ) + } } } } - } - override fun verifyArgs(args: String): ParseResult = - if (args.isEmpty()) ParseResult.Success else TODO("Write a message") - }, - object : - GameChatCommand( - prefix = "p2poff", - commandPermissions = CommandPermissions.LENIENT, - ) { - override fun performAction( - args: String, - game: KailleraGameImpl, - clientHandler: V086ClientHandler - ) { - if (game.owner == clientHandler.user) { - game.ignoringUnnecessaryServerActivity = false - for (u in game.players) { - u.ignoringUnnecessaryServerActivity = false - if (u.loggedIn) { - game.announce("This game will NOW receive ALL server activity during gameplay!", u) + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "p2poff", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + if (game.owner == clientHandler.user) { + game.ignoringUnnecessaryServerActivity = false + for (u in game.players) { + u.ignoringUnnecessaryServerActivity = false + if (u.loggedIn) { + game.announce("This game will NOW receive ALL server activity during gameplay!", u) + } } - } - } else { - clientHandler.user.ignoringUnnecessaryServerActivity = false - for (u in game.players) { - if (u.loggedIn) { - game.announce( - clientHandler.user.name + " will NOW receive ALL server activity during gameplay!", - u - ) + } else { + clientHandler.user.ignoringUnnecessaryServerActivity = false + for (u in game.players) { + if (u.loggedIn) { + game.announce( + clientHandler.user.name + + " will NOW receive ALL server activity during gameplay!", + u + ) + } } } } - } - override fun verifyArgs(args: String): ParseResult = - if (args.isEmpty()) ParseResult.Success else TODO("Write a message") - }, - object : - GameChatCommand( - prefix = "msg", - commandPermissions = CommandPermissions.LENIENT, - ) { - override fun performAction( - args: String, - game: KailleraGameImpl, - clientHandler: V086ClientHandler - ) { - val scanner = java.util.Scanner(args).useDelimiter(" ") - val access = - clientHandler.user.server.accessManager.getAccess( - clientHandler.user.socketAddress!!.address - ) - if ( - access < AccessManager.ACCESS_SUPERADMIN && - clientHandler.user.server.accessManager.isSilenced( + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "msg", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val scanner = Scanner(args).useDelimiter(" ") + val access = + clientHandler.user.server.accessManager.getAccess( clientHandler.user.socketAddress!!.address ) - ) { - game.announce("You are silenced!", clientHandler.user) - return - } - try { - val userID = scanner.nextInt() - val user = clientHandler.user.server.getUser(userID) - val sb = StringBuilder() - while (scanner.hasNext()) { - sb.append(scanner.next()) - sb.append(" ") - } - if (user == null) { - game.announce("User not found!", clientHandler.user) - return - } - if (user.game != game) { - game.announce("User not in this game!", clientHandler.user) - return - } - if (user === clientHandler.user) { - game.announce("You can't private message yourself!", clientHandler.user) - return - } if ( - !user.isAcceptingDirectMessages || - user.searchIgnoredUsers(clientHandler.user.connectSocketAddress.address.hostAddress) + access < AccessManager.ACCESS_SUPERADMIN && + clientHandler.user.server.accessManager.isSilenced( + clientHandler.user.socketAddress!!.address + ) ) { + game.announce("You are silenced!", clientHandler.user) + return + } + try { + val userID = scanner.nextInt() + val user = clientHandler.user.server.getUser(userID) + val sb = StringBuilder() + while (scanner.hasNext()) { + sb.append(scanner.next()) + sb.append(" ") + } + if (user == null) { + game.announce("User not found!", clientHandler.user) + return + } + if (user.game != game) { + game.announce("User not in this game!", clientHandler.user) + return + } + if (user === clientHandler.user) { + game.announce("You can't private message yourself!", clientHandler.user) + return + } + if ( + !user.isAcceptingDirectMessages || + user.searchIgnoredUsers(clientHandler.user.connectSocketAddress.address.hostAddress) + ) { + game.announce( + "<" + user.name + "> Is not accepting private messages!", + clientHandler.user + ) + return + } + var m = sb.toString() + m = m.trim { it <= ' ' } + if (m.isBlank() || m.startsWith("�") || m.startsWith("�")) return + if (access == AccessManager.ACCESS_NORMAL) { + val chars = m.toCharArray() + for (i in chars.indices) { + if (chars[i].code < 32) { + logger.atWarning().log("%s /msg denied: Illegal characters in message", user) + game.announce( + "Private Message Denied: Illegal characters in message", + clientHandler.user + ) + return + } + } + if (m.length > 320) { + logger.atWarning().log("%s /msg denied: Message Length > 320", user) + game.announce("Private Message Denied: Message Too Long", clientHandler.user) + return + } + } + clientHandler.user.lastMsgID = user.id + user.lastMsgID = clientHandler.user.id + + // user1.getServer().announce("TO: <" + user.getName() + ">(" + user.getID() + ") <" + + // clientHandler.getUser().getName() + "> (" + clientHandler.getUser().getID() + "): " + // + + // m, false, user1); + // user.getServer().announce("<" + clientHandler.getUser().getName() + "> (" + + // clientHandler.getUser().getID() + "): " + m, false, user); game.announce( - "<" + user.name + "> Is not accepting private messages!", + "TO: <${user.name}>(${user.id}) <${clientHandler.user.name}> (${clientHandler.user.id}): $m", clientHandler.user ) + user.game?.announce("<${clientHandler.user.name}> (${clientHandler.user.id}): $m", user) return - } - var m = sb.toString() - m = m.trim { it <= ' ' } - if (m.isBlank() || m.startsWith("�") || m.startsWith("�")) return - if (access == AccessManager.ACCESS_NORMAL) { - val chars = m.toCharArray() - for (i in chars.indices) { - if (chars[i].code < 32) { - logger.atWarning().log("%s /msg denied: Illegal characters in message", user) + } catch (e: NoSuchElementException) { + if (clientHandler.user.lastMsgID != -1) { + try { + val user = clientHandler.user.server.getUser(clientHandler.user.lastMsgID) + val sb = StringBuilder() + while (scanner.hasNext()) { + sb.append(scanner.next()) + sb.append(" ") + } + if (user == null) { + game.announce("User not found!", clientHandler.user) + return + } + if (user.game != game) { + game.announce("User not in this game!", clientHandler.user) + return + } + if (user === clientHandler.user) { + game.announce("You can't private message yourself!", clientHandler.user) + return + } + var m = sb.toString() + m = m.trim { it <= ' ' } + if (m.isBlank() || m.startsWith("�") || m.startsWith("�")) return + if (access == AccessManager.ACCESS_NORMAL) { + val chars = m.toCharArray() + var i = 0 + while (i < chars.size) { + if (chars[i].code < 32) { + logger.atWarning().log("%s /msg denied: Illegal characters in message", user) + game.announce( + "Private Message Denied: Illegal characters in message", + clientHandler.user + ) + return + } + i++ + } + if (m.length > 320) { + logger.atWarning().log("%s /msg denied: Message Length > 320", user) + game.announce("Private Message Denied: Message Too Long", clientHandler.user) + return + } + } + + // user1.getServer().announce("TO: <" + user.getName() + ">(" + user.getID() + ") + // <" + // + + // clientHandler.getUser().getName() + "> (" + clientHandler.getUser().getID() + + // "): + // " + // + m, false, user1); + // user.getServer().announce("<" + clientHandler.getUser().getName() + "> (" + + // clientHandler.getUser().getID() + "): " + m, false, user); game.announce( - "Private Message Denied: Illegal characters in message", + "TO: <${user.name}>(${user.id}) <${clientHandler.user.name}> (${clientHandler.user.id}): $m", clientHandler.user ) + user.game?.announce( + "<${clientHandler.user.name}> (${clientHandler.user.id}): $m", + user + ) + return + } catch (e1: Exception) { + game.announce("Private Message Error: /msg ", clientHandler.user) return } - } - if (m.length > 320) { - logger.atWarning().log("%s /msg denied: Message Length > 320", user) - game.announce("Private Message Denied: Message Too Long", clientHandler.user) + } else { + game.announce("Private Message Error: /msg ", clientHandler.user) return } } - clientHandler.user.lastMsgID = user.id - user.lastMsgID = clientHandler.user.id - - // user1.getServer().announce("TO: <" + user.getName() + ">(" + user.getID() + ") <" + - // clientHandler.getUser().getName() + "> (" + clientHandler.getUser().getID() + "): " - // + - // m, false, user1); - // user.getServer().announce("<" + clientHandler.getUser().getName() + "> (" + - // clientHandler.getUser().getID() + "): " + m, false, user); - game.announce( - "TO: <${user.name}>(${user.id}) <${clientHandler.user.name}> (${clientHandler.user.id}): $m", - clientHandler.user + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isNotBlank()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "ignoreall", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + clientHandler.user.ignoreAll = true + clientHandler.user.server.announce( + clientHandler.user.name + " is now ignoring everyone!", + false, + null ) - user.game?.announce("<${clientHandler.user.name}> (${clientHandler.user.id}): $m", user) - return - } catch (e: java.util.NoSuchElementException) { - if (clientHandler.user.lastMsgID != -1) { - try { - val user = clientHandler.user.server.getUser(clientHandler.user.lastMsgID) - val sb = StringBuilder() - while (scanner.hasNext()) { - sb.append(scanner.next()) - sb.append(" ") - } - if (user == null) { - game.announce("User not found!", clientHandler.user) - return - } - if (user.game != game) { - game.announce("User not in this game!", clientHandler.user) - return - } - if (user === clientHandler.user) { - game.announce("You can't private message yourself!", clientHandler.user) - return - } - var m = sb.toString() - m = m.trim { it <= ' ' } - if (m.isBlank() || m.startsWith("�") || m.startsWith("�")) return - if (access == AccessManager.ACCESS_NORMAL) { - val chars = m.toCharArray() - var i = 0 - while (i < chars.size) { - if (chars[i].code < 32) { - logger.atWarning().log("%s /msg denied: Illegal characters in message", user) - game.announce( - "Private Message Denied: Illegal characters in message", - clientHandler.user - ) - return - } - i++ - } - if (m.length > 320) { - logger.atWarning().log("%s /msg denied: Message Length > 320", user) - game.announce("Private Message Denied: Message Too Long", clientHandler.user) - return - } - } + } - // user1.getServer().announce("TO: <" + user.getName() + ">(" + user.getID() + ") - // <" - // + - // clientHandler.getUser().getName() + "> (" + clientHandler.getUser().getID() + - // "): - // " - // + m, false, user1); - // user.getServer().announce("<" + clientHandler.getUser().getName() + "> (" + - // clientHandler.getUser().getID() + "): " + m, false, user); - game.announce( - "TO: <${user.name}>(${user.id}) <${clientHandler.user.name}> (${clientHandler.user.id}): $m", - clientHandler.user - ) - user.game?.announce( - "<${clientHandler.user.name}> (${clientHandler.user.id}): $m", - user + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "unignoreall", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + clientHandler.user.ignoreAll = false + clientHandler.user.server.announce( + clientHandler.user.name + " is now unignoring everyone!", + gamesAlso = false, + targetUser = null + ) + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "ignore", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val scanner = Scanner(args).useDelimiter(" ") + try { + val userID = scanner.nextInt() + val user = clientHandler.user.server.getUser(userID) + if (user == null) { + game.announce("User not found!", clientHandler.user) + return + } + if (user === clientHandler.user) { + game.announce("You can't ignore yourself!", clientHandler.user) + return + } + if (clientHandler.user.findIgnoredUser(user.connectSocketAddress.address.hostAddress)) { + game.announce("You can't ignore a user that is already ignored!", clientHandler.user) + return + } + if (user.accessLevel >= AccessManager.ACCESS_MODERATOR) { + game.announce("You cannot ignore a moderator or admin!", clientHandler.user) + return + } + clientHandler.user.addIgnoredUser(user.connectSocketAddress.address.hostAddress) + user.server.announce( + "${clientHandler.user.name} is now ignoring <${user.name}> ID: ${user.id}", + false, + null + ) + return + } catch (e: NoSuchElementException) { + game.announce("Ignore User Error: /ignore ", clientHandler.user) + logger + .atInfo() + .withCause(e) + .log( + "IGNORE USER ERROR: %s: %s", + clientHandler.user.name, + clientHandler.remoteSocketAddress!!.hostName ) + return + } + } + + override fun verifyArgs(args: String): ParseResult { + TODO("Not yet implemented") + } + }, + object : + GameChatCommand( + prefix = "unignore", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val scanner = Scanner(args).useDelimiter(" ") + try { + scanner.next() + val userID = scanner.nextInt() + val user = clientHandler.user.server.getUser(userID) + if (user == null) { + game.announce("User Not Found!", clientHandler.user) return - } catch (e1: Exception) { - game.announce("Private Message Error: /msg ", clientHandler.user) + } + if ( + !clientHandler.user.findIgnoredUser(user.connectSocketAddress.address.hostAddress) + ) { + game.announce("You can't unignore a user that isn't ignored", clientHandler.user) return } - } else { - game.announce("Private Message Error: /msg ", clientHandler.user) + if ( + clientHandler.user.removeIgnoredUser( + user.connectSocketAddress.address.hostAddress, + false + ) + ) + user.server.announce( + "${clientHandler.user.name} is now unignoring <${user.name}> ID: ${user.id}", + false, + null + ) + else + clientHandler.send( + org.emulinker.kaillera.controller.v086.protocol.InformationMessage( + clientHandler.nextMessageNumber, + "server", + "User Not Found!" + ) + ) + } catch (e: NoSuchElementException) { + game.announce("Unignore User Error: /ignore ", clientHandler.user) + logger + .atInfo() + .withCause(e) + .log( + "UNIGNORE USER ERROR: %s: %s", + clientHandler.user.name, + clientHandler.remoteSocketAddress!!.hostName + ) + } + } + + override fun verifyArgs(args: String): ParseResult { + TODO("Not yet implemented") + } + }, + object : + GameChatCommand( + prefix = "me", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + var announcement = args + if (announcement.startsWith(":")) + announcement = + announcement.substring( + 1 + ) // this protects against people screwing up the emulinker supraclient + val access = + clientHandler.user.server.accessManager.getAccess( + clientHandler.user.socketAddress!!.address + ) + if ( + access < AccessManager.ACCESS_SUPERADMIN && + clientHandler.user.server.accessManager.isSilenced( + clientHandler.user.socketAddress!!.address + ) + ) { + game.announce("You are silenced!", clientHandler.user) return } + if (clientHandler.user.server.checkMe(clientHandler.user, announcement)) { + val m = announcement + announcement = "*" + clientHandler.user.name + " " + m + for (user in game.players) { + game.announce(announcement, user) + } + } } - } - override fun verifyArgs(args: String): ParseResult = - if (args.isNotBlank()) ParseResult.Success else TODO("Write a message") - }, - object : - GameChatCommand( - prefix = "ignoreall", - commandPermissions = CommandPermissions.LENIENT, - ) { - override fun performAction( - args: String, - game: KailleraGameImpl, - clientHandler: V086ClientHandler - ) { - clientHandler.user.ignoreAll = true - clientHandler.user.server.announce( - clientHandler.user.name + " is now ignoring everyone!", - false, - null - ) - } + override fun verifyArgs(args: String): ParseResult = + if (args.isNotBlank()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "help", + commandPermissions = CommandPermissions.LENIENT, + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + if (game.owner.id == clientHandler.user.id) { + val admin = clientHandler.user + if (admin != game.owner && admin.accessLevel < AccessManager.ACCESS_SUPERADMIN) return + // game.setIndividualGameAnnounce(admin.getPlayerNumber()); + // game.announce(EmuLang.getString("GameOwnerCommandAction.AvailableCommands")); + // try { Thread.sleep(20); } catch(Exception e) {} + game.announce(EmuLang.getString("GameOwnerCommandAction.SetAutofireDetection"), admin) + threadSleep(20.milliseconds) + game.announce("/maxusers <#> to set capacity of room", admin) + threadSleep(20.milliseconds) + game.announce("/maxping <#> to set maximum ping for room", admin) + threadSleep(20.milliseconds) + game.announce("/start or /startn <#> start game when n players are joined.", admin) + threadSleep(20.milliseconds) + game.announce( + "/mute /unmute or /muteall or /unmuteall to mute player(s).", + admin + ) + threadSleep(20.milliseconds) + game.announce( + "/swap eg. 123..n {n = total # of players; Each slot = new player#}", + admin + ) + threadSleep(20.milliseconds) + game.announce("/kick or /kickall to kick a player(s).", admin) + threadSleep(20.milliseconds) + game.announce("/setemu To restrict the gameroom to this emulator!", admin) + threadSleep(20.milliseconds) + game.announce("/setconn To restrict the gameroom to this connection type!", admin) + threadSleep(20.milliseconds) + game.announce( + "/lagstat To check who has the most lag spikes or /lagreset to reset lagstat!", + admin + ) + threadSleep(20.milliseconds) + game.announce( + "/samedelay {true | false} to play at the same delay as player with highest ping. Default is false.", + admin + ) + threadSleep(20.milliseconds) + } else { + game.announce( + "/me to make personal message eg. /me is bored ...SupraFast is bored.", + clientHandler.user + ) + threadSleep(20.milliseconds) + game.announce( + "/msg to PM somebody. /msgoff or /msgon to turn pm off | on.", + clientHandler.user + ) + threadSleep(20.milliseconds) + game.announce( + "/ignore or /unignore or /ignoreall or /unignoreall to ignore users.", + clientHandler.user + ) + threadSleep(20.milliseconds) + game.announce( + "/p2pon or /p2poff this option ignores all server activity during gameplay.", + clientHandler.user + ) + threadSleep(20.milliseconds) + } + } - override fun verifyArgs(args: String): ParseResult = - if (args.isEmpty()) ParseResult.Success else TODO("Write a message") - }, - object : - GameChatCommand( - prefix = "unignoreall", - commandPermissions = CommandPermissions.LENIENT, - ) { - override fun performAction( - args: String, - game: KailleraGameImpl, - clientHandler: V086ClientHandler - ) { - clientHandler.user.ignoreAll = false - clientHandler.user.server.announce( - clientHandler.user.name + " is now unignoring everyone!", - gamesAlso = false, - targetUser = null - ) - } + override fun verifyArgs(args: String): ParseResult = ParseResult.Success + }, + object : + GameChatCommand( + prefix = "stop", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = true, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + if (twitterBroadcaster.cancelActionsForUser(clientHandler.user.id)) { + game.announce( + EmuLang.getStringOrDefault( + "KailleraServerImpl.CanceledPendingTweet", + default = "Canceled pending tweet." + ), + clientHandler.user + ) + } else { + game.announce("No pending tweets.", clientHandler.user) + } + } - override fun verifyArgs(args: String): ParseResult = - if (args.isEmpty()) ParseResult.Success else TODO("Write a message") - }, - object : - GameChatCommand( - prefix = "ignore", - commandPermissions = CommandPermissions.LENIENT, - ) { - override fun performAction( - args: String, - game: KailleraGameImpl, - clientHandler: V086ClientHandler - ) { - val scanner = java.util.Scanner(args).useDelimiter(" ") - try { - val userID = scanner.nextInt() - val user = clientHandler.user.server.getUser(userID) - if (user == null) { - game.announce("User not found!", clientHandler.user) - return + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "detectautofire", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + fun autoFireHelp(game: KailleraGameImpl, admin: KailleraUser) { + val cur = game.autoFireDetector!!.sensitivity + game.announce(EmuLang.getString("GameOwnerCommandAction.HelpSensitivity"), admin) + threadSleep(20.milliseconds) + game.announce(EmuLang.getString("GameOwnerCommandAction.HelpDisable"), admin) + threadSleep(20.milliseconds) + game.announce( + EmuLang.getString("GameOwnerCommandAction.HelpCurrentSensitivity", cur) + + if (cur == 0) EmuLang.getString("GameOwnerCommandAction.HelpDisabled") else "", + admin + ) } - if (user === clientHandler.user) { - game.announce("You can't ignore yourself!", clientHandler.user) + + val admin = clientHandler.user + if (game.status != GameStatus.WAITING) { + game.announce( + EmuLang.getString("GameOwnerCommandAction.AutoFireChangeDeniedInGame"), + admin + ) return } - if (clientHandler.user.findIgnoredUser(user.connectSocketAddress.address.hostAddress)) { - game.announce("You can't ignore a user that is already ignored!", clientHandler.user) + val st = StringTokenizer(args, " ") + if (st.countTokens() != 2) { + autoFireHelp(game, admin) return } - if (user.accessLevel >= AccessManager.ACCESS_MODERATOR) { - game.announce("You cannot ignore a moderator or admin!", clientHandler.user) + val unusedCommand = st.nextToken() + val sensitivityStr = st.nextToken() + var sensitivity = -1 + try { + sensitivity = sensitivityStr.toInt() + } catch (e: NumberFormatException) {} + if (sensitivity > 5 || sensitivity < 0) { + autoFireHelp(game, admin) return } - clientHandler.user.addIgnoredUser(user.connectSocketAddress.address.hostAddress) - user.server.announce( - "${clientHandler.user.name} is now ignoring <${user.name}> ID: ${user.id}", - false, - null + game.autoFireDetector!!.sensitivity = sensitivity + game.announce( + EmuLang.getString("GameOwnerCommandAction.HelpCurrentSensitivity", sensitivity) + + if (sensitivity == 0) EmuLang.getString("GameOwnerCommandAction.HelpDisabled") + else "", ) - return - } catch (e: java.util.NoSuchElementException) { - game.announce("Ignore User Error: /ignore ", clientHandler.user) - logger - .atInfo() - .withCause(e) - .log( - "IGNORE USER ERROR: %s: %s", - clientHandler.user.name, - clientHandler.remoteSocketAddress!!.hostName - ) - return } - } - override fun verifyArgs(args: String): ParseResult { - TODO("Not yet implemented") - } - }, - object : - GameChatCommand( - prefix = "unignore", - commandPermissions = CommandPermissions.LENIENT, - ) { - override fun performAction( - args: String, - game: KailleraGameImpl, - clientHandler: V086ClientHandler - ) { - val scanner = java.util.Scanner(args).useDelimiter(" ") - try { - scanner.next() - val userID = scanner.nextInt() - val user = clientHandler.user.server.getUser(userID) - if (user == null) { - game.announce("User Not Found!", clientHandler.user) + override fun verifyArgs(args: String): ParseResult = TODO() + }, + object : + GameChatCommand( + prefix = "maxusers", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val admin = clientHandler.user + if (System.currentTimeMillis() - lastMaxUserChange <= 3000) { + game.announce("Max User Command Spam Detection...Please Wait!", admin) + lastMaxUserChange = System.currentTimeMillis() return + } else { + lastMaxUserChange = System.currentTimeMillis() } - if (!clientHandler.user.findIgnoredUser(user.connectSocketAddress.address.hostAddress)) { - game.announce("You can't unignore a user that isn't ignored", clientHandler.user) - return + val scanner = Scanner(args).useDelimiter(" ") + try { + val num = scanner.nextInt() + if (num in 1..100) { + game.maxUsers = num + game.announce( + "Max Users has been set to $num", + ) + } else { + game.announce("Max Users Error: Enter value between 1 and 100", admin) + } + } catch (e: NoSuchElementException) { + game.announce("Failed: /maxusers <#>", admin) } - if ( - clientHandler.user.removeIgnoredUser( - user.connectSocketAddress.address.hostAddress, - false - ) - ) - user.server.announce( - "${clientHandler.user.name} is now unignoring <${user.name}> ID: ${user.id}", - false, - null - ) - else - clientHandler.send( - org.emulinker.kaillera.controller.v086.protocol.InformationMessage( - clientHandler.nextMessageNumber, - "server", - "User Not Found!" + } + + override fun verifyArgs(args: String): ParseResult = TODO() + }, + object : + GameChatCommand( + prefix = "maxping", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val scanner = Scanner(args).useDelimiter(" ") + try { + val num = scanner.nextInt() + if (num in 1..1000) { + game.maxPing = num + game.announce( + "Max Ping has been set to $num", ) + } else { + game.announce("Max Ping Error: Enter value between 1 and 1000", clientHandler.user) + } + } catch (e: NoSuchElementException) { + game.announce("Failed: /maxping <#>", clientHandler.user) + } + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "start", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = true, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + game.start(clientHandler.user) + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "startn", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = true, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val scanner = Scanner(args).useDelimiter(" ") + try { + val num = scanner.nextInt() + if (num in 1..100) { + game.startN = num.toByte().toInt() + game.announce( + "This game will start when $num players have joined.", + ) + } else { + game.announce("StartN Error: Enter value between 1 and 100.", clientHandler.user) + } + } catch (e: NoSuchElementException) { + game.announce("Failed: /startn <#>", clientHandler.user) + } + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "mute", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = true, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val scanner = Scanner(args).useDelimiter(" ") + try { + val userID = scanner.nextInt() + val user = clientHandler.user.server.getUser(userID) + if (user == null) { + game.announce("Player doesn't exist!", clientHandler.user) + return + } + if (user === clientHandler.user) { + game.announce("You can't mute yourself!", clientHandler.user) + return + } + if ( + user.accessLevel >= AccessManager.ACCESS_ADMIN && + clientHandler.user.accessLevel != AccessManager.ACCESS_SUPERADMIN + ) { + user.game!!.announce("You can't mute an Admin", clientHandler.user) + return + } + + // mute by IP + game.mutedUsers.add(user.connectSocketAddress.address.hostAddress) + user.isMuted = true + val user1 = clientHandler.user + user1.game!!.announce( + user.name + " has been muted!", ) - } catch (e: java.util.NoSuchElementException) { - game.announce("Unignore User Error: /ignore ", clientHandler.user) - logger - .atInfo() - .withCause(e) - .log( - "UNIGNORE USER ERROR: %s: %s", - clientHandler.user.name, - clientHandler.remoteSocketAddress!!.hostName + } catch (e: NoSuchElementException) { + val user = clientHandler.user + user.game!!.announce("Mute Player Error: /mute ", clientHandler.user) + } + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "muteall", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = true, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val scanner = Scanner(args).useDelimiter(" ") + try { + for (w in 1..game.players.size) { + // do not mute owner or admin + if ( + game.getPlayer(w)!!.accessLevel < AccessManager.ACCESS_ADMIN && + game.getPlayer(w) != game.owner + ) { + game.getPlayer(w)!!.isMuted = true + game.mutedUsers.add(game.getPlayer(w)!!.connectSocketAddress.address.hostAddress) + } + } + game.announce( + "All players have been muted!", ) + return + } catch (e: NoSuchElementException) { + game.announce("Mute Player Error: /mute ", clientHandler.user) + } } - } - override fun verifyArgs(args: String): ParseResult { - TODO("Not yet implemented") - } - }, - object : - GameChatCommand( - prefix = "me", - commandPermissions = CommandPermissions.LENIENT, - ) { - override fun performAction( - args: String, - game: KailleraGameImpl, - clientHandler: V086ClientHandler - ) { - var announcement = args - if (announcement.startsWith(":")) - announcement = - announcement.substring( - 1 - ) // this protects against people screwing up the emulinker supraclient - val access = - clientHandler.user.server.accessManager.getAccess( - clientHandler.user.socketAddress!!.address + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "setemu", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + var emu = game.owner.clientType + if (args == "any") { + emu = "any" + } + game.aEmulator = emu!! + game.announce( + "Owner has restricted the emulator to: $emu", ) - if ( - access < AccessManager.ACCESS_SUPERADMIN && - clientHandler.user.server.accessManager.isSilenced( - clientHandler.user.socketAddress!!.address + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "setconn", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = true, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + var conn = game.owner.connectionType.readableName + if (args == "any") { + conn = "any" + } + game.aConnection = conn + game.announce( + "Owner has restricted the connection type to: $conn", + ) + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "unmute", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val scanner = Scanner(args).useDelimiter(" ") + try { + val userID = scanner.nextInt() + val user = clientHandler.user.server.getUser(userID) + if (user == null) { + game.announce("Player doesn't exist!", clientHandler.user) + return + } + if (user === clientHandler.user) { + user.game!!.announce("You can't unmute yourself!", clientHandler.user) + return + } + if ( + user.accessLevel >= AccessManager.ACCESS_ADMIN && + clientHandler.user.accessLevel != AccessManager.ACCESS_SUPERADMIN + ) { + user.game!!.announce("You can't unmute an Admin", clientHandler.user) + return + } + game.mutedUsers.remove(user.connectSocketAddress.address.hostAddress) + user.isMuted = false + val user1 = clientHandler.user + user1.game!!.announce( + user.name + " has been unmuted!", ) + } catch (e: NoSuchElementException) { + val user = clientHandler.user + user.game!!.announce("Unmute Player Error: /unmute ", clientHandler.user) + } + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "unmuteall", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler ) { - game.announce("You are silenced!", clientHandler.user) - return + try { + game.players.forEach { kailleraUser -> + kailleraUser.isMuted = false + game.mutedUsers.remove(kailleraUser.connectSocketAddress.address.hostAddress) + } + game.announce( + "All players have been unmuted!", + ) + } catch (e: NoSuchElementException) { + val user = clientHandler.user + user.game!!.announce("Unmute Player Error: /unmute ", clientHandler.user) + } } - if (clientHandler.user.server.checkMe(clientHandler.user, announcement)) { - val m = announcement - announcement = "*" + clientHandler.user.name + " " + m - for (user in game.players) { - game.announce(announcement, user) + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "swap", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + /*if(game.getStatus() != KailleraGame.STATUS_PLAYING){ + game.announce("Failed: wap Players can only be used during gameplay!", admin); + return; + }*/ + val scanner = Scanner(args).useDelimiter(" ") + try { + var i: Int + val str: String + val test = scanner.nextInt() + str = test.toString() + if (game.players.size < str.length) { + game.announce( + "Failed: You can't swap more than the # of players in the room.", + clientHandler.user + ) + return + } + if (test > 0) { + var numCount = 0 + val num = IntArray(str.length) + // before swap check numbers to prevent errors due to incorrectly entered numbers + i = 0 + while (i < num.size) { + num[i] = str[i].toString().toInt() + numCount = 1 + if (num[i] == 0 || num[i] > game.players.size) break + for (j in num.indices) { + if (num[i] != num[j]) numCount++ + } + i++ + } + if (numCount == game.players.size) { + game.swap = true + // PlayerActionQueue temp = game.getPlayerActionQueue()[0]; + i = 0 + while (i < str.length) { + val player = game.players[i] + player.playerNumber = num[i] + /*if(num[i] == 1){ + game.getPlayerActionQueue()[i] = temp; + } + else{ + game.getPlayerActionQueue()[i] = game.getPlayerActionQueue()[num[i]-1]; + }*/ game.announce( + player.name + " is now Player#: " + player.playerNumber, + ) + i++ + } + } else + game.announce( + "Swap Player Error: /swap eg. 123..n {n = total # of players; Each slot = new player#}", + clientHandler.user + ) + } + } catch (e: NoSuchElementException) { + game.announce( + "Swap Player Error: /swap eg. 123..n {n = total # of players; Each slot = new player#}", + clientHandler.user + ) } } - } - override fun verifyArgs(args: String): ParseResult = - if (args.isNotBlank()) ParseResult.Success else TODO("Write a message") - }, - object : - GameChatCommand( - prefix = "help", - commandPermissions = CommandPermissions.LENIENT, - ) { - override fun performAction( - args: String, - game: KailleraGameImpl, - clientHandler: V086ClientHandler - ) { - game.announce( - "/me to make personal message eg. /me is bored ...SupraFast is bored.", - clientHandler.user - ) - threadSleep(20.milliseconds) - game.announce( - "/msg to PM somebody. /msgoff or /msgon to turn pm off | on.", - clientHandler.user - ) - threadSleep(20.milliseconds) - game.announce( - "/ignore or /unignore or /ignoreall or /unignoreall to ignore users.", - clientHandler.user - ) - threadSleep(20.milliseconds) - game.announce( - "/p2pon or /p2poff this option ignores all server activity during gameplay.", - clientHandler.user - ) - threadSleep(20.milliseconds) - } + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "kick", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val scanner = Scanner(args).useDelimiter(" ") + try { + val playerNumber = scanner.nextInt() + if (playerNumber in 1..100) { + if (game.getPlayer(playerNumber) != null) + game.kick(clientHandler.user, game.getPlayer(playerNumber)!!.id) + else { + game.announce("Player doesn't exisit!", clientHandler.user) + } + } else { + game.announce("Kick Player Error: Enter value between 1 and 100", clientHandler.user) + } + } catch (e: NoSuchElementException) { + game.announce( + "Failed: /kick or /kickall to kick all players.", + clientHandler.user + ) + } + } - override fun verifyArgs(args: String): ParseResult = ParseResult.Success - }, - object : - GameChatCommand( - prefix = "stop", - commandPermissions = - CommandPermissions( - allowedLocations = CommandUsageLocation.ROOM_CHAT, - gameOwnerOnly = true, - ), - ) { - override fun performAction( - args: String, - game: KailleraGameImpl, - clientHandler: V086ClientHandler - ) { - if (twitterBroadcaster.cancelActionsForUser(clientHandler.user.id)) { + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "kickall", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + // start kick players from last to first and don't kick owner or admin + for (w in game.players.size downTo 1) { + if ( + game.getPlayer(w)!!.accessLevel < AccessManager.ACCESS_ADMIN && + game.getPlayer(w) != game.owner + ) + game.kick(clientHandler.user, game.getPlayer(w)!!.id) + } game.announce( - org.emulinker.util.EmuLang.getStringOrDefault( - "KailleraServerImpl.CanceledPendingTweet", - default = "Canceled pending tweet." + "All players have been kicked!", + ) + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "lagstat", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, ), - clientHandler.user + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + game.announce("Lagged frames per player:") + game.players + .asSequence() + .filter { !it.inStealthMode } + .forEach { + game.announce( + "P${it.playerNumber}: ${it.smallLagSpikesCausedByUser} (tiny), ${it.bigLagSpikesCausedByUser} (big)" + ) + } + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "lagreset", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + for (player in game.players) { + player.timeouts = 0 + player.smallLagSpikesCausedByUser = 0 + player.bigLagSpikesCausedByUser = 0 + } + game.announce( + "LagStat has been reset!", ) - } else { - game.announce("No pending tweets.", clientHandler.user) } - } - override fun verifyArgs(args: String): ParseResult = - if (args.isEmpty()) ParseResult.Success else TODO("Write a message ARGS IS " + args) - }, - ) + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "samedelay", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + if (args == "true") { + game.sameDelay = true + game.announce( + "Players will have the same delay when game starts (restarts)!", + ) + } else { + game.sameDelay = false + game.announce( + "Players will have independent delays when game starts (restarts)!", + ) + } + } -class CommandPermissions( - val allowedLocations: CommandUsageLocation, - val gameOwnerOnly: Boolean, - val minimumAccessRequired: Int = AccessManager.ACCESS_NORMAL, -) { - companion object { - val LENIENT = - CommandPermissions( - allowedLocations = CommandUsageLocation.ANYWHERE, - gameOwnerOnly = false, - minimumAccessRequired = AccessManager.ACCESS_NORMAL - ) - } -} + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "num", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + game.announce("${game.players.size} in the room!", clientHandler.user) + } -abstract class GameChatCommand( - /** For the command `/maxusers 42`, this would be `"maxusers"`. */ - val prefix: String, - // TODO(nue): Use this. - val commandPermissions: CommandPermissions, -) { - protected abstract fun performAction( - args: String, - game: KailleraGameImpl, - clientHandler: V086ClientHandler - ) + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "TEMPLATE", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) {} - /** Validates whether the command is valid. */ - protected abstract fun verifyArgs(args: String): ParseResult + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "TEMPLATE", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) {} - /** @param message Does not start with "/". */ - fun handle(message: String, game: KailleraGameImpl, handler: V086ClientHandler) { - val args = message.removePrefix(COMMAND_PREFIX + prefix).trim() - when (val parseResult = verifyArgs(args)) { - ParseResult.Success -> { - performAction(args, game, handler) + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + ) + + /** + * Handler for the `/help` command. + * + * Lists commands available to the user where they are using the command. + */ + fun help(game: KailleraGameImpl?, clientHandler: V086ClientHandler) { + for (it in commands) { + if ( + (it.commandPermissions.allowedLocations == CommandUsageLocation.SERVER_CHAT_ONLY && + game != null) || + (it.commandPermissions.allowedLocations == CommandUsageLocation.GAME_CHAT_ONLY && + game == null) + ) { + continue } - is ParseResult.Failure -> { - parseResult.messageToUser - // TODO(nue): Send messageToUser to user. + + if ( + clientHandler.user.accessLevel >= AccessManager.ACCESS_SUPERADMIN || + clientHandler.user.accessLevel >= it.commandPermissions.minimumAccessRequired + ) { + if (game == null) { + TODO("Support server chat") + } else { + game.announce(it.prefix, clientHandler.user) + } } } } companion object { - const val COMMAND_PREFIX = "/" + private var lastMaxUserChange: Long = 0 } } diff --git a/emulinker/src/main/java/org/emulinker/kaillera/pico/AppModule.kt b/emulinker/src/main/java/org/emulinker/kaillera/pico/AppModule.kt index 832b0e68a..7d246f066 100644 --- a/emulinker/src/main/java/org/emulinker/kaillera/pico/AppModule.kt +++ b/emulinker/src/main/java/org/emulinker/kaillera/pico/AppModule.kt @@ -21,9 +21,6 @@ import org.emulinker.kaillera.access.AccessManager import org.emulinker.kaillera.access.AccessManager2 import org.emulinker.kaillera.controller.KailleraServerController import org.emulinker.kaillera.controller.v086.V086Controller -import org.emulinker.kaillera.controller.v086.commands.GameChatCommand -import org.emulinker.kaillera.controller.v086.commands.buildGameChatCommands -import org.emulinker.kaillera.lookingforgame.TwitterBroadcaster import org.emulinker.kaillera.master.MasterListStatsCollector import org.emulinker.kaillera.master.StatsCollector import org.emulinker.kaillera.model.impl.AutoFireDetectorFactory @@ -132,10 +129,5 @@ abstract class AppModule { i++ } } - - @Provides - @Singleton - fun provideGameChatCommands(twitterBroadcaster: TwitterBroadcaster): List = - buildGameChatCommands(twitterBroadcaster) } } From f7ef96551ca354bcafde4696b16596f588fb51ee Mon Sep 17 00:00:00 2001 From: Jonn Date: Thu, 29 Aug 2024 23:52:47 +0900 Subject: [PATCH 4/4] This is where i was working. maybe i will abandon it? --- .../controller/v086/action/ChatAction.kt | 36 +--- .../controller/v086/action/GameChatAction.kt | 35 ++-- .../controller/v086/commands/Commands.kt | 181 +++++++++++++++++- .../lookingforgame/TwitterBroadcaster.kt | 3 +- .../controller/v086/commands/CommandsTest.kt | 17 ++ 5 files changed, 209 insertions(+), 63 deletions(-) create mode 100644 emulinker/src/test/java/org/emulinker/kaillera/controller/v086/commands/CommandsTest.kt diff --git a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/ChatAction.kt b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/ChatAction.kt index 24bb75aeb..6e38cfaa7 100644 --- a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/ChatAction.kt +++ b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/ChatAction.kt @@ -17,7 +17,6 @@ import org.emulinker.kaillera.controller.v086.protocol.InformationMessage import org.emulinker.kaillera.model.event.ChatEvent import org.emulinker.kaillera.model.exception.ActionException import org.emulinker.util.EmuLang -import org.emulinker.util.EmuUtil import org.emulinker.util.EmuUtil.threadSleep private const val ADMIN_COMMAND_ESCAPE_STRING = "/" @@ -85,41 +84,10 @@ class ChatAction @Inject internal constructor(private val adminCommandAction: Ad } if (doCommand) { // SF MOD - User Commands - if (chatMessage.message == "/alivecheck") { - try { - clientHandler.send( - InformationMessage( - clientHandler.nextMessageNumber, - "server", - ":ALIVECHECK=EmuLinker-K Alive Check: You are still logged in." - ) - ) - } catch (e: Exception) {} - } else if ( + if (chatMessage.message == "/alivecheck") {} else if ( chatMessage.message == "/version" && clientHandler.user.accessLevel < AccessManager.ACCESS_ADMIN - ) { - val releaseInfo = clientHandler.user.server.releaseInfo - try { - clientHandler.send( - InformationMessage( - clientHandler.nextMessageNumber, - "server", - "VERSION: ${releaseInfo.productName}: ${releaseInfo.version}: ${EmuUtil.toSimpleUtcDatetime(releaseInfo.buildDate)}" - ) - ) - } catch (e: Exception) {} - } else if (chatMessage.message == "/myip") { - try { - clientHandler.send( - InformationMessage( - clientHandler.nextMessageNumber, - "server", - "Your IP Address is: " + clientHandler.user.connectSocketAddress.address.hostAddress - ) - ) - } catch (e: Exception) {} - } else if (chatMessage.message == "/msgon") { + ) {TODO("THIS IS WHERE I WAS WORKING!!!")} else if (chatMessage.message == "/myip") {} else if (chatMessage.message == "/msgon") { clientHandler.user.isAcceptingDirectMessages = true try { clientHandler.send( diff --git a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt index 365438369..82aefedf9 100644 --- a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt +++ b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/action/GameChatAction.kt @@ -5,7 +5,6 @@ import java.util.* import javax.inject.Inject import javax.inject.Singleton import org.emulinker.kaillera.access.AccessManager -import org.emulinker.kaillera.controller.messaging.MessageFormatException import org.emulinker.kaillera.controller.v086.V086ClientHandler import org.emulinker.kaillera.controller.v086.commands.GameChatCommand import org.emulinker.kaillera.controller.v086.commands.GameChatCommandHandler @@ -73,27 +72,27 @@ internal constructor( override fun handleEvent(gameChatEvent: GameChatEvent, clientHandler: V086ClientHandler) { handledEventCount++ - try { - if ( - clientHandler.user.searchIgnoredUsers( - gameChatEvent.user.connectSocketAddress.address.hostAddress - ) + if ( + clientHandler.user.searchIgnoredUsers( + gameChatEvent.user.connectSocketAddress.address.hostAddress ) + ) { + return + } else if (clientHandler.user.ignoreAll) { + if ( + gameChatEvent.user.accessLevel < AccessManager.ACCESS_ADMIN && + gameChatEvent.user !== clientHandler.user + ) { return - else if (clientHandler.user.ignoreAll) { - if ( - gameChatEvent.user.accessLevel < AccessManager.ACCESS_ADMIN && - gameChatEvent.user !== clientHandler.user - ) - return } - val m = gameChatEvent.message - clientHandler.send( - GameChatNotification(clientHandler.nextMessageNumber, gameChatEvent.user.name!!, m) - ) - } catch (e: MessageFormatException) { - logger.atSevere().withCause(e).log("Failed to construct GameChat.Notification message") } + clientHandler.send( + GameChatNotification( + clientHandler.nextMessageNumber, + gameChatEvent.user.name!!, + gameChatEvent.message + ) + ) } companion object { diff --git a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt index fe3d4e425..54e407f72 100644 --- a/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt +++ b/emulinker/src/main/java/org/emulinker/kaillera/controller/v086/commands/Commands.kt @@ -9,11 +9,13 @@ import javax.inject.Singleton import kotlin.time.Duration.Companion.milliseconds import org.emulinker.kaillera.access.AccessManager import org.emulinker.kaillera.controller.v086.V086ClientHandler +import org.emulinker.kaillera.controller.v086.protocol.InformationMessage import org.emulinker.kaillera.lookingforgame.TwitterBroadcaster import org.emulinker.kaillera.model.GameStatus import org.emulinker.kaillera.model.KailleraUser import org.emulinker.kaillera.model.impl.KailleraGameImpl import org.emulinker.util.EmuLang +import org.emulinker.util.EmuUtil import org.emulinker.util.EmuUtil.threadSleep private val logger = FluentLogger.forEnclosingClass() @@ -45,12 +47,19 @@ class CommandPermissions( } } +abstract class ChatCommand() { + /** For the command `/maxusers 42`, this would be `"maxusers"`. */ + abstract val prefix: String + abstract val commandPermissions: CommandPermissions + + +} + abstract class GameChatCommand( /** For the command `/maxusers 42`, this would be `"maxusers"`. */ - val prefix: String, - // TODO(nue): Use this. - val commandPermissions: CommandPermissions, -) { + override val prefix: String, + override val commandPermissions: CommandPermissions, +): ChatCommand() { protected abstract fun performAction( args: String, game: KailleraGameImpl, @@ -1329,15 +1338,95 @@ class GameChatCommandHandler @Inject constructor(twitterBroadcaster: TwitterBroa override fun verifyArgs(args: String): ParseResult = if (args.isEmpty()) ParseResult.Success else TODO("Write a message") }, + // START OF SERVER CHAT COMMANDS object : GameChatCommand( - prefix = "TEMPLATE", + prefix = "alivecheck", commandPermissions = CommandPermissions( - allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, + allowedLocations = CommandUsageLocation.SERVER_CHAT_ONLY, gameOwnerOnly = false, ), ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + clientHandler.send( + InformationMessage( + clientHandler.nextMessageNumber, + "server", + ":ALIVECHECK=EmuLinker-K Alive Check: You are still logged in." + ) + ) + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "version", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.SERVER_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + val releaseInfo = clientHandler.user.server.releaseInfo + clientHandler.send( + InformationMessage( + clientHandler.nextMessageNumber, + "server", + "VERSION: ${releaseInfo.productName}: ${releaseInfo.version}: ${EmuUtil.toSimpleUtcDatetime(releaseInfo.buildDate)}" + ) + ) + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "myip", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.SERVER_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) { + clientHandler.send( + InformationMessage( + clientHandler.nextMessageNumber, + "server", + "Your IP Address is: " + clientHandler.user.connectSocketAddress.address.hostAddress + ) + ) + } + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "TEMPLATE", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.SERVER_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { override fun performAction( args: String, game: KailleraGameImpl, @@ -1351,10 +1440,82 @@ class GameChatCommandHandler @Inject constructor(twitterBroadcaster: TwitterBroa GameChatCommand( prefix = "TEMPLATE", commandPermissions = - CommandPermissions( - allowedLocations = CommandUsageLocation.GAME_CHAT_ONLY, - gameOwnerOnly = false, - ), + CommandPermissions( + allowedLocations = CommandUsageLocation.SERVER_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) {} + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "TEMPLATE", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.SERVER_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) {} + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "TEMPLATE", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.SERVER_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) {} + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "TEMPLATE", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.SERVER_CHAT_ONLY, + gameOwnerOnly = false, + ), + ) { + override fun performAction( + args: String, + game: KailleraGameImpl, + clientHandler: V086ClientHandler + ) {} + + override fun verifyArgs(args: String): ParseResult = + if (args.isEmpty()) ParseResult.Success else TODO("Write a message") + }, + object : + GameChatCommand( + prefix = "TEMPLATE", + commandPermissions = + CommandPermissions( + allowedLocations = CommandUsageLocation.SERVER_CHAT_ONLY, + gameOwnerOnly = false, + ), ) { override fun performAction( args: String, diff --git a/emulinker/src/main/java/org/emulinker/kaillera/lookingforgame/TwitterBroadcaster.kt b/emulinker/src/main/java/org/emulinker/kaillera/lookingforgame/TwitterBroadcaster.kt index febdd0202..50c6880a3 100644 --- a/emulinker/src/main/java/org/emulinker/kaillera/lookingforgame/TwitterBroadcaster.kt +++ b/emulinker/src/main/java/org/emulinker/kaillera/lookingforgame/TwitterBroadcaster.kt @@ -21,7 +21,8 @@ import org.emulinker.util.TaskScheduler * external services (e.g. Twitter, Discord). */ @Singleton -class TwitterBroadcaster +// Open for testing. +open class TwitterBroadcaster @Inject internal constructor( private val flags: RuntimeFlags, diff --git a/emulinker/src/test/java/org/emulinker/kaillera/controller/v086/commands/CommandsTest.kt b/emulinker/src/test/java/org/emulinker/kaillera/controller/v086/commands/CommandsTest.kt new file mode 100644 index 000000000..f773ed5e9 --- /dev/null +++ b/emulinker/src/test/java/org/emulinker/kaillera/controller/v086/commands/CommandsTest.kt @@ -0,0 +1,17 @@ +package org.emulinker.kaillera.controller.v086.commands + +import com.google.common.truth.Truth.assertThat +import org.emulinker.kaillera.controller.v086.protocol.ProtocolBaseTest +import org.emulinker.kaillera.lookingforgame.TwitterBroadcaster +import org.junit.Test +import org.mockito.kotlin.mock + +class CommandsTest : ProtocolBaseTest() { + val twitterBroadcaster = mock() + val target = GameChatCommandHandler(twitterBroadcaster) + + @Test + fun allCommandsUnique() { + assertThat(target.commands.map { it.prefix }).containsNoDuplicates() + } +}