diff --git a/.github/workflows/gradle.yml b/.github/workflows/build.yml similarity index 64% rename from .github/workflows/gradle.yml rename to .github/workflows/build.yml index fb6c44d..daf85c5 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/build.yml @@ -21,22 +21,22 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - - # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. - # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md - - name: Setup Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 - - - name: Make gradlew executable - run: chmod +x ./gradlew - - name: Build with Gradle Wrapper - run: ./gradlew build + - uses: actions/checkout@v4 + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. + # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md + - name: Setup Gradle + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 + + - name: Make gradlew executable + run: chmod +x ./gradlew + - name: Build with Gradle Wrapper + run: ./gradlew build # NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html). # If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version. @@ -47,4 +47,4 @@ jobs: # gradle-version: '8.9' # # - name: Build with Gradle 8.9 - # run: gradle build + # run: gradle build \ No newline at end of file diff --git a/api/build.gradle b/api/build.gradle new file mode 100644 index 0000000..15c9952 --- /dev/null +++ b/api/build.gradle @@ -0,0 +1,16 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/bastion/README.md b/bastion/README.md new file mode 100644 index 0000000..ade6867 --- /dev/null +++ b/bastion/README.md @@ -0,0 +1,5 @@ +
+ +

+

The best Minecraft AntiCheat.

