Skip to content

Commit

Permalink
Announce first player to complete an achievement (#26)
Browse files Browse the repository at this point in the history
* Announce first player to complete an achievement

* PR Fixes + Sound/Hover

* Fix cache not updating upon achievement earn + change item icons in achievement menu

* Fix emitter
  • Loading branch information
Mew2K authored Jul 13, 2024
1 parent 81e53b4 commit 11610c7
Show file tree
Hide file tree
Showing 24 changed files with 70 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package network.warzone.mars.player.achievements

import net.md_5.bungee.api.chat.ComponentBuilder
import net.md_5.bungee.api.chat.HoverEvent
import net.md_5.bungee.api.chat.TextComponent
import network.warzone.api.database.models.Achievement
import network.warzone.api.database.models.AchievementStatistic
import network.warzone.mars.Mars
import network.warzone.mars.api.ApiClient
import network.warzone.mars.api.socket.OutboundEvent
Expand All @@ -10,38 +14,46 @@ import network.warzone.mars.player.feature.PlayerFeature
import network.warzone.mars.player.models.PlayerProfile
import org.bukkit.Bukkit
import org.bukkit.ChatColor
import org.bukkit.Sound
import org.bukkit.entity.Player
import java.util.*



// A class for adding completed achievements to a player's profile.
class AchievementEmitter(private val achievement: Achievement) {
// Fetch a player profile as needed.
fun emit(player: Player) {
Mars.async {
val profile = PlayerFeature.fetch(player.name)!!
emit(profile)
}
}

// Add an achievement to the specified profile.
fun emit(profile: PlayerProfile) {
fun emit(playerName: String) {
val profile: PlayerProfile = PlayerFeature.getCached(playerName)!!
if (profile.stats.achievements.containsKey(achievement._id.toString())) return // Player already has achievement.

// Print achievement earn to player and console.
val player = Bukkit.getPlayer(profile._id)
if (player != null) {
player.sendMessage("${ChatColor.GRAY}You've earned an achievement: ${ChatColor.AQUA}" + achievement.name + "${ChatColor.GRAY}!")
println("Achievement " + achievement.name + " earned by " + player.name)
val achievementMessage = TextComponent("${ChatColor.GRAY}You've earned an achievement: ${ChatColor.AQUA}${achievement.name}${ChatColor.GRAY}!")
val hoverText = ComponentBuilder("${ChatColor.GOLD}${achievement.description}").create()
achievementMessage.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverText)
player.playSound(player.location, Sound.LEVEL_UP, 1.0f, 1.0f)
player.spigot().sendMessage(achievementMessage)
}

// Emit achievement completion to the database.
ApiClient.emit(
OutboundEvent.PlayerAchievement, PlayerAchievementData(
SimplePlayer(profile._id, profile.name),
achievement._id,
Date().time
)
val completionTime = Date().time

val data = PlayerAchievementData(
SimplePlayer(profile._id, profile.name),
achievement._id,
completionTime
)

if (achievement.firstCompletion == null) {
achievement.firstCompletion = profile._id
Bukkit.broadcastMessage("\n${ChatColor.GOLD}${player.name} ${ChatColor.GRAY}is the first to complete the achievement ${ChatColor.AQUA}\"${achievement.name}\" ${ChatColor.GRAY}!")
Bukkit.broadcastMessage("")
}

profile.stats.achievements[achievement._id.toString()] = AchievementStatistic(completionTime)

// Emit achievement completion to the database.
ApiClient.emit(OutboundEvent.PlayerAchievement, data)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,21 +103,21 @@ class AchievementMenu(player: Player) : Listener {

for ((index, achievement) in matchingAchievements.subList(nonEdgeStart, nonEdgeEnd).withIndex()) {
slot(getSlotFromNonEdgeIndex(index)) {
item = item(Material.PAPER) {
// Check if the player has the achievement
val hasAchievement = profile.stats.achievements.containsKey(achievement._id.toString())
val colorData: Short = if (hasAchievement) 5 else 14 // 5 for green, 14 for red

item = item(Material.STAINED_GLASS_PANE, colorData) {
name = "${ChatColor.LIGHT_PURPLE}${achievement.name}"
lore = wrap("${ChatColor.GRAY}${achievement.description}", 40)

// Check if the player has the achievement
if (profile.stats.achievements.containsKey(achievement._id.toString())) {
enchant(Enchantment.DURABILITY)
flags(ItemFlag.HIDE_ENCHANTS)

if (hasAchievement) {
val completionDate = profile.stats.achievements[achievement._id.toString()]?.completionTime?.let {
formatDate(it)
}

lore = wrap("${ChatColor.GRAY}${achievement.description}" +
"\n\n${ChatColor.AQUA}Completed: $completionDate", 40)
"\n\n${ChatColor.AQUA}Completed: $completionDate", 40)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ data class Achievement(
@Serializable
val category: AchievementCategory? = null,
val agent: Agent,
var firstCompletion: UUID? = null
) : NamedResource {

// Structural equality of _id's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class BowDistanceAchievement(
if (killData.data.key != "death.projectile.player.distance") return
val killerProfile = event.update.updated
if (killData.data.distance!! >= target) {
emitter.emit(killerProfile)
emitter.emit(killerProfile.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class CaptureNoSprintAchievement(override val emitter: AchievementEmitter) : Ach
event.match.participants.forEach { matchPlayer ->
if (matchPlayer.player?.id !in playersWhoSprinted) {
val player = matchPlayer ?: return@forEach
emitter.emit(player.bukkit)
emitter.emit(player.bukkit.name)
}
}
playersWhoSprinted.clear()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ class ChatMessageAchievement(
if (event.update.reason != PlayerUpdateReason.CHAT) return
val msg = ((event.update.data as PlayerUpdateData.ChatUpdateData).data.message)
if (msg != message) return
emitter.emit(event.update.updated)
emitter.emit(event.update.updated.name)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ControlPointCaptureAchievement(
if (event.update.reason != PlayerUpdateReason.CONTROL_POINT_CAPTURE) return
val profile = event.update.updated
if (profile.stats.objectives.controlPointCaptures >= captures) {
emitter.emit(profile)
emitter.emit(event.update.updated.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class FireDeathAchievement(
val victimProfile = event.update.updated
if (deathData.data.cause == DeathCause.FIRE ||
deathData.data.cause == DeathCause.LAVA) {
emitter.emit(victimProfile)
emitter.emit(victimProfile.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class FirstBloodAchievement(
if (!killData.firstBlood) return
val killerProfile = event.update.updated
if (killerProfile.stats.firstBloods >= target) {
emitter.emit(killerProfile)
emitter.emit(killerProfile.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class FlagCaptureAchievement(
if (event.update.reason != PlayerUpdateReason.FLAG_PLACE) return
val playerProfile = event.update.updated
if (playerProfile.stats.objectives.flagCaptures >= captures) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class FlagDefendAchievement(
if (event.update.reason != PlayerUpdateReason.FLAG_DEFEND) return
val playerProfile = event.update.updated
if (playerProfile.stats.objectives.flagDefends >= defends) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class KillConsecutiveAchievement(
val timeDifference = recentKillsTimestamps.last() - recentKillsTimestamps.first()
// All kills must be within y seconds
if (timeDifference <= params.seconds * 1000) {
emitter.emit(profile)
emitter.emit(profile.name)
}
} else {
// Check each consecutive pair to ensure all kills are within the timeframe
Expand All @@ -56,7 +56,7 @@ class KillConsecutiveAchievement(
return
}
}
emitter.emit(profile)
emitter.emit(profile.name)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class KillstreakAchievement(
targetStreak, 0
) >= 1
) {
emitter.emit(killerProfile)
emitter.emit(event.update.updated.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class LevelUpAchievement(
@EventHandler
fun onProfileUpdate(event: PlayerLevelUpEvent) {
if (event.data.level >= level) {
emitter.emit(event.data.player)
emitter.emit(event.data.player.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class MonumentDamageAchievement(
if (event.update.reason != PlayerUpdateReason.DESTROYABLE_DAMAGE) return
val playerProfile = event.update.updated
if (playerProfile.stats.objectives.destroyableBlockDestroys >= breaks) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class PlayTimeAchievement(
val targetMillis = hours * 60 * 60 * 1000 // Convert hours to milliseconds

if (playerProfile.stats.gamePlaytime >= targetMillis) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,43 +26,43 @@ class RecordAchievement(
val session = records.longestSession
val elapsedTime = session?.let { differenceInMinutes(it.createdAt, it.endedAt) }
if ((elapsedTime ?: 0L) >= params.threshold.toLong()) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
RecordType.LONGEST_PROJECTILE_KILL -> {
val longestKill = records.longestProjectileKill?.distance ?: 0
if (longestKill >= params.threshold.toInt()) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
RecordType.FASTEST_WOOL_CAPTURE -> {
val woolCaptureTime = records.fastestWoolCapture?.value ?: Long.MAX_VALUE
if (woolCaptureTime <= params.threshold.toLong()) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
RecordType.FASTEST_FLAG_CAPTURE -> {
val flagCaptureTime = records.fastestFlagCapture?.value ?: Long.MAX_VALUE
if (flagCaptureTime <= params.threshold.toLong()) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
RecordType.FASTEST_FIRST_BLOOD -> {
val firstBloodTime = records.fastestFirstBlood?.time ?: Long.MAX_VALUE
if (firstBloodTime <= params.threshold.toLong()) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
RecordType.KILLS_IN_MATCH -> {
val kills = records.killsInMatch?.value ?: 0
if (kills >= params.threshold.toInt()) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
RecordType.DEATHS_IN_MATCH -> {
val deaths = records.deathsInMatch?.value ?: 0
if (deaths >= params.threshold.toInt()) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class TotalDeathsAchievement(
if (event.update.reason != PlayerUpdateReason.DEATH) return
val playerProfile = event.update.updated
if (playerProfile.stats.deaths >= deaths) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class TotalKillsAchievement(
if (event.update.reason != PlayerUpdateReason.KILL) return
val killerProfile = event.update.updated
if (killerProfile.stats.kills >= targetKills) {
emitter.emit(killerProfile)
emitter.emit(event.update.updated.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class TotalLossesAchievement(
if (event.update.reason != PlayerUpdateReason.MATCH_END) return
val playerProfile = event.update.updated
if (playerProfile.stats.losses >= losses) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class TotalWinsAchievement(
if (event.update.reason != PlayerUpdateReason.MATCH_END) return
val playerProfile = event.update.updated
if (playerProfile.stats.wins >= wins) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class WoolCaptureAchievement(
if (event.update.reason != PlayerUpdateReason.WOOL_PLACE) return
val playerProfile = event.update.updated
if (playerProfile.stats.objectives.woolCaptures >= captures) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class WoolDefendAchievement(
if (event.update.reason != PlayerUpdateReason.WOOL_DEFEND) return
val playerProfile = event.update.updated
if (playerProfile.stats.objectives.woolDefends >= defends) {
emitter.emit(playerProfile)
emitter.emit(event.update.updated.name)
}
}
}
8 changes: 5 additions & 3 deletions src/main/kotlin/network/warzone/mars/utils/menu/Item.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import org.bukkit.inventory.meta.ItemMeta

fun item(
type: Material = Material.STAINED_GLASS,
data: Short = 0,
builder: Item.() -> Unit = {}
) = Item(type).apply(builder)
) = Item(type, data).apply(builder)

class Item(
val type: Material
val type: Material,
data: Short = 0
) {
var stack = ItemStack(type, 1)
var stack = ItemStack(type, 1, data)
val meta: ItemMeta get() = stack.itemMeta

fun stack(builder: ItemStack.() -> Unit) = stack
Expand Down

0 comments on commit 11610c7

Please sign in to comment.