Skip to content

Commit

Permalink
Make Loritta's reaction collecting events in a generic way to make it…
Browse files Browse the repository at this point in the history
… easier to create and add new Loritta events without always copying and pasting the old code
  • Loading branch information
MrPowerGamerBR committed Oct 28, 2024
1 parent 48c1c76 commit a7a4cef
Show file tree
Hide file tree
Showing 37 changed files with 1,503 additions and 600 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ object LorittaEmojis {
val applicationEmojis = mutableListOf<LorittaEmojiReference.ApplicationEmoji>()

val GarticBot = applicationEmoji("garticbot")
val LoriHead = applicationEmoji("lori_head")
val PantufaHead = applicationEmoji("pantufa_head")
val GabrielaHead = applicationEmoji("gabi_head")

private fun applicationEmoji(emojiName: String): LorittaEmojiReference.ApplicationEmoji {
val ref = LorittaEmojiReference.ApplicationEmoji(emojiName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ class LorittaBot(
val interactionsListener = InteractionsListener(this)
val christmasListener = ReactionListener(this)
val easter2023Listener = Easter2023ReactionListener(this)
val reactionListener = net.perfectdreams.loritta.morenitta.reactionevents.ReactionListener(this)
val giveawayInteractionsListener = GiveawayInteractionsListener(this)
val coinFlipBetGlobalListener = CoinFlipBetGlobalListener(this)
val sonhosTransferInteractionsListener = SonhosTransferInteractionsListener(this)
Expand Down Expand Up @@ -448,6 +449,7 @@ class LorittaBot(
giveawayInteractionsListener,
coinFlipBetGlobalListener,
easter2023Listener,
reactionListener,
sonhosTransferInteractionsListener
)
.addEventListenerProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import net.perfectdreams.loritta.morenitta.events.LorittaMessageEvent
import net.perfectdreams.loritta.morenitta.interactions.UnleashedContext
import net.perfectdreams.loritta.morenitta.interactions.commands.options.*
import net.perfectdreams.loritta.morenitta.interactions.vanilla.discord.*
import net.perfectdreams.loritta.morenitta.interactions.vanilla.easter2023.EventCommand
import net.perfectdreams.loritta.morenitta.interactions.vanilla.reactionevents.EventCommand
import net.perfectdreams.loritta.morenitta.interactions.vanilla.economy.*
import net.perfectdreams.loritta.morenitta.interactions.vanilla.`fun`.*
import net.perfectdreams.loritta.morenitta.interactions.vanilla.`fun`.text.TextTransformCommand
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,9 @@ class SonhosTransactionsExecutor(val loritta: LorittaBot) : LorittaSlashCommandE
// ===[ EASTER 2023 ]===
is Easter2023SonhosTransaction -> Easter2023SonhosTransactionTransformer.transform(loritta, i18nContext, cachedUserInfo, cachedUserInfos, transaction)

// ===[ REACTION EVENTS ]===
is ReactionEventSonhosTransaction -> ReactionEventSonhosTransactionTransformer.transform(loritta, i18nContext, cachedUserInfo, cachedUserInfos, transaction)

// ===[ POWERSTREAM ]===
is PowerStreamClaimedFirstSonhosRewardSonhosTransaction -> PowerStreamClaimedFirstSonhosRewardTransactionTransformer.transform(loritta, i18nContext, cachedUserInfo, cachedUserInfos, transaction)
is PowerStreamClaimedLimitedTimeSonhosRewardSonhosTransaction -> PowerStreamClaimedLimitedTimeSonhosRewardSonhosTransactionTransformer.transform(loritta, i18nContext, cachedUserInfo, cachedUserInfos, transaction)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package net.perfectdreams.loritta.morenitta.interactions.vanilla.economy.transactiontransformers

import net.perfectdreams.i18nhelper.core.I18nContext
import net.perfectdreams.loritta.morenitta.LorittaBot
import net.perfectdreams.loritta.morenitta.reactionevents.ReactionEventsAttributes
import net.perfectdreams.loritta.morenitta.utils.CachedUserInfo
import net.perfectdreams.loritta.serializable.ReactionEventSonhosTransaction
import net.perfectdreams.loritta.serializable.UserId

object ReactionEventSonhosTransactionTransformer : SonhosTransactionTransformer<ReactionEventSonhosTransaction> {
override suspend fun transform(
loritta: LorittaBot,
i18nContext: I18nContext,
cachedUserInfo: CachedUserInfo,
cachedUserInfos: MutableMap<UserId, CachedUserInfo?>,
transaction: ReactionEventSonhosTransaction
): suspend StringBuilder.() -> (Unit) = {
val event = ReactionEventsAttributes.attributes[transaction.eventInternalId]!!

appendMoneyEarnedEmoji()
append(event.createSonhosRewardTransactionMessage(i18nContext, transaction.sonhos, transaction.craftedCount))
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import net.perfectdreams.loritta.morenitta.events.LorittaMessageEvent
import net.perfectdreams.loritta.morenitta.messages.LorittaReply
import net.perfectdreams.loritta.morenitta.modules.*
import net.perfectdreams.loritta.morenitta.platform.discord.legacy.entities.jda.JDAUser
import net.perfectdreams.loritta.morenitta.reactionevents.DropPointsStuffModule
import net.perfectdreams.loritta.morenitta.utils.GuildLorittaUser
import net.perfectdreams.loritta.morenitta.utils.LorittaUser
import net.perfectdreams.loritta.morenitta.utils.chance
Expand Down Expand Up @@ -59,12 +60,14 @@ class MessageListener(val loritta: LorittaBot) : ListenerAdapter() {
private val quirkyModule = QuirkyModule(loritta)
private val christmasStuffModule = DropChristmasStuffModule(loritta)
private val dropEaster2023StuffModule = DropEaster2023StuffModule(loritta)
private val dropPointsStuffModule = DropPointsStuffModule(loritta)

private val messageReceivedModules = mutableListOf(
automodModule,
inviteLinkModule,
christmasStuffModule,
dropEaster2023StuffModule,
dropPointsStuffModule,
experienceModule,
afkModule,
bomDiaECiaModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ class ProfileDesignManager(val loritta: LorittaBot) {
registerBadge(SparklyStaffBadge(loritta))
registerBadge(StonksBadge(loritta.pudding))
registerBadge(StickerFanBadge(loritta.pudding))
registerBadge(ReactionEventBadge.Halloween2024ReactionEventBadge(loritta.pudding))
}

suspend fun createProfile(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package net.perfectdreams.loritta.morenitta.profile.badges

import net.perfectdreams.loritta.cinnamon.pudding.Pudding
import net.perfectdreams.loritta.cinnamon.pudding.tables.CollectedCandies
import net.perfectdreams.loritta.morenitta.dao.Profile
import net.perfectdreams.loritta.morenitta.profile.Badge
import net.perfectdreams.loritta.morenitta.profile.ProfileDesignManager
import net.perfectdreams.loritta.morenitta.profile.ProfileUserInfoData
import org.jetbrains.exposed.sql.select
import java.util.*

class Halloween2024Badge(val pudding: Pudding) : Badge.LorittaBadge(
UUID.fromString("3b74665b-d30c-4cc6-8465-0873ec3dc3b6"),
ProfileDesignManager.I18N_BADGES_PREFIX.Halloween2019.Title,
ProfileDesignManager.I18N_BADGES_PREFIX.Halloween2019.Description,
"halloween2019.png",
100
) {
override suspend fun checkIfUserDeservesBadge(user: ProfileUserInfoData, profile: Profile, mutualGuilds: Set<Long>): Boolean {
return pudding.transaction {
CollectedCandies.select {
CollectedCandies.user eq profile.id.value
}.count() >= 400L
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package net.perfectdreams.loritta.morenitta.profile.badges

import net.perfectdreams.i18nhelper.core.keydata.StringI18nData
import net.perfectdreams.loritta.cinnamon.pudding.Pudding
import net.perfectdreams.loritta.cinnamon.pudding.tables.reactionevents.CraftedReactionEventItems
import net.perfectdreams.loritta.cinnamon.pudding.tables.reactionevents.ReactionEventPlayers
import net.perfectdreams.loritta.morenitta.dao.Profile
import net.perfectdreams.loritta.morenitta.profile.Badge
import net.perfectdreams.loritta.morenitta.profile.ProfileDesignManager
import net.perfectdreams.loritta.morenitta.profile.ProfileUserInfoData
import net.perfectdreams.loritta.morenitta.reactionevents.ReactionEvent
import net.perfectdreams.loritta.morenitta.reactionevents.ReactionEventReward
import net.perfectdreams.loritta.morenitta.reactionevents.events.Halloween2024ReactionEvent
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.selectAll
import java.util.*

sealed class ReactionEventBadge(
val pudding: Pudding,
id: UUID,
title: StringI18nData,
description: StringI18nData,
badgeName: String,
priority: Int,
reactionEvent: ReactionEvent
) : Badge.LorittaBadge(
id,
title,
description,
badgeName,
priority
) {
class Halloween2024ReactionEventBadge(pudding: Pudding) : ReactionEventBadge(
pudding,
UUID.fromString("e9de17d4-8ee6-4f18-b8a0-ef9087b5ec43"),
ProfileDesignManager.I18N_BADGES_PREFIX.Halloween2024.Title,
ProfileDesignManager.I18N_BADGES_PREFIX.Halloween2024.Description,
"halloween2019.png",
100,
Halloween2024ReactionEvent
)

private val eventInternalId = reactionEvent.internalId
private val requiredPoints = reactionEvent.rewards.filterIsInstance<ReactionEventReward.BadgeReward>().first().requiredPoints

override suspend fun checkIfUserDeservesBadge(user: ProfileUserInfoData, profile: Profile, mutualGuilds: Set<Long>): Boolean {
return pudding.transaction {
CraftedReactionEventItems.innerJoin(ReactionEventPlayers).selectAll()
.where {
CraftedReactionEventItems.event eq eventInternalId and (ReactionEventPlayers.userId eq user.id)
}
.count() >= requiredPoints
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package net.perfectdreams.loritta.morenitta.reactionevents

import com.github.benmanes.caffeine.cache.Caffeine
import mu.KotlinLogging
import net.dv8tion.jda.api.Permission
import net.perfectdreams.i18nhelper.core.I18nContext
import net.perfectdreams.loritta.cinnamon.pudding.tables.reactionevents.ReactionEventDrops
import net.perfectdreams.loritta.cinnamon.pudding.tables.reactionevents.ReactionEventPlayers
import net.perfectdreams.loritta.common.locale.BaseLocale
import net.perfectdreams.loritta.morenitta.LorittaBot
import net.perfectdreams.loritta.morenitta.dao.Profile
import net.perfectdreams.loritta.morenitta.dao.ServerConfig
import net.perfectdreams.loritta.morenitta.events.LorittaMessageEvent
import net.perfectdreams.loritta.morenitta.modules.MessageReceivedModule
import net.perfectdreams.loritta.morenitta.utils.LorittaUser
import net.perfectdreams.loritta.morenitta.utils.extensions.toJDA
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.selectAll
import java.time.Instant
import java.util.concurrent.TimeUnit
import kotlin.collections.set

class DropPointsStuffModule(val m: LorittaBot) : MessageReceivedModule {
companion object {
private val logger = KotlinLogging.logger {}
}
private val dropInMessageAt = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build<Long, Long>()
.asMap()
private val lastDropsAt = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build<Long, Long>()
.asMap()
private val lastDropsByUserAt = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build<Long, Long>()
.asMap()

override suspend fun matches(event: LorittaMessageEvent, lorittaUser: LorittaUser, lorittaProfile: Profile?, serverConfig: ServerConfig, locale: BaseLocale, i18nContext: I18nContext): Boolean {
if (lorittaProfile == null)
return false

val now = Instant.now()

// Get the current active event
val activeEvent = ReactionEventsAttributes.getActiveEvent(now)

return event.guild?.selfMember?.hasPermission(Permission.MESSAGE_ADD_REACTION) == true && activeEvent != null
}

override suspend fun handle(event: LorittaMessageEvent, lorittaUser: LorittaUser, lorittaProfile: Profile?, serverConfig: ServerConfig, locale: BaseLocale, i18nContext: I18nContext): Boolean {
if (lorittaProfile == null)
return false

if (event.guild == null)
return false

val now = Instant.now()

// Get the current active event
val activeEvent = ReactionEventsAttributes.getActiveEvent(now) ?: return false

val date = System.currentTimeMillis()

val id = event.channel.idLong

val lastDrop = lastDropsAt.getOrDefault(id, 0L)
val lastDropDiff = date - lastDrop

if (1_000 >= lastDropDiff)
return false

val userDropTime = lastDropsByUserAt.getOrDefault(event.author.idLong, 0L)

if (10_000 >= date - userDropTime)
return false

if (event.message.contentStripped.hashCode() != lorittaProfile.lastMessageSentHash && event.message.contentRaw.length >= 5) {
for (reactionSet in activeEvent.reactionSets) {
val randomNumber = LorittaBot.RANDOM.nextFloat()

if (reactionSet.chance >= randomNumber) {
val shouldAddReaction = m.newSuspendedTransaction {
val spawnTheCandy = ReactionEventPlayers.selectAll()
.where {
ReactionEventPlayers.userId eq lorittaProfile.id.value and (ReactionEventPlayers.event eq activeEvent.internalId)
}.count() != 0L

if (spawnTheCandy) {
lastDropsAt[id] = date
lastDropsByUserAt[event.author.idLong] = date

// TODO: Fix this
// val type = LorittaEaster2023Event.easterEggColors.random()
// val emoji = LorittaEaster2023Event.easterEggColorToEmoji(type)
ReactionEventDrops.insert {
it[ReactionEventDrops.event] = activeEvent.internalId
it[ReactionEventDrops.reactionSetId] = reactionSet.reactionSetId
it[ReactionEventDrops.guildId] = event.guild.idLong
it[ReactionEventDrops.channelId] = event.channel.idLong
it[ReactionEventDrops.messageId] = event.message.idLong
it[ReactionEventDrops.createdAt] = Instant.now()
}
return@newSuspendedTransaction true
}

return@newSuspendedTransaction false
}

if (shouldAddReaction) {
event.message.addReaction(m.emojiManager.get(reactionSet.reaction).toJDA()).queue {
dropInMessageAt[event.message.idLong] = date
}
}
}
}
}

return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package net.perfectdreams.loritta.morenitta.reactionevents

import dev.minn.jda.ktx.messages.InlineMessage
import net.dv8tion.jda.api.entities.User
import net.perfectdreams.i18nhelper.core.I18nContext
import net.perfectdreams.loritta.common.emojis.LorittaEmojiReference
import net.perfectdreams.loritta.morenitta.LorittaBot
import net.perfectdreams.loritta.morenitta.interactions.UnleashedContext
import java.time.Instant
import java.util.*
import kotlin.math.round
import kotlin.random.Random

abstract class ReactionEvent {
abstract val internalId: String
abstract val startsAt: Instant
abstract val endsAt: Instant
abstract val reactionSets: List<ReactionSet>
abstract val rewards: List<ReactionEventReward>

abstract fun createJoinMessage(context: UnleashedContext): InlineMessage<*>.() -> (Unit)

// Reaction Set ID -> Required Quantity
fun getCurrentActiveCraft(user: User, alreadyCraftedQuantity: Long): Map<UUID, Int> = getCurrentActiveCraft(user.idLong, alreadyCraftedQuantity)
abstract fun getCurrentActiveCraft(userId: Long, alreadyCraftedQuantity: Long): Map<UUID, Int>

/**
* Creates the message used in the transaction command
*/
abstract fun createSonhosRewardTransactionMessage(i18nContext: I18nContext, sonhos: Long, craftedCount: Int): String

/**
* Creates the message used in the event inventory command to craft an item
*/
abstract fun createCraftItemButtonMessage(i18nContext: I18nContext): TextAndEmoji

/**
* Creates the message used in the event inventory command to craft an item
*/
abstract fun createHowManyCraftedItemsYouHaveMessage(i18nContext: I18nContext, craftedCount: Long, commandMention: String): String

/**
* Creates the message used in the event inventory command to craft an item
*/
abstract fun createItemsInYourInventoryMessage(i18nContext: I18nContext): String

/**
* Creates the message used in the event inventory command to craft an item
*/
abstract fun createYourNextCraftIngredientsAreMessage(i18nContext: I18nContext): String

abstract fun createYouDontHaveEnoughItemsMessage(i18nContext: I18nContext): String
abstract fun createYouCraftedAItemMessage(i18nContext: I18nContext): TextAndEmoji
abstract fun createCraftedXItemsMessage(loritta: LorittaBot, i18nContext: I18nContext, quantity: Long, commandMention: String): String
abstract fun createShortCraftedItemMessage(i18nContext: I18nContext, quantity: Int): String

// https://www.reddit.com/r/cpp_questions/comments/101f70k/how_to_generate_x_random_integers_whose_sum_is_100/j2okst5/
fun generateNormalizedIntegers(random: Random, count: Int, expectedSum: Int): List<Int> {
// Step 1: Generate random integers between 0 and 100
val randomIntegers = List(count) { random.nextInt(0, expectedSum + 1) }

// Step 2: Calculate the total sum of these integers
val totalSum = randomIntegers.sum()

// Step 3: Normalize each integer by rounding (100 * x / S)
val normalizedIntegers = randomIntegers.map { round(expectedSum.toDouble() * it / totalSum).toInt() }.toMutableList()

// Step 4: Adjust to make sure the sum is exactly 100
val normalizedSum = normalizedIntegers.sum()
val difference = expectedSum - normalizedSum

if (difference != 0) {
// Pick a random index to adjust
while (true) {
val randomIndex = random.nextInt(0, normalizedIntegers.size)
val newValue = normalizedIntegers[randomIndex] + difference
if (newValue >= 0) {
normalizedIntegers[randomIndex] += difference
break
}
}
}

return normalizedIntegers
}

data class TextAndEmoji(
val text: String,
val emoji: LorittaEmojiReference
)
}
Loading

0 comments on commit a7a4cef

Please sign in to comment.