Skip to content

Commit a5230e7

Browse files
committed
feat: Add support for customizable clan tag foreground, background, and shadow colors
- Introduce ClanTagColor data class to encapsulate clan tag color properties - Update clan creation and update flows to handle foreground, background, and shadow colors - Refactor database schema and repository to store separate color fields - Add command arguments and permissions for setting each color component - Enhance command suggestions and validation for hex color input
1 parent 1cbb490 commit a5230e7

File tree

19 files changed

+320
-108
lines changed

19 files changed

+320
-108
lines changed

surf-clan-api/src/main/kotlin/dev/slne/clan/api/clan/Clan.kt

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import dev.slne.clan.api.member.ClanMember
77
import dev.slne.clan.api.member.ClanMemberAddResult
88
import dev.slne.clan.api.member.ClanMemberRole
99
import dev.slne.surf.surfapi.core.api.messages.Colors
10+
import net.kyori.adventure.text.format.ShadowColor
1011
import net.kyori.adventure.text.format.TextColor
1112
import org.jetbrains.annotations.ApiStatus
1213
import java.util.*
@@ -43,12 +44,7 @@ interface Clan : ClanView {
4344
*/
4445
suspend fun setDiscordInvite(discordInvite: String?)
4546

46-
/**
47-
* Changes the color of the clan's tag.
48-
*
49-
* @param color the new text color for the clan tag
50-
*/
51-
suspend fun setClanTagColor(color: TextColor)
47+
suspend fun changeClanTagColor(update: ClanTagColor.Update)
5248

5349
/**
5450
* Retrieves all pending invitations sent by this clan.
@@ -131,7 +127,17 @@ interface Clan : ClanView {
131127
/**
132128
* The default color for clan tags when no custom color is set.
133129
*/
134-
val DEFAULT_CLAN_TAG_COLOR: TextColor = Colors.WHITE
130+
val DEFAULT_CLAN_TAG_BACKGROUND_COLOR: TextColor = Colors.WHITE
131+
132+
val DEFAULT_CLAN_TAG_FOREGROUND_COLOR: TextColor = Colors.WHITE
133+
134+
val DEFAULT_CLAN_TAG_SHADOW_COLOR: ShadowColor = ShadowColor.none()
135+
136+
val DEFAULT_CLAN_TAG_COLORS = ClanTagColor(
137+
DEFAULT_CLAN_TAG_FOREGROUND_COLOR,
138+
DEFAULT_CLAN_TAG_BACKGROUND_COLOR,
139+
DEFAULT_CLAN_TAG_SHADOW_COLOR
140+
)
135141

136142
/**
137143
* The minimum number of members required before a Discord link can be set.
@@ -249,4 +255,8 @@ interface Clan : ClanView {
249255
return ClanService.instance.createClan(builder.copy())
250256
}
251257
}
252-
}
258+
}
259+
260+
suspend inline fun Clan.changeClanTagColor(update: ClanTagColor.Update.Builder.() -> Unit) = changeClanTagColor(
261+
ClanTagColor.update(update)
262+
)

surf-clan-api/src/main/kotlin/dev/slne/clan/api/clan/ClanCreateBuilder.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ data class ClanCreateBuilder internal constructor(
2323
/**
2424
* The color for the clan tag, or `null` to use the default color.
2525
*/
26-
var tagColor: TextColor? = null
26+
var tagColor: ClanTagColor? = null
2727
private set
2828

2929
/**
@@ -44,7 +44,7 @@ data class ClanCreateBuilder internal constructor(
4444
* @param tagColor the color to use for the clan tag
4545
* @return this builder for method chaining
4646
*/
47-
fun tagColor(tagColor: TextColor) = apply { this.tagColor = tagColor }
47+
fun tagColor(tagColor: ClanTagColor) = apply { this.tagColor = tagColor }
4848

4949
/**
5050
* Sets the description for the clan.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package dev.slne.clan.api.clan
2+
3+
import dev.slne.surf.surfapi.core.api.serializer.adventure.component.shadowcolor.SerializableShadowColor
4+
import dev.slne.surf.surfapi.core.api.serializer.adventure.component.textcolor.SerializableTextColor
5+
import kotlinx.serialization.Serializable
6+
import net.kyori.adventure.text.format.ShadowColor
7+
import net.kyori.adventure.text.format.TextColor
8+
9+
@Serializable
10+
data class ClanTagColor(
11+
val foregroundColor: SerializableTextColor = Clan.DEFAULT_CLAN_TAG_FOREGROUND_COLOR,
12+
val backgroundColor: SerializableTextColor = Clan.DEFAULT_CLAN_TAG_BACKGROUND_COLOR,
13+
val shadowColor: SerializableShadowColor = Clan.DEFAULT_CLAN_TAG_SHADOW_COLOR
14+
) {
15+
16+
@ConsistentCopyVisibility
17+
data class Update private constructor(
18+
internal val foreground: Field<TextColor>,
19+
internal val background: Field<TextColor>,
20+
internal val shadow: Field<ShadowColor>
21+
) {
22+
class Builder @PublishedApi internal constructor() {
23+
private var foreground: Field<TextColor> = Field.Unset
24+
private var background: Field<TextColor> = Field.Unset
25+
private var shadow: Field<ShadowColor> = Field.Unset
26+
27+
fun foreground(color: TextColor) {
28+
foreground = Field.Set(color)
29+
}
30+
31+
fun background(color: TextColor) {
32+
background = Field.Set(color)
33+
}
34+
35+
fun shadow(color: ShadowColor) {
36+
shadow = Field.Set(color)
37+
}
38+
39+
fun resetForeground() {
40+
foreground = Field.Reset
41+
}
42+
43+
fun resetBackground() {
44+
background = Field.Reset
45+
}
46+
47+
fun resetShadow() {
48+
shadow = Field.Reset
49+
}
50+
51+
@PublishedApi
52+
internal fun build() = Update(foreground, background, shadow)
53+
}
54+
}
55+
56+
companion object {
57+
inline fun update(block: Update.Builder.() -> Unit): Update {
58+
return Update.Builder().apply(block).build()
59+
}
60+
61+
fun withDefaultsAsFallback(
62+
foreground: TextColor?,
63+
background: TextColor?,
64+
shadow: ShadowColor?
65+
): ClanTagColor {
66+
return ClanTagColor(
67+
foregroundColor = foreground ?: Clan.DEFAULT_CLAN_TAG_FOREGROUND_COLOR,
68+
backgroundColor = background ?: Clan.DEFAULT_CLAN_TAG_BACKGROUND_COLOR,
69+
shadowColor = shadow ?: Clan.DEFAULT_CLAN_TAG_SHADOW_COLOR
70+
)
71+
}
72+
}
73+
74+
internal sealed interface Field<out T> {
75+
data object Unset : Field<Nothing>
76+
data object Reset : Field<Nothing>
77+
data class Set<T>(val value: T) : Field<T>
78+
}
79+
80+
fun applyUpdate(update: Update, defaults: ClanTagColor): ClanTagColor {
81+
fun <T> Field<T>.resolve(current: T, default: T): T =
82+
when (this) {
83+
Field.Unset -> current
84+
Field.Reset -> default
85+
is Field.Set -> value
86+
}
87+
88+
return copy(
89+
foregroundColor = update.foreground.resolve(foregroundColor, defaults.foregroundColor),
90+
backgroundColor = update.background.resolve(backgroundColor, defaults.backgroundColor),
91+
shadowColor = update.shadow.resolve(shadowColor, defaults.shadowColor),
92+
)
93+
}
94+
}

surf-clan-api/src/main/kotlin/dev/slne/clan/api/clan/ClanView.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import dev.slne.clan.api.invite.ClanInviteView
44
import dev.slne.clan.api.member.ClanMemberView
55
import dev.slne.clan.api.permission.ClanPermission
66
import net.kyori.adventure.text.Component
7+
import net.kyori.adventure.text.format.ShadowColor
78
import net.kyori.adventure.text.format.TextColor
89
import org.jetbrains.annotations.ApiStatus
910
import java.time.OffsetDateTime
@@ -53,10 +54,7 @@ interface ClanView {
5354
*/
5455
val discordInvite: String?
5556

56-
/**
57-
* The color used to display this clan's tag.
58-
*/
59-
val clanTagColor: TextColor
57+
val clanTagColor: ClanTagColor
6058

6159
/**
6260
* An immutable set of all members in this clan.

surf-clan-core/src/main/kotlin/dev/slne/clan/core/clan/AbstractClanView.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import dev.slne.clan.api.permission.ClanPermission
55
import dev.slne.clan.core.config.ClanConfig
66
import dev.slne.surf.bitmap.common.provider.BitmapProvider
77
import dev.slne.surf.core.api.common.surfCoreApi
8-
import dev.slne.surf.surfapi.core.api.messages.Colors
98
import net.kyori.adventure.text.Component
109
import java.util.*
1110

@@ -24,7 +23,12 @@ abstract class AbstractClanView : ClanView {
2423
}
2524

2625
override fun getRichClanTag(): Component {
27-
return BitmapProvider.translateToComponent(tag, Colors.WHITE, clanTagColor)
26+
return BitmapProvider.translateToComponent(
27+
tag,
28+
clanTagColor.foregroundColor,
29+
clanTagColor.backgroundColor,
30+
clanTagColor.shadowColor
31+
)
2832
}
2933

3034
override fun renderClanTag(minSize: Int): Component {

surf-clan-core/src/main/kotlin/dev/slne/clan/core/clan/ClanImpl.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
package dev.slne.clan.core.clan
22

33
import dev.slne.clan.api.clan.Clan
4+
import dev.slne.clan.api.clan.ClanTagColor
45
import dev.slne.clan.api.invite.ClanInvite
56
import dev.slne.clan.api.invite.ClanInviteResult
67
import dev.slne.clan.api.member.ClanMember
78
import dev.slne.clan.api.member.ClanMemberAddResult
89
import dev.slne.clan.api.member.ClanMemberRole
910
import dev.slne.clan.core.member.ClanMemberImpl
10-
import dev.slne.surf.surfapi.core.api.serializer.adventure.component.textcolor.SerializableTextColor
1111
import dev.slne.surf.surfapi.core.api.serializer.java.datetime.datetime.offset.SerializableOffsetDateTime
1212
import dev.slne.surf.surfapi.core.api.serializer.java.uuid.SerializableStringUUID
1313
import dev.slne.surf.surfapi.core.api.util.mutableObjectSetOf
1414
import kotlinx.serialization.Serializable
15-
import net.kyori.adventure.text.format.TextColor
1615
import java.util.*
1716

1817
@Serializable
@@ -22,12 +21,12 @@ data class ClanImpl(
2221
override val name: String,
2322
override val tag: String,
2423
override val createdByUuid: SerializableStringUUID,
24+
override var clanTagColor: ClanTagColor,
2525
override var description: String?,
2626
override var discordInvite: String?,
27-
override var clanTagColor: SerializableTextColor,
2827
override var members: Set<ClanMemberImpl>,
2928
override val updatedAt: SerializableOffsetDateTime,
30-
override val createdAt: SerializableOffsetDateTime
29+
override val createdAt: SerializableOffsetDateTime,
3130
) : AbstractClanView(), Clan {
3231
override suspend fun setDescription(description: String?) {
3332
CoreClanService.updateDescription(this, description)
@@ -37,8 +36,8 @@ data class ClanImpl(
3736
CoreClanService.updateDiscordInvite(this, discordInvite)
3837
}
3938

40-
override suspend fun setClanTagColor(color: TextColor) {
41-
CoreClanService.updateTagColor(this, color)
39+
override suspend fun changeClanTagColor(update: ClanTagColor.Update) {
40+
CoreClanService.updateTagColor(this, update)
4241
}
4342

4443
override suspend fun getPendingInvites(): Set<ClanInvite> {

surf-clan-core/src/main/kotlin/dev/slne/clan/core/clan/ClanViewImpl.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package dev.slne.clan.core.clan
22

3+
import dev.slne.clan.api.clan.ClanTagColor
34
import dev.slne.clan.api.invite.ClanInviteView
45
import dev.slne.clan.api.member.ClanMemberView
56
import dev.slne.surf.surfapi.core.api.util.mutableObjectSetOf
6-
import net.kyori.adventure.text.format.TextColor
77
import java.time.OffsetDateTime
88
import java.util.*
99

@@ -15,10 +15,10 @@ data class ClanViewImpl(
1515
override val createdByUuid: UUID,
1616
override val description: String?,
1717
override val discordInvite: String?,
18-
override val clanTagColor: TextColor,
18+
override val clanTagColor: ClanTagColor,
1919
override val members: Set<ClanMemberView>,
2020
override val updatedAt: OffsetDateTime,
21-
override val createdAt: OffsetDateTime
21+
override val createdAt: OffsetDateTime,
2222
) : AbstractClanView() {
2323
override suspend fun getPendingInvites(): Set<ClanInviteView> {
2424
return CoreClanService.fetchPendingInvites(this).mapTo(mutableObjectSetOf()) { it.view() }

surf-clan-core/src/main/kotlin/dev/slne/clan/core/clan/CoreClanService.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.slne.clan.core.clan
22

33
import dev.slne.clan.api.clan.ClanService
4+
import dev.slne.clan.api.clan.ClanTagColor
45
import dev.slne.clan.api.invite.ClanInviteResult
56
import dev.slne.clan.api.member.ClanMemberAddResult
67
import dev.slne.clan.api.member.ClanMemberRole
@@ -20,7 +21,7 @@ interface CoreClanService : ClanService {
2021

2122
suspend fun updateDescription(clan: ClanImpl, description: String?): Boolean
2223
suspend fun updateDiscordInvite(clan: ClanImpl, discordInvite: String?): Boolean
23-
suspend fun updateTagColor(clan: ClanImpl, tagColor: TextColor): Boolean
24+
suspend fun updateTagColor(clan: ClanImpl, update: ClanTagColor.Update): Boolean
2425

2526
suspend fun fetchPendingInvites(clan: AbstractClanView): Set<ClanInviteImpl>
2627
suspend fun invitePlayer(clan: ClanImpl, invitee: UUID, invitedBy: UUID): ClanInviteResult
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dev.slne.clan.paper.commands.arguments.color
2+
3+
import dev.jorel.commandapi.arguments.CustomArgument
4+
import dev.jorel.commandapi.arguments.TextArgument
5+
6+
abstract class BaseHexColorArgument<T>(
7+
nodeName: String,
8+
private val factory: (String) -> T?
9+
) : CustomArgument<T, String>(TextArgument(nodeName), { info ->
10+
11+
val normalized = HexColorParser.normalizeToRgba(info.currentInput())
12+
?: throw CustomArgumentException.fromMessageBuilder(
13+
MessageBuilder()
14+
.append("Invalid hex color: ")
15+
.appendArgInput()
16+
)
17+
18+
factory(normalized)
19+
?: throw CustomArgumentException.fromMessageBuilder(
20+
MessageBuilder()
21+
.append("Invalid hex color: ")
22+
.appendArgInput()
23+
)
24+
}) {
25+
init {
26+
replaceSuggestions(HexColorSuggestions.create())
27+
}
28+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package dev.slne.clan.paper.commands.arguments.color
2+
3+
import net.kyori.adventure.text.format.TextColor
4+
5+
class ClanTagHexColorArgument(nodeName: String) : BaseHexColorArgument<TextColor>(nodeName, TextColor::fromHexString)

0 commit comments

Comments
 (0)