Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import com.lambda.util.extension.CommandBuilder
object ConfigCommand : LambdaCommand(
name = "config",
aliases = setOf("cfg"),
usage = "config <save|load>",
usage = "config <save | load>",
description = "Save or load the configuration files"
) {
override fun CommandBuilder.create() {
Expand Down
155 changes: 155 additions & 0 deletions common/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright 2024 Lambda
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.lambda.command.commands

import com.lambda.Lambda.mc
import com.lambda.brigadier.CommandResult.Companion.failure
import com.lambda.brigadier.CommandResult.Companion.success
import com.lambda.brigadier.argument.literal
import com.lambda.brigadier.argument.string
import com.lambda.brigadier.argument.uuid
import com.lambda.brigadier.argument.value
import com.lambda.brigadier.execute
import com.lambda.brigadier.executeWithResult
import com.lambda.brigadier.required
import com.lambda.command.LambdaCommand
import com.lambda.config.configurations.FriendConfig
import com.lambda.friend.FriendManager
import com.lambda.util.Communication.info
import com.lambda.util.extension.CommandBuilder
import com.lambda.util.text.ClickEvents
import com.lambda.util.text.buildText
import com.lambda.util.text.literal
import com.lambda.util.text.styled
import java.awt.Color

object FriendCommand : LambdaCommand(
name = "friends",
usage = "friends <add | remove> <name | uuid>",
description = "Add or remove a friend"
) {
override fun CommandBuilder.create() {
execute {
this@FriendCommand.info(
buildText {
if (FriendManager.friends.isEmpty()) {
literal("You have no friends yet. Go make some! :3\n")
} else {
literal("Your friends (${FriendManager.friends.size}):\n")

FriendManager.friends.forEachIndexed { index, gameProfile ->
literal(" ${index + 1}. ${gameProfile.name}\n")
}
}

literal("\n")
styled(
color = Color.CYAN,
underlined = true,
clickEvent = ClickEvents.openFile(FriendConfig.primary.path),
) {
literal("Click to open your friends list as a file")
}
}
)
}

required(literal("add")) {
required(string("player name")) { player ->
suggests { _, builder ->
mc.networkHandler
?.playerList
?.filter { it.profile != mc.gameProfile }
?.map { it.profile.name }
?.forEach { builder.suggest(it) }

builder.buildFuture()
}

executeWithResult {
val name = player().value()
val id = mc.networkHandler
?.playerList
?.firstOrNull {
it.profile.name == name &&
it.profile != mc.gameProfile
} ?: return@executeWithResult failure("Could not find the player on the server")

return@executeWithResult if (FriendManager.befriend(id.profile)) {
this@FriendCommand.info(FriendManager.befriendedText(id.profile.name))
success()
} else {
failure("This player is already in your friend list")
}
}
}

required(uuid("player uuid")) { player ->
suggests { _, builder ->
mc.networkHandler
?.playerList
?.filter { it.profile != mc.gameProfile }
?.map { it.profile.id }
?.forEach { builder.suggest(it.toString()) }

builder.buildFuture()
}

executeWithResult {
val uuid = player().value()
val id = mc.networkHandler
?.playerList
?.firstOrNull {
it.profile.id == uuid && it.profile != mc.gameProfile
} ?: return@executeWithResult failure("Could not find the player on the server")

return@executeWithResult if (FriendManager.befriend(id.profile)) {
this@FriendCommand.info(FriendManager.befriendedText(id.profile.name))
success()
} else {
failure("This player is already in your friend list")
}
}
}
}

required(literal("remove")) {
required(string("player name")) { player ->
suggests { _, builder ->
FriendManager.friends.map { it.name }
.forEach { builder.suggest(it) }

builder.buildFuture()
}

executeWithResult {
val name = player().value()
val profile = FriendManager.gameProfile(name)
?: return@executeWithResult failure("This player is not in your friend list")

return@executeWithResult if (FriendManager.unfriend(profile)) {
this@FriendCommand.info(FriendManager.unfriendedText(name))
success()
} else {
failure("This player is not in your friend list")
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import com.lambda.util.extension.CommandBuilder

object ReplayCommand : LambdaCommand(
name = "replay",
usage = "replay <play|load|save|prune>",
usage = "replay <play | load | save | prune>",
description = "Play, load, save, or prune a replay"
) {
override fun CommandBuilder.create() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import com.lambda.util.extension.CommandBuilder

object TransferCommand : LambdaCommand(
name = "transfer",
usage = "transfer <move|cancel|undo> <item> <amount> <to>",
usage = "transfer <move | cancel | undo> <item> <amount> <to>",
description = "Transfer items from anywhere to anywhere",
) {
private var lastTransfer: TransferResult.Transfer? = null
Expand Down
4 changes: 4 additions & 0 deletions common/src/main/kotlin/com/lambda/config/AbstractSetting.kt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ abstract class AbstractSetting<T : Any>(
value = gson.fromJson(serialized, type)
}

/**
* Will only register changes of the variable, not the content of the variable!
* 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.
*/
fun onValueChange(block: SafeContext.(from: T, to: T) -> Unit) {
listeners.add(ValueListener(true) { from, to ->
runSafe {
Expand Down
3 changes: 0 additions & 3 deletions common/src/main/kotlin/com/lambda/config/Configurable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,6 @@ abstract class Configurable(
* @param name The unique identifier for the setting.
* @param defaultValue The default [Set] value of type [T] for the setting.
* @param description A brief explanation of the setting's purpose and behavior.
* @param hackDelegates A flag that determines whether the setting should be serialized with the default value.
* @param visibility A lambda expression that determines the visibility status of the setting.
*
* ```kotlin
Expand All @@ -262,14 +261,12 @@ abstract class Configurable(
name: String,
defaultValue: Set<T>,
description: String = "",
hackDelegates: Boolean = false,
noinline visibility: () -> Boolean = { true },
) = SetSetting(
name,
defaultValue.toMutableSet(),
TypeToken.getParameterized(MutableSet::class.java, T::class.java).type,
description,
hackDelegates,
visibility,
).also {
settings.add(it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

package com.lambda.config.settings.collections

import com.google.gson.JsonElement
import com.lambda.Lambda.gson
import com.lambda.config.AbstractSetting
import java.lang.reflect.Type

Expand All @@ -27,27 +25,13 @@ import java.lang.reflect.Type
*/
class SetSetting<T : Any>(
override val name: String,
private val defaultValue: MutableSet<T>,
defaultValue: MutableSet<T>,
type: Type,
description: String,
private val hackDelegates: Boolean,
visibility: () -> Boolean,
) : AbstractSetting<MutableSet<T>>(
defaultValue,
type,
description,
visibility
) {
override fun toJson(): JsonElement {
return if (hackDelegates) gson.toJsonTree(defaultValue, type)
else super.toJson()
}

override fun loadFromJson(serialized: JsonElement) {
if (hackDelegates) {
defaultValue.addAll(gson.fromJson(serialized, type))
setValue(this, ::value, defaultValue.distinct().toMutableSet())
}
else super.loadFromJson(serialized)
}
}
)
57 changes: 45 additions & 12 deletions common/src/main/kotlin/com/lambda/friend/FriendManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,68 @@ package com.lambda.friend
import com.lambda.config.Configurable
import com.lambda.config.configurations.FriendConfig
import com.lambda.core.Loadable
import com.lambda.util.text.*
import com.mojang.authlib.GameProfile
import net.minecraft.client.network.OtherClientPlayerEntity
import net.minecraft.text.Text
import java.awt.Color
import java.util.*

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

fun add(profile: GameProfile) { if (!contains(profile)) friends.add(profile) }
fun befriend(profile: GameProfile) = friends.add(profile)
fun unfriend(profile: GameProfile): Boolean = friends.remove(profile)

fun remove(profile: GameProfile) { friends.remove(profile) }
fun gameProfile(name: String) = friends.firstOrNull { it.name == name }
fun gameProfile(uuid: UUID) = friends.firstOrNull { it.id == uuid }

fun get(name: String) = friends.firstOrNull { it.name == name }
fun get(uuid: UUID) = friends.firstOrNull { it.id == uuid }

fun contains(profile: GameProfile) = friends.contains(profile)
fun contains(name: String) = friends.any { it.name == name }
fun contains(uuid: UUID) = friends.any { it.id == uuid }
fun isFriend(profile: GameProfile) = friends.contains(profile)
fun isFriend(name: String) = friends.any { it.name == name }
fun isFriend(uuid: UUID) = friends.any { it.id == uuid }

fun clear() = friends.clear()

val OtherClientPlayerEntity.isFriend: Boolean
get() = contains(gameProfile)
get() = isFriend(gameProfile)

fun OtherClientPlayerEntity.befriend() = add(gameProfile)
fun OtherClientPlayerEntity.unfriend() = remove(gameProfile)
fun OtherClientPlayerEntity.befriend() = befriend(gameProfile)
fun OtherClientPlayerEntity.unfriend() = unfriend(gameProfile)

override fun load(): String {
// TODO: Because the settings are loaded after the property and the loadables, the friend list is empty at that point
return "Loaded ${friends.size} friends"
}

fun befriendedText(name: String): Text = befriendedText(Text.of(name))
fun befriendedText(name: Text) = buildText {
literal(Color.GREEN, "Added ")
text(name)
literal(" to your friend list ")
clickEvent(ClickEvents.suggestCommand(";friends remove ${name.string}")) {
styled(underlined = true, color = Color.LIGHT_GRAY) {
literal("[Undo]")
}
}
}

fun unfriendedText(name: String): Text = unfriendedText(Text.of(name))
fun unfriendedText(name: Text) = buildText {
literal(Color.RED, "Removed ")
text(name)
literal(" from your friend list ")
clickEvent(ClickEvents.suggestCommand(";friends add ${name.string}")) {
styled(underlined = true, color = Color.LIGHT_GRAY) {
literal("[Undo]")
}
}
}
}
Loading
Loading