8 Commits

Author SHA1 Message Date
skippyall 948741f663 Overlays 2026-05-31 15:06:34 +02:00
skippyall d3c84ac000 More Errors 2026-05-30 14:34:43 +02:00
skippyall 7ed686d8d9 Error & Nullability Spam 2026-05-26 13:45:14 +02:00
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
skippyall bd87a15cf2 You have no choice 2026-05-02 23:33:45 +02:00
skippyall f3c934c619 Gehen Sie zu Kotlin. Gehen Sie nicht über Los und ziehen Sie nicht 200 RM ein.
Migrate some classes to Kotlin, let's see if I will regret
2026-05-02 10:48:13 +02:00
skippyall 71016f9e70 Rename .java to .kt 2026-05-02 10:48:13 +02:00
125 changed files with 1648 additions and 1216 deletions
+1
View File
@@ -1,6 +1,7 @@
# User-specific stuff
.idea/
urlaub/
.kotlin/
*.iml
*.ipr
@@ -1,6 +1,7 @@
package io.github.skippyall.minions;
import net.fabricmc.loader.api.FabricLoader;
import org.jspecify.annotations.Nullable;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
@@ -15,6 +16,7 @@ public class MinionMixinConfigPlugin implements IMixinConfigPlugin {
}
@Override
@Nullable
public String getRefMapperConfig() {
return null;
}
@@ -34,6 +36,7 @@ public class MinionMixinConfigPlugin implements IMixinConfigPlugin {
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {}
@Override
@Nullable
public List<String> getMixins() {
return null;
}
@@ -1,16 +1,16 @@
package io.github.skippyall.minions;
import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils;
import io.github.skippyall.minions.command.MinionsCommand;
import io.github.skippyall.minions.docs.DocsManager;
import io.github.skippyall.minions.minion.MinionPersistentState;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.polymer.VersionSync;
import io.github.skippyall.minions.polymer.PolymerRegistration;
import io.github.skippyall.minions.registration.MinionRegistration;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.fabricmc.fabric.api.resource.v1.ResourceLoader;
import net.minecraft.resources.Identifier;
import net.minecraft.server.packs.PackType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,8 +26,6 @@ public class Minions implements ModInitializer {
MinionRegistration.register();
VersionSync.register();
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
MinionPersistentState.get(server).getMinionData().forEach((uuid, data) -> {
if(data.isSpawned()) {
@@ -38,8 +36,8 @@ public class Minions implements ModInitializer {
CommandRegistrationCallback.EVENT.register(MinionsCommand::register);
PolymerResourcePackUtils.addModAssets(Minions.MOD_ID);
PolymerRegistration.register();
ResourceManagerHelper.get(PackType.SERVER_DATA).registerReloadListener(new DocsManager());
ResourceLoader.get(PackType.SERVER_DATA).registerReloadListener(Identifier.fromNamespaceAndPath(Minions.MOD_ID, "docs"), new DocsManager());
}
}
@@ -11,13 +11,14 @@ import com.electronwill.nightconfig.core.serde.annotations.SerdeComment;
import com.electronwill.nightconfig.toml.TomlFormat;
import com.electronwill.nightconfig.toml.TomlParser;
import net.fabricmc.loader.api.FabricLoader;
import org.jspecify.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class MinionsConfig {
private static MinionsConfig INSTANCE;
private static @Nullable MinionsConfig INSTANCE;
public Minion minion = new Minion();
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.block.input;
import org.jspecify.annotations.NullMarked;
@@ -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;
@@ -9,12 +9,13 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import org.jspecify.annotations.Nullable;
import java.util.Optional;
import java.util.UUID;
public abstract class InstructionBoundBlockEntity<L extends BlockEntityMinionListener<?>> extends BlockEntity {
protected UUID minionUuid;
protected @Nullable UUID minionUuid;
protected String instructionName = "";
public InstructionBoundBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
@@ -56,7 +57,7 @@ public abstract class InstructionBoundBlockEntity<L extends BlockEntityMinionLis
return Optional.empty();
}
public UUID getMinionUuid() {
public @Nullable UUID getMinionUuid() {
return minionUuid;
}
@@ -72,7 +73,7 @@ public abstract class InstructionBoundBlockEntity<L extends BlockEntityMinionLis
return getMinion().flatMap(this::getInstruction);
}
public L getListener() {
public @Nullable L getListener() {
return BlockEntityMinionListener.getListener(level, worldPosition, minionUuid, getListenerClass());
}
}
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.block.instruction_bound;
import org.jspecify.annotations.NullMarked;
@@ -1,32 +1,14 @@
package io.github.skippyall.minions.block.miniontrigger;
import com.mojang.serialization.MapCodec;
import eu.pb4.polymer.core.api.block.PolymerBlock;
import eu.pb4.polymer.core.api.utils.PolymerClientDecoded;
import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils;
import eu.pb4.polymer.virtualentity.api.BlockWithElementHolder;
import eu.pb4.polymer.virtualentity.api.ElementHolder;
import eu.pb4.polymer.virtualentity.api.elements.ItemDisplayElement;
import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.block.instruction_bound.InstructionBoundBlock;
import io.github.skippyall.minions.polymer.VersionSync;
import io.github.skippyall.minions.registration.MinionBlocks;
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.DiodeBlock;
import net.minecraft.world.level.block.SupportType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
@@ -36,9 +18,10 @@ import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.redstone.Orientation;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
public class MinionTriggerBlock extends InstructionBoundBlock implements PolymerBlock, PolymerClientDecoded, BlockWithElementHolder {
public class MinionTriggerBlock extends InstructionBoundBlock {
public static final MapCodec<MinionTriggerBlock> CODEC = simpleCodec(MinionTriggerBlock::new);
public static final BooleanProperty POWERED = BooleanProperty.create("powered");
@@ -70,6 +53,7 @@ public class MinionTriggerBlock extends InstructionBoundBlock implements Polymer
}
@Override
@NullMarked
protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) {
if(!canSurvive(state, world, pos)) {
dropResources(state, world, pos);
@@ -103,35 +87,4 @@ public class MinionTriggerBlock extends InstructionBoundBlock implements Polymer
protected BlockEntityType<MinionTriggerBlockEntity> getBlockEntityType() {
return MinionBlocks.MINION_TRIGGER_BE_TYPE;
}
@Override
public BlockState getPolymerBlockState(BlockState state, PacketContext context) {
return VersionSync.isOnClient(context) ? state : net.minecraft.world.level.block.Blocks.COMPARATOR.defaultBlockState().setValue(DiodeBlock.POWERED, state.getValue(POWERED));
}
@Override
public boolean handleMiningOnServer(ItemStack tool, BlockState state, BlockPos pos, ServerPlayer player) {
return false;
}
@Override
public @Nullable ElementHolder createElementHolder(ServerLevel world, BlockPos pos, BlockState initialBlockState) {
ElementHolder holder = new ElementHolder() {
@Override
public boolean startWatching(ServerGamePacketListenerImpl player) {
if(PolymerResourcePackUtils.hasMainPack(player) && !VersionSync.isOnClient(player)) {
return super.startWatching(player);
} else {
return false;
}
}
};
ItemStack stack = new ItemStack(Items.BARRIER);
stack.set(DataComponents.ITEM_MODEL, Identifier.fromNamespaceAndPath(Minions.MOD_ID, "minion_trigger_no_plate_" + (initialBlockState.getValue(MinionTriggerBlock.POWERED) ? "active" : "inactive")));
ItemDisplayElement element = new ItemDisplayElement(stack);
element.setItemDisplayContext(ItemDisplayContext.NONE);
holder.addElement(element);
return holder;
}
}
@@ -1,26 +0,0 @@
package io.github.skippyall.minions.block.miniontrigger;
import eu.pb4.polymer.core.api.item.PolymerBlockItem;
import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils;
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.core.HolderLookup;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import org.jetbrains.annotations.Nullable;
public class MinionTriggerBlockItem extends PolymerBlockItem {
public MinionTriggerBlockItem(Block block, Properties settings, Item polymerItem) {
super(block, settings, polymerItem, true);
}
@Override
public @Nullable Identifier getPolymerItemModel(ItemStack stack, PacketContext context, HolderLookup.Provider lookup) {
if(PolymerResourcePackUtils.hasMainPack(context)) {
return super.getPolymerItemModel(stack, context, lookup);
} else {
return null;
}
}
}
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.block.miniontrigger;
import org.jspecify.annotations.NullMarked;
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.block;
import org.jspecify.annotations.NullMarked;
@@ -1,42 +1,13 @@
package io.github.skippyall.minions.clipboard;
import eu.pb4.polymer.core.api.item.PolymerItem;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.registration.MinionComponentTypes;
import io.github.skippyall.minions.registration.MinionItems;
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.core.BlockPos;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
public class ClipboardItem extends Item implements PolymerItem {
public ClipboardItem(Properties settings) {
super(settings);
}
@Override
public Item getPolymerItem(ItemStack itemStack, PacketContext context) {
return /*VersionSync.isOnClient(context) ? this : */Items.PAPER;
}
@Override
public @Nullable Identifier getPolymerItemModel(ItemStack stack, PacketContext context, HolderLookup.Provider lookup) {
return null;
}
@Override
public ItemStack getPolymerItemStack(ItemStack itemStack, TooltipFlag tooltipType, PacketContext context, HolderLookup.Provider lookup) {
ItemStack stack = PolymerItem.super.getPolymerItemStack(itemStack, tooltipType, context, lookup);
stack.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, true);
return stack;
}
public class ClipboardItem {
public static ItemStack createInstructionReference(MinionFakePlayer minion, String instructionName) {
ItemStack stack = new ItemStack(MinionItems.REFERENCE_ITEM);
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.clipboard;
import org.jspecify.annotations.NullMarked;
@@ -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();
@@ -10,6 +10,7 @@ import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.coordinates.Coordinates;
import net.minecraft.commands.arguments.coordinates.Vec3Argument;
import net.minecraft.server.permissions.Permissions;
import org.jspecify.annotations.Nullable;
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
@@ -48,7 +49,7 @@ public class SpawnSubcommand {
))
);
public static int spawnCommand(CommandSourceStack source, String minion, Coordinates pos, boolean force) throws CommandSyntaxException {
public static int spawnCommand(CommandSourceStack source, String minion, @Nullable Coordinates pos, boolean force) throws CommandSyntaxException {
MinionData data = MinionArgument.parse(source.getServer(), minion);
MinionFakePlayer.spawnMinion(data, source.getLevel(), pos != null ? pos.getPosition(source) : null, pos != null ? pos.getRotation(source) : null, force);
return 0;
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.command;
import org.jspecify.annotations.NullMarked;
@@ -3,7 +3,7 @@ package io.github.skippyall.minions.docs;
import com.google.gson.JsonParseException;
import com.mojang.serialization.JsonOps;
import io.github.skippyall.minions.Minions;
import net.fabricmc.fabric.api.resource.SimpleResourceReloadListener;
import net.fabricmc.fabric.api.resource.v1.reloader.SimpleReloadListener;
import net.minecraft.core.Holder;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
@@ -16,9 +16,10 @@ import net.minecraft.server.dialog.MultiActionDialog;
import net.minecraft.server.dialog.action.StaticAction;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.StrictJsonParser;
import net.minecraft.util.Tuple;
import org.jspecify.annotations.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
@@ -27,14 +28,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
public class DocsManager implements SimpleResourceReloadListener<Tuple<Map<Identifier, DocsEntry>, DocsTree>> {
private static Map<Identifier, DocsEntry> docs;
private static DocsTree tree;
public class DocsManager extends SimpleReloadListener<Tuple<Map<Identifier, DocsEntry>, DocsTree>> {
private static @Nullable Map<Identifier, DocsEntry> docs;
private static @Nullable DocsTree tree;
public static DocsTree getTree() {
public static @Nullable DocsTree getTree() {
return tree;
}
@@ -87,20 +86,27 @@ public class DocsManager implements SimpleResourceReloadListener<Tuple<Map<Ident
);
}
public static DocsEntry getDocsEntry(Identifier id) {
public static @Nullable DocsEntry getDocsEntry(Identifier id) {
if(docs != null) {
return docs.get(id);
} else {
return null;
}
}
public static Collection<Identifier> getDocsEntryIds() {
public static @Nullable Collection<Identifier> getDocsEntryIds() {
if (docs != null) {
return docs.keySet();
} else {
return null;
}
}
@Override
public CompletableFuture<Tuple<Map<Identifier, DocsEntry>, DocsTree>> load(ResourceManager resourceManager, Executor executor) {
return CompletableFuture.supplyAsync(() -> {
Map<Identifier, Resource> resources = resourceManager.listResources("docs", id -> id.getNamespace().equals(Minions.MOD_ID) && id.getPath().endsWith(".json"));
public Tuple<Map<Identifier, DocsEntry>, DocsTree> prepare(SharedState state) {
Map<Identifier, Resource> resources = state.resourceManager().listResources("docs", id -> id.getNamespace().equals(Minions.MOD_ID) && id.getPath().endsWith(".json"));
final DocsTree.BranchElement[] root = {null};
final DocsTree. @Nullable BranchElement[] root = {null};
Map<Identifier, DocsEntry> docsEntries = new HashMap<>();
resources.forEach((id, resource) -> {
try(Reader reader = resource.openAsReader()) {
@@ -124,20 +130,11 @@ public class DocsManager implements SimpleResourceReloadListener<Tuple<Map<Ident
} else {
return new Tuple<>(docsEntries, null);
}
}, executor);
}
@Override
public CompletableFuture<Void> apply(Tuple<Map<Identifier, DocsEntry>, DocsTree> o, ResourceManager resourceManager, Executor executor) {
return CompletableFuture.supplyAsync(() -> {
public void apply(Tuple<Map<Identifier, DocsEntry>, DocsTree> o, SharedState state) {
docs = o.getA();
tree = o.getB();
return null;
});
}
@Override
public Identifier getFabricId() {
return Identifier.fromNamespaceAndPath(Minions.MOD_ID, "docs");
}
}
@@ -3,6 +3,7 @@ package io.github.skippyall.minions.docs;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import net.minecraft.resources.Identifier;
import org.jspecify.annotations.Nullable;
import java.util.HashMap;
import java.util.List;
@@ -35,9 +36,9 @@ public class DocsTree {
}
public static abstract sealed class Element {
private BranchElement parent;
private @Nullable BranchElement parent;
public BranchElement getParent() {
public @Nullable BranchElement getParent() {
return parent;
}
@@ -45,12 +46,20 @@ public class DocsTree {
this.parent = parent;
}
public DocElement next() {
public @Nullable DocElement next() {
if (parent != null) {
return parent.next(this);
} else {
return null;
}
}
public DocElement previous() {
public @Nullable DocElement previous() {
if (parent != null) {
return parent.previous(this);
} else {
return null;
}
}
}
@@ -101,7 +110,7 @@ public class DocsTree {
};
}
public DocElement next(Element current) {
public @Nullable DocElement next(Element current) {
int nextIndex = e.indexOf(current) + 1;
if(nextIndex < e.size()) {
return switch (e.get(nextIndex)) {
@@ -117,7 +126,7 @@ public class DocsTree {
}
}
public DocElement previous(Element current) {
public @Nullable DocElement previous(Element current) {
int previousIndex = e.indexOf(current) - 1;
if(previousIndex >= 0) {
return switch (e.get(previousIndex)) {
@@ -15,6 +15,7 @@ import net.minecraft.server.dialog.body.DialogBody;
import net.minecraft.server.dialog.body.ItemBody;
import net.minecraft.server.dialog.body.PlainMessage;
import net.minecraft.world.item.Item;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -55,7 +56,7 @@ public record ReferenceEntry(Metadata metadata, ResourceKey<?> object, Component
return bodyElements;
}
public GuiDisplay getObjectDisplay(RegistryAccess manager) {
public @Nullable GuiDisplay getObjectDisplay(RegistryAccess manager) {
GuiDisplay display = GuiDisplay.DEFAULT_DISPLAY;
if(object.isFor(Registries.ITEM) || object.isFor(Registries.BLOCK)) {
Item item;
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.docs;
import org.jspecify.annotations.NullMarked;
@@ -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()
);
}
@@ -1,98 +0,0 @@
package io.github.skippyall.minions.gui;
import com.mojang.authlib.GameProfile;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.gui.input.TextInput;
import io.github.skippyall.minions.minion.MinionData;
import io.github.skippyall.minions.minion.MinionItem;
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 net.fabricmc.fabric.api.entity.FakePlayer;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.ResolvableProfile;
import java.util.Optional;
public class MinionLookGui extends SimpleGui {
private ItemStack minionItem;
private SkinProvider currentSkinProvider;
public MinionLookGui(ServerPlayer player, ItemStack minionItem) {
super(MenuType.GENERIC_9x3, player, false);
this.minionItem = minionItem;
this.currentSkinProvider = SkinProviders.NAME;
}
public void update() {
updateName();
updateSkin();
updateSkinProvider();
}
private void updateName() {
setSlot(10, new GuiElementBuilder()
.setItem(Items.OAK_SIGN)
.setName(Component.literal(getData().name()))
.setCallback(() -> {
openRenameGui(player, minionItem);
})
);
}
private void updateSkin() {
GuiElementBuilder builder = new GuiElementBuilder()
.setItem(Items.PLAYER_HEAD)
.setCallback(() -> currentSkinProvider.openSkinMenu(player)
.thenCompose(profile -> profile.resolveProfile(player.level().getServer().services().profileResolver()))
.thenAccept(skin -> MinionItem.setData(player.level().getServer(), getData().withSkin(Optional.of(skin.properties())), minionItem)));
if(MinionItem.getData(player.level().getServer(), minionItem) != null && MinionItem.getData(player.level().getServer(), minionItem).skin().isPresent()) {
builder.setComponent(DataComponents.PROFILE, ResolvableProfile.createResolved(new GameProfile(FakePlayer.DEFAULT_UUID, "", getData().skin().get())));
}
setSlot(16, builder);
}
private void cycleSkinProvider() {
int currentId = MinionRegistries.SKIN_PROVIDERS.getId(currentSkinProvider);
currentId++;
if(MinionRegistries.SKIN_PROVIDERS.size() == currentId) {
currentId = 0;
}
currentSkinProvider = MinionRegistries.SKIN_PROVIDERS.byId(currentId);
updateSkinProvider();
}
private void updateSkinProvider() {
setSlot(25, new GuiElementBuilder()
.setItem(Items.GREEN_STAINED_GLASS_PANE)
.setComponent(DataComponents.CUSTOM_NAME, currentSkinProvider.getDisplayName())
.setCallback(this::cycleSkinProvider)
);
}
private MinionData getData() {
return MinionItem.getDataOrDefault(player.level().getServer(), minionItem);
}
public static void open(ServerPlayer player, ItemStack minionItem) {
MinionLookGui gui = new MinionLookGui(player, minionItem);
gui.update();
gui.open();
}
public void openRenameGui(ServerPlayer player, ItemStack minionItem) {
TextInput.inputSync(player, Component.translatable("minions.gui.look.rename.title"), "Minion", name -> MinionProfileUtils.checkMinionNameWithoutPrefix(player.level().getServer(), name))
.thenAccept(name -> {
MinionItem.setData(player.level().getServer(), getData().withName(MinionProfileUtils.getPrefix() + name), minionItem);
open();
});
}
}
@@ -0,0 +1,131 @@
package io.github.skippyall.minions.gui
import com.mojang.authlib.GameProfile
import eu.pb4.sgui.api.elements.GuiElementBuilder
import eu.pb4.sgui.api.gui.SimpleGui
import io.github.skippyall.minions.gui.input.TextInput
import io.github.skippyall.minions.minion.MinionData
import io.github.skippyall.minions.minion.MinionItem
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
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.component.ResolvableProfile
import java.util.*
class MinionLookGui(
viewer: ServerPlayer,
private val minionItem: ItemStack
) : MinionsGui(viewer) {
private lateinit var gui: SimpleGui
private var currentSkinProvider: SkinProvider = SkinProviders.NAME
private val data: MinionData
get() = MinionItem.getDataOrDefault(viewer.level().server, minionItem)
init {
open()
}
override fun open() {
gui = object : SimpleGui(MenuType.GENERIC_9x3, viewer, false) {
override fun onPlayerClose(success: Boolean) {
onBackingClosed()
}
}
update()
gui.open()
}
override fun closeBacking() {
gui.close()
}
fun update() {
updateName()
updateSkin()
updateSkinProvider()
}
private fun updateName() {
gui.setSlot(
10, GuiElementBuilder()
.setItem(Items.OAK_SIGN)
.setName(Component.literal(this.data.name))
.setCallback(this::openRenameGui)
)
}
private fun updateSkin() {
val builder = GuiElementBuilder()
.setItem(Items.PLAYER_HEAD)
.setCallback(this::openSkinGui)
if (MinionItem.getData(viewer.level().server, minionItem)?.skin?.isPresent ?: false) {
builder.setComponent(
DataComponents.PROFILE,
ResolvableProfile.createResolved(GameProfile(FakePlayer.DEFAULT_UUID, "", this.data.skin.get()))
)
}
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() {
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() {
var currentId = MinionRegistries.SKIN_PROVIDERS.getId(currentSkinProvider)
currentId++
if (MinionRegistries.SKIN_PROVIDERS.size() == currentId) {
currentId = 0
}
currentSkinProvider = MinionRegistries.SKIN_PROVIDERS.byId(currentId)!!
updateSkinProvider()
}
fun openRenameGui() {
scope.launch {
val newName = TextInput.input(
this@MinionLookGui,
Component.translatable("minions.gui.look.rename.title"),
"Minion",
) { name ->
MinionProfileUtils.checkMinionNameWithoutPrefix(viewer.level().server, name)
}.await()
if(newName != null) {
data.name = newName
updateName()
}
}
}
}
@@ -1,76 +0,0 @@
package io.github.skippyall.minions.gui;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable;
public abstract class MinionsGui {
protected final @Nullable MinionsGui parent;
protected final ServerPlayer viewer;
protected @Nullable MinionsGui child = null;
private boolean open = true;
public MinionsGui(MinionsGui parent) {
this.viewer = parent.viewer;
this.parent = parent;
parent.child = this;
}
public MinionsGui(ServerPlayer viewer) {
this.viewer = viewer;
this.parent = null;
}
public ServerPlayer getViewer() {
return viewer;
}
protected abstract void open();
protected void reopen() {
open();
}
public void onBackingClosed() {
if(child != null && child.open) {
return;
}
close(true);
}
public void close() {
close(false);
}
public void close(boolean alreadyClosed) {
if(open) {
open = false;
if(child != null) {
child.close(alreadyClosed);
} else if(!alreadyClosed) {
closeBacking();
}
}
}
public void goBack() {
if(parent != null) {
open = false;
parent.child = null;
parent.reopen();
} else {
close(false);
}
}
public GuiElementBuilder backButton() {
return new GuiElementBuilder(Items.MANGROVE_DOOR)
.setName(Component.translatable("gui.back"))
.setCallback(this::goBack);
}
protected abstract void closeBacking();
}
@@ -0,0 +1,84 @@
package io.github.skippyall.minions.gui
import eu.pb4.sgui.api.elements.GuiElementBuilder
import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.Items
abstract class MinionsGui {
protected val parent: MinionsGui?
@JvmField
val viewer: ServerPlayer
protected var child: MinionsGui? = null
private var open = true
private val job: CompletableJob
val scope: CoroutineScope
constructor(parent: MinionsGui) {
this.viewer = parent.viewer
this.parent = parent
parent.child = this
job = Job(parent.job)
scope = CoroutineScope(Dispatchers.Unconfined.plus(CoroutineName("MinionsGui")).plus(job))
}
constructor(viewer: ServerPlayer) {
this.viewer = viewer
this.parent = null
job = Job(null)
scope = CoroutineScope(Dispatchers.Unconfined.plus(CoroutineName("MinionsGui")).plus(job))
}
protected abstract fun open()
protected open fun reopen() {
open()
}
fun onBackingClosed() {
if (child != null && child!!.open) {
return
}
close(true)
}
@JvmOverloads
fun close(alreadyClosed: Boolean = false) {
if (open) {
scope.cancel(null)
open = false
if (child != null) {
child!!.close(alreadyClosed)
} else if (!alreadyClosed) {
closeBacking()
}
}
}
fun goBack() {
if (parent != null) {
open = false
parent.child = null
parent.reopen()
close(true)
} else {
close(false)
}
}
fun backButton(): GuiElementBuilder? {
return GuiElementBuilder(Items.MANGROVE_DOOR)
.setName(Component.translatable("gui.back"))
.setCallback(Runnable { this.goBack() })
}
protected abstract fun closeBacking()
}
@@ -6,9 +6,12 @@ import net.minecraft.core.IdMap;
import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items;
import org.jspecify.annotations.Nullable;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Function;
public class PaginatedList extends MinionsGui {
private int page = 0;
@@ -16,6 +19,7 @@ public class PaginatedList extends MinionsGui {
private final Component title;
private final int size;
private final BiFunction<Integer, PaginatedList, GuiElementBuilder> display;
private @Nullable Runnable onClose = null;
public PaginatedList(MinionsGui parent, Component title, int size, BiFunction<Integer, PaginatedList, GuiElementBuilder> display) {
super(parent);
@@ -25,12 +29,20 @@ public class PaginatedList extends MinionsGui {
open();
}
public PaginatedList(MinionsGui parent, Component title, int size, BiFunction<Integer, PaginatedList, GuiElementBuilder> display, Runnable onClose) {
this(parent, title, size, display);
this.onClose = onClose;
}
@Override
protected void open() {
gui = new SimpleGui(MenuType.GENERIC_9x6, viewer, false) {
@Override
public void onPlayerClose(boolean success) {
onBackingClosed();
if(onClose != null) {
onClose.run();
}
}
};
gui.setTitle(title);
@@ -53,6 +65,17 @@ public class PaginatedList extends MinionsGui {
new PaginatedList(parent, title, list.size(), (i, gui) -> display.apply(list.byId(i), gui));
}
public static <T> CompletableFuture<T> createListFuture(MinionsGui parent, Component title, List<T> list, Function<T, GuiElementBuilder> display) {
CompletableFuture<T> future = new CompletableFuture<>();
new PaginatedList(parent, title, list.size(), (i, me) -> display.apply(list.get(i))
.setCallback(() -> {
future.complete(list.get(i));
me.goBack();
})
);
return future;
}
private void addItems() {
int slot = 9;
for(int i = 36 * page; i < Math.min(36 * (page + 1), size); i++) {
@@ -0,0 +1,55 @@
package io.github.skippyall.minions.gui.input
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 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")
): 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) {
future.complete(false)
onClose.run()
}
}
gui.setTitle(title)
gui.setSlot(
3, GuiElementBuilder(Items.REDSTONE_BLOCK)
.setName(falseText)
.setCallback(Runnable {
future.complete(false)
me.goBack()
})
)
gui.setSlot(
5, GuiElementBuilder(Items.EMERALD_BLOCK)
.setName(trueText)
.setCallback(Runnable {
future.complete(true)
me.goBack()
})
)
gui.open()
gui
}
return future
}
}
@@ -1,122 +0,0 @@
package io.github.skippyall.minions.gui.input;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.gui.Displayable;
import io.github.skippyall.minions.gui.GuiDisplay;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.minion.SimpleMinionsGui;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Function;
public class ChoiceInput {
public static <T> BiFunction<ServerPlayer, T, CompletableFuture<T>> createDialogOpener(MenuType<?> screen, Component title, Function<T, GuiDisplay> displayFunction, T[] values, @Nullable T fallback) {
return (player, object) -> {
CompletableFuture<T> future = new CompletableFuture<>();
SimpleGui gui = new SimpleGui(screen, player, false) {
@Override
public void onPlayerClose(boolean success) {
if(fallback == null) {
future.cancel(false);
} else {
future.complete(fallback);
}
}
};
gui.setTitle(title);
for(T value : values) {
gui.addSlot(new GuiElementBuilder(displayFunction.apply(value).createItemStack())
.setCallback(() -> future.complete(value))
);
}
gui.open();
return future;
};
}
public static <T extends Displayable> BiFunction<ServerPlayer, T, CompletableFuture<T>> createDialogOpener(T[] values) {
return createDialogOpener(MenuType.GENERIC_9x3, Component.empty(), t -> t != null ? t.getDisplay() : null, values, null);
}
public static CompletableFuture<Void> confirm(ServerPlayer player, Component title) {
CompletableFuture<Void> future = new CompletableFuture<>();
SimpleGui gui = new SimpleGui(MenuType.GENERIC_3x3, player, false) {
@Override
public void onPlayerClose(boolean success) {
future.cancel(false);
}
};
gui.setTitle(title);
gui.setSlot(3, new GuiElementBuilder(Items.REDSTONE_BLOCK)
.setName(Component.translatable("minions.gui.abort"))
.setCallback(() -> future.cancel(false))
);
gui.setSlot(5, new GuiElementBuilder(Items.EMERALD_BLOCK)
.setName(Component.translatable("minions.gui.confirm"))
.setCallback(() -> future.complete(null))
);
gui.open();
return future;
}
public static CompletableFuture<Boolean> confirm(MinionsGui parent, Component title) {
CompletableFuture<Boolean> future = new CompletableFuture<>();
new SimpleMinionsGui(parent, (onClose, me) -> {
SimpleGui gui = new SimpleGui(MenuType.GENERIC_3x3, parent.getViewer(), false) {
@Override
public void onPlayerClose(boolean success) {
future.complete(false);
onClose.run();
}
};
gui.setTitle(title);
gui.setSlot(3, new GuiElementBuilder(Items.REDSTONE_BLOCK)
.setName(Component.translatable("minions.gui.abort"))
.setCallback(() -> {
future.complete(false);
me.goBack();
})
);
gui.setSlot(5, new GuiElementBuilder(Items.EMERALD_BLOCK)
.setName(Component.translatable("minions.gui.confirm"))
.setCallback(() -> {
future.complete(true);
me.goBack();
})
);
gui.open();
return gui;
});
return future;
}
public static BiFunction<ServerPlayer, Boolean, CompletableFuture<Boolean>> inputBoolean(Component title) {
return createDialogOpener(MenuType.GENERIC_3x3, title, value -> {
if(value) {
return new GuiDisplay.ItemBased(Items.EMERALD_BLOCK);
} else {
return new GuiDisplay.ItemBased(Items.REDSTONE_BLOCK);
}
}, new Boolean[]{false, true}, false);
}
}
@@ -1,14 +1,13 @@
package io.github.skippyall.minions.gui.input;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
import java.util.Optional;
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);
}
@@ -27,7 +26,7 @@ public interface Result<T, E> {
static <T, E> Result<T, E> ofNullable(@Nullable T value, E error) {
if(value != null) {
return new Success<>(value);
return new Success<T, E>(value);
} else {
return new Error<>(error);
}
@@ -35,7 +34,7 @@ public interface Result<T, E> {
static <T, E> Result<T, E> ofNullable(@Nullable T value, Supplier<E> error) {
if(value != null) {
return new Success<>(value);
return new Success<T, E>(value);
} else {
return new Error<>(error.get());
}
@@ -49,13 +48,13 @@ public interface Result<T, E> {
E getErrorOrThrow();
@NotNull Optional<T> getOptional();
Optional<T> getOptional();
@NotNull Optional<E> getOptionalError();
Optional<E> getOptionalError();
void ifSuccess(@NotNull Consumer<T> handler);
void ifSuccess(Consumer<T> handler);
void ifError(@NotNull Consumer<Error<T, E>> handler);
void ifError(Consumer<Error<T, E>> handler);
<U> Result<U,E> map(Function<T, U> mapper);
@@ -75,7 +74,7 @@ public interface Result<T, E> {
}
@Override
public @NotNull Optional<E> getOptionalError() {
public Optional<E> getOptionalError() {
return Optional.empty();
}
@@ -90,17 +89,17 @@ public interface Result<T, E> {
}
@Override
public @NotNull Optional<T> getOptional() {
public Optional<T> getOptional() {
return Optional.ofNullable(result);
}
@Override
public void ifSuccess(@NotNull Consumer<T> handler) {
public void ifSuccess(Consumer<T> handler) {
handler.accept(result);
}
@Override
public void ifError(@NotNull Consumer<Error<T, E>> handler) {
public void ifError(Consumer<Error<T, E>> handler) {
}
@@ -143,22 +142,22 @@ public interface Result<T, E> {
}
@Override
public @NotNull Optional<T> getOptional() {
public Optional<T> getOptional() {
return Optional.empty();
}
@Override
public @NotNull Optional<E> getOptionalError() {
public Optional<E> getOptionalError() {
return Optional.ofNullable(message);
}
@Override
public void ifSuccess(@NotNull Consumer<T> handler) {
public void ifSuccess(Consumer<T> handler) {
}
@Override
public void ifError(@NotNull Consumer<Error<T, E>> handler) {
public void ifError(Consumer<Error<T, E>> handler) {
handler.accept(this);
}
@@ -1,125 +0,0 @@
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 io.github.skippyall.minions.gui.minion.SimpleMinionsGui;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.inventory.AnvilMenu;
import net.minecraft.world.item.Items;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
public class TextInput<T> extends AnvilInputGui {
private final GuiElementBuilder valid = new GuiElementBuilder()
.setItem(Items.EMERALD_BLOCK)
.setName(Component.literal("OK"))
.setCallback(this::onConfirm);
private final GuiElementBuilder invalid = new GuiElementBuilder()
.setItem(Items.REDSTONE_BLOCK);
private final Function<String, CompletableFuture<Result<T, Component>>> parser;
private final CompletableFuture<T> future;
private Result<T, Component> result;
private boolean isConfirm;
public TextInput(ServerPlayer player, Component title, String defaultValue, Function<String, CompletableFuture<Result<T, Component>>> parser, CompletableFuture<T> future) {
super(player, false);
setTitle(title);
setDefaultInputValue(defaultValue);
this.parser = parser;
this.future = future;
updateConfirmButton(defaultValue);
}
public static <T> CompletableFuture<T> inputSync(ServerPlayer player, Component title, String defaultValue, Function<String, Result<T, Component>> parser) {
return input(player, title, defaultValue, (String string) -> CompletableFuture.completedFuture(parser.apply(string)));
}
public static <T> CompletableFuture<T> inputSync(MinionsGui gui, Component title, String defaultValue, Function<String, Result<T, Component>> parser) {
return input(gui, title, defaultValue, (String string) -> CompletableFuture.completedFuture(parser.apply(string)));
}
public static <T> CompletableFuture<T> input(ServerPlayer player, Component title, String defaultValue, Function<String, CompletableFuture<Result<T, Component>>> parser) {
CompletableFuture<T> future = new CompletableFuture<>();
new TextInput<>(player, title, defaultValue, parser, future).open();
return future;
}
public static <T> CompletableFuture<T> input(MinionsGui gui, Component title, String defaultValue, Function<String, CompletableFuture<Result<T, Component>>> parser) {
CompletableFuture<T> future = new CompletableFuture<>();
new SimpleMinionsGui(gui, (onClose, me) -> {
TextInput<T> input = new TextInput<>(gui.getViewer(), title, defaultValue, parser, future);
future.whenComplete((v, e) -> {
if(e != null) {
onClose.run();
} else {
me.goBack();
}
});
input.open();
return input;
});
return future;
}
public static CompletableFuture<String> inputString(ServerPlayer player, Component title, String defaultValue) {
return inputSync(player, title, defaultValue, Result.Success::new);
}
public static CompletableFuture<String> inputString(MinionsGui gui, Component title, String defaultValue) {
return inputSync(gui, title, defaultValue, Result.Success::new);
}
public static CompletableFuture<Long> inputLong(ServerPlayer player, Component title, String defaultValue) {
return inputSync(player, title, defaultValue, string -> Result.wrapCustomError(() -> Long.valueOf(string), Component.translatable("minions.command.input.int.fail")));
}
public static CompletableFuture<Long> inputLong(MinionsGui gui, Component title, String defaultValue) {
return inputSync(gui, title, defaultValue, string -> Result.wrapCustomError(() -> Long.valueOf(string), Component.translatable("minions.command.input.int.fail")));
}
public static CompletableFuture<Double> inputDouble(ServerPlayer player, Component title, String defaultValue) {
return inputSync(player, title, defaultValue, string -> Result.wrapCustomError(() -> Double.valueOf(string), Component.translatable("minions.command.input.float.fail")));
}
public static CompletableFuture<Double> inputDouble(MinionsGui gui, Component title, String defaultValue) {
return inputSync(gui, title, defaultValue, string -> Result.wrapCustomError(() -> Double.valueOf(string), Component.translatable("minions.command.input.float.fail")));
}
@Override
public void onInput(String input) {
updateConfirmButton(input);
}
public void updateConfirmButton(String input) {
parser.apply(input).thenAccept(result -> {
this.result = result;
if(result.isSuccess()) {
setSlot(AnvilMenu.RESULT_SLOT, valid);
} else {
Component text = result.getErrorOrThrow();
setSlot(AnvilMenu.RESULT_SLOT, invalid.setName(text));
}
});
}
@Override
public void onPlayerClose(boolean success) {
if(!future.isDone() && !isConfirm) {
future.cancel(false);
}
}
public void onConfirm() {
if(result != null) {
result.ifSuccess(success -> {
isConfirm = true;
future.complete(success);
});
}
}
}
@@ -0,0 +1,149 @@
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.launch
import net.minecraft.network.chat.Component
import net.minecraft.world.inventory.AnvilMenu
import net.minecraft.world.item.Items
import java.util.concurrent.CompletableFuture
class TextInput<T : Any>(
parent: MinionsGui,
val title: Component,
val defaultValue: String,
val parser: suspend (String) -> Result<T, Component>
) : MinionsGui(parent) {
private val valid: GuiElementBuilder = GuiElementBuilder()
.setItem(Items.EMERALD_BLOCK)
.setName(Component.literal("OK"))
.setCallback(Runnable { this.onConfirm() })
private val invalid: GuiElementBuilder = GuiElementBuilder()
.setItem(Items.REDSTONE_BLOCK)
private lateinit var gui: AnvilInputGui
private var result: Result<T, Component>? = null
val future = CompletableFuture<T?>()
init {
open()
}
override fun open() {
gui = object : AnvilInputGui(viewer, false) {
override fun onInput(input: String) {
updateConfirmButton(input)
}
override fun onPlayerClose(success: Boolean) {
onBackingClosed()
if (!future.isDone) {
future.complete(null)
}
}
}
gui.setTitle(title)
gui.setDefaultInputValue(defaultValue)
updateConfirmButton(defaultValue)
gui.open()
}
override fun closeBacking() {
gui.close()
}
fun updateConfirmButton(input: String) {
scope.launch {
val result = parser(input)
this@TextInput.result = result
if (result.isSuccess()) {
gui.setSlot(AnvilMenu.RESULT_SLOT, valid)
} else {
val text = result.getErrorOrThrow()
gui.setSlot(AnvilMenu.RESULT_SLOT, invalid.setName(text))
}
}
}
fun onConfirm() {
result?.ifSuccess { success: T ->
future.complete(success)
goBack()
}
}
companion object {
@JvmStatic
fun <T : Any>input(
gui: MinionsGui,
title: Component,
defaultValue: String,
parser: suspend (String) -> Result<T, Component>,
): CompletableFuture<T?> {
val input = TextInput(
parent = gui,
title = title,
defaultValue = defaultValue,
parser = parser,
)
return input.future
}
@JvmStatic
fun inputString(
gui: MinionsGui,
title: Component,
defaultValue: String,
): CompletableFuture<String?> {
return input<String>(
gui = gui,
title = title,
defaultValue = defaultValue,
parser = { result -> Result.Success(result) },
)
}
@JvmStatic
fun inputLong(
gui: MinionsGui,
title: Component,
defaultValue: Long,
): CompletableFuture<Long?> {
return input<Long>(
gui = gui,
title = title,
defaultValue = defaultValue.toString(),
parser = { string ->
Result.wrapCustomError<Long, Component>(
{ string.toLong() },
Component.translatable("value_type.minions.long.not_long")
)
},
)
}
@JvmStatic
fun inputDouble(
gui: MinionsGui,
title: Component,
defaultValue: Double,
): CompletableFuture<Double?> {
return input<Double>(
gui = gui,
title = title,
defaultValue = defaultValue.toString(),
parser = { string ->
Result.wrapCustomError<Double, Component>(
{ string.toDouble() },
Component.translatable("value_type.minions.long.not_long")
)
},
)
}
}
}
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.gui.input;
import org.jspecify.annotations.NullMarked;
@@ -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;
@@ -19,7 +18,7 @@ import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
public class ArgumentGui extends MinionsGui {
private final GuiContext.ValueSupplier context;
@@ -29,7 +28,7 @@ public class ArgumentGui extends MinionsGui {
private SimpleGui gui;
private @Nullable ValueSupplierType<MinionRuntime> argumentType;
private @Nullable ValueSupplierList.ValueSupplierEntry<?, MinionRuntime> entry;
private ValueSupplierList. @Nullable ValueSupplierEntry<?, MinionRuntime> entry;
public ArgumentGui(MinionsGui parent, GuiContext.ValueSupplier context) {
super(parent);
@@ -38,9 +37,7 @@ public class ArgumentGui extends MinionsGui {
this.context = context;
this.entry = instruction.getArguments().getEntry(parameter);
if(entry != null) {
this.argumentType = entry.getSupplier().getType();
}
open();
}
@@ -4,7 +4,7 @@ import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.clipboard.ClipboardItem;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.input.ChoiceInput;
import io.github.skippyall.minions.gui.input.BooleanInput;
import io.github.skippyall.minions.gui.minion.GuiContext;
import io.github.skippyall.minions.minion.MinionListener;
import io.github.skippyall.minions.minion.MinionRuntime;
@@ -19,6 +19,8 @@ import net.minecraft.sounds.SoundSource;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items;
import java.util.List;
public class ConfigureInstructionGui extends MinionsGui implements ConfiguredInstructionListener, MinionListener {
private String name;
private final ConfiguredInstruction<MinionRuntime> instruction;
@@ -53,14 +55,16 @@ public class ConfigureInstructionGui extends MinionsGui implements ConfiguredIns
gui.setSlot(6, new GuiElementBuilder(Items.ANVIL)
.setName(Component.translatable("minions.gui.instruction.configure.rename"))
.setCallback(() -> InstructionGui.inputInstructionName(this, context, name).thenAccept(newName -> {
if(newName != null) {
minion.getInstructionManager().setInstructionName(name, newName);
}
reopen();
}))
);
gui.setSlot(7, new GuiElementBuilder(Items.LAVA_BUCKET)
.setName(Component.translatable("minions.gui.instruction.configure.delete"))
.setCallback(() -> ChoiceInput.confirm(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);
@@ -84,6 +88,7 @@ public class ConfigureInstructionGui extends MinionsGui implements ConfiguredIns
})
);
updateLastError();
updateRunSlot();
gui.open();
}
@@ -105,6 +110,7 @@ public class ConfigureInstructionGui extends MinionsGui implements ConfiguredIns
@Override
public void onRun(ConfiguredInstruction<?> instruction) {
updateRunSlot();
updateLastError();
}
@Override
@@ -118,6 +124,8 @@ public class ConfigureInstructionGui extends MinionsGui implements ConfiguredIns
}
private void updateRunSlot() {
List<Component> errors = instruction.preCheck();
if(errors.isEmpty()) {
if (!instruction.isRunning()) {
gui.setSlot(26, new GuiElementBuilder(Items.ARROW)
.setName(Component.translatable("minions.gui.instruction.run"))
@@ -129,6 +137,26 @@ public class ConfigureInstructionGui extends MinionsGui implements ConfiguredIns
.setCallback(() -> instruction.stop(minion.getInstructionManager()))
);
}
} else {
GuiElementBuilder builder = new GuiElementBuilder(Items.RED_WOOL)
.setName(Component.translatable("minions.gui.instruction.errors"));
for(Component error : errors) {
builder.addLoreLine(error);
}
gui.setSlot(26, builder);
}
}
private void updateLastError() {
List<Component> errors = instruction.getLastErrors();
if(!errors.isEmpty()) {
GuiElementBuilder builder = new GuiElementBuilder(Items.RED_WOOL)
.setName(Component.translatable("minions.gui.instruction.last_errors"));
for(Component error : errors) {
builder.addLoreLine(error);
}
gui.setSlot(17, builder);
}
}
private void updateSuppliers() {
@@ -14,7 +14,7 @@ import net.minecraft.core.RegistryAccess;
import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
public class ConverterGui extends MinionsGui {
private @Nullable ValueConverterType<?> valueConverterType;
@@ -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
@@ -12,6 +12,7 @@ import io.github.skippyall.minions.util.TranslationUtil;
import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items;
import org.jspecify.annotations.Nullable;
public class ConverterListGui extends MinionsGui {
private ConverterList converters;
@@ -54,53 +55,121 @@ 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();
}
})
);
gui.setSlot(8, backButton());
gui.setSlot(26, backButton());
gui.open();
}
private ValueType<?> getInputTypeOfConverter(int converterIndex) {
if(converterIndex < converters.getConverters().size() && converterIndex >= 0) {
return converters.getConverters().get(converterIndex).getFrom();
} else if(converterIndex == converters.getConverters().size()) {
return outputType;
} else {
throw new IndexOutOfBoundsException("Can't get input type of converter " + converterIndex + ", list size " + converters.getConverters().size());
}
}
private ValueType<?> getOutputTypeOfConverter(int converterIndex) {
if(converterIndex < converters.getConverters().size() && converterIndex >= 0) {
return converters.getConverters().get(converterIndex).getTo();
} else if(converterIndex == -1) {
return inputType;
} else {
throw new IndexOutOfBoundsException("Can't get output type of converter " + converterIndex + ", list size " + converters.getConverters().size());
}
}
public void updateConverters() {
for(int slot = 0; 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
int converterIndex = page * 4 + i;
//without input element
int actualConverterIndex = converterIndex - 1;
//The input element index is -1, the output element index is the list size
int converterIndex = page * 4 + i - 1;
int slot = 9 + 2 * i;
if(converterIndex == 0) {
gui.setSlot(slot, new GuiElementBuilder(Items.DROPPER));
} else if(converterIndex == converters.getConverters().size() + 1) {
if(converterIndex == converters.getConverters().size()) {
gui.setSlot(slot, new GuiElementBuilder(Items.HOPPER));
} else {
ValueConverter<?, ?> converter = converters.getConverters().get(actualConverterIndex);
ValueType<?> fromType = actualConverterIndex >= 1 ? converters.getConverters().get(actualConverterIndex - 1).getTo() : inputType;
ValueType<?> toType = actualConverterIndex < converters.getConverters().size() - 1 ? converters.getConverters().get(actualConverterIndex + 1).getFrom() : outputType;
ValueType<?> thisConverterOutput = getOutputTypeOfConverter(converterIndex);
ValueType<?> nextConverterInput = getInputTypeOfConverter(converterIndex + 1);
gui.setSlot(slot, new GuiElementBuilder(GuiDisplay.getDisplayStackWithName(MinionRegistries.VALUE_CONVERTER_TYPES, converter.getType(), viewer.registryAccess()))
.addLoreLine(converter.getDisplayText())
.setCallback(() -> new ConverterGui(this, converter, fromType, toType, converters, false, actualConverterIndex))
);
//What should be shown at the converter's slot
if (converterIndex == -1) {
gui.setSlot(slot, new GuiElementBuilder(Items.DROPPER));
} else {
ValueType<?> previousConverterOutput = getOutputTypeOfConverter(converterIndex - 1);
ValueConverter<?, ?> converter = converters.getConverters().get(converterIndex);
gui.setSlot(slot, createConverterDisplay(converter, previousConverterOutput, nextConverterInput, converterIndex));
GuiElementBuilder warning = createConverterWarning(converter);
if (warning != null) {
gui.setSlot(slot - 8, warning);
}
}
if(i != 4 && actualConverterIndex != converters.getConverters().size()) {
ValueType<?> fromType = actualConverterIndex >= 0 ? converters.getConverters().get(actualConverterIndex).getTo() : inputType;
ValueType<?> toType = actualConverterIndex < converters.getConverters().size() - 1 ? converters.getConverters().get(actualConverterIndex + 1).getFrom() : outputType;
gui.setSlot(slot + 1, new GuiElementBuilder(Items.MAGENTA_GLAZED_TERRACOTTA)
//show cast in the next slot
if (i != 4 && converterIndex != converters.getConverters().size()) {
gui.setSlot(slot + 1, createCastDisplay(thisConverterOutput, nextConverterInput, converterIndex + 1));
GuiElementBuilder warning = createCastWarning(thisConverterOutput, nextConverterInput);
if (warning != null) {
gui.setSlot(slot - 8, warning);
}
}
}
}
}
private GuiElementBuilder createConverterDisplay(ValueConverter<?, ?> converter, ValueType<?> fromType, ValueType<?> toType, int converterIndex) {
return new GuiElementBuilder(GuiDisplay.getDisplayStackWithName(MinionRegistries.VALUE_CONVERTER_TYPES, converter.getType(), viewer.registryAccess()))
.addLoreLine(converter.getDisplayText())
.setCallback(() -> new ConverterGui(this, converter, fromType, toType, converters, false, converterIndex));
}
private GuiElementBuilder createCastDisplay(ValueType<?> fromType, ValueType<?> toType, int newConverterIndex) {
return new GuiElementBuilder(Items.MAGENTA_GLAZED_TERRACOTTA)
.setName(Component.translatable(
"minions.gui.instruction.converters.cast",
Component.translatable(TranslationUtil.getTranslationKey(fromType, MinionRegistries.VALUE_TYPES)),
Component.translatable(TranslationUtil.getTranslationKey(toType, MinionRegistries.VALUE_TYPES))
))
.setCallback(() -> new ConverterGui(this, null, fromType, toType, converters, true, actualConverterIndex + 1))
);
.setCallback(() -> new ConverterGui(this, null, fromType, toType, converters, true, newConverterIndex));
}
private @Nullable GuiElementBuilder createCastWarning(ValueType<?> fromType, ValueType<?> toType) {
Component warning = ConverterList.createCastWarning(fromType, toType);
if(warning != null) {
return new GuiElementBuilder(Items.RED_BANNER)
.setName(Component.translatable("minions.generic.error"))
.addLoreLine(warning);
} else {
return null;
}
}
private @Nullable GuiElementBuilder createConverterWarning(ValueConverter<?,?> converter) {
Component warning = ConverterList.createConverterWarning(converter);
if(warning != null) {
return new GuiElementBuilder(Items.RED_BANNER)
.setName(Component.translatable("minions.generic.error"))
.addLoreLine(warning);
} else {
return null;
}
}
@@ -24,7 +24,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
import java.util.NoSuchElementException;
import java.util.concurrent.CompletableFuture;
@@ -32,7 +32,7 @@ import java.util.concurrent.CompletableFuture;
public class InstructionGui {
public static void openInstructionMainMenu(MinionsGui parent, GuiContext.Minion context) {
new SimpleMinionsGui(parent, (onClose, me) -> {
ServerPlayer player = parent.getViewer();
ServerPlayer player = parent.viewer;
SimpleGui gui = new SimpleGui(MenuType.GENERIC_3x3, player, false) {
@Override
@@ -61,10 +61,10 @@ public class InstructionGui {
public static void createNewInstruction(MinionsGui parent, GuiContext.Minion context) {
MinionFakePlayer minion = context.getMinion();
ServerPlayer viewer = parent.getViewer();
ServerPlayer viewer = parent.viewer;
selectInstructionModuleMenu(parent, context).thenAccept(instructionType ->
inputInstructionName(parent, context, "Instruction").thenAccept(name -> {
if (!minion.isRemoved() && !minion.hasDisconnected()) {
if (!minion.isRemoved() && !minion.hasDisconnected() && name != null) {
ConfiguredInstruction<MinionRuntime> configuredInstruction = minion.getInstructionManager().createInstruction(name, instructionType);
new ConfigureInstructionGui(parent, GuiContext.Instruction.create(context, configuredInstruction, name));
}
@@ -72,8 +72,8 @@ public class InstructionGui {
);
}
public static CompletableFuture<String> inputInstructionName(MinionsGui parent, GuiContext.Minion context, String defaultValue) {
return TextInput.inputSync(parent, Component.translatable("minions.gui.instruction.enter_name"), defaultValue, name -> {
public static CompletableFuture<@Nullable String> inputInstructionName(MinionsGui parent, GuiContext.Minion context, String defaultValue) {
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"));
}
@@ -92,7 +92,7 @@ public class InstructionGui {
public static CompletableFuture<InstructionType<MinionRuntime>> selectInstructionModuleMenu(MinionsGui parent, GuiContext.Minion context) {
MinionFakePlayer minion = context.getMinion();
ServerPlayer viewer = parent.getViewer();
ServerPlayer viewer = parent.viewer;
if (minion.getModuleInventory().getModules().isEmpty()) {
viewer.sendSystemMessage(Component.translatable("minions.gui.instruction.no_modules"));
@@ -137,7 +137,7 @@ public class InstructionGui {
CompletableFuture<InstructionType<MinionRuntime>> future = new CompletableFuture<>();
new SimpleMinionsGui(parent, (closeHandler, me) -> {
SimpleGui gui = new SimpleGui(MenuType.GENERIC_9x4, parent.getViewer(), false) {
SimpleGui gui = new SimpleGui(MenuType.GENERIC_9x4, parent.viewer, false) {
@Override
public void onPlayerClose(boolean success) {
if (!future.isDone()) {
@@ -151,7 +151,7 @@ public class InstructionGui {
gui.setSlot(8, me.backButton());
int slot = 9;
for (InstructionType<MinionRuntime> instructionType : module.instructions()) {
gui.setSlot(slot, createInstructionElement(instructionType, parent.getViewer().registryAccess())
gui.setSlot(slot, createInstructionElement(instructionType, parent.viewer.registryAccess())
.setCallback(() -> future.complete(instructionType))
);
slot++;
@@ -163,7 +163,7 @@ public class InstructionGui {
return future;
}
public static GuiElementBuilder createInstructionElement(InstructionType<MinionRuntime> instructionType, RegistryAccess manager) {
public static GuiElementBuilder createInstructionElement(@Nullable InstructionType<MinionRuntime> instructionType, RegistryAccess manager) {
GuiElementBuilder instructionBuilder;
if (instructionType != null) {
instructionBuilder = new GuiElementBuilder(GuiDisplay.getDisplayStackWithName(MinionRegistries.INSTRUCTION_TYPES, instructionType, manager));
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.gui.instruction;
import org.jspecify.annotations.NullMarked;
@@ -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,66 @@
package io.github.skippyall.minions.gui.minion
import io.github.skippyall.minions.gui.minion.GuiContextImpl.InstructionImpl
import io.github.skippyall.minions.gui.minion.GuiContextImpl.MinionImpl
import io.github.skippyall.minions.gui.minion.GuiContextImpl.ValueSupplierImpl
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
}
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.gui.minion;
import org.jspecify.annotations.NullMarked;
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.gui;
import org.jspecify.annotations.NullMarked;
@@ -13,7 +13,7 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
import java.util.Optional;
import java.util.UUID;
@@ -45,9 +45,9 @@ public abstract class BlockEntityMinionListener<E extends BlockEntity> implement
this.minion = null;
}
public static <T extends BlockEntityMinionListener<?>> T getListener(Level world, BlockPos pos, UUID minionUuid, Class<T> clazz) {
public static <T extends BlockEntityMinionListener<?>> @Nullable T getListener(Level world, BlockPos pos, @Nullable 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.*
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
}
)
}
}
}
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.listener;
import org.jspecify.annotations.NullMarked;
@@ -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,81 @@
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.*
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()
}
}
}
@@ -1,14 +1,8 @@
package io.github.skippyall.minions.minion;
import eu.pb4.polymer.core.api.item.PolymerItem;
import io.github.skippyall.minions.gui.MinionLookGui;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.registration.MinionComponentTypes;
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@@ -17,51 +11,29 @@ import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.TooltipDisplay;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec2;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
import java.util.function.Consumer;
public class MinionItem extends Item implements PolymerItem {
public class MinionItem extends Item {
public MinionItem(Properties settings) {
super(settings);
}
@Override
public @Nullable Identifier getPolymerItemModel(ItemStack stack, PacketContext context, HolderLookup.Provider lookup) {
return null;
}
@Override
public Item getPolymerItem(ItemStack itemStack, PacketContext player) {
return Items.ARMOR_STAND;
}
@Override
public ItemStack getPolymerItemStack(ItemStack stack, TooltipFlag tooltipType, PacketContext player, HolderLookup.Provider lookup) {
ItemStack out = PolymerItem.super.getPolymerItemStack(stack, tooltipType, player, lookup);
out.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, true);
return out;
}
@Override
/*@Override
public void appendHoverText(ItemStack stack, TooltipContext context, TooltipDisplay component, Consumer<Component> tooltip, TooltipFlag type) {
MinionData data = null /*getData(stack)*/;
if(data != null) {
tooltip.accept(Component.translatable("minions.minion_item.tooltip", data.name()));
}
}
//MinionData data = getData(stack);
//if(data != null) {
// tooltip.accept(Component.translatable("minions.minion_item.tooltip", data.name()));
//}
}*/
@Override
public InteractionResult use(Level world, Player user, InteractionHand hand) {
if(user instanceof ServerPlayer serverPlayer) {
ItemStack stack = user.getItemInHand(hand);
MinionLookGui.open(serverPlayer, stack);
new MinionLookGui(serverPlayer, stack);
return InteractionResult.SUCCESS;
}
@@ -79,7 +51,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);
}
@@ -3,7 +3,7 @@ package io.github.skippyall.minions.minion;
import io.github.skippyall.minions.listener.SerializableListenerManager;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
public interface MinionListener extends SerializableListenerManager.SerializableListener {
default void onMinionSpawn(MinionFakePlayer minion) {}
@@ -7,6 +7,8 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.saveddata.SavedData;
import net.minecraft.world.level.saveddata.SavedDataType;
import org.jspecify.annotations.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -31,11 +33,12 @@ 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);
}
}
public MinionData getMinionData(UUID uuid) {
public @Nullable MinionData getMinionData(UUID uuid) {
return minionData.get(uuid);
}
@@ -48,7 +51,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 +66,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();
}
@@ -8,6 +8,8 @@ import io.github.skippyall.minions.gui.input.Result;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.StringUtil;
import org.jspecify.annotations.Nullable;
import java.util.UUID;
import static io.github.skippyall.minions.Minions.LOGGER;
@@ -17,7 +19,7 @@ public class MinionProfileUtils {
return MinionsConfig.get().minion.minionPrefix;
}
public static GameProfile makeNewMinionProfile(UUID uuidMinion, String username, PropertyMap skin) {
public static GameProfile makeNewMinionProfile(@Nullable UUID uuidMinion, String username, @Nullable PropertyMap skin) {
if(uuidMinion == null) {
uuidMinion = UUID.randomUUID();
}
@@ -51,12 +51,14 @@ import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
@NullMarked
public class MinionFakePlayer extends ServerPlayer {
public Runnable fixStartingPosition = () -> {};
@@ -73,16 +75,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 +160,7 @@ public class MinionFakePlayer extends ServerPlayer {
}
public SerializableListenerManager<MinionListener> listeners() {
return getData().listeners();
return getData().getListeners();
}
public void addMinionListener(MinionListener listener) {
@@ -174,7 +176,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 +215,7 @@ public class MinionFakePlayer extends ServerPlayer {
}));
}
MinionPersistentState.get(getServer()).updateMinionData(getData().withSpawned(false));
getData().setSpawned(false);
}
@Override
@@ -310,6 +312,7 @@ public class MinionFakePlayer extends ServerPlayer {
public void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) {
super.dropAllDeathLoot(world, damageSource);
ItemEntity entity = drop(toItemStack(world.getServer()), true, false);
//noinspection ConstantValue (Wrong nullability of drop)
if (entity != null) {
entity.setUnlimitedLifetime();
}
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.minion;
import org.jspecify.annotations.NullMarked;
@@ -16,9 +16,10 @@ import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.jspecify.annotations.Nullable;
public class MineBlockExecution implements InstructionExecution<MinionRuntime> {
private BlockPos currentBlock;
private @Nullable BlockPos currentBlock;
private float currentBlockDamage = 0;
private boolean first = true;
private boolean done = false;
@@ -38,7 +39,6 @@ public class MineBlockExecution implements InstructionExecution<MinionRuntime> {
}
if (player.blockActionRestricted(player.level(), hit.getBlockPos(), player.gameMode.getGameModeForPlayer())) {
done = true;
return;
}
} else {
done = true;
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.minion.program.instruction.inventory;
import org.jspecify.annotations.NullMarked;
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.minion.program.instruction.move;
import org.jspecify.annotations.NullMarked;
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.minion.program.instruction;
import org.jspecify.annotations.NullMarked;
@@ -22,7 +22,7 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
@@ -79,7 +79,7 @@ public class AnalogInputSupplier implements ValueSupplier<Long, MinionRuntime> {
public <T> CompletableFuture<ValueSupplier<?, MinionRuntime>> openConfiguration(MinionsGui parent, ValueType<T> valueType, @Nullable ValueSupplier<?, MinionRuntime> previous) {
CompletableFuture<ValueSupplier<?, MinionRuntime>> future = new CompletableFuture<>();
new SimpleMinionsGui(parent, (onClose, me) -> {
SimpleGui gui = new SimpleGui(MenuType.GENERIC_3x3, parent.getViewer(), false) {
SimpleGui gui = new SimpleGui(MenuType.GENERIC_3x3, parent.viewer, false) {
@Override
public void onPlayerClose(boolean success) {
onClose.run();
@@ -90,7 +90,7 @@ public class AnalogInputSupplier implements ValueSupplier<Long, MinionRuntime> {
gui.setSlot(2, me.backButton());
gui.setSlot(4, new GuiElementBuilder(MinionItems.REFERENCE_ITEM)
.setCallback(() -> {
ItemStack cursor = parent.getViewer().containerMenu.getCarried();
ItemStack cursor = parent.viewer.containerMenu.getCarried();
if (cursor.is(MinionItems.REFERENCE_ITEM) && cursor.get(MinionComponentTypes.REFERENCE) instanceof BlockPosClipboard pos) {
future.complete(new AnalogInputSupplier(pos.world(), pos.pos()));
me.goBack();
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.minion.program.supplier;
import org.jspecify.annotations.NullMarked;
@@ -5,6 +5,7 @@ import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.gui.MinionsGui;
import net.fabricmc.fabric.api.entity.FakePlayer;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
@@ -22,7 +23,6 @@ import net.minecraft.server.dialog.Input;
import net.minecraft.server.dialog.NoticeDialog;
import net.minecraft.server.dialog.action.CustomAll;
import net.minecraft.server.dialog.input.TextInput;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.component.ResolvableProfile;
import java.util.HashMap;
@@ -39,9 +39,9 @@ public class Base64SkinProvider implements SkinProvider {
private static Map<Long, CompletableFuture<ResolvableProfile>> futures = new HashMap<>();
@Override
public CompletableFuture<ResolvableProfile> openSkinMenu(ServerPlayer player) {
public CompletableFuture<ResolvableProfile> openSkinMenu(MinionsGui parent) {
dialogIdCounter++;
player.openDialog(getDialog());
parent.viewer.openDialog(getDialog());
CompletableFuture<ResolvableProfile> future = new CompletableFuture<>();
futures.put(dialogIdCounter, future);
return future;
@@ -1,17 +1,18 @@
package io.github.skippyall.minions.minion.skin;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.input.TextInput;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.component.ResolvableProfile;
import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
public class NameSkinProvider implements SkinProvider {
@Override
public CompletableFuture<ResolvableProfile> openSkinMenu(ServerPlayer player) {
return TextInput.inputString(player, Component.translatable("minions.gui.look.skin.name.title"), "")
.thenApply(ResolvableProfile::createUnresolved);
public CompletableFuture<@Nullable ResolvableProfile> openSkinMenu(MinionsGui parent) {
return TextInput.inputString(parent, Component.translatable("minions.gui.look.skin.name.title"), "")
.thenApply(name -> name != null ? ResolvableProfile.createUnresolved(name) : null);
}
@Override
@@ -1,13 +1,14 @@
package io.github.skippyall.minions.minion.skin;
import io.github.skippyall.minions.gui.MinionsGui;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.component.ResolvableProfile;
import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
public interface SkinProvider {
CompletableFuture<ResolvableProfile> openSkinMenu(ServerPlayer player);
CompletableFuture<@Nullable ResolvableProfile> openSkinMenu(MinionsGui parent);
Component getDisplayName();
}
@@ -1,17 +1,18 @@
package io.github.skippyall.minions.minion.skin;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.input.TextInput;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.component.ResolvableProfile;
import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
public class UUIDSkinProvider implements SkinProvider {
@Override
public CompletableFuture<ResolvableProfile> openSkinMenu(ServerPlayer player) {
return TextInput.inputString(player, Component.translatable("minions.gui.look.skin.uuid.title"), "")
.thenApply(ResolvableProfile::createUnresolved);
public CompletableFuture<@Nullable ResolvableProfile> openSkinMenu(MinionsGui parent) {
return TextInput.inputString(parent, Component.translatable("minions.gui.look.skin.uuid.title"), "")
.thenApply(name -> name != null ? ResolvableProfile.createUnresolved(name) : null);
}
@Override
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.minion.skin;
import org.jspecify.annotations.NullMarked;
@@ -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;
}
@@ -3,7 +3,7 @@ package io.github.skippyall.minions.mixins;
import io.github.skippyall.minions.mixinhelper.EntityViewMixinHelper;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.EntityGetter;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
@@ -4,7 +4,7 @@ import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@@ -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);
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.module;
import org.jspecify.annotations.NullMarked;
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions;
import org.jspecify.annotations.NullMarked;
@@ -0,0 +1,67 @@
package io.github.skippyall.minions.polymer;
import eu.pb4.polymer.core.api.block.PolymerBlock;
import eu.pb4.polymer.core.api.block.PolymerBlockUtils;
import eu.pb4.polymer.core.api.item.PolymerCreativeModeTabUtils;
import eu.pb4.polymer.core.api.item.PolymerItem;
import eu.pb4.polymer.core.api.other.PolymerComponent;
import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils;
import eu.pb4.polymer.virtualentity.api.BlockWithElementHolder;
import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.polymer.block.AnalogInputBlockOverlay;
import io.github.skippyall.minions.polymer.block.MinionTriggerBlockOverlay;
import io.github.skippyall.minions.polymer.item.ClipboardItemOverlay;
import io.github.skippyall.minions.polymer.item.MinionItemOverlay;
import io.github.skippyall.minions.polymer.item.MinionsBlockItemOverlay;
import io.github.skippyall.minions.polymer.item.SimpleItemOverlay;
import io.github.skippyall.minions.registration.MinionBlocks;
import io.github.skippyall.minions.registration.MinionComponentTypes;
import io.github.skippyall.minions.registration.MinionCreativeTab;
import io.github.skippyall.minions.registration.MinionItems;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Block;
public class PolymerRegistration {
public static void register() {
VersionSync.register();
PolymerResourcePackUtils.addModAssets(Minions.MOD_ID);
registerBlockOverlay(MinionBlocks.MINION_TRIGGER_BLOCK, new MinionTriggerBlockOverlay());
PolymerBlockUtils.registerBlockEntity(MinionBlocks.MINION_TRIGGER_BE_TYPE);
registerBlockOverlay(MinionBlocks.ANALOG_INPUT_BLOCK, new AnalogInputBlockOverlay());
registerItemOverlay(MinionItems.MINION_TRIGGER_ITEM, new MinionsBlockItemOverlay(MinionItems.MINION_TRIGGER_ITEM, Items.COMPARATOR));
registerItemOverlay(MinionItems.ANALOG_INPUT_ITEM, new MinionsBlockItemOverlay(MinionItems.ANALOG_INPUT_ITEM, Items.REPEATER));
registerItemOverlay(MinionItems.REFERENCE_ITEM, new ClipboardItemOverlay());
registerItemOverlay(MinionItems.MINION_ITEM, new MinionItemOverlay());
registerSimpleItemOverlay(MinionItems.BASIC_UPGRADE_BASE, Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE);
registerSimpleItemOverlay(MinionItems.ADVANCED_UPGRADE_BASE, Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE);
registerSimpleItemOverlay(MinionItems.MOVE_MODULE, Items.IRON_BOOTS);
registerSimpleItemOverlay(MinionItems.ATTACK_MODULE, Items.IRON_PICKAXE);
registerSimpleItemOverlay(MinionItems.INTERACT_MODULE, Items.LEVER);
registerSimpleItemOverlay(MinionItems.MOB_SPAWNING_MODULE, Items.SPAWNER);
PolymerComponent.registerDataComponent(MinionComponentTypes.MINION_DATA, MinionComponentTypes.REFERENCE, MinionComponentTypes.MODULE);
PolymerCreativeModeTabUtils.registerPolymerCreativeModeTab(Identifier.fromNamespaceAndPath(Minions.MOD_ID, "main"), MinionCreativeTab.group);
}
private static void registerBlockOverlay(Block block, PolymerBlock overlay) {
PolymerBlock.registerOverlay(block, overlay);
if(overlay instanceof BlockWithElementHolder elementHolderOverlay) {
BlockWithElementHolder.registerOverlay(block, elementHolderOverlay);
}
}
private static void registerItemOverlay(Item item, PolymerItem overlay) {
PolymerItem.registerOverlay(item, overlay);
}
private static void registerSimpleItemOverlay(Item serverItem, Item polymerItem) {
registerItemOverlay(serverItem, SimpleItemOverlay.withoutModel(serverItem, polymerItem));
}
}
@@ -8,11 +8,12 @@ import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.Identifier;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jspecify.annotations.Nullable;
public class VersionSync {
public static final int NETWORK_VERSION = 1;
public static boolean isOnClient(PacketContext context) {
public static boolean isOnClient(@Nullable PacketContext context) {
if(context != null && context.get(PacketContext.CONNECTION).getPacketListener() instanceof ServerGamePacketListenerImpl gamePacketListener) {
return isOnClient(gamePacketListener);
}
@@ -0,0 +1,14 @@
package io.github.skippyall.minions.polymer.block;
import eu.pb4.polymer.core.api.block.PolymerBlock;
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import org.jspecify.annotations.Nullable;
public class AnalogInputBlockOverlay implements PolymerBlock {
@Override
public BlockState getPolymerBlockState(BlockState blockState, @Nullable PacketContext packetContext) {
return Blocks.AMETHYST_BLOCK.defaultBlockState();
}
}
@@ -0,0 +1,57 @@
package io.github.skippyall.minions.polymer.block;
import eu.pb4.polymer.core.api.block.PolymerBlock;
import eu.pb4.polymer.core.api.utils.PolymerClientDecoded;
import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils;
import eu.pb4.polymer.virtualentity.api.BlockWithElementHolder;
import eu.pb4.polymer.virtualentity.api.ElementHolder;
import eu.pb4.polymer.virtualentity.api.elements.ItemDisplayElement;
import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.block.miniontrigger.MinionTriggerBlock;
import io.github.skippyall.minions.polymer.VersionSync;
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.core.BlockPos;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.DiodeBlock;
import net.minecraft.world.level.block.state.BlockState;
import org.jspecify.annotations.Nullable;
public class MinionTriggerBlockOverlay implements PolymerBlock, PolymerClientDecoded, BlockWithElementHolder {
@Override
public BlockState getPolymerBlockState(BlockState state, @Nullable PacketContext context) {
return VersionSync.isOnClient(context) ? state : net.minecraft.world.level.block.Blocks.COMPARATOR.defaultBlockState().setValue(DiodeBlock.POWERED, state.getValue(MinionTriggerBlock.POWERED));
}
@Override
public boolean handleMiningOnServer(ItemStack tool, BlockState state, BlockPos pos, ServerPlayer player) {
return false;
}
@Override
public @Nullable ElementHolder createElementHolder(ServerLevel world, BlockPos pos, BlockState initialBlockState) {
ElementHolder holder = new ElementHolder() {
@Override
public boolean startWatching(ServerGamePacketListenerImpl player) {
if(PolymerResourcePackUtils.hasMainPack(player) && !VersionSync.isOnClient(player)) {
return super.startWatching(player);
} else {
return false;
}
}
};
ItemStack stack = new ItemStack(Items.BARRIER);
stack.set(DataComponents.ITEM_MODEL, Identifier.fromNamespaceAndPath(Minions.MOD_ID, "minion_trigger_no_plate_" + (initialBlockState.getValue(MinionTriggerBlock.POWERED) ? "active" : "inactive")));
ItemDisplayElement element = new ItemDisplayElement(stack);
element.setItemDisplayContext(ItemDisplayContext.NONE);
holder.addElement(element);
return holder;
}
}
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.polymer.block;
import org.jspecify.annotations.NullMarked;
@@ -0,0 +1,31 @@
package io.github.skippyall.minions.polymer.item;
import eu.pb4.polymer.core.api.item.PolymerItem;
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.TooltipFlag;
import org.jspecify.annotations.Nullable;
public class ClipboardItemOverlay implements PolymerItem {
@Override
public Item getPolymerItem(ItemStack itemStack, PacketContext context) {
return /*VersionSync.isOnClient(context) ? this : */Items.PAPER;
}
@Override
public @Nullable Identifier getPolymerItemModel(ItemStack stack, PacketContext context, HolderLookup.Provider lookup) {
return null;
}
@Override
public ItemStack getPolymerItemStack(ItemStack itemStack, TooltipFlag tooltipType, PacketContext context, HolderLookup.Provider lookup) {
ItemStack stack = PolymerItem.super.getPolymerItemStack(itemStack, tooltipType, context, lookup);
stack.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, true);
return stack;
}
}
@@ -0,0 +1,31 @@
package io.github.skippyall.minions.polymer.item;
import eu.pb4.polymer.core.api.item.PolymerItem;
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.TooltipFlag;
import org.jspecify.annotations.Nullable;
public class MinionItemOverlay implements PolymerItem {
@Override
public @Nullable Identifier getPolymerItemModel(ItemStack stack, PacketContext context, HolderLookup.Provider lookup) {
return null;
}
@Override
public Item getPolymerItem(ItemStack itemStack, PacketContext player) {
return Items.ARMOR_STAND;
}
@Override
public ItemStack getPolymerItemStack(ItemStack stack, TooltipFlag tooltipType, PacketContext player, HolderLookup.Provider lookup) {
ItemStack out = PolymerItem.super.getPolymerItemStack(stack, tooltipType, player, lookup);
out.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, true);
return out;
}
}
@@ -0,0 +1,55 @@
package io.github.skippyall.minions.polymer.item;
import eu.pb4.polymer.core.api.item.PolymerItem;
import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils;
import io.github.skippyall.minions.polymer.VersionSync;
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.core.HolderLookup;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import org.jspecify.annotations.Nullable;
public class MinionsBlockItemOverlay implements PolymerItem {
private final Item serverItem;
private final Item polymerItem;
public MinionsBlockItemOverlay(Item serverItem, Item polymerItem) {
this.serverItem = serverItem;
this.polymerItem = polymerItem;
}
@Override
public Item getPolymerItem(ItemStack itemStack, PacketContext context) {
if(VersionSync.isOnClient(context)) {
return serverItem;
} else {
return polymerItem;
}
}
@Override
public @Nullable Identifier getPolymerItemModel(ItemStack stack, PacketContext context, HolderLookup.Provider lookup) {
if(PolymerResourcePackUtils.hasMainPack(context)) {
return PolymerItem.super.getPolymerItemModel(stack, context, lookup);
} else {
return null;
}
}
@Override
public boolean isPolymerBlockInteraction(BlockState state, ServerPlayer player, InteractionHand hand, ItemStack stack, ServerLevel world, BlockHitResult blockHitResult, InteractionResult actionResult) {
return true;
}
@Override
public boolean isIgnoringBlockInteractionPlaySoundExceptedEntity(BlockState state, ServerPlayer player, InteractionHand hand, ItemStack stack, ServerLevel world, BlockHitResult blockHitResult) {
return true;
}
}
@@ -0,0 +1,48 @@
package io.github.skippyall.minions.polymer.item;
import eu.pb4.polymer.core.api.item.PolymerItem;
import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils;
import io.github.skippyall.minions.polymer.VersionSync;
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.core.HolderLookup;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.Nullable;
public class SimpleItemOverlay implements PolymerItem {
private final Item serverItem;
private final Item polymerItem;
private final boolean useModel;
private SimpleItemOverlay(Item serverItem, Item polymerItem, boolean useModel) {
this.serverItem = serverItem;
this.polymerItem = polymerItem;
this.useModel = useModel;
}
public static SimpleItemOverlay withModel(Item serverItem, Item polymerItem) {
return new SimpleItemOverlay(serverItem, polymerItem, true);
}
public static SimpleItemOverlay withoutModel(Item serverItem, Item polymerItem) {
return new SimpleItemOverlay(serverItem, polymerItem, false);
}
public Item getPolymerItem(ItemStack itemStack, PacketContext context) {
if(useModel && VersionSync.isOnClient(context)) {
return serverItem;
} else {
return polymerItem;
}
}
@Override
public @Nullable Identifier getPolymerItemModel(ItemStack stack, PacketContext context, HolderLookup.Provider lookup) {
if(PolymerResourcePackUtils.hasMainPack(context) && useModel) {
return PolymerItem.super.getPolymerItemModel(stack, context, lookup);
} else {
return null;
}
}
}
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.polymer.item;
import org.jspecify.annotations.NullMarked;
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.polymer;
import org.jspecify.annotations.NullMarked;
@@ -4,7 +4,7 @@ import com.mojang.serialization.Codec;
import io.github.skippyall.minions.program.InstructionRuntime;
import io.github.skippyall.minions.program.value.ValueType;
import io.github.skippyall.minions.registration.MinionRegistries;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
/**
* An <code>ValueSupplier</code> can be supplied to an instruction with a matching parameter.
@@ -3,6 +3,7 @@ package io.github.skippyall.minions.program.consumer;
import com.mojang.serialization.Codec;
import io.github.skippyall.minions.program.InstructionRuntime;
import io.github.skippyall.minions.program.supplier.Parameter;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
@@ -22,7 +23,7 @@ public class ValueConsumerList<R extends InstructionRuntime<R>> {
this.valueConsumers = new HashMap<>(valueConsumers);
}
public <T, A extends ValueConsumer<T,R>> A getValueConsumer(Parameter<T> parameter) {
public <T, A extends ValueConsumer<T,R>> @Nullable A getValueConsumer(Parameter<T> parameter) {
ValueConsumer<?,R> argument = valueConsumers.get(parameter.name());
return argument == null ? null : argument.cast(parameter.type());
}
@@ -4,7 +4,7 @@ import com.mojang.serialization.Codec;
import io.github.skippyall.minions.program.InstructionRuntime;
import io.github.skippyall.minions.program.value.ValueType;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.program.consumer;
import org.jspecify.annotations.NullMarked;
@@ -10,7 +10,7 @@ import io.github.skippyall.minions.registration.MinionRegistries;
import io.github.skippyall.minions.registration.ValueConverters;
import io.github.skippyall.minions.util.TranslationUtil;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
@@ -7,7 +7,7 @@ import io.github.skippyall.minions.registration.MinionRegistries;
import io.github.skippyall.minions.registration.ValueTypes;
import io.github.skippyall.minions.util.TranslationUtil;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
public class Casts {
public static <F,T> @Nullable Cast<F,T> getCast(ValueType<F> from, ValueType<T> to) {
@@ -28,11 +28,16 @@ public class Casts {
return (Cast<F, T>) new Cast.CastCrafter<>(from, ValueTypes.STRING, String::valueOf).craftCast();
}
if(from == ValueTypes.STRING && to == ValueTypes.DOUBLE) {
//noinspection unchecked
return (Cast<F, T>) new Cast.CastCrafter<>(ValueTypes.STRING, ValueTypes.DOUBLE, Double::parseDouble).canFail().lossy().craftCast();
}
return null;
}
public static <F,T> @Nullable T cast(TypedValue<F> from, ValueType<T> to) {
@Nullable Cast<F,T> cast = getCast(from.type(), to);
Cast<F,T> cast = getCast(from.type(), to);
if(cast != null) {
return cast.cast(from.value());
} else {
@@ -4,13 +4,16 @@ import com.mojang.serialization.Codec;
import io.github.skippyall.minions.gui.input.Result;
import io.github.skippyall.minions.program.value.TypedValue;
import io.github.skippyall.minions.program.value.ValueType;
import io.github.skippyall.minions.registration.MinionRegistries;
import io.github.skippyall.minions.util.TranslationUtil;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.function.Consumer;
public class ConverterList {
public static final Codec<ConverterList> CODEC = ValueConverter.CODEC.listOf().xmap(ConverterList::new, l -> l.converters);
@@ -53,7 +56,11 @@ public class ConverterList {
private <F,I,T> Result<TypedValue<?>, Component> convert(TypedValue<F> from, ValueConverter<I,T> converter, ListIterator<ValueConverter<?,?>> iterator) {
Result<I, Component> inter = Casts.castOrError(from, converter.getFrom());
if(inter instanceof Result.Error<I, Component> error) {
return new Result.Error<>(Component.translatable("minions.converter.list.passing_error", iterator.previousIndex(), error.message()));
return new Result.Error<>(
Component.translatable("minions.converter.list.passing_error", iterator.previousIndex())
.append("\n")
.append(error.message())
);
}
Result<T, Component> to = converter.convert(inter.getOrThrow());
@@ -64,8 +71,37 @@ public class ConverterList {
}
}
public Result<@Nullable Void, Component> check() {
return new Result.Success<>(null);
public static @Nullable Component createConverterWarning(ValueConverter<?, ?> converter) {
return null;
}
public static @Nullable Component createCastWarning(ValueType<?> fromType, ValueType<?> toType) {
Component warning = null;
Cast<?,?> cast = Casts.getCast(fromType, toType);
if(cast == null) {
warning = Component.translatable("minions.converter.cast.not_found", TranslationUtil.getTranslation(fromType, MinionRegistries.VALUE_TYPES), TranslationUtil.getTranslation(toType, MinionRegistries.VALUE_TYPES));
}
return warning;
}
public void check(Consumer<Component> errorConsumer, ValueType<?> input, ValueType<?> output) {
Component firstCastWarning = createCastWarning(input, converters.isEmpty() ? output : converters.get(0).getFrom());
if(firstCastWarning != null) {
errorConsumer.accept(firstCastWarning);
}
for(int i = 0; i < converters.size(); i++) {
ValueConverter<?,?> converter = converters.get(i);
Component converterWarning = createConverterWarning(converter);
if(converterWarning != null) {
errorConsumer.accept(converterWarning);
}
Component castWarning = createCastWarning(converter.getTo(), i + 1 < converters.size() ? converters.get(i + 1).getFrom() : output);
if(castWarning != null) {
errorConsumer.accept(castWarning);
}
}
}
@Override
@@ -8,7 +8,7 @@ import io.github.skippyall.minions.registration.MinionRegistries;
import io.github.skippyall.minions.registration.ValueConverters;
import io.github.skippyall.minions.registration.ValueTypes;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
@@ -5,6 +5,7 @@ import io.github.skippyall.minions.gui.input.Result;
import io.github.skippyall.minions.program.value.ValueType;
import io.github.skippyall.minions.registration.MinionRegistries;
import net.minecraft.network.chat.Component;
import org.jspecify.annotations.Nullable;
public interface ValueConverter<F,T> {
Codec<ValueConverter<?,?>> CODEC = MinionRegistries.VALUE_CONVERTER_TYPES.byNameCodec().dispatch(ValueConverter::getType, ValueConverterType::getCodec);
@@ -19,7 +20,7 @@ public interface ValueConverter<F,T> {
Component getDisplayText();
default <F2,T2> ValueConverter<F2,T2> cast(ValueType<F2> from, ValueType<T2> to) {
default <F2,T2> @Nullable ValueConverter<F2,T2> cast(ValueType<F2> from, ValueType<T2> to) {
if(from == getFrom() && to == getTo()) {
//noinspection unchecked
return (ValueConverter<F2, T2>) this;

Some files were not shown because too many files have changed in this diff Show More