Skip to content

Commit f9d6b60

Browse files
emyfopsAvanatiker
andauthored
[1.20.4] Feat: Click Friend (#88)
Allow the player to add another player as a friend using any mouse button and unfriend using any key combination with the mouse --------- Co-authored-by: Constructor <[email protected]>
1 parent b4ff344 commit f9d6b60

File tree

10 files changed

+300
-36
lines changed

10 files changed

+300
-36
lines changed

common/src/main/kotlin/com/lambda/command/commands/ConfigCommand.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import com.lambda.util.extension.CommandBuilder
2929
object ConfigCommand : LambdaCommand(
3030
name = "config",
3131
aliases = setOf("cfg"),
32-
usage = "config <save|load>",
32+
usage = "config <save | load>",
3333
description = "Save or load the configuration files"
3434
) {
3535
override fun CommandBuilder.create() {
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Copyright 2024 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.command.commands
19+
20+
import com.lambda.Lambda.mc
21+
import com.lambda.brigadier.CommandResult.Companion.failure
22+
import com.lambda.brigadier.CommandResult.Companion.success
23+
import com.lambda.brigadier.argument.literal
24+
import com.lambda.brigadier.argument.string
25+
import com.lambda.brigadier.argument.uuid
26+
import com.lambda.brigadier.argument.value
27+
import com.lambda.brigadier.execute
28+
import com.lambda.brigadier.executeWithResult
29+
import com.lambda.brigadier.required
30+
import com.lambda.command.LambdaCommand
31+
import com.lambda.config.configurations.FriendConfig
32+
import com.lambda.friend.FriendManager
33+
import com.lambda.util.Communication.info
34+
import com.lambda.util.extension.CommandBuilder
35+
import com.lambda.util.text.ClickEvents
36+
import com.lambda.util.text.buildText
37+
import com.lambda.util.text.literal
38+
import com.lambda.util.text.styled
39+
import java.awt.Color
40+
41+
object FriendCommand : LambdaCommand(
42+
name = "friends",
43+
usage = "friends <add | remove> <name | uuid>",
44+
description = "Add or remove a friend"
45+
) {
46+
override fun CommandBuilder.create() {
47+
execute {
48+
this@FriendCommand.info(
49+
buildText {
50+
if (FriendManager.friends.isEmpty()) {
51+
literal("You have no friends yet. Go make some! :3\n")
52+
} else {
53+
literal("Your friends (${FriendManager.friends.size}):\n")
54+
55+
FriendManager.friends.forEachIndexed { index, gameProfile ->
56+
literal(" ${index + 1}. ${gameProfile.name}\n")
57+
}
58+
}
59+
60+
literal("\n")
61+
styled(
62+
color = Color.CYAN,
63+
underlined = true,
64+
clickEvent = ClickEvents.openFile(FriendConfig.primary.path),
65+
) {
66+
literal("Click to open your friends list as a file")
67+
}
68+
}
69+
)
70+
}
71+
72+
required(literal("add")) {
73+
required(string("player name")) { player ->
74+
suggests { _, builder ->
75+
mc.networkHandler
76+
?.playerList
77+
?.filter { it.profile != mc.gameProfile }
78+
?.map { it.profile.name }
79+
?.forEach { builder.suggest(it) }
80+
81+
builder.buildFuture()
82+
}
83+
84+
executeWithResult {
85+
val name = player().value()
86+
val id = mc.networkHandler
87+
?.playerList
88+
?.firstOrNull {
89+
it.profile.name == name &&
90+
it.profile != mc.gameProfile
91+
} ?: return@executeWithResult failure("Could not find the player on the server")
92+
93+
return@executeWithResult if (FriendManager.befriend(id.profile)) {
94+
this@FriendCommand.info(FriendManager.befriendedText(id.profile.name))
95+
success()
96+
} else {
97+
failure("This player is already in your friend list")
98+
}
99+
}
100+
}
101+
102+
required(uuid("player uuid")) { player ->
103+
suggests { _, builder ->
104+
mc.networkHandler
105+
?.playerList
106+
?.filter { it.profile != mc.gameProfile }
107+
?.map { it.profile.id }
108+
?.forEach { builder.suggest(it.toString()) }
109+
110+
builder.buildFuture()
111+
}
112+
113+
executeWithResult {
114+
val uuid = player().value()
115+
val id = mc.networkHandler
116+
?.playerList
117+
?.firstOrNull {
118+
it.profile.id == uuid && it.profile != mc.gameProfile
119+
} ?: return@executeWithResult failure("Could not find the player on the server")
120+
121+
return@executeWithResult if (FriendManager.befriend(id.profile)) {
122+
this@FriendCommand.info(FriendManager.befriendedText(id.profile.name))
123+
success()
124+
} else {
125+
failure("This player is already in your friend list")
126+
}
127+
}
128+
}
129+
}
130+
131+
required(literal("remove")) {
132+
required(string("player name")) { player ->
133+
suggests { _, builder ->
134+
FriendManager.friends.map { it.name }
135+
.forEach { builder.suggest(it) }
136+
137+
builder.buildFuture()
138+
}
139+
140+
executeWithResult {
141+
val name = player().value()
142+
val profile = FriendManager.gameProfile(name)
143+
?: return@executeWithResult failure("This player is not in your friend list")
144+
145+
return@executeWithResult if (FriendManager.unfriend(profile)) {
146+
this@FriendCommand.info(FriendManager.unfriendedText(name))
147+
success()
148+
} else {
149+
failure("This player is not in your friend list")
150+
}
151+
}
152+
}
153+
}
154+
}
155+
}

common/src/main/kotlin/com/lambda/command/commands/ReplayCommand.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import com.lambda.util.extension.CommandBuilder
3333

3434
object ReplayCommand : LambdaCommand(
3535
name = "replay",
36-
usage = "replay <play|load|save|prune>",
36+
usage = "replay <play | load | save | prune>",
3737
description = "Play, load, save, or prune a replay"
3838
) {
3939
override fun CommandBuilder.create() {

common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import com.lambda.util.extension.CommandBuilder
3232

3333
object TransferCommand : LambdaCommand(
3434
name = "transfer",
35-
usage = "transfer <move|cancel|undo> <item> <amount> <to>",
35+
usage = "transfer <move | cancel | undo> <item> <amount> <to>",
3636
description = "Transfer items from anywhere to anywhere",
3737
) {
3838
private var lastTransfer: TransferResult.Transfer? = null

common/src/main/kotlin/com/lambda/config/AbstractSetting.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ abstract class AbstractSetting<T : Any>(
100100
value = gson.fromJson(serialized, type)
101101
}
102102

103+
/**
104+
* Will only register changes of the variable, not the content of the variable!
105+
* E.g., if the variable is a list, it will only register if the list reference changes, not if the content of the list changes.
106+
*/
103107
fun onValueChange(block: SafeContext.(from: T, to: T) -> Unit) {
104108
listeners.add(ValueListener(true) { from, to ->
105109
runSafe {

common/src/main/kotlin/com/lambda/config/Configurable.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,6 @@ abstract class Configurable(
248248
* @param name The unique identifier for the setting.
249249
* @param defaultValue The default [Set] value of type [T] for the setting.
250250
* @param description A brief explanation of the setting's purpose and behavior.
251-
* @param hackDelegates A flag that determines whether the setting should be serialized with the default value.
252251
* @param visibility A lambda expression that determines the visibility status of the setting.
253252
*
254253
* ```kotlin
@@ -262,14 +261,12 @@ abstract class Configurable(
262261
name: String,
263262
defaultValue: Set<T>,
264263
description: String = "",
265-
hackDelegates: Boolean = false,
266264
noinline visibility: () -> Boolean = { true },
267265
) = SetSetting(
268266
name,
269267
defaultValue.toMutableSet(),
270268
TypeToken.getParameterized(MutableSet::class.java, T::class.java).type,
271269
description,
272-
hackDelegates,
273270
visibility,
274271
).also {
275272
settings.add(it)

common/src/main/kotlin/com/lambda/config/settings/collections/SetSetting.kt

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717

1818
package com.lambda.config.settings.collections
1919

20-
import com.google.gson.JsonElement
21-
import com.lambda.Lambda.gson
2220
import com.lambda.config.AbstractSetting
2321
import java.lang.reflect.Type
2422

@@ -27,27 +25,13 @@ import java.lang.reflect.Type
2725
*/
2826
class SetSetting<T : Any>(
2927
override val name: String,
30-
private val defaultValue: MutableSet<T>,
28+
defaultValue: MutableSet<T>,
3129
type: Type,
3230
description: String,
33-
private val hackDelegates: Boolean,
3431
visibility: () -> Boolean,
3532
) : AbstractSetting<MutableSet<T>>(
3633
defaultValue,
3734
type,
3835
description,
3936
visibility
40-
) {
41-
override fun toJson(): JsonElement {
42-
return if (hackDelegates) gson.toJsonTree(defaultValue, type)
43-
else super.toJson()
44-
}
45-
46-
override fun loadFromJson(serialized: JsonElement) {
47-
if (hackDelegates) {
48-
defaultValue.addAll(gson.fromJson(serialized, type))
49-
setValue(this, ::value, defaultValue.distinct().toMutableSet())
50-
}
51-
else super.loadFromJson(serialized)
52-
}
53-
}
37+
)

common/src/main/kotlin/com/lambda/friend/FriendManager.kt

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,68 @@ package com.lambda.friend
2020
import com.lambda.config.Configurable
2121
import com.lambda.config.configurations.FriendConfig
2222
import com.lambda.core.Loadable
23+
import com.lambda.util.text.*
2324
import com.mojang.authlib.GameProfile
2425
import net.minecraft.client.network.OtherClientPlayerEntity
26+
import net.minecraft.text.Text
27+
import java.awt.Color
2528
import java.util.*
2629

30+
// ToDo:
31+
// - Allow adding of offline players by name or uuid.
32+
// - Should store the data until the player was seen.
33+
// Either no UUID but with name or no name but with uuid or both.
34+
// -> Should update the record if the player was seen again.
35+
// - Handle player changing names.
36+
// - Improve save file structure.
2737
object FriendManager : Configurable(FriendConfig), Loadable {
2838
override val name = "friends"
29-
val friends by setting("friends", listOf<GameProfile>(), hackDelegates = true)
39+
val friends by setting("friends", setOf<GameProfile>())
3040

31-
fun add(profile: GameProfile) { if (!contains(profile)) friends.add(profile) }
41+
fun befriend(profile: GameProfile) = friends.add(profile)
42+
fun unfriend(profile: GameProfile): Boolean = friends.remove(profile)
3243

33-
fun remove(profile: GameProfile) { friends.remove(profile) }
44+
fun gameProfile(name: String) = friends.firstOrNull { it.name == name }
45+
fun gameProfile(uuid: UUID) = friends.firstOrNull { it.id == uuid }
3446

35-
fun get(name: String) = friends.firstOrNull { it.name == name }
36-
fun get(uuid: UUID) = friends.firstOrNull { it.id == uuid }
37-
38-
fun contains(profile: GameProfile) = friends.contains(profile)
39-
fun contains(name: String) = friends.any { it.name == name }
40-
fun contains(uuid: UUID) = friends.any { it.id == uuid }
47+
fun isFriend(profile: GameProfile) = friends.contains(profile)
48+
fun isFriend(name: String) = friends.any { it.name == name }
49+
fun isFriend(uuid: UUID) = friends.any { it.id == uuid }
4150

4251
fun clear() = friends.clear()
4352

4453
val OtherClientPlayerEntity.isFriend: Boolean
45-
get() = contains(gameProfile)
54+
get() = isFriend(gameProfile)
4655

47-
fun OtherClientPlayerEntity.befriend() = add(gameProfile)
48-
fun OtherClientPlayerEntity.unfriend() = remove(gameProfile)
56+
fun OtherClientPlayerEntity.befriend() = befriend(gameProfile)
57+
fun OtherClientPlayerEntity.unfriend() = unfriend(gameProfile)
4958

5059
override fun load(): String {
5160
// TODO: Because the settings are loaded after the property and the loadables, the friend list is empty at that point
5261
return "Loaded ${friends.size} friends"
5362
}
63+
64+
fun befriendedText(name: String): Text = befriendedText(Text.of(name))
65+
fun befriendedText(name: Text) = buildText {
66+
literal(Color.GREEN, "Added ")
67+
text(name)
68+
literal(" to your friend list ")
69+
clickEvent(ClickEvents.suggestCommand(";friends remove ${name.string}")) {
70+
styled(underlined = true, color = Color.LIGHT_GRAY) {
71+
literal("[Undo]")
72+
}
73+
}
74+
}
75+
76+
fun unfriendedText(name: String): Text = unfriendedText(Text.of(name))
77+
fun unfriendedText(name: Text) = buildText {
78+
literal(Color.RED, "Removed ")
79+
text(name)
80+
literal(" from your friend list ")
81+
clickEvent(ClickEvents.suggestCommand(";friends add ${name.string}")) {
82+
styled(underlined = true, color = Color.LIGHT_GRAY) {
83+
literal("[Undo]")
84+
}
85+
}
86+
}
5487
}

0 commit comments

Comments
 (0)