2 Commits

Author SHA1 Message Date
skippyall f8eb1578b2 Back to the Future
Fun is still suspended though
2026-05-09 17:04:27 +02:00
skippyall 48a38b87c5 Rename .java to .kt 2026-05-09 17:04:27 +02:00
40 changed files with 352 additions and 484 deletions
+1
View File
@@ -1,6 +1,7 @@
# User-specific stuff
.idea/
urlaub/
.kotlin/
*.iml
*.ipr
@@ -51,7 +51,7 @@ public abstract class InstructionBoundBlock extends Block implements EntityBlock
}
world.getBlockEntity(pos, getBlockEntityType()).ifPresent(be -> {
String name = MinionPersistentState.get(world.getServer()).getMinionData(be.getMinionUuid()).name();
String name = MinionPersistentState.get(world.getServer()).getMinionData(be.getMinionUuid()).getName();
player.sendSystemMessage(Component.translatable("minions.reference.instruction.tooltip", be.getInstructionName(), name));
});
return InteractionResult.SUCCESS;
@@ -18,7 +18,7 @@ public class ListSubcommand {
public static int list(CommandContext<CommandSourceStack> context) {
Collection<MinionData> minions = MinionPersistentState.get(context.getSource().getServer()).getMinionData().values();
for (MinionData minion : minions) {
context.getSource().sendSuccess(() -> Component.literal(minion.name() + "(" + minion.uuid() + "):" + minion.isSpawned()), false);
context.getSource().sendSuccess(() -> Component.literal(minion.getName() + "(" + minion.getUuid() + "):" + minion.isSpawned()), false);
}
return 0;
}
@@ -41,7 +41,7 @@ public class MinionArgument {
@Override
public CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) throws CommandSyntaxException {
for (MinionData data : MinionPersistentState.get(context.getSource().getServer()).getMinionDataList()) {
builder.suggest(data.name());
builder.suggest(data.getName());
}
return builder.buildFuture();
@@ -27,9 +27,10 @@ import java.util.UUID;
public interface GuiDisplay {
Codec<GuiDisplay> CODEC = MinionRegistries.GUI_DISPLAY_TYPE.byNameCodec().dispatch(GuiDisplay::getCodec, codec -> codec.fieldOf("data"));
GuiDisplay DEFAULT_DISPLAY = new ItemBased(Items.BARRIER);
TooltipDisplay TOOLTIP_DISPLAY = createTooltipDisplay();
private static TooltipDisplay createTooltipDisplay() {
TooltipDisplay TOOLTIP_HIDE_ALL_COMPONENTS = createHideAllTooltip();
private static TooltipDisplay createHideAllTooltip() {
LinkedHashSet<DataComponentType<?>> set = new LinkedHashSet<>();
for(DataComponentType<?> type : BuiltInRegistries.DATA_COMPONENT_TYPE) {
if(type != DataComponents.LORE) {
@@ -106,7 +107,7 @@ public interface GuiDisplay {
@Override
public ItemStackTemplate createItemStackTemplate() {
return new ItemStackTemplate(item, DataComponentPatch.builder()
.set(DataComponents.TOOLTIP_DISPLAY, TOOLTIP_DISPLAY)
.set(DataComponents.TOOLTIP_DISPLAY, TOOLTIP_HIDE_ALL_COMPONENTS)
.set(DataComponents.RARITY, Rarity.COMMON)
.build());
}
@@ -130,6 +131,7 @@ public interface GuiDisplay {
public ItemStackTemplate createItemStackTemplate() {
return new ItemStackTemplate(Items.PLAYER_HEAD, DataComponentPatch.builder()
.set(DataComponents.PROFILE, ResolvableProfile.createUnresolved(uuid))
.set(DataComponents.TOOLTIP_DISPLAY, TOOLTIP_HIDE_ALL_COMPONENTS)
.build()
);
}
@@ -10,6 +10,7 @@ import io.github.skippyall.minions.minion.MinionProfileUtils
import io.github.skippyall.minions.minion.skin.SkinProvider
import io.github.skippyall.minions.registration.MinionRegistries
import io.github.skippyall.minions.registration.SkinProviders
import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
import net.fabricmc.fabric.api.entity.FakePlayer
import net.minecraft.core.component.DataComponents
@@ -20,8 +21,6 @@ import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.component.ResolvableProfile
import java.util.Optional
import java.util.function.Consumer
import java.util.function.Function
class MinionLookGui(
viewer: ServerPlayer,
@@ -82,20 +81,24 @@ class MinionLookGui(
gui.setSlot(16, builder)
}
private fun updateSkinProvider() {
gui.setSlot(
25, GuiElementBuilder()
.setItem(Items.GREEN_STAINED_GLASS_PANE)
.setComponent(DataComponents.CUSTOM_NAME, currentSkinProvider.getDisplayName())
.setCallback(Runnable { this.cycleSkinProvider() })
)
}
fun openSkinGui() {
currentSkinProvider.openSkinMenu(this)
.thenCompose(Function { profile: ResolvableProfile? ->
profile!!.resolveProfile(
viewer.level().server.services().profileResolver()
)
})
.thenAccept(Consumer { skin: GameProfile? ->
MinionItem.setData(
viewer.level().server, this.data.withSkin(
Optional.of(skin!!.properties())
), minionItem
)
})
scope.launch {
val profile = currentSkinProvider.openSkinMenu(this@MinionLookGui).await()
val skin = profile.resolveProfile(viewer.level().server.services().profileResolver()).await()
data.skin = Optional.ofNullable(skin?.properties())
updateSkin()
}
}
private fun cycleSkinProvider() {
@@ -109,15 +112,6 @@ class MinionLookGui(
updateSkinProvider()
}
private fun updateSkinProvider() {
gui.setSlot(
25, GuiElementBuilder()
.setItem(Items.GREEN_STAINED_GLASS_PANE)
.setComponent(DataComponents.CUSTOM_NAME, currentSkinProvider.getDisplayName())
.setCallback(Runnable { this.cycleSkinProvider() })
)
}
fun openRenameGui() {
scope.launch {
val newName = TextInput.input(
@@ -126,10 +120,11 @@ class MinionLookGui(
"Minion",
) { name ->
MinionProfileUtils.checkMinionNameWithoutPrefix(viewer.level().server, name)
}
}.await()
if(newName != null) {
this@MinionLookGui.data.withName(newName)
data.name = newName
updateName()
}
}
}
@@ -38,7 +38,7 @@ abstract class MinionsGui {
protected abstract fun open()
protected fun reopen() {
protected open fun reopen() {
open()
}
@@ -4,26 +4,26 @@ import eu.pb4.sgui.api.elements.GuiElementBuilder
import eu.pb4.sgui.api.gui.SimpleGui
import io.github.skippyall.minions.gui.MinionsGui
import io.github.skippyall.minions.gui.minion.SimpleMinionsGui
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.future.asCompletableFuture
import net.minecraft.network.chat.Component
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.item.Items
import java.util.concurrent.CompletableFuture
object BooleanInput {
@JvmStatic
@JvmOverloads
fun confirm(
parent: MinionsGui,
title: Component,
falseText: Component = Component.translatable("minions.gui.abort"),
trueText: Component = Component.translatable("minions.gui.confirm")
): CompletableDeferred<Boolean> {
val deferred = CompletableDeferred<Boolean>()
): CompletableFuture<Boolean> {
val future = CompletableFuture<Boolean>()
SimpleMinionsGui(parent) { onClose: Runnable, me: SimpleMinionsGui ->
val gui: SimpleGui = object : SimpleGui(MenuType.GENERIC_3x3, parent.viewer, false) {
override fun onPlayerClose(success: Boolean) {
deferred.complete(false)
future.complete(false)
onClose.run()
}
}
@@ -33,7 +33,7 @@ object BooleanInput {
3, GuiElementBuilder(Items.REDSTONE_BLOCK)
.setName(falseText)
.setCallback(Runnable {
deferred.complete(false)
future.complete(false)
me.goBack()
})
)
@@ -42,7 +42,7 @@ object BooleanInput {
5, GuiElementBuilder(Items.EMERALD_BLOCK)
.setName(trueText)
.setCallback(Runnable {
deferred.complete(true)
future.complete(true)
me.goBack()
})
)
@@ -50,22 +50,6 @@ object BooleanInput {
gui.open()
gui
}
return deferred
}
@JvmStatic
@JvmOverloads
fun confirmFuture(
parent: MinionsGui,
title: Component,
falseText: Component = Component.translatable("minions.gui.abort"),
trueText: Component = Component.translatable("minions.gui.confirm")
): CompletableFuture<Boolean> {
return confirm(
parent,
title,
falseText,
trueText
).asCompletableFuture()
return future
}
}
@@ -8,7 +8,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public interface Result<T, E> {
public sealed interface Result<T, E> permits Result.Success, Result.Error {
static <T> Result<T, String> wrap(UnsafeOperation<T> toWrap) {
return wrapCustomError(toWrap, Exception::getMessage);
}
@@ -3,9 +3,6 @@ package io.github.skippyall.minions.gui.input
import eu.pb4.sgui.api.elements.GuiElementBuilder
import eu.pb4.sgui.api.gui.AnvilInputGui
import io.github.skippyall.minions.gui.MinionsGui
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.launch
import net.minecraft.network.chat.Component
import net.minecraft.world.inventory.AnvilMenu
@@ -29,10 +26,9 @@ class TextInput<T>(
private lateinit var gui: AnvilInputGui
private var result: Result<T, Component>? = null
val deferred = CompletableDeferred<T?>()
val future = CompletableFuture<T?>()
init {
updateConfirmButton(defaultValue)
open()
}
@@ -44,14 +40,15 @@ class TextInput<T>(
override fun onPlayerClose(success: Boolean) {
onBackingClosed()
if (deferred.isActive) {
deferred.complete(null)
if (!future.isDone) {
future.complete(null)
}
}
}
gui.setTitle(title)
gui.setDefaultInputValue(defaultValue)
updateConfirmButton(defaultValue)
gui.open()
}
@@ -74,7 +71,8 @@ class TextInput<T>(
fun onConfirm() {
result?.ifSuccess { success: T ->
deferred.complete(success)
future.complete(success)
goBack()
}
}
@@ -85,7 +83,7 @@ class TextInput<T>(
title: Component,
defaultValue: String,
parser: suspend (String) -> Result<T, Component>,
): Deferred<T?> {
): CompletableFuture<T?> {
val input = TextInput(
parent = gui,
title = title,
@@ -93,22 +91,7 @@ class TextInput<T>(
parser = parser,
)
return input.deferred
}
@JvmStatic
fun <T>inputFuture(
gui: MinionsGui,
title: Component,
defaultValue: String,
parser: (String) -> Result<T, Component>,
): CompletableFuture<T?> {
return input(
gui = gui,
title = title,
defaultValue = defaultValue,
parser = parser
).asCompletableFuture()
return input.future
}
@JvmStatic
@@ -116,7 +99,7 @@ class TextInput<T>(
gui: MinionsGui,
title: Component,
defaultValue: String,
): Deferred<String?> {
): CompletableFuture<String?> {
return input<String>(
gui = gui,
title = title,
@@ -125,25 +108,12 @@ class TextInput<T>(
)
}
@JvmStatic
fun inputStringFuture(
gui: MinionsGui,
title: Component,
defaultValue: String,
): CompletableFuture<String?> {
return inputString(
gui = gui,
title = title,
defaultValue = defaultValue,
).asCompletableFuture()
}
@JvmStatic
fun inputLong(
gui: MinionsGui,
title: Component,
defaultValue: Long,
): Deferred<Long?> {
): CompletableFuture<Long?> {
return input<Long>(
gui = gui,
title = title,
@@ -157,25 +127,12 @@ class TextInput<T>(
)
}
@JvmStatic
fun inputLongFuture(
gui: MinionsGui,
title: Component,
defaultValue: Long,
): CompletableFuture<Long?> {
return inputLong(
gui = gui,
title = title,
defaultValue = defaultValue,
).asCompletableFuture()
}
@JvmStatic
fun inputDouble(
gui: MinionsGui,
title: Component,
defaultValue: Double,
): Deferred<Double?> {
): CompletableFuture<Double?> {
return input<Double>(
gui = gui,
title = title,
@@ -188,18 +145,5 @@ class TextInput<T>(
},
)
}
@JvmStatic
fun inputDoubleFuture(
gui: MinionsGui,
title: Component,
defaultValue: Double,
): CompletableFuture<Double?> {
return inputDouble(
gui = gui,
title = title,
defaultValue = defaultValue,
).asCompletableFuture()
}
}
}
@@ -7,7 +7,6 @@ import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.PaginatedList;
import io.github.skippyall.minions.gui.minion.GuiContext;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.supplier.Parameter;
import io.github.skippyall.minions.program.supplier.ValueSupplier;
@@ -60,7 +60,7 @@ public class ConfigureInstructionGui extends MinionsGui implements ConfiguredIns
gui.setSlot(7, new GuiElementBuilder(Items.LAVA_BUCKET)
.setName(Component.translatable("minions.gui.instruction.configure.delete"))
.setCallback(() -> BooleanInput.confirmFuture(this, Component.translatable("minions.gui.instruction.configure.delete.confirm", name))
.setCallback(() -> BooleanInput.confirm(this, Component.translatable("minions.gui.instruction.configure.delete.confirm", name))
.thenAccept((confirmed) -> {
if(confirmed) {
minion.getInstructionManager().removeInstruction(name);
@@ -28,7 +28,6 @@ public class ConverterGui extends MinionsGui {
public ConverterGui(MinionsGui parent, @Nullable ValueConverter<?,?> converter, ValueType<?> from, ValueType<?> to, ConverterList list, boolean isNew, int index) {
super(parent);
open();
this.converter = converter;
if(converter != null) {
this.valueConverterType = converter.getType();
@@ -38,6 +37,8 @@ public class ConverterGui extends MinionsGui {
this.list = list;
this.isNew = isNew;
this.index = index;
open();
}
@Override
@@ -54,7 +54,7 @@ public class ConverterListGui extends MinionsGui {
gui.setSlot(23, new GuiElementBuilder(Items.ARROW)
.setCallback(() -> {
if(page * 4 + 4 < converters.getConverters().size()) {
if(page * 4 + 4 < converters.getConverters().size() + 1) {
page++;
updateConverters();
}
@@ -67,6 +67,10 @@ public class ConverterListGui extends MinionsGui {
}
public void updateConverters() {
for(int slot = 9; slot < 18; slot++) {
gui.clearSlot(slot);
}
int lastConverter = Math.min(5, converters.getConverters().size() + 2 - page * 4);
for(int i = 0; i < lastConverter; i++) {
//Each page has 5 converters, but the last is displayed on the next page as well
@@ -73,7 +73,7 @@ public class InstructionGui {
}
public static CompletableFuture<String> inputInstructionName(MinionsGui parent, GuiContext.Minion context, String defaultValue) {
return TextInput.inputFuture(parent, Component.translatable("minions.gui.instruction.enter_name"), defaultValue, name -> {
return TextInput.input(parent, Component.translatable("minions.gui.instruction.enter_name"), defaultValue, (name, _) -> {
if (context.getMinion().getInstructionManager().hasInstruction(name)) {
return new Result.Error<>(Component.translatable("minions.gui.instruction.name_already_used"));
}
@@ -1,43 +0,0 @@
package io.github.skippyall.minions.gui.minion;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.supplier.Parameter;
import net.minecraft.server.level.ServerPlayer;
public interface GuiContext {
ServerPlayer getViewer();
static GuiContext create(ServerPlayer viewer) {
return new GuiContextImpl(viewer);
}
interface Minion extends GuiContext {
MinionFakePlayer getMinion();
static GuiContext.Minion create(GuiContext context, MinionFakePlayer minion) {
return new GuiContextImpl.MinionImpl(context, minion);
}
}
interface Instruction extends Minion {
ConfiguredInstruction<MinionRuntime> getInstruction();
String getName();
void setName(String name);
static GuiContext.Instruction create(GuiContext.Minion context, ConfiguredInstruction<MinionRuntime> instruction, String name) {
return new GuiContextImpl.InstructionImpl(context, instruction, name);
}
}
interface ValueSupplier extends Instruction {
Parameter<?> getParameter();
static GuiContext.ValueSupplier create(GuiContext.Instruction context, Parameter<?> parameter) {
return new GuiContextImpl.ValueSupplierImpl(context, parameter);
}
}
}
@@ -0,0 +1,64 @@
package io.github.skippyall.minions.gui.minion
import io.github.skippyall.minions.gui.minion.GuiContextImpl.*
import io.github.skippyall.minions.minion.MinionRuntime
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction
import io.github.skippyall.minions.program.supplier.Parameter
import net.minecraft.server.level.ServerPlayer
interface GuiContext {
val viewer: ServerPlayer
companion object {
@JvmStatic
fun create(viewer: ServerPlayer): GuiContext {
return GuiContextImpl(viewer)
}
}
interface Minion : GuiContext {
val minion: MinionFakePlayer
companion object {
@JvmStatic
fun create(context: GuiContext, minion: MinionFakePlayer): Minion {
return MinionImpl(
if(context is MinionImpl) context.context else context,
minion
)
}
}
}
interface Instruction : Minion {
val instruction: ConfiguredInstruction<MinionRuntime>
var name: String
companion object {
@JvmStatic
fun create(context: Minion, instruction: ConfiguredInstruction<MinionRuntime>, name: String): Instruction {
return InstructionImpl(
if(context is InstructionImpl) context.context else context,
instruction,
name
)
}
}
}
interface ValueSupplier : Instruction {
val parameter: Parameter<*>
companion object {
@JvmStatic
fun create(context: Instruction, parameter: Parameter<*>): ValueSupplier {
return ValueSupplierImpl(
if(context is ValueSupplierImpl) context.context else context,
parameter
)
}
}
}
}
@@ -1,120 +0,0 @@
package io.github.skippyall.minions.gui.minion;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.supplier.Parameter;
import net.minecraft.server.level.ServerPlayer;
//If only this mod was kotlin
public class GuiContextImpl implements GuiContext {
private final ServerPlayer viewer;
public GuiContextImpl(ServerPlayer viewer) {
this.viewer = viewer;
}
@Override
public ServerPlayer getViewer() {
return viewer;
}
public static class MinionImpl extends DelegatingGuiContextImpl<GuiContext> implements GuiContext.Minion {
private final MinionFakePlayer minion;
public MinionImpl(GuiContext context, MinionFakePlayer minion) {
super(context instanceof DelegatingGuiContextImpl<?> impl ? impl.context : context);
this.minion = minion;
}
@Override
public MinionFakePlayer getMinion() {
return minion;
}
}
public static class InstructionImpl extends DelegatingMinionImpl<GuiContext.Minion> implements GuiContext.Instruction {
private final ConfiguredInstruction<MinionRuntime> instruction;
private String name;
public InstructionImpl(GuiContext.Minion context, ConfiguredInstruction<MinionRuntime> instruction, String name) {
super(context instanceof DelegatingMinionImpl<?> impl ? impl.context : context);
this.instruction = instruction;
this.name = name;
}
@Override
public ConfiguredInstruction<MinionRuntime> getInstruction() {
return instruction;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
public static class ValueSupplierImpl extends DelegatingInstructionImpl<GuiContext.Instruction> implements GuiContext.ValueSupplier {
private final Parameter<?> parameter;
public ValueSupplierImpl(GuiContext.Instruction context, Parameter<?> parameter) {
super(context instanceof DelegatingInstructionImpl<?> impl ? impl.context : context);
this.parameter = parameter;
}
@Override
public Parameter<?> getParameter() {
return parameter;
}
}
public static class DelegatingGuiContextImpl<C extends GuiContext> implements GuiContext {
protected final C context;
public DelegatingGuiContextImpl(C context) {
this.context = context;
}
@Override
public ServerPlayer getViewer() {
return context.getViewer();
}
}
public static class DelegatingMinionImpl<C extends GuiContext.Minion> extends DelegatingGuiContextImpl<C> implements GuiContext.Minion {
public DelegatingMinionImpl(C context) {
super(context);
}
@Override
public MinionFakePlayer getMinion() {
return context.getMinion();
}
}
public static class DelegatingInstructionImpl<C extends GuiContext.Instruction> extends DelegatingMinionImpl<C> implements GuiContext.Instruction {
public DelegatingInstructionImpl(C context) {
super(context);
}
@Override
public ConfiguredInstruction<MinionRuntime> getInstruction() {
return context.getInstruction();
}
@Override
public String getName() {
return context.getName();
}
@Override
public void setName(String name) {
context.setName(name);
}
}
}
@@ -0,0 +1,26 @@
package io.github.skippyall.minions.gui.minion
import io.github.skippyall.minions.minion.MinionRuntime
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction
import io.github.skippyall.minions.program.supplier.Parameter
import net.minecraft.server.level.ServerPlayer
//Thank you kotlin
class GuiContextImpl(override val viewer: ServerPlayer) : GuiContext {
class MinionImpl(
val context: GuiContext,
override val minion: MinionFakePlayer
) : GuiContext by context, GuiContext.Minion
class InstructionImpl(
val context: GuiContext.Minion,
override val instruction: ConfiguredInstruction<MinionRuntime>,
override var name: String
) : GuiContext.Minion by context, GuiContext.Instruction
class ValueSupplierImpl(
val context: GuiContext.Instruction,
override val parameter: Parameter<*>
) : GuiContext.Instruction by context, GuiContext.ValueSupplier
}
@@ -47,7 +47,7 @@ public abstract class BlockEntityMinionListener<E extends BlockEntity> implement
public static <T extends BlockEntityMinionListener<?>> T getListener(Level world, BlockPos pos, UUID minionUuid, Class<T> clazz) {
if(minionUuid != null) {
for (MinionListener listener : MinionPersistentState.get(world.getServer()).getMinionData(minionUuid).listeners()) {
for (MinionListener listener : MinionPersistentState.get(world.getServer()).getMinionData(minionUuid).getListeners()) {
if (listener instanceof BlockEntityMinionListener<?> tl && tl.pos.equals(pos) && tl.worldKey.equals(world.dimension()) && clazz.isInstance(tl)) {
return clazz.cast(tl);
}
@@ -95,13 +95,13 @@ public abstract class BlockEntityMinionListener<E extends BlockEntity> implement
}
public void add(MinecraftServer server) {
MinionPersistentState.get(server).getMinionData(minionUuid).listeners().addListener(this);
MinionPersistentState.get(server).getMinionData(minionUuid).getListeners().addListener(this);
MinionPersistentState.get(server).setDirty();
this.minion = (MinionFakePlayer) server.getPlayerList().getPlayer(minionUuid);
}
public void remove(MinecraftServer server) {
MinionPersistentState.get(server).getMinionData(minionUuid).listeners().removeListener(this);
MinionPersistentState.get(server).getMinionData(minionUuid).getListeners().removeListener(this);
MinionPersistentState.get(server).setDirty();
}
@@ -1,37 +0,0 @@
package io.github.skippyall.minions.listener;
import org.jetbrains.annotations.NotNull;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
public class ListenerManager<T> implements Iterable<T> {
protected final Set<T> listeners;
public ListenerManager() {
this(new CopyOnWriteArraySet<>());
}
protected ListenerManager(Set<T> listeners) {
this.listeners = listeners;
}
public void addListener(T listener) {
listeners.add(listener);
}
public void removeListener(T listener) {
listeners.remove(listener);
}
@Override
public @NotNull Iterator<T> iterator() {
return listeners.iterator();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
}
@@ -0,0 +1,29 @@
package io.github.skippyall.minions.listener
import java.util.concurrent.CopyOnWriteArraySet
open class ListenerManager<T>(
protected val listeners: MutableSet<T> = CopyOnWriteArraySet(),
val onChange: () -> Unit = {},
) : MutableIterable<T> by listeners {
fun addListener(listener: T) {
listeners.add(listener)
onChange()
}
fun removeListener(listener: T) {
listeners.remove(listener)
onChange()
}
override fun iterator(): MutableIterator<T> {
val iterator = listeners.iterator()
return object : MutableIterator<T> by iterator {
override fun remove() {
iterator.remove()
onChange()
}
}
}
}
@@ -1,46 +0,0 @@
package io.github.skippyall.minions.listener;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.Registry;
import net.minecraft.resources.Identifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
public class SerializableListenerManager<T extends SerializableListenerManager.SerializableListener> extends ListenerManager<T> {
public SerializableListenerManager() {
super();
}
protected SerializableListenerManager(Set<T> listeners) {
super(listeners);
}
public static <T extends SerializableListener> Codec<SerializableListenerManager<T>> getCodec(Registry<Codec<? extends T>> registry) {
return registry.byNameCodec().<T>dispatch(
listener -> listener.getCodecId().map(registry::getValue).orElse(MapCodec.unitCodec(null)),
codec -> codec.fieldOf("data")
).listOf().xmap(
list -> new SerializableListenerManager<>(new CopyOnWriteArraySet<>(list)),
manager -> {
List<T> serializableListeners = new ArrayList<>();
for(T listener : manager.listeners) {
if(listener.getCodecId().isPresent()) {
serializableListeners.add(listener);
}
}
return serializableListeners;
}
);
}
public interface SerializableListener {
default Optional<Identifier> getCodecId() {
return Optional.empty();
}
}
}
@@ -0,0 +1,47 @@
package io.github.skippyall.minions.listener
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import io.github.skippyall.minions.listener.SerializableListenerManager.SerializableListener
import net.minecraft.core.Registry
import net.minecraft.resources.Identifier
import java.util.Optional
import java.util.concurrent.CopyOnWriteArraySet
class SerializableListenerManager<T : SerializableListener>(
listeners: MutableSet<T> = CopyOnWriteArraySet(),
onChange: () -> Unit = {},
) : ListenerManager<T>(listeners, onChange) {
interface SerializableListener {
val codecId: Optional<Identifier>
get() = Optional.empty<Identifier>()
}
companion object {
@JvmStatic
@JvmOverloads
fun <T : SerializableListener> getCodec(
registry: Registry<Codec<out T>>,
onChange: () -> Unit = {},
): Codec<SerializableListenerManager<T>> {
return registry.byNameCodec().dispatch(
{ listener -> listener.codecId.map(registry::getValue)
.orElseGet { MapCodec.unitCodec(null) }
},
{ codec -> codec.fieldOf("data") }
).listOf().xmap(
{ list -> SerializableListenerManager<T>(CopyOnWriteArraySet<T>(list), onChange) },
{ manager ->
val serializableListeners: MutableList<T> = mutableListOf()
for (listener in manager.listeners) {
if (listener.codecId.isPresent) {
serializableListeners.add(listener)
}
}
return@xmap serializableListeners
}
)
}
}
}
@@ -1,58 +0,0 @@
package io.github.skippyall.minions.minion;
import com.mojang.authlib.properties.PropertyMap;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.github.skippyall.minions.listener.SerializableListenerManager;
import io.github.skippyall.minions.registration.MinionRegistries;
import net.minecraft.core.UUIDUtil;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.ExtraCodecs;
import java.util.Optional;
import java.util.UUID;
public record MinionData(
UUID uuid,
String name,
Optional<PropertyMap> skin,
boolean isSpawned,
SerializableListenerManager<MinionListener> listeners,
MinionConfig config
) {
public static final Codec<MinionData> CODEC = RecordCodecBuilder.create(instance ->
instance.group(
UUIDUtil.AUTHLIB_CODEC.fieldOf("uuid").forGetter(MinionData::uuid),
Codec.STRING.fieldOf("name").forGetter(MinionData::name),
ExtraCodecs.PROPERTY_MAP.optionalFieldOf("skin").forGetter(MinionData::skin),
Codec.BOOL.optionalFieldOf("isSpawned", false).forGetter(MinionData::isSpawned),
SerializableListenerManager.getCodec(MinionRegistries.MINION_LISTENER_CODECS).optionalFieldOf("listeners").xmap(
optional -> optional.orElseGet(SerializableListenerManager::new),
Optional::of
).forGetter(MinionData::listeners),
MinionConfig.CODEC.optionalFieldOf("config", new MinionConfig()).forGetter(MinionData::config)
).apply(instance, MinionData::new)
);
public static MinionData createDefault(MinecraftServer server) {
return new MinionData(
UUID.randomUUID(),
MinionProfileUtils.newDefaultMinionName(server),
Optional.empty(),
false,
new SerializableListenerManager<>(),
new MinionConfig()
);
}
public MinionData withName(String name) {
return new MinionData(uuid, name, skin, isSpawned, listeners, config);
}
public MinionData withSkin(Optional<PropertyMap> skin) {
return new MinionData(uuid, name, skin, isSpawned, listeners, config);
}
public MinionData withSpawned(boolean isSpawned) {
return new MinionData(uuid, name, skin, isSpawned, listeners, config);
}
}
@@ -0,0 +1,82 @@
package io.github.skippyall.minions.minion
import com.mojang.authlib.properties.PropertyMap
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import io.github.skippyall.minions.listener.SerializableListenerManager
import io.github.skippyall.minions.registration.MinionRegistries
import net.minecraft.core.UUIDUtil
import net.minecraft.server.MinecraftServer
import net.minecraft.util.ExtraCodecs
import java.util.Optional
import java.util.UUID
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class MinionData(
initialUuid: UUID,
initialName: String,
initialSkin: Optional<PropertyMap>,
initialIsSpawned: Boolean,
val listeners: SerializableListenerManager<MinionListener>,
initialConfig: MinionConfig,
var onDirty: Runnable = {},
) {
var uuid by DirtyProperty(initialUuid, this::setDirty)
var name by DirtyProperty(initialName, this::setDirty)
var skin by DirtyProperty(initialSkin, this::setDirty)
var isSpawned by DirtyProperty(initialIsSpawned, this::setDirty)
var config by DirtyProperty(initialConfig, this::setDirty)
fun setDirty() {
this@MinionData.onDirty.run()
}
companion object {
@JvmField
val CODEC: Codec<MinionData> =
RecordCodecBuilder.create { instance ->
instance.group(
UUIDUtil.AUTHLIB_CODEC.fieldOf("uuid").forGetter(MinionData::uuid),
Codec.STRING.fieldOf("name").forGetter(MinionData::name),
ExtraCodecs.PROPERTY_MAP.optionalFieldOf("skin").forGetter(MinionData::skin),
Codec.BOOL.optionalFieldOf("isSpawned", false).forGetter(MinionData::isSpawned),
SerializableListenerManager.getCodec<MinionListener>(MinionRegistries.MINION_LISTENER_CODECS)
.optionalFieldOf("listeners").xmap(
{ optional -> optional.orElseGet { SerializableListenerManager() } },
Optional<SerializableListenerManager<MinionListener>>::of
).forGetter(MinionData::listeners),
MinionConfig.CODEC.optionalFieldOf("config", MinionConfig())
.forGetter(MinionData::config)
).apply(
instance,
::MinionData
)
}
@JvmStatic
fun createDefault(server: MinecraftServer): MinionData {
return MinionData(
UUID.randomUUID(),
MinionProfileUtils.newDefaultMinionName(server),
Optional.empty<PropertyMap>(),
false,
SerializableListenerManager(),
MinionConfig()
) {
MinionPersistentState.get(server).isDirty = true
}
}
}
class DirtyProperty<V>(var value: V, val onDirty: () -> Unit) : ReadWriteProperty<Any?, V> {
override fun getValue(thisRef: Any?, property: KProperty<*>): V {
return value
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) {
this.value = value
onDirty()
}
}
}
@@ -79,7 +79,7 @@ public class MinionItem extends Item implements PolymerItem {
}
public static void setData(MinecraftServer server, MinionData data, ItemStack item) {
item.set(MinionComponentTypes.MINION_DATA, data.uuid());
item.set(MinionComponentTypes.MINION_DATA, data.getUuid());
MinionPersistentState.get(server).updateMinionData(data);
}
@@ -31,7 +31,8 @@ public class MinionPersistentState extends SavedData {
public MinionPersistentState(List<MinionData> dataList) {
for (MinionData data : dataList) {
minionData.put(data.uuid(), data);
data.setOnDirty(this::setDirty);
minionData.put(data.getUuid(), data);
}
}
@@ -48,7 +49,8 @@ public class MinionPersistentState extends SavedData {
}
public void updateMinionData(MinionData data) {
minionData.put(data.uuid(), data);
minionData.put(data.getUuid(), data);
data.setOnDirty(this::setDirty);
setDirty();
}
@@ -62,7 +64,7 @@ public class MinionPersistentState extends SavedData {
public Optional<MinionData> getMinionWithName(String name) {
return minionData.values().stream()
.filter(data -> data.name().equals(name))
.filter(data -> data.getName().equals(name))
.findFirst();
}
@@ -73,16 +73,16 @@ public class MinionFakePlayer extends ServerPlayer {
if(!data.isSpawned() || force) {
MinecraftServer server = level.getServer();
PropertyMap skin = data.skin().orElse(null);
PropertyMap skin = data.getSkin().orElse(null);
GameProfile profile = MinionProfileUtils.makeNewMinionProfile(data.uuid(), data.name(), skin);
GameProfile profile = MinionProfileUtils.makeNewMinionProfile(data.getUuid(), data.getName(), skin);
server.schedule(server.wrapRunnable(() -> doSpawn(data, profile, server, level, pos, rot)));
}
}
private static void doSpawn(MinionData data, GameProfile profile, MinecraftServer server, ServerLevel level, @Nullable Vec3 pos, @Nullable Vec2 rot) {
MinionFakePlayer instance = new MinionFakePlayer(server, level, profile, ClientInformation.createDefault());
MinionPersistentState.get(server).updateMinionData(data.withSpawned(true));
data.setSpawned(true);
if(pos != null && rot != null) {
instance.fixStartingPosition = () -> instance.snapTo(pos.x, pos.y, pos.z, rot.x, rot.y);
@@ -158,7 +158,7 @@ public class MinionFakePlayer extends ServerPlayer {
}
public SerializableListenerManager<MinionListener> listeners() {
return getData().listeners();
return getData().getListeners();
}
public void addMinionListener(MinionListener listener) {
@@ -174,7 +174,7 @@ public class MinionFakePlayer extends ServerPlayer {
}
public boolean canSpawnMobs() {
return moduleInventory.hasAbility(SpecialAbilities.MOB_SPAWNING) || getData().config().getOption(MinionConfigOptions.spawnAndDespawnMobs);
return moduleInventory.hasAbility(SpecialAbilities.MOB_SPAWNING) || getData().getConfig().getOption(MinionConfigOptions.spawnAndDespawnMobs);
}
public boolean canDespawnMobs() {
@@ -213,7 +213,7 @@ public class MinionFakePlayer extends ServerPlayer {
}));
}
MinionPersistentState.get(getServer()).updateMinionData(getData().withSpawned(false));
getData().setSpawned(false);
}
@Override
@@ -10,7 +10,7 @@ import java.util.concurrent.CompletableFuture;
public class NameSkinProvider implements SkinProvider {
@Override
public CompletableFuture<ResolvableProfile> openSkinMenu(MinionsGui parent) {
return TextInput.inputStringFuture(parent, Component.translatable("minions.gui.look.skin.name.title"), "")
return TextInput.inputString(parent, Component.translatable("minions.gui.look.skin.name.title"), "")
.thenApply(name -> name != null ? ResolvableProfile.createUnresolved(name) : null);
}
@@ -10,7 +10,7 @@ import java.util.concurrent.CompletableFuture;
public class UUIDSkinProvider implements SkinProvider {
@Override
public CompletableFuture<ResolvableProfile> openSkinMenu(MinionsGui parent) {
return TextInput.inputStringFuture(parent, Component.translatable("minions.gui.look.skin.uuid.title"), "")
return TextInput.inputString(parent, Component.translatable("minions.gui.look.skin.uuid.title"), "")
.thenApply(name -> name != null ? ResolvableProfile.createUnresolved(name) : null);
}
@@ -13,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.ModifyArg;
public class ClientboundPlayerInfoUpdatePacket$EntryMixin {
@ModifyArg(method = "<init>(Lnet/minecraft/server/level/ServerPlayer;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket$Entry;<init>(Ljava/util/UUID;Lcom/mojang/authlib/GameProfile;ZILnet/minecraft/world/level/GameType;Lnet/minecraft/network/chat/Component;ZILnet/minecraft/network/chat/RemoteChatSession$Data;)V"), index = 2)
private static boolean removeMinionFromTabList(boolean original, @Local(argsOnly = true) ServerPlayer player) {
if(player instanceof MinionFakePlayer minion && !minion.getData().config().getOption(MinionConfigOptions.showInTabList)) {
if(player instanceof MinionFakePlayer minion && !minion.getData().getConfig().getOption(MinionConfigOptions.showInTabList)) {
return false;
}
@@ -24,7 +24,7 @@ public class MinecraftServerMixin {
public List<ServerPlayer> ignoreFakePlayers(List<ServerPlayer> original) {
return original.stream()
.filter(player -> !(player instanceof MinionFakePlayer minion
&& !minion.getData().config().getOption(MinionConfigOptions.showInServerList)))
&& !minion.getData().getConfig().getOption(MinionConfigOptions.showInServerList)))
.collect(Collectors.toCollection(ArrayList::new));
}
@@ -59,7 +59,7 @@ public class PlayerListMixin {
@WrapOperation(method = "placeNewPlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/players/PlayerList;broadcastSystemMessage(Lnet/minecraft/network/chat/Component;Z)V"))
public void noLoginMessage(PlayerList instance, Component message, boolean overlay, Operation<Void> original, @Local(argsOnly = true) ServerPlayer player) {
if(!(player instanceof MinionFakePlayer minion && !minion.getData().config().getOption(MinionConfigOptions.sendLoginMessage))) {
if(!(player instanceof MinionFakePlayer minion && !minion.getData().getConfig().getOption(MinionConfigOptions.sendLoginMessage))) {
original.call(instance, message, overlay);
}
}
@@ -67,7 +67,7 @@ public class PlayerListMixin {
@ModifyReceiver(method = "canPlayerLogin", at = @At(value = "INVOKE", target = "Ljava/util/List;size()I"))
public List<ServerPlayer> noMinionCounting(List<ServerPlayer> instance) {
return instance.stream()
.filter(player -> !(player instanceof MinionFakePlayer minion && !minion.getData().config().getOption(MinionConfigOptions.countForPlayerLimit)))
.filter(player -> !(player instanceof MinionFakePlayer minion && !minion.getData().getConfig().getOption(MinionConfigOptions.countForPlayerLimit)))
.collect(Collectors.toCollection(ArrayList::new));
}
}
@@ -19,7 +19,7 @@ public class ServerGamePacketListenerImplMixin {
@WrapOperation(method = "removePlayerFromWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/players/PlayerList;broadcastSystemMessage(Lnet/minecraft/network/chat/Component;Z)V"))
public void noLogoutMessage(PlayerList instance, Component message, boolean overlay, Operation<Void> original) {
if(!(player instanceof MinionFakePlayer minion && !minion.getData().config().getOption(MinionConfigOptions.sendLogoutMessage))) {
if(!(player instanceof MinionFakePlayer minion && !minion.getData().getConfig().getOption(MinionConfigOptions.sendLogoutMessage))) {
original.call(instance, message, overlay);
}
}
@@ -13,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.At;
public class SleepStatusMixin {
@WrapOperation(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;isSpectator()Z"))
public boolean excludeMinions(ServerPlayer instance, Operation<Boolean> original) {
if (instance instanceof MinionFakePlayer minion && !minion.getData().config().getOption(MinionConfigOptions.countForSleeping)) {
if (instance instanceof MinionFakePlayer minion && !minion.getData().getConfig().getOption(MinionConfigOptions.countForSleeping)) {
return true;
} else {
return original.call(instance);
@@ -78,8 +78,8 @@ public class ConfiguredInstruction<R extends InstructionRuntime<R>> {
public void run(R minion) {
if(canRun() && !isRunning()) {
ParameterValueList resolvedArguments = arguments.resolve(minion);
try {
ParameterValueList resolvedArguments = arguments.resolve(minion);
execution = instruction.createExecution(resolvedArguments, minion);
execution.start(minion);
} catch (Exception e) {
@@ -37,11 +37,6 @@ public class ValueSupplierList<R extends InstructionRuntime<R>> {
return List.copyOf(arguments.values());
}
public <T> T getValue(Parameter<T> parameter, R runtime) {
ValueSupplierEntry<?,R> entry = getEntry(parameter);
return parameter.type().checkedCast(entry.getValue(runtime));
}
public ValueSupplier<?,R> getArgument(Parameter<?> parameter) {
if(arguments.containsKey(parameter)) {
return arguments.get(parameter).supplier;
@@ -153,20 +148,24 @@ public class ValueSupplierList<R extends InstructionRuntime<R>> {
this.supplier = supplier;
}
private void addToList(ParameterValueList list, R runtime) {
list.setValue(parameter, getValue(runtime));
private @Nullable Component addToList(ParameterValueList list, R runtime) {
Result<P, Component> result = getValue(supplier, runtime);
switch (result) {
case Result.Success<P, Component> success -> {
list.setValue(parameter, success.result());
return null;
}
case Result.Error<P, Component> error -> {
return error.message();
}
}
}
public @Nullable P getValue(R runtime) {
return getValue(supplier, runtime);
}
//Ich liebe generische Typen (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)
private <S> @Nullable P getValue(ValueSupplier<S, R> supplier, R runtime) {
private <S> Result<P, Component> getValue(ValueSupplier<S, R> supplier, R runtime) {
S value = supplier.resolve(runtime);
Result<TypedValue<?>, Component> convertedResult = converters.convert(new TypedValue<>(value, supplier.getValueType()));
return convertedResult.flatMap(convertedValue -> Casts.castOrError(convertedValue, parameter.type())).getOrDefault(null);
return convertedResult.flatMap(convertedValue -> Casts.castOrError(convertedValue, parameter.type()));
}
public @Nullable Component check() {
@@ -22,7 +22,7 @@ public class ValueTypes {
Codec.LONG,
0L,
o -> o instanceof Long l ? l : null,
(parent, oldValue) -> TextInput.inputLongFuture(
(parent, oldValue) -> TextInput.inputLong(
parent,
Component.literal("Integer"),
oldValue
@@ -37,10 +37,10 @@ public class ValueTypes {
Codec.DOUBLE,
0D,
o -> o instanceof Double d ? d : null,
(parent, oldValue) -> TextInput.inputDoubleFuture(
(parent, oldValue) -> TextInput.inputDouble(
parent,
Component.literal("Number"),
oldValue
oldValue != null ? oldValue : 0D
),
value -> Component.literal(value.toString())
)
@@ -52,7 +52,7 @@ public class ValueTypes {
Codec.BOOL,
false,
o -> o instanceof Boolean b ? b : null,
(parent, value) -> BooleanInput.confirmFuture(parent, Component.literal(""), Component.translatable("value_type.minions.boolean.false"), Component.translatable("value_type.minions.boolean.true")),
(parent, value) -> BooleanInput.confirm(parent, Component.literal(""), Component.translatable("value_type.minions.boolean.false"), Component.translatable("value_type.minions.boolean.true")),
value -> Component.literal(value.toString())
)
);
@@ -63,7 +63,7 @@ public class ValueTypes {
Codec.STRING,
"",
o -> o instanceof String s ? s : null,
((parent, oldValue) -> TextInput.inputStringFuture(
((parent, oldValue) -> TextInput.inputString(
parent,
Component.literal("Text"),
oldValue
@@ -1,11 +1,4 @@
{
"type": "minions:stack",
"data": {
"id": "minecraft:player_head",
"components": {
"minecraft:profile": {
"name": "CastCrafter"
}
}
}
"type": "minions:head",
"data": "459dbceed3ea4abc903cfdcae3065dd3"
}