+
\ No newline at end of file diff --git a/bastion/bastion.png b/bastion/bastion.png new file mode 100644 index 0000000..6f75bf4 Binary files /dev/null and b/bastion/bastion.png differ diff --git a/bastion/build.gradle.kts b/bastion/build.gradle.kts new file mode 100644 index 0000000..f9f5a0e --- /dev/null +++ b/bastion/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + kotlin("jvm") + id("io.papermc.paperweight.userdev") +} + +repositories { + mavenCentral() + maven("https://repo.papermc.io/repository/maven-public/") +} + +dependencies { + paperweight.foliaDevBundle("1.21.4-R0.1-SNAPSHOT") +} \ No newline at end of file diff --git a/bastion/src/main/resources/paper-plugin.yml b/bastion/src/main/resources/paper-plugin.yml new file mode 100644 index 0000000..16a44f8 --- /dev/null +++ b/bastion/src/main/resources/paper-plugin.yml @@ -0,0 +1,4 @@ +name: "Bastion" +version: "${version}" +main: "com.otfhee.bastion.Bastion" +api-version: "1.21.1" \ No newline at end of file diff --git a/build.gradle b/build.gradle index eea52ba..d9b2636 100644 --- a/build.gradle +++ b/build.gradle @@ -1,57 +1,26 @@ plugins { - id 'java' - id 'io.papermc.paperweight.userdev' version '1.7.2' - id 'xyz.jpenilla.run-paper' version '2.3.0' + id 'com.github.johnrengelman.shadow' version '8.1.1' + id 'io.papermc.paperweight.userdev' version '2.0.0-beta.11' apply false + id 'org.jetbrains.kotlin.jvm' version '2.1.10' apply false } -group = 'com.otfhee' -version = '1.2.2' +subprojects { + apply plugin: 'java' + apply plugin: 'maven-publish' -compileJava.options.encoding = 'UTF-8' + group = 'com.otfhee' + version = '1.3.0-SNAPSHOT' -repositories { - mavenCentral() - maven { - name = "papermc-repo" - url = "https://repo.papermc.io/repository/maven-public/" - } - maven { - name = "sonatype" - url = "https://oss.sonatype.org/content/groups/public/" - } - maven { - name = "codemc-repo" - url = "https://repo.codemc.org/repository/maven-public/" - } -} - -dependencies { - paperweight.paperDevBundle('1.21.1-R0.1-SNAPSHOT') - compileOnly "dev.jorel:commandapi-bukkit-core:9.4.1" -} + project.ext.version = version -def targetJavaVersion = 21 -java { - def javaVersion = JavaVersion.toVersion(targetJavaVersion) - sourceCompatibility = javaVersion - targetCompatibility = javaVersion - if (JavaVersion.current() < javaVersion) { - toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) - } -} + sourceCompatibility = 21 + targetCompatibility = 21 -tasks.withType(JavaCompile).configureEach { - if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { - options.release.set(targetJavaVersion) + tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' } - options.encoding = 'UTF-8' -} -processResources { - def props = [version: version] - inputs.properties props - filteringCharset 'UTF-8' - filesMatching('plugin.yml') { - expand props + repositories { + mavenCentral() } -} +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index e69de29..af82e00 100644 --- a/gradle.properties +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.parallel=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e09..cea7a79 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/paper/build.gradle b/paper/build.gradle new file mode 100644 index 0000000..f4dbba9 --- /dev/null +++ b/paper/build.gradle @@ -0,0 +1,69 @@ +plugins { + id 'java-library' + id 'io.papermc.paperweight.userdev' + id 'xyz.jpenilla.run-paper' version '2.3.0' + id "com.github.johnrengelman.shadow" version "8.1.1" + id 'org.jetbrains.kotlin.jvm' version '2.1.10' +} + +repositories { + maven { url 'https://repo.papermc.io/repository/maven-public/' } + maven { url 'https://nexus.scarsz.me/content/groups/public/' } + maven { url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } + maven { url = 'https://repo.codemc.org/repository/maven-public/' } + maven { url "https://repo.dmulloy2.net/repository/public/" } + mavenCentral() +} + +processResources { + filesMatching('paper-plugin.yml') { + expand 'version': project.ext.version + } +} + +dependencies { + implementation project(":api") + + implementation 'org.jetbrains.exposed:exposed-core:0.59.0' + implementation 'org.jetbrains.exposed:exposed-dao:0.59.0' + implementation 'org.jetbrains.exposed:exposed-jdbc:0.59.0' + implementation 'org.jetbrains.exposed:exposed-kotlin-datetime:0.59.0' + + implementation 'org.jetbrains.kotlinx:kotlinx-datetime:0.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1' + + implementation 'org.mariadb.jdbc:mariadb-java-client:3.5.2' + implementation 'mysql:mysql-connector-java:8.0.33' + implementation 'org.xerial:sqlite-jdbc:3.42.0.0' + + paperweight.foliaDevBundle('1.21.4-R0.1-SNAPSHOT') + + compileOnly 'dev.jorel:commandapi-bukkit-core:9.7.0' + compileOnly 'net.luckperms:api:5.4' + compileOnly 'com.comphenix.protocol:ProtocolLib:5.1.0' + + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' +} + + +shadowJar { + archiveFileName = "OTFHEE-Paper-${project.ext.version}.jar" + + mergeServiceFiles() + + dependencies { + include(dependency('com.otfhee:.*') as Spec) + include(dependency('org.jetbrains.exposed:.*') as Spec) + include(dependency('org.jetbrains.kotlin:.*') as Spec) + include(dependency('org.jetbrains.kotlinx:kotlinx-datetime-jvm:.*') as Spec) + include(dependency('org.mariadb.jdbc:mariadb-java-client') as Spec) + include(dependency('mysql:mysql-connector-java') as Spec) + } +} + +artifacts { + archives shadowJar +} +kotlin { + jvmToolchain(21) +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/Plugin.kt b/paper/src/main/kotlin/com/otfhee/paper/Plugin.kt new file mode 100644 index 0000000..2196b1d --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/Plugin.kt @@ -0,0 +1,261 @@ +package com.otfhee.paper + +import com.comphenix.protocol.ProtocolLibrary +import com.comphenix.protocol.ProtocolManager +import com.otfhee.paper.chat.ChatManager +import com.otfhee.paper.commands.action.CoinCommand +import com.otfhee.paper.commands.action.DoCommand +import com.otfhee.paper.commands.action.MeCommand +import com.otfhee.paper.commands.action.TryCommand +import com.otfhee.paper.commands.chat.GlobalModeCommand +import com.otfhee.paper.commands.chat.PrivateMessagesCommand +import com.otfhee.paper.commands.display.DisplayCommand +import com.otfhee.paper.commands.security.* +import com.otfhee.paper.commands.social.DiscordCommand +import com.otfhee.paper.commands.social.RulesCommand +import com.otfhee.paper.commands.social.SiteCommand +import com.otfhee.paper.commands.social.TelegramCommand +import com.otfhee.paper.commands.util.LanguageCommand +import com.otfhee.paper.commands.util.PingCommand +import com.otfhee.paper.commands.util.PremiumCommand +import com.otfhee.paper.commands.util.StatusCommand +import com.otfhee.paper.database.ban.Bans +import com.otfhee.paper.database.mute.Mutes +import com.otfhee.paper.database.user.Users +import com.otfhee.paper.database.warn.Warns +import com.otfhee.paper.listeners.* +import com.otfhee.paper.recipes.DebugStickRecipe +import com.otfhee.paper.schedulers.AutoRestart +import com.otfhee.paper.schedulers.PlayerList +import com.otfhee.paper.schedulers.PremiumChecker +import com.otfhee.paper.utils.PetUtil +import dev.jorel.commandapi.CommandAPI +import net.luckperms.api.LuckPerms +import net.luckperms.api.LuckPermsProvider +import org.bukkit.event.world.SpawnChangeEvent +import org.bukkit.plugin.java.JavaPlugin +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction +import org.sqlite.SQLiteDataSource +import java.io.File + +class Plugin : JavaPlugin() { + private lateinit var provider: LuckPerms + private var lastModified: Long = 0 + private lateinit var protocolManager: ProtocolManager + private lateinit var configFile: File + private lateinit var petUtil: PetUtil + private lateinit var chatManager: ChatManager + + private lateinit var antiGriefListener: AntiGriefListener + private lateinit var loginListener: LoginListener + private lateinit var playerHeadDropListener: PlayerHeadDropListener + private lateinit var chatListener: ChatListener + private lateinit var statusListener: StatusListener + private lateinit var banListener: BanListener + private lateinit var anvilListener: AnvilListener + private lateinit var playtimeListener: PlaytimeListener + private lateinit var invisibleFrameListener: InvisibleFrameListener + private lateinit var phantomSpawnListener: PhantomSpawnListener + private lateinit var resourcePackDamageListener: ResourcePackDamageListener + private lateinit var boatUphillListener: BoatUphillListener + private lateinit var leashListener: LeashListener + private lateinit var blockHeadListener: BlockHeadListener + private lateinit var petListener: PetListener + private lateinit var allayInteractListener: AllayInteractListener + private lateinit var debugStickInteractListener: DebugStickInteractListener + + private lateinit var autoRestart: AutoRestart + private lateinit var playerList: PlayerList + private lateinit var premiumChecker: PremiumChecker + + private lateinit var displayCommand: DisplayCommand + private lateinit var statusCommand: StatusCommand + private lateinit var languageCommand: LanguageCommand + private lateinit var banCommand: BanCommand + private lateinit var unbanCommand: UnbanCommand + private lateinit var privateMessagesCommand: PrivateMessagesCommand + private lateinit var coinCommand: CoinCommand + private lateinit var globalModeCommand: GlobalModeCommand + private lateinit var doCommand: DoCommand + private lateinit var meCommand: MeCommand + private lateinit var tryCommand: TryCommand + private lateinit var discordCommand: DiscordCommand + private lateinit var rulesCommand: RulesCommand + private lateinit var siteCommand: SiteCommand + private lateinit var telegramCommand: TelegramCommand + private lateinit var pingCommand: PingCommand + private lateinit var warnCommand: WarnCommand + private lateinit var premiumCommand: PremiumCommand + private lateinit var muteCommand: MuteCommand + private lateinit var opCommand: OpCommand + + private lateinit var debugStickRecipe: DebugStickRecipe + + override fun onLoad() { + super.onLoad() + protocolManager = ProtocolLibrary.getProtocolManager() + } + + override fun onEnable() { + super.onEnable() + saveDefaultConfig() + connectDB() + + provider = LuckPermsProvider.get() + petUtil = PetUtil(this) + chatManager = ChatManager(this, provider) + + logger.info("Loading commands...") + CommandAPI.onEnable() + displayCommand = DisplayCommand(this, provider, petUtil) + statusCommand = StatusCommand(this) + languageCommand = LanguageCommand(this) + banCommand = BanCommand(this) + unbanCommand = UnbanCommand(this) + privateMessagesCommand = PrivateMessagesCommand(this, chatManager) + coinCommand = CoinCommand(this) + globalModeCommand = GlobalModeCommand(this) + doCommand = DoCommand(this) + meCommand = MeCommand(this) + tryCommand = TryCommand(this) + discordCommand = DiscordCommand(this) + rulesCommand = RulesCommand(this) + siteCommand = SiteCommand(this) + telegramCommand = TelegramCommand(this) + pingCommand = PingCommand(this) + warnCommand = WarnCommand(this) + premiumCommand = PremiumCommand(this, provider) + muteCommand = MuteCommand(this) + opCommand = OpCommand() + + configFile = File(dataFolder, "config.yml") + lastModified = configFile.lastModified() + + logger.info("Loading recipes...") + debugStickRecipe = DebugStickRecipe(this) + + server.addRecipe(debugStickRecipe.recipe()) + + logger.info("Loading events...") + antiGriefListener = AntiGriefListener(this) + loginListener = LoginListener(this, chatManager) + playerHeadDropListener = PlayerHeadDropListener(this) + chatListener = ChatListener(this, chatManager, provider) + statusListener = StatusListener(this) + banListener = BanListener(this) + anvilListener = AnvilListener() + playtimeListener = PlaytimeListener(this) + invisibleFrameListener = InvisibleFrameListener() + phantomSpawnListener = PhantomSpawnListener(this) + resourcePackDamageListener = ResourcePackDamageListener() + boatUphillListener = BoatUphillListener(this) + leashListener = LeashListener() + blockHeadListener = BlockHeadListener() + petListener = PetListener(petUtil, provider) + allayInteractListener = AllayInteractListener() + debugStickInteractListener = DebugStickInteractListener() + + server.pluginManager.registerEvents(antiGriefListener, this) + server.pluginManager.registerEvents(loginListener, this) + server.pluginManager.registerEvents(playerHeadDropListener, this) + server.pluginManager.registerEvents(chatListener, this) + server.pluginManager.registerEvents(statusListener, this) + server.pluginManager.registerEvents(banListener, this) + server.pluginManager.registerEvents(anvilListener, this) + server.pluginManager.registerEvents(playtimeListener, this) + server.pluginManager.registerEvents(invisibleFrameListener, this) + server.pluginManager.registerEvents(phantomSpawnListener, this) + server.pluginManager.registerEvents(resourcePackDamageListener, this) + server.pluginManager.registerEvents(boatUphillListener, this) + server.pluginManager.registerEvents(leashListener, this) + server.pluginManager.registerEvents(blockHeadListener, this) + server.pluginManager.registerEvents(petListener, this) + server.pluginManager.registerEvents(allayInteractListener, this) + server.pluginManager.registerEvents(debugStickInteractListener, this) + + logger.info("Starting threads...") + autoRestart = AutoRestart(this) + premiumChecker = PremiumChecker(this, provider) + playerList = PlayerList(this) + server.globalRegionScheduler.runAtFixedRate(this, { playtimeListener.run() }, 1L, 20L) + server.globalRegionScheduler.runAtFixedRate(this, { autoRestart.run() }, 1L, 20L) + server.globalRegionScheduler.runAtFixedRate(this, { playerList.run() }, 1L, 20L) + server.globalRegionScheduler.runAtFixedRate(this, { statusListener.run() }, 1L, 20L) + server.globalRegionScheduler.runAtFixedRate(this, { premiumChecker.run() }, 1L, 20L) + server.globalRegionScheduler.runAtFixedRate(this, { checkConfigChanges() }, 20L * 5, 20L * 5) + } + + override fun onDisable() { + super.onDisable() + CommandAPI.onDisable() + petUtil.removeAllPets() + server.globalRegionScheduler.cancelTasks(this) + } + + private fun connectDB() { + when(val uri: String? = config.getString("database.url")) { + null -> { + logger.warning("URL database is empty!") + server.pluginManager.disablePlugin(this) + } + "jdbc:sqlite:database.db" -> { + Database.connect(SQLiteDataSource().apply { + url = "jdbc:sqlite:database.db" + }) + } + else -> { + val driver = when { + uri.contains("jdbc:mysql") -> "com.mysql.cj.jdbc.Driver" + else -> { "org.mariadb.jdbc.Driver" } + } + + try { + Database.connect( + "$uri?createDatabaseIfNotExist=true", + driver = driver, + user = config.getString("database.user").toString(), + password = config.getString("database.password").toString(), + setupConnection = { connection -> + connection.autoCommit = false + } + ) + logger.info("Database connected!") + } catch (exception: Exception) { + logger.warning("Database cant connected: $exception") + server.pluginManager.disablePlugin(this) + } + } + } + + transaction { + SchemaUtils.create( + Users, + Bans, + Warns, + Mutes + ) + } + } + + private fun checkConfigChanges() { + val currentModified = configFile.lastModified() + + if (currentModified > lastModified) { + server.globalRegionScheduler.run(this) { _ -> + reloadConfig() + logger.info("Configuration has been reloaded!") + } + lastModified = currentModified + } + } + + fun luckPerms(): LuckPerms { + return provider + } + + fun protocolLib(): ProtocolManager { + return protocolManager + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/chat/ChatManager.kt b/paper/src/main/kotlin/com/otfhee/paper/chat/ChatManager.kt new file mode 100644 index 0000000..1291513 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/chat/ChatManager.kt @@ -0,0 +1,61 @@ +package com.otfhee.paper.chat + +import com.otfhee.paper.Plugin +import com.otfhee.paper.chat.processors.* +import com.otfhee.paper.chat.renders.GlobalChatRenderer +import com.otfhee.paper.chat.renders.LocalChatRenderer +import com.otfhee.paper.utils.MuteUtil +import net.kyori.adventure.audience.Audience +import net.luckperms.api.LuckPerms +import org.bukkit.entity.Player +import java.util.concurrent.CopyOnWriteArrayList + +class ChatManager( + private val plugin: Plugin, + luckPerms: LuckPerms +) { + private val messageProcessors = CopyOnWriteArrayList() + private val muteUtil = MuteUtil(plugin) + + init { + registerProcessor(URLProcessor(plugin)) + registerProcessor(CoordinatesProcessor()) + registerProcessor(MentionProcessor()) + registerProcessor(FormattingProcessor(luckPerms)) + registerProcessor(DiceProcessor()) + } + + fun registerProcessor(processor: MessageProcessor) { + messageProcessors.add(processor) + } + + fun unregisterProcessor(processor: MessageProcessor) { + messageProcessors.remove(processor) + } + + fun processMessage(message: String, player: Player, viewer: Audience? = null): String { + var processedMessage = message + + for (processor in messageProcessors) { + processedMessage = processor.process(processedMessage, player, viewer) + } + + return processedMessage + } + + fun getGlobalChatRenderer(): GlobalChatRenderer { + return GlobalChatRenderer(plugin, this) + } + + fun getLocalChatRenderer(): LocalChatRenderer { + return LocalChatRenderer(plugin, this) + } + + fun isMuted(player: Player): Boolean { + return muteUtil.isMute(player) + } + + fun sendMuteMessage(player: Player) { + muteUtil.muteMessage(player) + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/chat/processors/CoordinatesProcessor.kt b/paper/src/main/kotlin/com/otfhee/paper/chat/processors/CoordinatesProcessor.kt new file mode 100644 index 0000000..00f38d2 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/chat/processors/CoordinatesProcessor.kt @@ -0,0 +1,18 @@ +package com.otfhee.paper.chat.processors + +import net.kyori.adventure.audience.Audience +import org.bukkit.entity.Player + +class CoordinatesProcessor : MessageProcessor { + override fun process(message: String, player: Player, viewer: Audience?): String { + var processedMessage = message + val coordinates = "${"%.1f".format(player.x)} ${"%.1f".format(player.y)} ${"%.1f".format(player.z)}" + + val coordVariations = listOf(":coords:", ":coord:", ":cord:", ":cords:", ":coordinate:", ":coordinates:") + for (variation in coordVariations) { + processedMessage = processedMessage.replace(variation, coordinates) + } + + return processedMessage + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/chat/processors/DiceProcessor.kt b/paper/src/main/kotlin/com/otfhee/paper/chat/processors/DiceProcessor.kt new file mode 100644 index 0000000..a4a24f4 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/chat/processors/DiceProcessor.kt @@ -0,0 +1,16 @@ +package com.otfhee.paper.chat.processors + +import net.kyori.adventure.audience.Audience +import org.bukkit.entity.Player + +class DiceProcessor : MessageProcessor { + override fun process(message: String, player: Player, viewer: Audience?): String { + var processedMessage = message + + val dices = listOf("⚅", "⚄", "⚃", "⚂", "⚁", "⚀") + + processedMessage = processedMessage.replace(Regex(":dice:")) { dices.random() } + + return processedMessage + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/chat/processors/FormattingProcessor.kt b/paper/src/main/kotlin/com/otfhee/paper/chat/processors/FormattingProcessor.kt new file mode 100644 index 0000000..bb989ca --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/chat/processors/FormattingProcessor.kt @@ -0,0 +1,15 @@ +package com.otfhee.paper.chat.processors + +import net.kyori.adventure.audience.Audience +import net.luckperms.api.LuckPerms +import org.bukkit.entity.Player + +class FormattingProcessor(private val luckPerms: LuckPerms) : MessageProcessor { + override fun process(message: String, player: Player, viewer: Audience?): String { + val lp = luckPerms.userManager.loadUser(player.uniqueId) + + val decoration = lp.get().cachedData.metaData.getMetaValue("text.decoration") ?: "" + val color = lp.get().cachedData.metaData.getMetaValue("text.color") ?: "" + return "$decoration$color$message" + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/chat/processors/MentionProcessor.kt b/paper/src/main/kotlin/com/otfhee/paper/chat/processors/MentionProcessor.kt new file mode 100644 index 0000000..4d5298b --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/chat/processors/MentionProcessor.kt @@ -0,0 +1,54 @@ +package com.otfhee.paper.chat.processors + +import com.otfhee.paper.database.user.User +import com.otfhee.paper.database.user.UserStatus +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.key.Key +import net.kyori.adventure.sound.Sound +import org.bukkit.entity.Player +import org.jetbrains.exposed.sql.transactions.transaction + +class MentionProcessor : MessageProcessor { + private val mentionPattern = Regex("@(\\w+)") + + override fun process(message: String, player: Player, viewer: Audience?): String { + var processedMessage = message + val mentionedPlayers = mutableListOf() + + val matches = mentionPattern.findAll(message).toList() + + for (match in matches) { + val mentionedName = match.groupValues[1] + val mentionedPlayer = player.server.getPlayerExact(mentionedName) + + if (mentionedPlayer != null && mentionedPlayer.isOnline) { + mentionedPlayers.add(mentionedPlayer) + + val formattedMention = if (viewer is Player && viewer.name.equals(mentionedName, ignoreCase = true)) { + "@$mentionedName" + } else { + "@$mentionedName" + } + + processedMessage = processedMessage.replace(match.value, formattedMention) + } + } + + for (mentionedPlayer in mentionedPlayers) { + val user = transaction { + User[mentionedPlayer.uniqueId] + } + + if (user.status != UserStatus.DO_NOT_DISTURB.name) { + mentionedPlayer.playSound( + Sound.sound( + Key.key("minecraft:entity.experience_orb.pickup"), + Sound.Source.MASTER, 1f, 1f + ) + ) + } + } + + return processedMessage + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/chat/processors/MessageProcessor.kt b/paper/src/main/kotlin/com/otfhee/paper/chat/processors/MessageProcessor.kt new file mode 100644 index 0000000..85d5a43 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/chat/processors/MessageProcessor.kt @@ -0,0 +1,8 @@ +package com.otfhee.paper.chat.processors + +import net.kyori.adventure.audience.Audience +import org.bukkit.entity.Player + +interface MessageProcessor { + fun process(message: String, player: Player, viewer: Audience? = null): String +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/chat/processors/URLProcessor.kt b/paper/src/main/kotlin/com/otfhee/paper/chat/processors/URLProcessor.kt new file mode 100644 index 0000000..ddad806 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/chat/processors/URLProcessor.kt @@ -0,0 +1,49 @@ +package com.otfhee.paper.chat.processors + +import com.otfhee.paper.Plugin +import com.otfhee.paper.utils.LanguageUtil +import net.kyori.adventure.audience.Audience +import org.bukkit.entity.Player + +class URLProcessor(plugin: Plugin) : MessageProcessor { + private val urlPattern = java.util.regex.Pattern.compile( + "((https?|ftp)://|(www|ftp)\\.)?[a-z0-9-]+(\\.[a-z0-9-]+)+([/?][^\\s<>\"']*)?", + java.util.regex.Pattern.CASE_INSENSITIVE + ) + + private val languageUtil = LanguageUtil(plugin) + + override fun process(message: String, player: Player, viewer: Audience?): String { + val matcher = urlPattern.matcher(message) + val builder = StringBuilder() + var lastEnd = 0 + + while (matcher.find()) { + val url = matcher.group() + + if (url.matches(Regex("^\\d+\\.\\d+(\\.\\d+)?$"))) { + builder.append(message.substring(lastEnd, matcher.end())) + lastEnd = matcher.end() + continue + } + + builder.append(message.substring(lastEnd, matcher.start())) + + val displayUrl = if (url.length > 40) { + val halfLength = 20 + "${url.take(halfLength)}...${url.takeLast(halfLength)}" + } else { + url + } + + builder.append("${languageUtil.getMessage(viewer ?: player, "chat.link")}\n$url'>$displayUrl") + lastEnd = matcher.end() + } + + if (lastEnd < message.length) { + builder.append(message.substring(lastEnd)) + } + + return if (builder.isEmpty()) message else builder.toString() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/chat/renders/GlobalChatRenderer.kt b/paper/src/main/kotlin/com/otfhee/paper/chat/renders/GlobalChatRenderer.kt new file mode 100644 index 0000000..8b2038f --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/chat/renders/GlobalChatRenderer.kt @@ -0,0 +1,33 @@ +package com.otfhee.paper.chat.renders + +import com.otfhee.paper.Plugin +import com.otfhee.paper.chat.ChatManager +import com.otfhee.paper.messenger.PastelMessage +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.text.Component +import org.bukkit.entity.Player + +class GlobalChatRenderer( + plugin: Plugin, + chatManager: ChatManager +) : MainChatRenderer(plugin, chatManager) { + + override fun getChatFormat(): String { + return plugin.config.getString("chat.style.global-mode") ?: "[G] : " + } + + override fun render(source: Player, sourceDisplayName: Component, message: Component, viewer: Audience): Component { + var messageText = miniMessage.serialize(message) + if (messageText.startsWith("!")) { + messageText = messageText.substring(1) + } + + if (messageText.startsWith(" ")) { + messageText = messageText.substring(1) + } + + return super.render(source, sourceDisplayName, + PastelMessage.survivalOf(messageText), + viewer) + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/chat/renders/LocalChatRenderer.kt b/paper/src/main/kotlin/com/otfhee/paper/chat/renders/LocalChatRenderer.kt new file mode 100644 index 0000000..094d56a --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/chat/renders/LocalChatRenderer.kt @@ -0,0 +1,18 @@ +package com.otfhee.paper.chat.renders + +import com.otfhee.paper.Plugin +import com.otfhee.paper.chat.ChatManager +import com.otfhee.paper.messenger.PastelMessage +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.text.Component +import org.bukkit.entity.Player + +class LocalChatRenderer( + plugin: Plugin, + chatManager: ChatManager, +) : MainChatRenderer(plugin, chatManager) { + + override fun getChatFormat(): String { + return plugin.config.getString("chat.style.local-mode") ?: ": " + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/chat/renders/MainChatRenderer.kt b/paper/src/main/kotlin/com/otfhee/paper/chat/renders/MainChatRenderer.kt new file mode 100644 index 0000000..98f25b7 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/chat/renders/MainChatRenderer.kt @@ -0,0 +1,39 @@ +package com.otfhee.paper.chat.renders + +import com.otfhee.paper.Plugin +import com.otfhee.paper.chat.ChatManager +import com.otfhee.paper.display.DisplayName +import com.otfhee.paper.messenger.PastelMessage +import io.papermc.paper.chat.ChatRenderer +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.TextReplacementConfig +import net.kyori.adventure.text.format.TextColor +import net.kyori.adventure.text.minimessage.MiniMessage +import org.bukkit.entity.Player + +abstract class MainChatRenderer( + protected val plugin: Plugin, + protected val chatManager: ChatManager +) : ChatRenderer { + protected val miniMessage = MiniMessage.miniMessage() + private val displayNameProvider = DisplayName(plugin) + + protected abstract fun getChatFormat(): String + + override fun render(source: Player, sourceDisplayName: Component, message: Component, viewer: Audience): Component { + val displayName = if (viewer is Player) + displayNameProvider.get(source, viewer) + else + displayNameProvider.get(source) + + return miniMessage.deserialize(getChatFormat()) + .replaceText(TextReplacementConfig.builder() + .match("").replacement(miniMessage.deserialize(displayName)) + .build()) + .replaceText(TextReplacementConfig.builder() + .match("").replacement(PastelMessage.survivalOf( + chatManager.processMessage(PastelMessage.deOf(message), source, viewer))) + .build()) + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/action/CoinCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/action/CoinCommand.kt new file mode 100644 index 0000000..634735e --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/action/CoinCommand.kt @@ -0,0 +1,36 @@ +package com.otfhee.paper.commands.action + +import com.otfhee.paper.Plugin +import com.otfhee.paper.display.DisplayName +import com.otfhee.paper.messenger.Messenger +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.executors.PlayerCommandExecutor +import kotlin.random.Random + +class CoinCommand(private val plugin: Plugin) { + init { + CommandAPICommand("coin") + .withPermission("otfhee.commands.default.coin") + .executesPlayer(PlayerCommandExecutor { sender, _ -> + val messenger = Messenger(plugin) + val displayName = DisplayName(plugin) + val languageUtil = LanguageUtil(plugin) + + val heads = { + plugin.server.onlinePlayers.forEach { player -> + messenger.info("${displayName.get(sender)} ${languageUtil.getMessage(player, "commands.coin.heads")}", player) + } + } + val tails = { + plugin.server.onlinePlayers.forEach { player -> + messenger.info("${displayName.get(sender)} ${languageUtil.getMessage(player, "commands.coin.tails")}", player) + } + } + val coins = arrayOf(heads, tails) + val randomIndex = Random.nextInt(coins.size) + coins[randomIndex].invoke() + }) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/action/DoCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/action/DoCommand.kt new file mode 100644 index 0000000..9dc37cb --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/action/DoCommand.kt @@ -0,0 +1,24 @@ +package com.otfhee.paper.commands.action + +import com.otfhee.paper.Plugin +import com.otfhee.paper.display.DisplayName +import com.otfhee.paper.messenger.Messenger +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.GreedyStringArgument +import dev.jorel.commandapi.executors.PlayerCommandExecutor + +class DoCommand(plugin: Plugin) { + init { + CommandAPICommand("do") + .withArguments(GreedyStringArgument("action")) + .withPermission("otfhee.commands.default.do") + .executesPlayer(PlayerCommandExecutor { sender, args -> + val action = args.get("action") as String + val messenger = Messenger(plugin) + val displayName = DisplayName(plugin) + + messenger.info("*${displayName.get(sender)} $action *") + }) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/action/MeCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/action/MeCommand.kt new file mode 100644 index 0000000..09bcbea --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/action/MeCommand.kt @@ -0,0 +1,26 @@ +package com.otfhee.paper.commands.action + +import com.otfhee.paper.Plugin +import com.otfhee.paper.display.DisplayName +import com.otfhee.paper.messenger.Messenger +import dev.jorel.commandapi.CommandAPI +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.GreedyStringArgument +import dev.jorel.commandapi.executors.PlayerCommandExecutor + +class MeCommand(plugin: Plugin) { + init { + CommandAPI.unregister("me") + CommandAPICommand("me") + .withArguments(GreedyStringArgument("action")) + .withPermission("otfhee.commands.default.me") + .executesPlayer(PlayerCommandExecutor { sender, args -> + val action = args.get("action") as String + val messenger = Messenger(plugin) + val displayName = DisplayName(plugin) + + messenger.info("${displayName.get(sender)} $action *") + }) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/action/TryCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/action/TryCommand.kt new file mode 100644 index 0000000..ddbded0 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/action/TryCommand.kt @@ -0,0 +1,47 @@ +package com.otfhee.paper.commands.action + +import com.otfhee.paper.Plugin +import com.otfhee.paper.display.DisplayName +import com.otfhee.paper.messenger.Messenger +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.GreedyStringArgument +import dev.jorel.commandapi.executors.PlayerCommandExecutor +import kotlin.random.Random + +class TryCommand(private val plugin: Plugin) { + init { + CommandAPICommand("try") + .withPermission("otfhee.commands.default.try") + .withArguments(GreedyStringArgument("action")) + .executesPlayer(PlayerCommandExecutor { sender, args -> + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + val displayName = DisplayName(plugin) + val action = args.get("action") as String + + val success = { + plugin.server.onlinePlayers.forEach { player -> + messenger.info( + "${displayName.get(sender)} ${ + languageUtil.getMessage(player, "commands.try.success") + .replace("", action) + }", player) + } + } + val fail = { + plugin.server.onlinePlayers.forEach { player -> + messenger.info( + "${displayName.get(sender)} ${ + languageUtil.getMessage(player, "commands.try.fail") + .replace("", action) + }", player) + } + } + val arr = arrayOf(success, fail) + val randomIndex = Random.nextInt(arr.size) + arr[randomIndex].invoke() + }) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/chat/GlobalModeCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/chat/GlobalModeCommand.kt new file mode 100644 index 0000000..f349c4b --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/chat/GlobalModeCommand.kt @@ -0,0 +1,31 @@ +package com.otfhee.paper.commands.chat + +import com.otfhee.paper.Plugin +import com.otfhee.paper.database.user.User +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.executors.PlayerCommandExecutor +import org.jetbrains.exposed.sql.transactions.transaction + +class GlobalModeCommand(private val plugin: Plugin) { + init { + CommandAPICommand("global") + .withAliases("g") + .withPermission("otfhee.commands.default.global") + .executesPlayer(PlayerCommandExecutor { sender, _ -> + val languageUtil = LanguageUtil(plugin) + + transaction { + val user = User[sender.uniqueId] + if (user.globalMode) { + user.globalMode = false + languageUtil.success("commands.global-mode.false", sender) + } else { + user.globalMode = true + languageUtil.success("commands.global-mode.true", sender) + } + } + }) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/chat/PrivateMessagesCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/chat/PrivateMessagesCommand.kt new file mode 100644 index 0000000..506d4d3 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/chat/PrivateMessagesCommand.kt @@ -0,0 +1,176 @@ +package com.otfhee.paper.commands.chat + +import com.otfhee.paper.Plugin +import com.otfhee.paper.chat.ChatManager +import com.otfhee.paper.database.user.User +import com.otfhee.paper.database.user.UserStatus +import com.otfhee.paper.display.DisplayName +import com.otfhee.paper.messenger.PastelMessage +import com.otfhee.paper.messenger.PastelMiniMessage +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPI +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.GreedyStringArgument +import dev.jorel.commandapi.arguments.PlayerArgument +import dev.jorel.commandapi.executors.PlayerCommandExecutor +import net.kyori.adventure.key.Key +import net.kyori.adventure.sound.Sound +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.TextReplacementConfig +import net.kyori.adventure.text.minimessage.MiniMessage +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer +import org.bukkit.Bukkit +import org.bukkit.entity.Player +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.* +import kotlin.collections.HashMap + +class PrivateMessagesCommand( + private val plugin: Plugin, + private val chatManager: ChatManager +) { + private val lastMessengers = HashMap() + private val miniMessage = MiniMessage.miniMessage() + private val languageUtil = LanguageUtil(plugin) + + init { + CommandAPI.unregister("msg") + CommandAPI.unregister("tell") + CommandAPI.unregister("w") + + CommandAPICommand("message") + .withPermission("otfhee.commands.default.pm") + .withAliases("tell", "msg", "w") + .withArguments(PlayerArgument("player")) + .withArguments(GreedyStringArgument("message")) + .executesPlayer(PlayerCommandExecutor{ player, args -> + val recipient = args.get("player") as Player + val message = args.get("message") as String + + sendPrivateMessage(player, recipient, miniMessage.deserialize(message)) + lastMessengers[recipient.uniqueId] = player.uniqueId + }) + .register() + + CommandAPICommand("reply") + .withAliases("r") + .withPermission("otfhee.commands.default.reply") + .withArguments(GreedyStringArgument("message")) + .executesPlayer(PlayerCommandExecutor{ player, args -> + val message = args.get("message") as String + val lastMessengerId = lastMessengers[player.uniqueId] + + if (lastMessengerId == null) { + languageUtil.error("commands.private-messages.reply-error", player) + return@PlayerCommandExecutor + } + + val recipient = Bukkit.getPlayer(lastMessengerId) + if (recipient == null || !recipient.isOnline) { + languageUtil.error("util.player-not-online", player) + return@PlayerCommandExecutor + } + + sendPrivateMessage(player, recipient, miniMessage.deserialize(message)) + lastMessengers[recipient.uniqueId] = player.uniqueId + }) + .register() + } + + private fun sendPrivateMessage(sender: Player, recipient: Player, message: Component) { + val inFormat = plugin.config.getString("chat.style.private-format.in") + ?: ": " + val outFormat = plugin.config.getString("chat.style.private-format.out") + ?: ": " + + val pastelMiniMessage = PastelMiniMessage.builder() + .tags( + TagResolver.builder() + .resolvers( + StandardTags.color(), + StandardTags.gradient(), + StandardTags.clickEvent(), + StandardTags.hoverEvent(), + StandardTags.decorations(), + StandardTags.reset(), + StandardTags.rainbow()) + .build() + ) + .pastelFactor(0.4) + .build() + val messageText = pastelMiniMessage.serialize(message) + + val processedMessage = chatManager.processMessage(messageText, sender) + + val miniMessage = MiniMessage.miniMessage() + val displayName = DisplayName(plugin) + val displaySender = displayName.get(sender) + val displayRecipient = displayName.get(recipient) + + val inMessage = miniMessage.deserialize(inFormat) + .replaceText( + TextReplacementConfig.builder() + .match("").replacement(miniMessage.deserialize(displaySender)) + .build()) + .replaceText( + TextReplacementConfig.builder() + .match("").replacement(miniMessage.deserialize(displayRecipient)) + .build()) + .replaceText( + TextReplacementConfig.builder() + .match("").replacement( + if (sender.hasPermission("otfhee.premium.chat.formatting")) { + PastelMessage.survivalOf(processedMessage) + } else { + Component.text(processedMessage) + } + ) + .build()) + + val outMessage = miniMessage.deserialize(outFormat) + .replaceText( + TextReplacementConfig.builder() + .match("").replacement(miniMessage.deserialize(displaySender)) + .build()) + .replaceText( + TextReplacementConfig.builder() + .match("").replacement(miniMessage.deserialize(displayRecipient)) + .build()) + .replaceText( + TextReplacementConfig.builder() + .match("").replacement( + if (sender.hasPermission("otfhee.premium.chat.formatting")) { + PastelMessage.survivalOf(processedMessage) + } else { + Component.text(processedMessage) + } + ) + .build()) + + val dbSender = + transaction { + User[sender.uniqueId] + } + val dbRecipient = + transaction { + User[recipient.uniqueId] + } + + if (dbSender.status != UserStatus.DO_NOT_DISTURB.name) { + sender.playSound(Sound.sound(Key.key("minecraft:entity.experience_orb.pickup"), + net.kyori.adventure.sound.Sound.Source.MASTER, 1f, 1f)) + } + sender.sendMessage(inMessage) + if (dbRecipient.status != UserStatus.DO_NOT_DISTURB.name) { + recipient.playSound(Sound.sound(Key.key("minecraft:entity.player.levelup"), + net.kyori.adventure.sound.Sound.Source.MASTER, 1f, 1f)) + } + recipient.sendMessage(outMessage) + } + + fun cleanupPlayerData(player: Player) { + lastMessengers.remove(player.uniqueId) + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayColor.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayColor.kt new file mode 100644 index 0000000..2b31520 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayColor.kt @@ -0,0 +1,73 @@ +package com.otfhee.paper.commands.display + +import com.otfhee.paper.Plugin +import com.otfhee.paper.database.user.User +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.GreedyStringArgument +import dev.jorel.commandapi.executors.PlayerCommandExecutor +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.minimessage.MiniMessage +import org.jetbrains.exposed.sql.transactions.transaction + +class DisplayColor(private val plugin: Plugin) { + private val namedColors = NamedTextColor.NAMES.keys().joinToString("|") + private val hexColor = "#[0-9A-Fa-f]{6}" + private val anyColor = "$hexColor|$namedColors" + + fun register(): CommandAPICommand { + return CommandAPICommand("color") + .withPermission("otfhee.commands.premium.display.color") + .withArguments(GreedyStringArgument("color")) + .executesPlayer(PlayerCommandExecutor { player, args -> + val rawInput = args.get("color") as String + val languageUtil = LanguageUtil(plugin) + + if (rawInput.length >= 255) { + languageUtil.error("commands.display.color.limit", player) + return@PlayerCommandExecutor + } + + val fullTag = if (!isValidFormat(rawInput)) { + languageUtil.error("commands.display.color.format", player) + return@PlayerCommandExecutor + } else { + "<${rawInput}>" + } + + try { + MiniMessage.miniMessage().deserialize(fullTag) + } catch (e: Exception) { + languageUtil.error("commands.display.color.format", player) + return@PlayerCommandExecutor + } + + try { + transaction { + val user = User[player.uniqueId] + user.displayColor = fullTag + } + languageUtil.success("commands.display.color.success", player) + } catch (e: Exception) { + languageUtil.error("commands.display.color.error", player) + e.printStackTrace() + } + }) + } + + private fun isValidFormat(input: String): Boolean { + if (input.startsWith("color:")) { + val content = input.substringAfter("color:") + return content.matches(Regex(hexColor)) || content.matches(Regex(namedColors)) + } + + if (input.startsWith("gradient:")) { + val content = input.substringAfter("gradient:") + val colors = content.split(":") + return colors.size >= 2 && + colors.all { it.matches(Regex(anyColor)) } + } + + return false + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayCommand.kt new file mode 100644 index 0000000..d2242eb --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayCommand.kt @@ -0,0 +1,19 @@ +package com.otfhee.paper.commands.display + +import com.otfhee.paper.Plugin +import com.otfhee.paper.utils.PetUtil +import dev.jorel.commandapi.CommandAPICommand +import net.luckperms.api.LuckPerms + +class DisplayCommand(plugin: Plugin, luckPerms: LuckPerms, petUtil: PetUtil) { + init { + CommandAPICommand("display") + .withSubcommands( + DisplayColor(plugin).register(), + DisplayPrefix(plugin, luckPerms).register(), + DisplayPet(plugin, luckPerms, petUtil).register(), + DisplayText(plugin, luckPerms).register() + ) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayPet.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayPet.kt new file mode 100644 index 0000000..f494482 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayPet.kt @@ -0,0 +1,164 @@ +package com.otfhee.paper.commands.display + +import com.otfhee.paper.Plugin +import com.otfhee.paper.messenger.Messenger +import com.otfhee.paper.utils.LanguageUtil +import com.otfhee.paper.utils.PetUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.* +import dev.jorel.commandapi.executors.PlayerCommandExecutor +import io.papermc.paper.math.Position +import net.luckperms.api.LuckPerms +import net.luckperms.api.model.user.User +import org.bukkit.entity.Player +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.UUID + +class DisplayPet( + private val plugin: Plugin, + private val luckPerms: LuckPerms, + private val petUtil: PetUtil +) { + fun register(): CommandAPICommand { + return CommandAPICommand("pet") + .withSubcommand(CommandAPICommand("set") + .withArguments(petArgument()) + .executesPlayer(PlayerCommandExecutor { player, args -> + val pet = args.get("pet") as String + val user = luckPerms.userManager.loadUser(player.uniqueId) + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + + plugin.server.regionScheduler.execute(plugin, player.location) { + petUtil.removePet(player) + } + + if (!isValidPet(user.get(), pet)) { + languageUtil.error("commands.display.pet.set.permission", player) + return@PlayerCommandExecutor + } + + transaction { + val db = com.otfhee.paper.database.user.User[player.uniqueId] + + db.pet = pet.uppercase() + } + + plugin.server.regionScheduler.execute(plugin, player.location) { + PetUtil.PetType.fromString(pet.uppercase())?.let { + petUtil.builder( + player, + it + ) + .shoulderPosition( + PetUtil.ShoulderPosition.valueOf( + user.get().cachedData.metaData.getMetaValue("position")?.uppercase() ?: "RIGHT" + ) + ) + .build { pet, exception -> + messenger.success( + languageUtil.getMessage(player, "commands.display.pet.set.success") + .replace("", pet?.type?.name!!), player + ) + if (exception != null) plugin.logger.warning("$exception") + } + } + } + })) + .withSubcommand(CommandAPICommand("hide") + .executesPlayer(PlayerCommandExecutor { player, _ -> + val languageUtil = LanguageUtil(plugin) + + plugin.server.regionScheduler.execute(plugin, player.location) { + petUtil.removePet(player) + } + transaction { + val db = com.otfhee.paper.database.user.User[player.uniqueId] + + db.pet = null + } + + languageUtil.success("commands.pet.hide", player) + })) + .withSubcommand(CommandAPICommand("position") + .withArguments(StringArgument("position") + .replaceSuggestions(ArgumentSuggestions.strings("left", "right", "above")) + ) + .executesPlayer(PlayerCommandExecutor { player, args -> + val languageUtil = LanguageUtil(plugin) + val user = luckPerms.userManager.loadUser(player.uniqueId) + val position = args.get("position") as String + val db = transaction { + com.otfhee.paper.database.user.User[player.uniqueId] + } + + when (position.lowercase()) { + "left" -> PetUtil.ShoulderPosition.LEFT + "right" -> PetUtil.ShoulderPosition.RIGHT + "above" -> PetUtil.ShoulderPosition.ABOVE + else -> { + languageUtil.error("commands.display.pet.position.error", player) + return@PlayerCommandExecutor + } + } + + val meta = luckPerms.nodeBuilderRegistry + .forMeta() + .key("position") + .value(position) + .build() + + user.get().data().clear { node -> + node.key.startsWith("meta.position.") + } + + user.get().data().add(meta) + + luckPerms.userManager.saveUser(user.get()) + + plugin.server.regionScheduler.execute(plugin, player.location) { + petUtil.removePet(player) + val pet = db.pet + if (pet != null) { + PetUtil.PetType.fromString(pet)?.let { + petUtil.builder(player, it) + .shoulderPosition(PetUtil.ShoulderPosition.valueOf(position.uppercase())) + .build { _, exception -> + languageUtil.success("commands.display.pet.position.success", player) + if (exception != null) plugin.logger.warning("$exception") + } + } + } else { + languageUtil.success("commands.display.pet.position.success", player) + return@execute + } + } + })) + } + + private fun petArgument(): Argument? { + return GreedyStringArgument("pet") + .replaceSuggestions(ArgumentSuggestions.strings { info -> + val player = info.sender as? Player ?: return@strings emptyArray() + getAvailablePets(player.uniqueId).toTypedArray() + }) + } + + private fun isValidPet(user: User, pet: String): Boolean { + return getAvailablePets(user.uniqueId).contains(pet) + } + + private fun getAvailablePets(uuid: UUID): List { + val user = luckPerms.userManager.loadUser(uuid) + + return user.get().cachedData.permissionData.permissionMap + .entries + .filter { (key, value) -> + key.startsWith("pet.") && value + } + .map { (key, _) -> + key.substring(4) + } + .distinct() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayPrefix.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayPrefix.kt new file mode 100644 index 0000000..9d7abf4 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayPrefix.kt @@ -0,0 +1,99 @@ +package com.otfhee.paper.commands.display + +import com.otfhee.paper.Plugin +import com.otfhee.paper.messenger.Messenger +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.Argument +import dev.jorel.commandapi.arguments.ArgumentSuggestions +import dev.jorel.commandapi.arguments.GreedyStringArgument +import dev.jorel.commandapi.executors.PlayerCommandExecutor +import net.luckperms.api.LuckPerms +import net.luckperms.api.model.user.User +import net.luckperms.api.node.types.PrefixNode +import org.bukkit.entity.Player +import java.util.* + +class DisplayPrefix(private val plugin: Plugin, private val luckPerms: LuckPerms) { + + fun register(): CommandAPICommand { + return CommandAPICommand("prefix") + .withSubcommand(CommandAPICommand("set") + .withArguments(prefixArgument()) + .executesPlayer(PlayerCommandExecutor { player, args -> + val prefix = args.get("prefix") as String + val user = luckPerms.userManager.loadUser(player.uniqueId) + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + + if (!isValidPrefix(user.get(), prefix)) { + languageUtil.error("commands.display.prefix.set.permission", player) + return@PlayerCommandExecutor + } + + val metaNode = luckPerms.nodeBuilderRegistry + .forPrefix() + .prefix(prefix) + .priority(100) + .build() + + user.get().data().clear { node -> + node.key.startsWith("prefix.") + } + + user.get().data().add(metaNode) + + luckPerms.userManager.saveUser(user.get()) + + messenger.success(languageUtil.getMessage(player, "commands.display.prefix.set.success") + .replace("", prefix), player) + }) + ) + .withSubcommand(CommandAPICommand("hide") + .executesPlayer(PlayerCommandExecutor { player, _ -> + val user = luckPerms.userManager.loadUser(player.uniqueId) + val languageUtil = LanguageUtil(plugin) + + val metaNode = luckPerms.nodeBuilderRegistry + .forPrefix() + .prefix("") + .priority(100) + .build() + + user.get().data().clear { node -> + node.key.startsWith("prefix.") + } + + user.get().data().add(metaNode) + + luckPerms.userManager.saveUser(user.get()) + + languageUtil.success("commands.display.prefix.hide.success", player) + }) + ) + + } + + private fun prefixArgument(): Argument? { + return GreedyStringArgument("prefix") + .replaceSuggestions(ArgumentSuggestions.strings { info -> + val player = info.sender as? Player ?: return@strings emptyArray() + getAvailablePrefixes(player.uniqueId).toTypedArray() + }) + } + + private fun isValidPrefix(user: User, prefix: String): Boolean { + return getAvailablePrefixes(user.uniqueId).contains(prefix) + } + + private fun getAvailablePrefixes(uuid: UUID): List { + val user = luckPerms.userManager.getUser(uuid) ?: return emptyList() + return user.getInheritedGroups(user.queryOptions) + .flatMap { group -> + group.nodes + .filterIsInstance() + .map { it.metaValue } + } + .distinct() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayText.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayText.kt new file mode 100644 index 0000000..d28fbd5 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/display/DisplayText.kt @@ -0,0 +1,150 @@ +package com.otfhee.paper.commands.display + +import com.otfhee.paper.Plugin +import com.otfhee.paper.database.user.User +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.ArgumentSuggestions +import dev.jorel.commandapi.arguments.GreedyStringArgument +import dev.jorel.commandapi.arguments.StringArgument +import dev.jorel.commandapi.executors.PlayerCommandExecutor +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.minimessage.MiniMessage +import net.luckperms.api.LuckPerms +import org.jetbrains.exposed.sql.transactions.transaction + +class DisplayText( + private val plugin: Plugin, + private val luckPerms: LuckPerms +) { + private val namedColors = NamedTextColor.NAMES.keys().joinToString("|") + private val hexColor = "#[0-9A-Fa-f]{6}" + private val anyColor = "$hexColor|$namedColors" + + fun register(): CommandAPICommand { + return CommandAPICommand("text") + .withPermission("otfhee.commands.premium.display.text.color") + .withSubcommand(CommandAPICommand("color") + .withArguments(GreedyStringArgument("color")) + .executesPlayer(PlayerCommandExecutor { player, args -> + val rawInput = args.get("color") as String + val languageUtil = LanguageUtil(plugin) + + if (rawInput.length >= 255) { + languageUtil.error("commands.display.color.limit", player) + return@PlayerCommandExecutor + } + + val fullTag = if (!isValidFormat(rawInput)) { + languageUtil.error("commands.display.color.format", player) + return@PlayerCommandExecutor + } else { + "<${rawInput}>" + } + + try { + MiniMessage.miniMessage().deserialize(fullTag) + } catch (e: Exception) { + languageUtil.error("commands.display.color.format", player) + return@PlayerCommandExecutor + } + + try { + val user = luckPerms.userManager.loadUser(player.uniqueId) + + val meta = luckPerms.nodeBuilderRegistry + .forMeta() + .key("text.color") + .value(fullTag) + .build() + + user.get().data().clear { node -> + node.key.startsWith("meta.text\\.color.") + } + + user.get().data().add(meta) + + luckPerms.userManager.saveUser(user.get()) + + languageUtil.success("commands.display.color.success", player) + } catch (e: Exception) { + languageUtil.error("commands.display.color.error", player) + e.printStackTrace() + } + }) + ) + .withSubcommand( + CommandAPICommand("decoration") + .withPermission("otfhee.commands.premium.display.text.decoration") + .withArguments( + StringArgument("decoration") + .replaceSuggestions(ArgumentSuggestions.strings("bold", "italic", "underlined", "strikethrough", "obfuscated")) + ) + .executesPlayer(PlayerCommandExecutor { player, args -> + val decoration = args.get("decoration") as String + val languageUtil = LanguageUtil(plugin) + + val final = when(decoration) { + "bold" -> "" + "italic" -> "" + "underlined" -> "" + "strikethrough" -> "" + "obfuscated" -> "" + else -> "" + } + + val user = luckPerms.userManager.loadUser(player.uniqueId) + + val meta = luckPerms.nodeBuilderRegistry + .forMeta() + .key("text.decoration") + .value(final) + .build() + + user.get().data().clear { node -> + node.key.startsWith("meta.text\\.decoration.") + } + + user.get().data().add(meta) + + luckPerms.userManager.saveUser(user.get()) + languageUtil.success("commands.display.text.decoration.success", player) + }) + ) + .withSubcommand(CommandAPICommand("reset") + .withPermission("otfhee.commands.premium.display.text.reset") + .executesPlayer(PlayerCommandExecutor { player, _ -> + val user = luckPerms.userManager.loadUser(player.uniqueId) + val languageUtil = LanguageUtil(plugin) + + user.get().data().clear { node -> + node.key.startsWith("meta.text\\.color.") + } + + user.get().data().clear { node -> + node.key.startsWith("meta.text\\.decoration.") + } + + luckPerms.userManager.saveUser(user.get()) + + languageUtil.success("commands.display.text.reset.success", player) + }) + ) + } + + private fun isValidFormat(input: String): Boolean { + if (input.startsWith("color:")) { + val content = input.substringAfter("color:") + return content.matches(Regex(hexColor)) || content.matches(Regex(namedColors)) + } + + if (input.startsWith("gradient:")) { + val content = input.substringAfter("gradient:") + val colors = content.split(":") + return colors.size >= 2 && + colors.all { it.matches(Regex(anyColor)) } + } + + return false + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/security/BanCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/security/BanCommand.kt new file mode 100644 index 0000000..fdf1226 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/security/BanCommand.kt @@ -0,0 +1,107 @@ +package com.otfhee.paper.commands.security + +import com.otfhee.paper.Plugin +import com.otfhee.paper.utils.BanUtil +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPI +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.ArgumentSuggestions +import dev.jorel.commandapi.arguments.GreedyStringArgument +import dev.jorel.commandapi.arguments.OfflinePlayerArgument +import dev.jorel.commandapi.arguments.StringArgument +import dev.jorel.commandapi.executors.CommandExecutor +import net.kyori.adventure.text.Component +import org.bukkit.Bukkit +import org.bukkit.OfflinePlayer +import org.bukkit.entity.Player +import java.util.regex.Pattern + +class BanCommand(private val plugin: Plugin) { + private val banUtil = BanUtil(plugin) + private val durationPattern = Pattern.compile("(\\d+)([dhms])") + + init { + CommandAPI.unregister("ban") + CommandAPICommand("ban") + .withPermission("otfhee.commands.staff.ban") + .withArguments(OfflinePlayerArgument("player")) + .withArguments( + StringArgument("duration") + .replaceSuggestions(ArgumentSuggestions.strings("5s", "10m", "12h", "1d", "7d", "30d", "permanent")).setOptional(true) + ) + .withArguments(GreedyStringArgument("reason").setOptional(true)) + .executes(CommandExecutor { sender, args -> + val target = args.get("player") as OfflinePlayer + val durationArg = args.get("duration") as String? ?: "permanent" + val reason = args.get("reason") as String? ?: "Не указана" + val languageUtil = LanguageUtil(plugin) + + if (target.isOnline && target.player?.hasPermission("otfhee.commands.ban.bypass") == true) { + languageUtil.error("commands.ban.cannot-ban", sender) + return@CommandExecutor + } + + val duration = if (durationArg.equals("permanent", ignoreCase = true)) { + null + } else { + parseDuration(durationArg) + } + + val adminUUID = if (sender is Player) sender.uniqueId else Bukkit.getOfflinePlayer("CONSOLE").uniqueId + + val success = banUtil.banPlayer( + playerUUID = target.uniqueId, + reason = reason, + adminUUID = adminUUID, + durationMillis = duration + ) + + if (success) { + val banInfo = banUtil.getBanInfo(target.uniqueId) + if (banInfo != null) { + languageUtil.success("commands.ban.success", sender) + plugin.server.onlinePlayers.forEach { player -> + if (player.hasPermission("otfhee.util.ban.message")) { + banUtil.banMessage(banInfo, player) + } + } + + if (target.isOnline && target.player != null) { + banUtil.kickBannedPlayer(target.player!!, reason, banInfo.expireDate) + } + } else { + languageUtil.success("commands.ban.success-fail", sender) + } + } else { + languageUtil.error("commands.ban.fail", sender) + } + }) + .register() + } + + private fun parseDuration(input: String): Long? { + try { + val matcher = durationPattern.matcher(input) + var totalMillis: Long = 0 + + while (matcher.find()) { + val amount = matcher.group(1).toLong() + val unit = matcher.group(2) + + val millis = when (unit) { + "s" -> amount * 1000 + "m" -> amount * 60 * 1000 + "h" -> amount * 60 * 60 * 1000 + "d" -> amount * 24 * 60 * 60 * 1000 + else -> 0 + } + + totalMillis += millis + } + + return if (totalMillis == 0L) null else totalMillis + } catch (e: Exception) { + return null + } + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/security/MuteCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/security/MuteCommand.kt new file mode 100644 index 0000000..be95714 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/security/MuteCommand.kt @@ -0,0 +1,100 @@ +package com.otfhee.paper.commands.security + +import com.otfhee.paper.Plugin +import com.otfhee.paper.utils.LanguageUtil +import com.otfhee.paper.utils.MuteUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.ArgumentSuggestions +import dev.jorel.commandapi.arguments.OfflinePlayerArgument +import dev.jorel.commandapi.arguments.StringArgument +import dev.jorel.commandapi.executors.CommandExecutor +import net.kyori.adventure.key.Key +import net.kyori.adventure.sound.Sound +import org.bukkit.OfflinePlayer +import org.bukkit.entity.Player +import java.util.regex.Pattern + +class MuteCommand(private val plugin: Plugin) { + private val durationPattern = Pattern.compile("(\\d+)([dhm])") + private val muteUtil = MuteUtil(plugin) + + init { + CommandAPICommand("mute") + .withPermission("otfhee.commands.staff.mute") + .withArguments(OfflinePlayerArgument("player")) + .withArguments( + StringArgument("duration") + .replaceSuggestions(ArgumentSuggestions.strings("1m", "10m", "12h", "1d", "7d", "30d", "permanent")).setOptional(true) + ) + .executes(CommandExecutor { sender, args -> + val player = args.get("player") as OfflinePlayer + val durationArg = args.get("duration") as String? ?: "permanent" + val languageUtil = LanguageUtil(plugin) + + if (player.isOnline && player.player?.hasPermission("otfhee.commands.stuff.mute.bypass") == true) { + languageUtil.error("commands.mute.cannot", sender) + return@CommandExecutor + } + + val duration = parseDurationMinutes(durationArg) + + muteUtil.mutePlayer( + uuid = player.uniqueId, + durationMinutes = duration, + sender = sender + ) + + if (player.isOnline) { + player as Player + for (n in 1..100) { + player.playSound( + Sound.sound( + Key.key("minecraft:entity.enderman.death"), + Sound.Source.MASTER, 1f, 1f)) + } + muteUtil.muteMessage(player) + } + }) + .register() + + CommandAPICommand("unmute") + .withPermission("otfhee.commands.staff.unmute") + .withArguments(OfflinePlayerArgument("player")) + .executes(CommandExecutor { sender, args -> + val player = args.get("player") as OfflinePlayer + val languageUtil = LanguageUtil(plugin) + + if (muteUtil.unmute(player.uniqueId)) { + languageUtil.success("commands.unmute.success", sender) + } else { + languageUtil.error("commands.unmute.error", sender) + } + }) + .register() + } + + private fun parseDurationMinutes(input: String): Int? { + try { + val matcher = durationPattern.matcher(input) + var totalMinutes = 0 + + while (matcher.find()) { + val amount = matcher.group(1).toInt() + val unit = matcher.group(2) + + val minutes = when (unit) { + "m" -> amount + "h" -> amount * 60 + "d" -> amount * 60 * 24 + else -> 0 + } + + totalMinutes += minutes + } + + return if (totalMinutes == 0) null else totalMinutes + } catch (e: Exception) { + return null + } + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/security/OpCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/security/OpCommand.kt new file mode 100644 index 0000000..8330bdc --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/security/OpCommand.kt @@ -0,0 +1,10 @@ +package com.otfhee.paper.commands.security + +import dev.jorel.commandapi.CommandAPI + +class OpCommand { + init { + CommandAPI.unregister("op") + CommandAPI.unregister("deop") + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/security/UnbanCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/security/UnbanCommand.kt new file mode 100644 index 0000000..f8f4bcf --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/security/UnbanCommand.kt @@ -0,0 +1,54 @@ +package com.otfhee.paper.commands.security + +import com.otfhee.paper.Plugin +import com.otfhee.paper.messenger.Messenger +import com.otfhee.paper.utils.BanUtil +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPI +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.OfflinePlayerArgument +import dev.jorel.commandapi.executors.CommandExecutor +import org.bukkit.OfflinePlayer +import org.bukkit.entity.Player + +class UnbanCommand(private val plugin: Plugin) { + private val banUtil = BanUtil(plugin) + + init { + CommandAPI.unregister("pardon") + CommandAPICommand("unban") + .withPermission("otfhee.commands.staff.unban") + .withArguments(OfflinePlayerArgument("player")) + .executes(CommandExecutor { sender, args -> + val target = args.get("player") as OfflinePlayer + val playerName = target.name ?: "Unknown" + val banInfo = banUtil.getBanInfo(target.uniqueId) + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + + if (banInfo == null) { + languageUtil.error("commands.unban.player-not-banned", sender) + return@CommandExecutor + } + + val success = banUtil.unbanPlayer(target.uniqueId) + + if (success) { + val adminName = if (sender is Player) sender.name else "CONSOLE" + languageUtil.success("commands.unban.success", sender) + plugin.server.onlinePlayers.forEach { player -> + if (player.hasPermission("otfhee.util.unban.message")) { + messenger.info( + languageUtil.getMessage(player, "util.unban.message") + .replace("", playerName) + .replace("", adminName), player + ) + } + } + } else { + languageUtil.error("commands.unban.error", sender) + } + }) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/security/WarnCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/security/WarnCommand.kt new file mode 100644 index 0000000..208690d --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/security/WarnCommand.kt @@ -0,0 +1,231 @@ +package com.otfhee.paper.commands.security + +import com.otfhee.paper.Plugin +import com.otfhee.paper.database.ban.Ban +import com.otfhee.paper.messenger.Messenger +import com.otfhee.paper.utils.BanUtil +import com.otfhee.paper.utils.LanguageUtil +import com.otfhee.paper.utils.WarnUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.ArgumentSuggestions +import dev.jorel.commandapi.arguments.GreedyStringArgument +import dev.jorel.commandapi.arguments.OfflinePlayerArgument +import dev.jorel.commandapi.arguments.StringArgument +import dev.jorel.commandapi.executors.CommandExecutor +import net.kyori.adventure.key.Key +import net.kyori.adventure.sound.Sound +import org.bukkit.Bukkit +import org.bukkit.OfflinePlayer +import org.bukkit.entity.Player +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.regex.Pattern + +class WarnCommand(private val plugin: Plugin) { + private val warnUtil = WarnUtil(plugin) + private val durationPattern = Pattern.compile("(\\d+)([dhm])") + + init { + CommandAPICommand("warn") + .withPermission("otfhee.commands.staff.warn") + .withArguments(OfflinePlayerArgument("player")) + .withArguments( + GreedyStringArgument("reason") + .replaceSuggestions(ArgumentSuggestions.strings(plugin.config.getConfigurationSection("warnings.reasons")?.getKeys(true))) + ) + .executes(CommandExecutor { sender, args -> + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + val target = args.get("player") as OfflinePlayer + val reason = args.get("reason") as String + val reasonsSection = plugin.config.getConfigurationSection("warnings.reasons") + val durationArg = if (reasonsSection != null && reasonsSection.contains(reason)) { + plugin.config.getString("warnings.reasons.${reason}") ?: "permanent" + } else if (sender.hasPermission("otfhee.commands.warn.reason")) { + "permanent" + } else { + languageUtil.error("commands.warn.reason-out", sender) + return@CommandExecutor + } + + if (target.isOnline && target.player?.hasPermission("otfhee.commands.warn.bypass") == true) { + languageUtil.error("commands.warn.cannot-warn", sender) + return@CommandExecutor + } + + val durationMinutes = if (durationArg.equals("permanent", ignoreCase = true)) { + null + } else { + parseDurationMinutes(durationArg) + } + + val adminUUID = if (sender is Player) sender.uniqueId else Bukkit.getOfflinePlayer("CONSOLE").uniqueId + + val warnInfo = warnUtil.warnPlayer( + playerUUID = target.uniqueId, + reason = reason, + adminUUID = adminUUID, + durationMinutes = durationMinutes + ) + + if (warnInfo != null) { + plugin.server.onlinePlayers.forEach { player -> + if (player.hasPermission("otfhee.util.warn.message")) { + warnUtil.warnMessage(warnInfo, player) + } + } + + if (target.isOnline && target.player != null) { + target as Player + val warningCount = warnUtil.getActiveWarningsCount(target.uniqueId) + val message = languageUtil.getMessage(target.player!!, "util.warn.received") + .replace("", reason) + .replace("", warningCount.toString()) + .replace("", "2") + + for (n in 1..100) { + target.playSound( + Sound.sound( + Key.key("minecraft:entity.enderman.death"), + Sound.Source.MASTER, 1f, 1f)) + } + + messenger.warn(message, target.player!!) + } + languageUtil.success("commands.warn.success", sender) + } else { + languageUtil.success("commands.warn.third-warning", sender) + } + }) + .register() + + CommandAPICommand("warns") + .withPermission("otfhee.commands.staff.warnings") + .withArguments(OfflinePlayerArgument("player")) + .executes(CommandExecutor { sender, args -> + val target = args.get("player") as OfflinePlayer + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + + val activeWarnings = warnUtil.getActiveWarnings(target.uniqueId) + + if (activeWarnings.isEmpty()) { + languageUtil.info("commands.warnings.none", sender) + return@CommandExecutor + } + + val playerName = target.name ?: target.uniqueId.toString() + messenger.info(languageUtil.getMessage(sender, "commands.warnings.header") + .replace("", playerName) + .replace("", activeWarnings.size.toString()) + .replace("", "2"), sender) + + activeWarnings.forEachIndexed { index, warn -> + val admin = if (Bukkit.getOfflinePlayer("CONSOLE").uniqueId == warn.adminUUID) { + "CONSOLE" + } else { + Bukkit.getOfflinePlayer(warn.adminUUID).name ?: warn.adminUUID.toString() + } + val date = java.time.LocalDateTime.of( + warn.warnDate.year, warn.warnDate.monthNumber, warn.warnDate.dayOfMonth, + warn.warnDate.hour, warn.warnDate.minute, warn.warnDate.second + ).format(java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy")) + + val timeLeftInfo = if (warn.remainingTime != null && warn.remainingTime > 0) { + "истекает после ${warnUtil.formatTimeLeft(warn.remainingTime, sender)} игрового времени" + } else { + "навсегда" + } + + val message = "#${index + 1} (ID: ${warn.id}): ${warn.reason} - От $admin в $date ($timeLeftInfo)" + messenger.info(message, sender) + } + }) + .register() + + CommandAPICommand("unwarn") + .withPermission("otfhee.commands.staff.unwarn") + .withArguments(OfflinePlayerArgument("player")) + .withArguments(StringArgument("warn_id")) + .executes(CommandExecutor { sender, args -> + val target = args.get("player") as OfflinePlayer + val warnIdStr = args.get("warn_id") as String + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + + val warnId = warnIdStr.toIntOrNull() + if (warnId == null) { + languageUtil.error("commands.unwarn.invalid-id", sender) + return@CommandExecutor + } + + val success = warnUtil.removeWarning(warnId) + + if (success) { + languageUtil.success("commands.unwarn.success", sender) + + if (target.isOnline && target.player != null) { + val message = languageUtil.getMessage(target.player!!, "util.unwarn.received") + messenger.info(message, target.player!!) + } + } else { + languageUtil.error("commands.unwarn.not-found", sender) + } + }) + .register() + + CommandAPICommand("unwarns") + .withPermission("otfhee.commands.staff.unwarns") + .withArguments(OfflinePlayerArgument("player")) + .executes(CommandExecutor { sender, args -> + val target = args.get("player") as OfflinePlayer + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + + val count = warnUtil.clearWarnings(target.uniqueId) + + if (count > 0) { + messenger.success(languageUtil.getMessage(sender, "commands.unwarns.success") + .replace("", count.toString())) + + if (target.isOnline && target.player != null) { + val message = languageUtil.getMessage(target.player!!, "util.unwarns.received") + messenger.info(message, target.player!!) + } + + transaction { + if (Ban.isActive(target.uniqueId)) { + BanUtil(plugin).unbanPlayer(target.uniqueId) + } + } + } else { + languageUtil.info("commands.unwarns.none", sender) + } + }) + .register() + } + + private fun parseDurationMinutes(input: String): Int? { + try { + val matcher = durationPattern.matcher(input) + var totalMinutes = 0 + + while (matcher.find()) { + val amount = matcher.group(1).toInt() + val unit = matcher.group(2) + + val minutes = when (unit) { + "m" -> amount + "h" -> amount * 60 + "d" -> amount * 60 * 24 + else -> 0 + } + + totalMinutes += minutes + } + + return if (totalMinutes == 0) null else totalMinutes + } catch (e: Exception) { + return null + } + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/social/DiscordCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/social/DiscordCommand.kt new file mode 100644 index 0000000..20b130e --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/social/DiscordCommand.kt @@ -0,0 +1,25 @@ +package com.otfhee.paper.commands.social + +import com.otfhee.paper.Plugin +import com.otfhee.paper.messenger.Messenger +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.executors.PlayerCommandExecutor + +class DiscordCommand(private val plugin: Plugin) { + init { + CommandAPICommand("discord") + .withAliases("ds") + .withPermission("otfhee.commands.default.discord") + .executesPlayer(PlayerCommandExecutor { sender, _ -> + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + + messenger.info( + languageUtil.getMessage(sender, "commands.discord") + .replace("", plugin.config.getString("links.discord") ?: "https://otfhee.com/discord"), sender + ) + }) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/social/RulesCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/social/RulesCommand.kt new file mode 100644 index 0000000..b77d578 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/social/RulesCommand.kt @@ -0,0 +1,24 @@ +package com.otfhee.paper.commands.social + +import com.otfhee.paper.Plugin +import com.otfhee.paper.messenger.Messenger +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.executors.PlayerCommandExecutor + +class RulesCommand(private val plugin: Plugin) { + init { + CommandAPICommand("rules") + .withPermission("otfhee.commands.default.rules") + .executesPlayer(PlayerCommandExecutor { sender, _ -> + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + + messenger.info( + languageUtil.getMessage(sender, "commands.rules") + .replace("", plugin.config.getString("links.rules") ?: "https://otfhee.com/rules"), sender + ) + }) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/social/SiteCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/social/SiteCommand.kt new file mode 100644 index 0000000..128aa4a --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/social/SiteCommand.kt @@ -0,0 +1,24 @@ +package com.otfhee.paper.commands.social + +import com.otfhee.paper.Plugin +import com.otfhee.paper.messenger.Messenger +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.executors.PlayerCommandExecutor + +class SiteCommand(private val plugin: Plugin) { + init { + CommandAPICommand("site") + .withPermission("otfhee.commands.default.site") + .executesPlayer(PlayerCommandExecutor { sender, _ -> + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + + messenger.info( + languageUtil.getMessage(sender, "commands.site") + .replace("", plugin.config.getString("links.site") ?: "https://otfhee.com/"), sender + ) + }) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/social/TelegramCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/social/TelegramCommand.kt new file mode 100644 index 0000000..0b0e593 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/social/TelegramCommand.kt @@ -0,0 +1,25 @@ +package com.otfhee.paper.commands.social + +import com.otfhee.paper.Plugin +import com.otfhee.paper.messenger.Messenger +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.executors.PlayerCommandExecutor + +class TelegramCommand(private val plugin: Plugin) { + init { + CommandAPICommand("telegram") + .withAliases("tg") + .withPermission("otfhee.commands.default.telegram") + .executesPlayer(PlayerCommandExecutor { sender, _ -> + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + + messenger.info( + languageUtil.getMessage(sender, "commands.telegram") + .replace("", plugin.config.getString("links.telegram") ?: "https://otfhee.com/telegram"), sender + ) + }) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/util/LanguageCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/util/LanguageCommand.kt new file mode 100644 index 0000000..4a574a2 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/util/LanguageCommand.kt @@ -0,0 +1,34 @@ +package com.otfhee.paper.commands.util + +import com.otfhee.paper.Plugin +import com.otfhee.paper.database.user.User +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.ListArgumentBuilder +import dev.jorel.commandapi.executors.PlayerCommandExecutor +import org.jetbrains.exposed.sql.transactions.transaction + +class LanguageCommand(plugin: Plugin) { + private val languageUtil = LanguageUtil(plugin) + + init { + CommandAPICommand("language") + .withAliases("lang") + .withPermission("otfhee.commands.default.language") + .withArguments( + ListArgumentBuilder("language") + .withList(languageUtil.getAvailableLanguages()) + .withMapper { language -> language.lowercase() } + .buildGreedy() + ) + .executesPlayer(PlayerCommandExecutor { sender, args -> + val language = args["language"] as List<*> + transaction { + val user = User[sender.uniqueId] + user.lang = language.stream().findAny().get().toString() + languageUtil.success("commands.lang.success", sender) + } + }) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/util/PingCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/util/PingCommand.kt new file mode 100644 index 0000000..02497d5 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/util/PingCommand.kt @@ -0,0 +1,26 @@ +package com.otfhee.paper.commands.util + +import com.otfhee.paper.Plugin +import com.otfhee.paper.messenger.Messenger +import com.otfhee.paper.telemetry.PingTelemetry +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.executors.PlayerCommandExecutor + +class PingCommand(private val plugin: Plugin) { + init { + CommandAPICommand("ping") + .withPermission("otfhee.commands.default.ping") + .executesPlayer(PlayerCommandExecutor { sender, _ -> + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + val ping = PingTelemetry(sender, plugin) + + messenger.info( + languageUtil.getMessage(sender, "commands.ping") + .replace("", ping.get()), sender + ) + }) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/util/PremiumCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/util/PremiumCommand.kt new file mode 100644 index 0000000..d1d6b3a --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/util/PremiumCommand.kt @@ -0,0 +1,93 @@ +package com.otfhee.paper.commands.util + +import com.otfhee.paper.Plugin +import com.otfhee.paper.database.user.User +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.arguments.ArgumentSuggestions +import dev.jorel.commandapi.arguments.OfflinePlayerArgument +import dev.jorel.commandapi.arguments.StringArgument +import dev.jorel.commandapi.executors.CommandExecutor +import kotlinx.datetime.* +import net.luckperms.api.LuckPerms +import net.luckperms.api.node.Node +import org.bukkit.OfflinePlayer +import org.bukkit.entity.Player +import org.jetbrains.exposed.sql.transactions.transaction + +class PremiumCommand(private val plugin: Plugin, private val luckPerms: LuckPerms) { + init { + CommandAPICommand("premium") + .withPermission("otfhee.commands.staff.premium") + .withSubcommand(CommandAPICommand("give") + .withPermission("otfhee.commands.staff.premium") + .withArguments(OfflinePlayerArgument("player")) + .withArguments( + StringArgument("rate") + .replaceSuggestions(ArgumentSuggestions.strings("monthly", "quarterly", "half_year", "year")) + ) + .executes(CommandExecutor {sender, args -> + val player = args.get("player") as OfflinePlayer + val rate = args.get("rate") as String + val languageUtil = LanguageUtil(plugin) + transaction { + if (!User.isExist(player.uniqueId)) { + User.create(player.uniqueId) + } + val user = User[player.uniqueId] + val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + + val date = when (rate) { + "monthly" -> now.date.plus(1, DateTimeUnit.MONTH) + "quarterly" -> now.date.plus(3, DateTimeUnit.MONTH) + "half_year" -> now.date.plus(6, DateTimeUnit.MONTH) + "year" -> now.date.plus(12, DateTimeUnit.MONTH) + else -> null + } + val luser = luckPerms.userManager.loadUser(player.uniqueId) + + if (date != null) { + if (user.premiumDate != null) { + user.premiumDate = + LocalDateTime(user.premiumDate!!.date.plus(date.periodUntil(date)), now.time) + } else { + user.premiumDate = LocalDateTime(date, now.time) + } + luser.get().data().add(Node.builder("group.premium").build()) + luckPerms.userManager.saveUser(luser.get()) + languageUtil.success("commands.premium.give.success", sender) + if (player.isOnline) { + player as Player + languageUtil.info("commands.premium.give.notification", player) + } + } else { + languageUtil.error("commands.premium.give.rate.error", sender) + } + } + })) + .withSubcommand(CommandAPICommand("revoke") + .withArguments(OfflinePlayerArgument("player")) + .withPermission("otfhee.commands.staff.premium") + .executes(CommandExecutor {sender, args -> + val player = args.get("player") as OfflinePlayer + val languageUtil = LanguageUtil(plugin) + transaction { + if (!User.isExist(player.uniqueId)) { + languageUtil.error("util.database.not-found", sender) + } + val user = User[player.uniqueId] + val luser = luckPerms.userManager.loadUser(player.uniqueId) + + if (user.premiumDate != null) { + user.premiumDate = null + luser.get().data().remove(Node.builder("group.premium").build()) + luckPerms.userManager.saveUser(luser.get()) + languageUtil.success("commands.premium.revoke.success", sender) + } else { + languageUtil.error("commands.premium.revoke.not-found", sender) + } + } + })) + .register() + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/commands/util/StatusCommand.kt b/paper/src/main/kotlin/com/otfhee/paper/commands/util/StatusCommand.kt new file mode 100644 index 0000000..4bb1f63 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/commands/util/StatusCommand.kt @@ -0,0 +1,50 @@ +package com.otfhee.paper.commands.util + +import com.otfhee.paper.Plugin +import com.otfhee.paper.database.user.User +import com.otfhee.paper.database.user.UserStatus +import com.otfhee.paper.messenger.Messenger +import com.otfhee.paper.utils.LanguageUtil +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.executors.PlayerCommandExecutor +import org.bukkit.entity.Player +import org.jetbrains.exposed.sql.transactions.transaction + +class StatusCommand(private val plugin: Plugin) { + init { + CommandAPICommand("status") + .withPermission("otfhee.commands.default.status") + .withSubcommand( + CommandAPICommand("afk") + .executesPlayer(PlayerCommandExecutor { player, _ -> + setStatus(player, UserStatus.AFK) + }) + ) + .withSubcommand( + CommandAPICommand("dnd") + .executesPlayer(PlayerCommandExecutor { player, _ -> + setStatus(player, UserStatus.DO_NOT_DISTURB) + }) + ) + .withSubcommand( + CommandAPICommand("online") + .executesPlayer(PlayerCommandExecutor { player, _ -> + setStatus(player, UserStatus.ONLINE) + }) + ) + .register() + } + + private fun setStatus(player: Player, status: UserStatus) { + transaction { + val user = User[player.uniqueId] + user.status = status.name + } + val languageUtil = LanguageUtil(plugin) + val messenger = Messenger(plugin) + + messenger.success( + languageUtil.getMessage(player, "commands.status.success") + .replace("", status.name), player) + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/database/ban/Ban.kt b/paper/src/main/kotlin/com/otfhee/paper/database/ban/Ban.kt new file mode 100644 index 0000000..0c1660e --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/database/ban/Ban.kt @@ -0,0 +1,62 @@ +package com.otfhee.paper.database.ban + +import kotlinx.datetime.Clock +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.UUIDEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.* + +class Ban(id: EntityID) : UUIDEntity(id) { + companion object : UUIDEntityClass(Bans) { + fun isActive(uuid: UUID): Boolean { + return transaction { + val ban = Ban.findById(uuid) ?: return@transaction false + + val expire = ban.expireDate + val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + + if (expire != null && expire < now) { + ban.delete() + return@transaction false + } + + return@transaction true + } + } + + fun create( + playerUUID: UUID, + reason: String, + adminUUID: UUID, + expireDate: LocalDateTime? = null + ): Ban? { + return transaction { + val existingBan = Ban.findById(playerUUID) + if (existingBan != null) { + val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + if (existingBan.expireDate == null || existingBan.expireDate!! > now) { + return@transaction null + } + + existingBan.delete() + } + + Ban.new(playerUUID) { + this.reason = reason + this.adminUUID = adminUUID + this.banDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + this.expireDate = expireDate + } + } + } + } + + var reason by Bans.reason + var adminUUID by Bans.adminUUID + var banDate by Bans.banDate + var expireDate by Bans.expireDate +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/database/ban/BanInfo.kt b/paper/src/main/kotlin/com/otfhee/paper/database/ban/BanInfo.kt new file mode 100644 index 0000000..3e5dbae --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/database/ban/BanInfo.kt @@ -0,0 +1,24 @@ +package com.otfhee.paper.database.ban + +import kotlinx.datetime.LocalDateTime +import java.util.* + +data class BanInfo( + val uuid: UUID, + val reason: String, + val adminUUID: UUID, + val banDate: LocalDateTime, + val expireDate: LocalDateTime? +) { + companion object { + fun fromBan(ban: Ban): BanInfo { + return BanInfo( + uuid = ban.id.value, + reason = ban.reason, + adminUUID = ban.adminUUID, + banDate = ban.banDate, + expireDate = ban.expireDate + ) + } + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/database/ban/Bans.kt b/paper/src/main/kotlin/com/otfhee/paper/database/ban/Bans.kt new file mode 100644 index 0000000..2a33e3b --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/database/ban/Bans.kt @@ -0,0 +1,11 @@ +package com.otfhee.paper.database.ban + +import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.datetime + +object Bans : UUIDTable("bans") { + val reason = text("reason") + val adminUUID = uuid("admin-uuid") + val banDate = datetime("ban-date") + val expireDate = datetime("expire-date").nullable() +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/database/mute/Mute.kt b/paper/src/main/kotlin/com/otfhee/paper/database/mute/Mute.kt new file mode 100644 index 0000000..c1e333e --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/database/mute/Mute.kt @@ -0,0 +1,67 @@ +package com.otfhee.paper.database.mute + +import com.otfhee.paper.database.warn.Warn +import com.otfhee.paper.database.warn.Warns +import kotlinx.datetime.Clock +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.UUIDEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.SqlExpressionBuilder.isNotNull +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.* + +class Mute(id: EntityID) : UUIDEntity(id) { + companion object : UUIDEntityClass(Mutes) { + fun create( + uuid: UUID, + durationMinutes: Int? = null + ): Mute { + return transaction { + Mute.new(uuid) { + this.muteDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + this.remainingTime = durationMinutes + } + } + } + + fun unmute(uuid: UUID) : Boolean { + return transaction { + val user = Mute.findById(uuid) ?: return@transaction false + user.delete() + return@transaction true + } + } + + fun isMute(uuid: UUID) : Boolean { + return transaction { + Mute.findById(uuid) != null + } + } + + fun decreaseRemainingTime(uuid: UUID, minutesPlayed: Int) { + transaction { + Mute.find { + (Mutes.id eq uuid) and + (Mutes.remainingTime.isNotNull()) + }.forEach { mute -> + val timeLeft = mute.remainingTime + if (timeLeft != null) { + val newTimeLeft = (timeLeft - minutesPlayed).coerceAtLeast(0) + mute.remainingTime = newTimeLeft + + if (newTimeLeft <= 0) { + unmute(uuid = uuid) + } + } + } + } + } + } + + var muteDate by Mutes.muteDate + var remainingTime by Mutes.remainingTime +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/database/mute/Mutes.kt b/paper/src/main/kotlin/com/otfhee/paper/database/mute/Mutes.kt new file mode 100644 index 0000000..b0fc1de --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/database/mute/Mutes.kt @@ -0,0 +1,9 @@ +package com.otfhee.paper.database.mute + +import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.datetime + +object Mutes : UUIDTable("mutes") { + val muteDate = datetime("mute-date") + val remainingTime = integer("remaining-time").nullable() +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/database/user/User.kt b/paper/src/main/kotlin/com/otfhee/paper/database/user/User.kt new file mode 100644 index 0000000..098567a --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/database/user/User.kt @@ -0,0 +1,48 @@ +package com.otfhee.paper.database.user + +import kotlinx.datetime.Clock +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.UUIDEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.* + +class User(id: EntityID) : UUIDEntity(id) { + var firstJoin by Users.firstJoin + var lastJoin by Users.lastJoin + var globalMode by Users.globalMode + var displayColor by Users.displayColor + var guild by Users.guild + var premiumDate by Users.premiumDate + var status by Users.status + var lang by Users.lang + var pet by Users.pet + + companion object : UUIDEntityClass(Users) { + fun isExist(uuid: UUID): Boolean { + return transaction { + User.findById(uuid) != null + } + } + + fun create(uuid: UUID) { + val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + + transaction { + User.new(uuid) { + firstJoin = now + lastJoin = now + globalMode = false + displayColor = "" + guild = null + premiumDate = null + status = "ONLINE" + lang = "russian" + pet = null + } + } + } + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/database/user/UserStatus.kt b/paper/src/main/kotlin/com/otfhee/paper/database/user/UserStatus.kt new file mode 100644 index 0000000..8e1150d --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/database/user/UserStatus.kt @@ -0,0 +1,7 @@ +package com.otfhee.paper.database.user + +enum class UserStatus { + ONLINE, + AFK, + DO_NOT_DISTURB +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/database/user/Users.kt b/paper/src/main/kotlin/com/otfhee/paper/database/user/Users.kt new file mode 100644 index 0000000..5313dbe --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/database/user/Users.kt @@ -0,0 +1,16 @@ +package com.otfhee.paper.database.user + +import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.datetime + +object Users : UUIDTable("users") { + val firstJoin = datetime("first-join") + val lastJoin = datetime("last-join") + val premiumDate = datetime("premium-date").nullable() + val globalMode = bool("global-mode") + val displayColor = varchar("display-color", 255) + val guild = uuid("guild").nullable() + val status = varchar("status", 255) + val lang = varchar("lang", 255) + val pet = varchar("pet", 255).nullable() +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/database/warn/Warn.kt b/paper/src/main/kotlin/com/otfhee/paper/database/warn/Warn.kt new file mode 100644 index 0000000..f25169d --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/database/warn/Warn.kt @@ -0,0 +1,108 @@ +package com.otfhee.paper.database.warn + +import kotlinx.datetime.Clock +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.SortOrder +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.* + +class Warn(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(Warns) { + fun create( + playerUUID: UUID, + reason: String, + adminUUID: UUID, + durationMinutes: Int? = null + ): Warn { + return transaction { + Warn.new { + this.playerUUID = playerUUID + this.reason = reason + this.adminUUID = adminUUID + this.warnDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + this.remainingTime = durationMinutes + this.active = true + } + } + } + + fun getActiveWarningsCount(playerUUID: UUID): Int { + return transaction { + Warn.find { + (Warns.playerUUID eq playerUUID) and (Warns.active eq true) + }.count().toInt() + } + } + + fun getAllWarnings(playerUUID: UUID): List { + return transaction { + Warn.find { Warns.playerUUID eq playerUUID } + .orderBy(Warns.warnDate to SortOrder.DESC) + .map { WarnInfo.fromWarn(it) } + } + } + + fun getActiveWarnings(playerUUID: UUID): List { + return transaction { + Warn.find { + (Warns.playerUUID eq playerUUID) and (Warns.active eq true) + } + .orderBy(Warns.warnDate to SortOrder.DESC) + .map { WarnInfo.fromWarn(it) } + } + } + + fun decreaseRemainingTime(playerUUID: UUID, minutesPlayed: Int) { + transaction { + Warn.find { + (Warns.playerUUID eq playerUUID) and + (Warns.active eq true) and + (Warns.remainingTime.isNotNull()) + }.forEach { warn -> + val timeLeft = warn.remainingTime + if (timeLeft != null) { + val newTimeLeft = (timeLeft - minutesPlayed).coerceAtLeast(0) + warn.remainingTime = newTimeLeft + + if (newTimeLeft <= 0) { + warn.active = false + } + } + } + } + } + + fun removeWarning(warnId: Int): Boolean { + return transaction { + val warn = Warn.findById(warnId) ?: return@transaction false + warn.active = false + return@transaction true + } + } + + fun clearWarnings(playerUUID: UUID): Int { + return transaction { + var count = 0 + Warn.find { + (Warns.playerUUID eq playerUUID) and (Warns.active eq true) + }.forEach { + it.active = false + count++ + } + count + } + } + } + + var playerUUID by Warns.playerUUID + var reason by Warns.reason + var adminUUID by Warns.adminUUID + var warnDate by Warns.warnDate + var remainingTime by Warns.remainingTime + var active by Warns.active +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/database/warn/WarnInfo.kt b/paper/src/main/kotlin/com/otfhee/paper/database/warn/WarnInfo.kt new file mode 100644 index 0000000..934c8d5 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/database/warn/WarnInfo.kt @@ -0,0 +1,28 @@ +package com.otfhee.paper.database.warn + +import kotlinx.datetime.LocalDateTime +import java.util.* + +data class WarnInfo( + val id: Int, + val playerUUID: UUID, + val reason: String?, + val adminUUID: UUID, + val warnDate: LocalDateTime, + val remainingTime: Int?, + val active: Boolean +) { + companion object { + fun fromWarn(warn: Warn): WarnInfo { + return WarnInfo( + id = warn.id.value, + playerUUID = warn.playerUUID, + reason = warn.reason, + adminUUID = warn.adminUUID, + warnDate = warn.warnDate, + remainingTime = warn.remainingTime, + active = warn.active + ) + } + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/database/warn/Warns.kt b/paper/src/main/kotlin/com/otfhee/paper/database/warn/Warns.kt new file mode 100644 index 0000000..92b72a2 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/database/warn/Warns.kt @@ -0,0 +1,13 @@ +package com.otfhee.paper.database.warn + +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.kotlin.datetime.datetime + +object Warns : IntIdTable("warns") { + val playerUUID = uuid("player-uuid") + val reason = text("reason").nullable() + val adminUUID = uuid("admin-uuid") + val warnDate = datetime("warn-date") + val remainingTime = integer("remaining-time").nullable() + val active = bool("active").default(true) +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/display/Display.kt b/paper/src/main/kotlin/com/otfhee/paper/display/Display.kt new file mode 100644 index 0000000..7a3db60 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/display/Display.kt @@ -0,0 +1,8 @@ +package com.otfhee.paper.display + +import net.kyori.adventure.audience.Audience +import org.bukkit.entity.Player + +interface Display { + fun get(player: Player, viewer: Audience? = null): String +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/display/DisplayName.kt b/paper/src/main/kotlin/com/otfhee/paper/display/DisplayName.kt new file mode 100644 index 0000000..6b20527 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/display/DisplayName.kt @@ -0,0 +1,50 @@ +package com.otfhee.paper.display + +import com.otfhee.paper.Plugin +import com.otfhee.paper.database.mute.Mutes.default +import com.otfhee.paper.database.user.User +import com.otfhee.paper.utils.LanguageUtil +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.text.format.TextColor +import net.kyori.adventure.text.minimessage.MiniMessage +import net.luckperms.api.LuckPerms +import org.bukkit.entity.Player +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.transactions.transaction + +class DisplayName(private val plugin: Plugin) : Display { + private val displayName = plugin.config.getString("display-name") ?: "" + + override fun get(player: Player, viewer: Audience?): String { + val luckPerms: LuckPerms = plugin.luckPerms() + val user = luckPerms.userManager.getUser(player.uniqueId)!! + val db: User = transaction { + User[player.uniqueId] + } + val default = "" + val languageUtil = LanguageUtil(plugin) + val displayColor = if (player.hasPermission("otfhee.premium.display.color")) + db.displayColor + else + "" + + val lpPrefix = user.cachedData.metaData.prefix + val miniMessage = MiniMessage.miniMessage() + + val prefix = if (lpPrefix == "") + default + else if (lpPrefix != null) + miniMessage.serialize(miniMessage.deserialize("$lpPrefix ").color(TextColor.color(78, 92, 36))) + else + default + + val display = displayName + .replace("", prefix) + .replace("", displayColor) + .replace("", "${languageUtil.getMessage(viewer ?: player, "chat.private-messages.display-name")}'>" + + "${player.name}") + + return display + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/display/DisplayStatus.kt b/paper/src/main/kotlin/com/otfhee/paper/display/DisplayStatus.kt new file mode 100644 index 0000000..60dc03d --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/display/DisplayStatus.kt @@ -0,0 +1,22 @@ +package com.otfhee.paper.display + +import com.otfhee.paper.Plugin +import com.otfhee.paper.database.user.User +import net.kyori.adventure.audience.Audience +import org.bukkit.entity.Player +import org.jetbrains.exposed.sql.transactions.transaction + +class DisplayStatus(private val plugin: Plugin) : Display { + override fun get(player: Player, viewer: Audience?): String { + val user = transaction { + User[player.uniqueId] + } + + return when(user.status.lowercase()) { + "online" -> plugin.config.getString("icons.online").toString() + "afk" -> plugin.config.getString("icons.afk").toString() + "do_not_disturb" -> plugin.config.getString("icons.do_not_disturb").toString() + else -> "?" + } + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/display/DisplayWorld.kt b/paper/src/main/kotlin/com/otfhee/paper/display/DisplayWorld.kt new file mode 100644 index 0000000..9ed0055 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/display/DisplayWorld.kt @@ -0,0 +1,16 @@ +package com.otfhee.paper.display + +import com.otfhee.paper.Plugin +import net.kyori.adventure.audience.Audience +import org.bukkit.entity.Player + +class DisplayWorld(private val plugin: Plugin) : Display { + override fun get(player: Player, viewer: Audience?): String { + return when(player.world.name) { + "world" -> plugin.config.getString("icons.world").toString() + "world_nether" -> plugin.config.getString("icons.world_nether").toString() + "world_the_end" -> plugin.config.getString("icons.world_the_end").toString() + else -> "?" + } + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/listeners/AllayInteractListener.kt b/paper/src/main/kotlin/com/otfhee/paper/listeners/AllayInteractListener.kt new file mode 100644 index 0000000..f4f6039 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/listeners/AllayInteractListener.kt @@ -0,0 +1,19 @@ +package com.otfhee.paper.listeners + +import org.bukkit.entity.Allay +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerInteractEntityEvent + +class AllayInteractListener : Listener { + @EventHandler + fun onPlayerInteractWithAllay(event: PlayerInteractEntityEvent) { + val metadata = event.rightClicked.getMetadata("interact") + + val data = if (metadata.isNotEmpty()) metadata[0].value() as? Boolean ?: false else true + + if (event.rightClicked is Allay && !data) { + event.isCancelled = true + } + } +} \ No newline at end of file diff --git a/paper/src/main/kotlin/com/otfhee/paper/listeners/AntiGriefListener.kt b/paper/src/main/kotlin/com/otfhee/paper/listeners/AntiGriefListener.kt new file mode 100644 index 0000000..ba70ec0 --- /dev/null +++ b/paper/src/main/kotlin/com/otfhee/paper/listeners/AntiGriefListener.kt @@ -0,0 +1,84 @@ +package com.otfhee.paper.listeners + +import com.otfhee.paper.Plugin +import com.otfhee.paper.messenger.PastelMessage +import com.otfhee.paper.utils.LanguageUtil +import com.otfhee.paper.utils.TimeUnit +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.Statistic +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.block.BlockPlaceEvent + +class AntiGriefListener(private val plugin: Plugin) : Listener { + private val languageUtil: LanguageUtil = LanguageUtil(plugin) + + private val spawnProtectionRadius: Int = plugin.config.getInt("anti-grief.spawn-protection-radius", 1500) + + private val restrictedBlocks: Set = plugin.config.getStringList("anti-grief.restricted-blocks") + .mapNotNull { blockName -> + try { + Material.valueOf(blockName.uppercase()) + } catch (e: IllegalArgumentException) { + plugin.logger.warning("Incorrect material in the configuration: $blockName") + null + } + }.toSet().ifEmpty { + setOf( + Material.TNT, + Material.FIRE, + Material.LAVA, + Material.LAVA_BUCKET, + Material.FLINT_AND_STEEL, + Material.END_CRYSTAL, + Material.RESPAWN_ANCHOR, + Material.WITHER_SKELETON_SKULL + ) + } + + @EventHandler(priority = EventPriority.HIGH) + fun onBlockPlace(event: BlockPlaceEvent) { + val player = event.player + val block = event.block + + if (isInSpawnProtection(block.location) && restrictedBlocks.contains(block.type)) { + val playTime = getPlayerPlaytime(player) + + if (playTime < 24 && !player.hasPermission("otfhee.staff.anti-grief.spawn")) { + event.isCancelled = true + val remainingTime = 24 - playTime + val message = languageUtil.getMessage(player, "anti-grief.spawn-protection") + .replace("