6 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
121 changed files with 1311 additions and 1022 deletions
+1
View File
@@ -1,6 +1,7 @@
# User-specific stuff # User-specific stuff
.idea/ .idea/
urlaub/ urlaub/
.kotlin/
*.iml *.iml
*.ipr *.ipr
@@ -1,6 +1,7 @@
package io.github.skippyall.minions; package io.github.skippyall.minions;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import org.jspecify.annotations.Nullable;
import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo; import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
@@ -15,6 +16,7 @@ public class MinionMixinConfigPlugin implements IMixinConfigPlugin {
} }
@Override @Override
@Nullable
public String getRefMapperConfig() { public String getRefMapperConfig() {
return null; return null;
} }
@@ -34,6 +36,7 @@ public class MinionMixinConfigPlugin implements IMixinConfigPlugin {
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {} public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {}
@Override @Override
@Nullable
public List<String> getMixins() { public List<String> getMixins() {
return null; return null;
} }
@@ -1,16 +1,16 @@
package io.github.skippyall.minions; package io.github.skippyall.minions;
import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils;
import io.github.skippyall.minions.command.MinionsCommand; import io.github.skippyall.minions.command.MinionsCommand;
import io.github.skippyall.minions.docs.DocsManager; import io.github.skippyall.minions.docs.DocsManager;
import io.github.skippyall.minions.minion.MinionPersistentState; import io.github.skippyall.minions.minion.MinionPersistentState;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; 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 io.github.skippyall.minions.registration.MinionRegistration;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; 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 net.minecraft.server.packs.PackType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -26,8 +26,6 @@ public class Minions implements ModInitializer {
MinionRegistration.register(); MinionRegistration.register();
VersionSync.register();
ServerLifecycleEvents.SERVER_STARTED.register(server -> { ServerLifecycleEvents.SERVER_STARTED.register(server -> {
MinionPersistentState.get(server).getMinionData().forEach((uuid, data) -> { MinionPersistentState.get(server).getMinionData().forEach((uuid, data) -> {
if(data.isSpawned()) { if(data.isSpawned()) {
@@ -38,8 +36,8 @@ public class Minions implements ModInitializer {
CommandRegistrationCallback.EVENT.register(MinionsCommand::register); 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.TomlFormat;
import com.electronwill.nightconfig.toml.TomlParser; import com.electronwill.nightconfig.toml.TomlParser;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import org.jspecify.annotations.Nullable;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
public class MinionsConfig { public class MinionsConfig {
private static MinionsConfig INSTANCE; private static @Nullable MinionsConfig INSTANCE;
public Minion minion = new Minion(); 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 -> { 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)); player.sendSystemMessage(Component.translatable("minions.reference.instruction.tooltip", be.getInstructionName(), name));
}); });
return InteractionResult.SUCCESS; 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.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.jspecify.annotations.Nullable;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public abstract class InstructionBoundBlockEntity<L extends BlockEntityMinionListener<?>> extends BlockEntity { public abstract class InstructionBoundBlockEntity<L extends BlockEntityMinionListener<?>> extends BlockEntity {
protected UUID minionUuid; protected @Nullable UUID minionUuid;
protected String instructionName = ""; protected String instructionName = "";
public InstructionBoundBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) { public InstructionBoundBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
@@ -56,7 +57,7 @@ public abstract class InstructionBoundBlockEntity<L extends BlockEntityMinionLis
return Optional.empty(); return Optional.empty();
} }
public UUID getMinionUuid() { public @Nullable UUID getMinionUuid() {
return minionUuid; return minionUuid;
} }
@@ -72,7 +73,7 @@ public abstract class InstructionBoundBlockEntity<L extends BlockEntityMinionLis
return getMinion().flatMap(this::getInstruction); return getMinion().flatMap(this::getInstruction);
} }
public L getListener() { public @Nullable L getListener() {
return BlockEntityMinionListener.getListener(level, worldPosition, minionUuid, getListenerClass()); 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; package io.github.skippyall.minions.block.miniontrigger;
import com.mojang.serialization.MapCodec; 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.block.instruction_bound.InstructionBoundBlock;
import io.github.skippyall.minions.polymer.VersionSync;
import io.github.skippyall.minions.registration.MinionBlocks; 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.BlockPos;
import net.minecraft.core.Direction; 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.BlockGetter;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block; 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.SupportType;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType; 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.level.redstone.Orientation;
import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape; 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 MapCodec<MinionTriggerBlock> CODEC = simpleCodec(MinionTriggerBlock::new);
public static final BooleanProperty POWERED = BooleanProperty.create("powered"); public static final BooleanProperty POWERED = BooleanProperty.create("powered");
@@ -70,6 +53,7 @@ public class MinionTriggerBlock extends InstructionBoundBlock implements Polymer
} }
@Override @Override
@NullMarked
protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) { protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) {
if(!canSurvive(state, world, pos)) { if(!canSurvive(state, world, pos)) {
dropResources(state, world, pos); dropResources(state, world, pos);
@@ -103,35 +87,4 @@ public class MinionTriggerBlock extends InstructionBoundBlock implements Polymer
protected BlockEntityType<MinionTriggerBlockEntity> getBlockEntityType() { protected BlockEntityType<MinionTriggerBlockEntity> getBlockEntityType() {
return MinionBlocks.MINION_TRIGGER_BE_TYPE; 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; 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.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.registration.MinionComponentTypes; import io.github.skippyall.minions.registration.MinionComponentTypes;
import io.github.skippyall.minions.registration.MinionItems; 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.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.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
public class ClipboardItem extends Item implements PolymerItem { public class ClipboardItem {
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 static ItemStack createInstructionReference(MinionFakePlayer minion, String instructionName) { public static ItemStack createInstructionReference(MinionFakePlayer minion, String instructionName) {
ItemStack stack = new ItemStack(MinionItems.REFERENCE_ITEM); 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) { public static int list(CommandContext<CommandSourceStack> context) {
Collection<MinionData> minions = MinionPersistentState.get(context.getSource().getServer()).getMinionData().values(); Collection<MinionData> minions = MinionPersistentState.get(context.getSource().getServer()).getMinionData().values();
for (MinionData minion : minions) { 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; return 0;
} }
@@ -41,7 +41,7 @@ public class MinionArgument {
@Override @Override
public CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) throws CommandSyntaxException { public CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) throws CommandSyntaxException {
for (MinionData data : MinionPersistentState.get(context.getSource().getServer()).getMinionDataList()) { for (MinionData data : MinionPersistentState.get(context.getSource().getServer()).getMinionDataList()) {
builder.suggest(data.name()); builder.suggest(data.getName());
} }
return builder.buildFuture(); 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.Coordinates;
import net.minecraft.commands.arguments.coordinates.Vec3Argument; import net.minecraft.commands.arguments.coordinates.Vec3Argument;
import net.minecraft.server.permissions.Permissions; import net.minecraft.server.permissions.Permissions;
import org.jspecify.annotations.Nullable;
import static net.minecraft.commands.Commands.argument; import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal; 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); 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); MinionFakePlayer.spawnMinion(data, source.getLevel(), pos != null ? pos.getPosition(source) : null, pos != null ? pos.getRotation(source) : null, force);
return 0; 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.google.gson.JsonParseException;
import com.mojang.serialization.JsonOps; import com.mojang.serialization.JsonOps;
import io.github.skippyall.minions.Minions; 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.core.Holder;
import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component; 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.dialog.action.StaticAction;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.StrictJsonParser; import net.minecraft.util.StrictJsonParser;
import net.minecraft.util.Tuple; import net.minecraft.util.Tuple;
import org.jspecify.annotations.Nullable;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.util.ArrayList; import java.util.ArrayList;
@@ -27,14 +28,12 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
public class DocsManager implements SimpleResourceReloadListener<Tuple<Map<Identifier, DocsEntry>, DocsTree>> { public class DocsManager extends SimpleReloadListener<Tuple<Map<Identifier, DocsEntry>, DocsTree>> {
private static Map<Identifier, DocsEntry> docs; private static @Nullable Map<Identifier, DocsEntry> docs;
private static DocsTree tree; private static @Nullable DocsTree tree;
public static DocsTree getTree() { public static @Nullable DocsTree getTree() {
return tree; return tree;
} }
@@ -87,57 +86,55 @@ public class DocsManager implements SimpleResourceReloadListener<Tuple<Map<Ident
); );
} }
public static DocsEntry getDocsEntry(Identifier id) { public static @Nullable DocsEntry getDocsEntry(Identifier id) {
return docs.get(id); if(docs != null) {
} return docs.get(id);
} else {
public static Collection<Identifier> getDocsEntryIds() {
return docs.keySet();
}
@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"));
final DocsTree.BranchElement[] root = {null};
Map<Identifier, DocsEntry> docsEntries = new HashMap<>();
resources.forEach((id, resource) -> {
try(Reader reader = resource.openAsReader()) {
if(id.getPath().equals("docs/tree.json")) {
DocsTree.BranchElement.CODEC.decode(JsonOps.INSTANCE, StrictJsonParser.parse(reader))
.ifSuccess(entry -> root[0] = entry.getFirst())
.ifError(error -> Minions.LOGGER.warn("Could not parse docs tree {}: {}", id, error.message()));
} else {
Identifier docId = Identifier.fromNamespaceAndPath(id.getNamespace(), id.getPath().substring("docs/".length(), id.getPath().length() - ".json".length()));
DocsEntry.CODEC.decode(JsonOps.INSTANCE, StrictJsonParser.parse(reader))
.ifSuccess(entry -> docsEntries.put(docId, entry.getFirst()))
.ifError(error -> Minions.LOGGER.warn("Could not parse docs entry {}: {}", id, error.message()));
}
} catch (IOException | JsonParseException e) {
Minions.LOGGER.warn("Could not read file {}", id, e);
}
});
if(root[0] != null) {
DocsTree tree = new DocsTree(root[0]);
return new Tuple<>(docsEntries, tree);
} 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(() -> {
docs = o.getA();
tree = o.getB();
return null; return null;
}); }
}
public static @Nullable Collection<Identifier> getDocsEntryIds() {
if (docs != null) {
return docs.keySet();
} else {
return null;
}
} }
@Override @Override
public Identifier getFabricId() { public Tuple<Map<Identifier, DocsEntry>, DocsTree> prepare(SharedState state) {
return Identifier.fromNamespaceAndPath(Minions.MOD_ID, "docs"); Map<Identifier, Resource> resources = state.resourceManager().listResources("docs", id -> id.getNamespace().equals(Minions.MOD_ID) && id.getPath().endsWith(".json"));
final DocsTree. @Nullable BranchElement[] root = {null};
Map<Identifier, DocsEntry> docsEntries = new HashMap<>();
resources.forEach((id, resource) -> {
try(Reader reader = resource.openAsReader()) {
if(id.getPath().equals("docs/tree.json")) {
DocsTree.BranchElement.CODEC.decode(JsonOps.INSTANCE, StrictJsonParser.parse(reader))
.ifSuccess(entry -> root[0] = entry.getFirst())
.ifError(error -> Minions.LOGGER.warn("Could not parse docs tree {}: {}", id, error.message()));
} else {
Identifier docId = Identifier.fromNamespaceAndPath(id.getNamespace(), id.getPath().substring("docs/".length(), id.getPath().length() - ".json".length()));
DocsEntry.CODEC.decode(JsonOps.INSTANCE, StrictJsonParser.parse(reader))
.ifSuccess(entry -> docsEntries.put(docId, entry.getFirst()))
.ifError(error -> Minions.LOGGER.warn("Could not parse docs entry {}: {}", id, error.message()));
}
} catch (IOException | JsonParseException e) {
Minions.LOGGER.warn("Could not read file {}", id, e);
}
});
if(root[0] != null) {
DocsTree tree = new DocsTree(root[0]);
return new Tuple<>(docsEntries, tree);
} else {
return new Tuple<>(docsEntries, null);
}
}
@Override
public void apply(Tuple<Map<Identifier, DocsEntry>, DocsTree> o, SharedState state) {
docs = o.getA();
tree = o.getB();
} }
} }
@@ -3,6 +3,7 @@ package io.github.skippyall.minions.docs;
import com.mojang.datafixers.util.Either; import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
import org.jspecify.annotations.Nullable;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -35,9 +36,9 @@ public class DocsTree {
} }
public static abstract sealed class Element { public static abstract sealed class Element {
private BranchElement parent; private @Nullable BranchElement parent;
public BranchElement getParent() { public @Nullable BranchElement getParent() {
return parent; return parent;
} }
@@ -45,12 +46,20 @@ public class DocsTree {
this.parent = parent; this.parent = parent;
} }
public DocElement next() { public @Nullable DocElement next() {
return parent.next(this); if (parent != null) {
return parent.next(this);
} else {
return null;
}
} }
public DocElement previous() { public @Nullable DocElement previous() {
return parent.previous(this); 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; int nextIndex = e.indexOf(current) + 1;
if(nextIndex < e.size()) { if(nextIndex < e.size()) {
return switch (e.get(nextIndex)) { 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; int previousIndex = e.indexOf(current) - 1;
if(previousIndex >= 0) { if(previousIndex >= 0) {
return switch (e.get(previousIndex)) { 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.ItemBody;
import net.minecraft.server.dialog.body.PlainMessage; import net.minecraft.server.dialog.body.PlainMessage;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -55,7 +56,7 @@ public record ReferenceEntry(Metadata metadata, ResourceKey<?> object, Component
return bodyElements; return bodyElements;
} }
public GuiDisplay getObjectDisplay(RegistryAccess manager) { public @Nullable GuiDisplay getObjectDisplay(RegistryAccess manager) {
GuiDisplay display = GuiDisplay.DEFAULT_DISPLAY; GuiDisplay display = GuiDisplay.DEFAULT_DISPLAY;
if(object.isFor(Registries.ITEM) || object.isFor(Registries.BLOCK)) { if(object.isFor(Registries.ITEM) || object.isFor(Registries.BLOCK)) {
Item item; 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 { public interface GuiDisplay {
Codec<GuiDisplay> CODEC = MinionRegistries.GUI_DISPLAY_TYPE.byNameCodec().dispatch(GuiDisplay::getCodec, codec -> codec.fieldOf("data")); Codec<GuiDisplay> CODEC = MinionRegistries.GUI_DISPLAY_TYPE.byNameCodec().dispatch(GuiDisplay::getCodec, codec -> codec.fieldOf("data"));
GuiDisplay DEFAULT_DISPLAY = new ItemBased(Items.BARRIER); 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<>(); LinkedHashSet<DataComponentType<?>> set = new LinkedHashSet<>();
for(DataComponentType<?> type : BuiltInRegistries.DATA_COMPONENT_TYPE) { for(DataComponentType<?> type : BuiltInRegistries.DATA_COMPONENT_TYPE) {
if(type != DataComponents.LORE) { if(type != DataComponents.LORE) {
@@ -106,7 +107,7 @@ public interface GuiDisplay {
@Override @Override
public ItemStackTemplate createItemStackTemplate() { public ItemStackTemplate createItemStackTemplate() {
return new ItemStackTemplate(item, DataComponentPatch.builder() 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) .set(DataComponents.RARITY, Rarity.COMMON)
.build()); .build());
} }
@@ -130,6 +131,7 @@ public interface GuiDisplay {
public ItemStackTemplate createItemStackTemplate() { public ItemStackTemplate createItemStackTemplate() {
return new ItemStackTemplate(Items.PLAYER_HEAD, DataComponentPatch.builder() return new ItemStackTemplate(Items.PLAYER_HEAD, DataComponentPatch.builder()
.set(DataComponents.PROFILE, ResolvableProfile.createUnresolved(uuid)) .set(DataComponents.PROFILE, ResolvableProfile.createUnresolved(uuid))
.set(DataComponents.TOOLTIP_DISPLAY, TOOLTIP_HIDE_ALL_COMPONENTS)
.build() .build()
); );
} }
@@ -10,6 +10,7 @@ import io.github.skippyall.minions.minion.MinionProfileUtils
import io.github.skippyall.minions.minion.skin.SkinProvider import io.github.skippyall.minions.minion.skin.SkinProvider
import io.github.skippyall.minions.registration.MinionRegistries import io.github.skippyall.minions.registration.MinionRegistries
import io.github.skippyall.minions.registration.SkinProviders import io.github.skippyall.minions.registration.SkinProviders
import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.fabricmc.fabric.api.entity.FakePlayer import net.fabricmc.fabric.api.entity.FakePlayer
import net.minecraft.core.component.DataComponents import net.minecraft.core.component.DataComponents
@@ -19,9 +20,7 @@ import net.minecraft.world.inventory.MenuType
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items import net.minecraft.world.item.Items
import net.minecraft.world.item.component.ResolvableProfile import net.minecraft.world.item.component.ResolvableProfile
import java.util.Optional import java.util.*
import java.util.function.Consumer
import java.util.function.Function
class MinionLookGui( class MinionLookGui(
viewer: ServerPlayer, viewer: ServerPlayer,
@@ -82,20 +81,24 @@ class MinionLookGui(
gui.setSlot(16, builder) 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() { fun openSkinGui() {
currentSkinProvider.openSkinMenu(this) scope.launch {
.thenCompose(Function { profile: ResolvableProfile? -> val profile = currentSkinProvider.openSkinMenu(this@MinionLookGui).await()
profile!!.resolveProfile( val skin = profile?.resolveProfile(viewer.level().server.services().profileResolver())?.await()
viewer.level().server.services().profileResolver()
) data.skin = Optional.ofNullable(skin?.properties())
})
.thenAccept(Consumer { skin: GameProfile? -> updateSkin()
MinionItem.setData( }
viewer.level().server, this.data.withSkin(
Optional.of(skin!!.properties())
), minionItem
)
})
} }
private fun cycleSkinProvider() { private fun cycleSkinProvider() {
@@ -109,15 +112,6 @@ class MinionLookGui(
updateSkinProvider() updateSkinProvider()
} }
private fun updateSkinProvider() {
gui.setSlot(
25, GuiElementBuilder()
.setItem(Items.GREEN_STAINED_GLASS_PANE)
.setComponent(DataComponents.CUSTOM_NAME, currentSkinProvider.getDisplayName())
.setCallback(Runnable { this.cycleSkinProvider() })
)
}
fun openRenameGui() { fun openRenameGui() {
scope.launch { scope.launch {
val newName = TextInput.input( val newName = TextInput.input(
@@ -126,10 +120,11 @@ class MinionLookGui(
"Minion", "Minion",
) { name -> ) { name ->
MinionProfileUtils.checkMinionNameWithoutPrefix(viewer.level().server, name) MinionProfileUtils.checkMinionNameWithoutPrefix(viewer.level().server, name)
} }.await()
if(newName != null) { if(newName != null) {
this@MinionLookGui.data.withName(newName) data.name = newName
updateName()
} }
} }
} }
@@ -38,7 +38,7 @@ abstract class MinionsGui {
protected abstract fun open() protected abstract fun open()
protected fun reopen() { protected open fun reopen() {
open() open()
} }
@@ -6,9 +6,12 @@ import net.minecraft.core.IdMap;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import org.jspecify.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function;
public class PaginatedList extends MinionsGui { public class PaginatedList extends MinionsGui {
private int page = 0; private int page = 0;
@@ -16,6 +19,7 @@ public class PaginatedList extends MinionsGui {
private final Component title; private final Component title;
private final int size; private final int size;
private final BiFunction<Integer, PaginatedList, GuiElementBuilder> display; 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) { public PaginatedList(MinionsGui parent, Component title, int size, BiFunction<Integer, PaginatedList, GuiElementBuilder> display) {
super(parent); super(parent);
@@ -25,12 +29,20 @@ public class PaginatedList extends MinionsGui {
open(); 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 @Override
protected void open() { protected void open() {
gui = new SimpleGui(MenuType.GENERIC_9x6, viewer, false) { gui = new SimpleGui(MenuType.GENERIC_9x6, viewer, false) {
@Override @Override
public void onPlayerClose(boolean success) { public void onPlayerClose(boolean success) {
onBackingClosed(); onBackingClosed();
if(onClose != null) {
onClose.run();
}
} }
}; };
gui.setTitle(title); 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)); 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() { private void addItems() {
int slot = 9; int slot = 9;
for(int i = 36 * page; i < Math.min(36 * (page + 1), size); i++) { 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.viewer, 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; package io.github.skippyall.minions.gui.input;
import org.jetbrains.annotations.NotNull; import org.jspecify.annotations.Nullable;
import org.jetbrains.annotations.Nullable;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; 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) { static <T> Result<T, String> wrap(UnsafeOperation<T> toWrap) {
return wrapCustomError(toWrap, Exception::getMessage); 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) { static <T, E> Result<T, E> ofNullable(@Nullable T value, E error) {
if(value != null) { if(value != null) {
return new Success<>(value); return new Success<T, E>(value);
} else { } else {
return new Error<>(error); 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) { static <T, E> Result<T, E> ofNullable(@Nullable T value, Supplier<E> error) {
if(value != null) { if(value != null) {
return new Success<>(value); return new Success<T, E>(value);
} else { } else {
return new Error<>(error.get()); return new Error<>(error.get());
} }
@@ -49,13 +48,13 @@ public interface Result<T, E> {
E getErrorOrThrow(); 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); <U> Result<U,E> map(Function<T, U> mapper);
@@ -75,7 +74,7 @@ public interface Result<T, E> {
} }
@Override @Override
public @NotNull Optional<E> getOptionalError() { public Optional<E> getOptionalError() {
return Optional.empty(); return Optional.empty();
} }
@@ -90,17 +89,17 @@ public interface Result<T, E> {
} }
@Override @Override
public @NotNull Optional<T> getOptional() { public Optional<T> getOptional() {
return Optional.ofNullable(result); return Optional.ofNullable(result);
} }
@Override @Override
public void ifSuccess(@NotNull Consumer<T> handler) { public void ifSuccess(Consumer<T> handler) {
handler.accept(result); handler.accept(result);
} }
@Override @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 @Override
public @NotNull Optional<T> getOptional() { public Optional<T> getOptional() {
return Optional.empty(); return Optional.empty();
} }
@Override @Override
public @NotNull Optional<E> getOptionalError() { public Optional<E> getOptionalError() {
return Optional.ofNullable(message); return Optional.ofNullable(message);
} }
@Override @Override
public void ifSuccess(@NotNull Consumer<T> handler) { public void ifSuccess(Consumer<T> handler) {
} }
@Override @Override
public void ifError(@NotNull Consumer<Error<T, E>> handler) { public void ifError(Consumer<Error<T, E>> handler) {
handler.accept(this); handler.accept(this);
} }
@@ -3,16 +3,13 @@ package io.github.skippyall.minions.gui.input
import eu.pb4.sgui.api.elements.GuiElementBuilder import eu.pb4.sgui.api.elements.GuiElementBuilder
import eu.pb4.sgui.api.gui.AnvilInputGui import eu.pb4.sgui.api.gui.AnvilInputGui
import io.github.skippyall.minions.gui.MinionsGui import io.github.skippyall.minions.gui.MinionsGui
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.world.inventory.AnvilMenu import net.minecraft.world.inventory.AnvilMenu
import net.minecraft.world.item.Items import net.minecraft.world.item.Items
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
class TextInput<T>( class TextInput<T : Any>(
parent: MinionsGui, parent: MinionsGui,
val title: Component, val title: Component,
val defaultValue: String, val defaultValue: String,
@@ -29,12 +26,9 @@ class TextInput<T>(
private lateinit var gui: AnvilInputGui private lateinit var gui: AnvilInputGui
private var result: Result<T, Component>? = null private var result: Result<T, Component>? = null
private var isConfirm = false val future = CompletableFuture<T?>()
val job = Job()
init { init {
updateConfirmButton(defaultValue)
open() open()
} }
@@ -46,14 +40,15 @@ class TextInput<T>(
override fun onPlayerClose(success: Boolean) { override fun onPlayerClose(success: Boolean) {
onBackingClosed() onBackingClosed()
if (job.isActive && !isConfirm) { if (!future.isDone) {
job.cancel() future.complete(null)
} }
} }
} }
gui.setTitle(title) gui.setTitle(title)
gui.setDefaultInputValue(defaultValue) gui.setDefaultInputValue(defaultValue)
updateConfirmButton(defaultValue)
gui.open() gui.open()
} }
@@ -75,20 +70,20 @@ class TextInput<T>(
} }
fun onConfirm() { fun onConfirm() {
result?.ifSuccess { _: T? -> result?.ifSuccess { success: T ->
isConfirm = true future.complete(success)
goBack()
} }
job.complete()
} }
companion object { companion object {
@JvmStatic @JvmStatic
suspend fun <T>input( fun <T : Any>input(
gui: MinionsGui, gui: MinionsGui,
title: Component, title: Component,
defaultValue: String, defaultValue: String,
parser: suspend (String) -> Result<T, Component>, parser: suspend (String) -> Result<T, Component>,
): T? { ): CompletableFuture<T?> {
val input = TextInput( val input = TextInput(
parent = gui, parent = gui,
title = title, title = title,
@@ -96,67 +91,29 @@ class TextInput<T>(
parser = parser, parser = parser,
) )
input.job.join() return input.future
return input.result?.getOrDefault(null)
} }
@JvmStatic @JvmStatic
fun <T>inputFuture( fun inputString(
gui: MinionsGui,
title: Component,
defaultValue: String,
parser: (String) -> Result<T, Component>,
): CompletableFuture<T?> {
return gui.scope.async {
val input = TextInput(
parent = gui,
title = title,
defaultValue = defaultValue,
parser = parser,
)
input.job.join()
return@async input.result?.getOrDefault(null)
}.asCompletableFuture()
}
@JvmStatic
suspend fun inputString(
gui: MinionsGui,
title: Component,
defaultValue: String,
): String? {
return input<String>(
gui = gui,
title = title,
defaultValue = defaultValue,
parser = { result: String? -> Result.Success<String, Component>(result) },
)
}
@JvmStatic
fun inputStringFuture(
gui: MinionsGui, gui: MinionsGui,
title: Component, title: Component,
defaultValue: String, defaultValue: String,
): CompletableFuture<String?> { ): CompletableFuture<String?> {
return gui.scope.async { return input<String>(
inputString( gui = gui,
gui, title = title,
title, defaultValue = defaultValue,
defaultValue parser = { result -> Result.Success(result) },
) )
}.asCompletableFuture()
} }
@JvmStatic @JvmStatic
suspend fun inputLong( fun inputLong(
gui: MinionsGui, gui: MinionsGui,
title: Component, title: Component,
defaultValue: Long, defaultValue: Long,
): Long? { ): CompletableFuture<Long?> {
return input<Long>( return input<Long>(
gui = gui, gui = gui,
title = title, title = title,
@@ -164,33 +121,18 @@ class TextInput<T>(
parser = { string -> parser = { string ->
Result.wrapCustomError<Long, Component>( Result.wrapCustomError<Long, Component>(
{ string.toLong() }, { string.toLong() },
Component.translatable("minions.command.input.int.fail") Component.translatable("value_type.minions.long.not_long")
) )
}, },
) )
} }
@JvmStatic @JvmStatic
fun inputLongFuture( fun inputDouble(
gui: MinionsGui,
title: Component,
defaultValue: Long,
): CompletableFuture<Long?> {
return gui.scope.async {
inputLong(
gui,
title,
defaultValue
)
}.asCompletableFuture()
}
@JvmStatic
suspend fun inputDouble(
gui: MinionsGui, gui: MinionsGui,
title: Component, title: Component,
defaultValue: Double, defaultValue: Double,
): Double? { ): CompletableFuture<Double?> {
return input<Double>( return input<Double>(
gui = gui, gui = gui,
title = title, title = title,
@@ -198,25 +140,10 @@ class TextInput<T>(
parser = { string -> parser = { string ->
Result.wrapCustomError<Double, Component>( Result.wrapCustomError<Double, Component>(
{ string.toDouble() }, { string.toDouble() },
Component.translatable("minions.command.input.int.fail") Component.translatable("value_type.minions.long.not_long")
) )
}, },
) )
} }
@JvmStatic
fun inputDoubleFuture(
gui: MinionsGui,
title: Component,
defaultValue: Double,
): CompletableFuture<Double?> {
return gui.scope.async {
inputDouble(
gui,
title,
defaultValue
)
}.asCompletableFuture()
}
} }
} }
@@ -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.PaginatedList;
import io.github.skippyall.minions.gui.minion.GuiContext; import io.github.skippyall.minions.gui.minion.GuiContext;
import io.github.skippyall.minions.minion.MinionRuntime; 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.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.supplier.Parameter; import io.github.skippyall.minions.program.supplier.Parameter;
import io.github.skippyall.minions.program.supplier.ValueSupplier; 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.inventory.MenuType;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.Nullable;
public class ArgumentGui extends MinionsGui { public class ArgumentGui extends MinionsGui {
private final GuiContext.ValueSupplier context; private final GuiContext.ValueSupplier context;
@@ -29,7 +28,7 @@ public class ArgumentGui extends MinionsGui {
private SimpleGui gui; private SimpleGui gui;
private @Nullable ValueSupplierType<MinionRuntime> argumentType; private @Nullable ValueSupplierType<MinionRuntime> argumentType;
private @Nullable ValueSupplierList.ValueSupplierEntry<?, MinionRuntime> entry; private ValueSupplierList. @Nullable ValueSupplierEntry<?, MinionRuntime> entry;
public ArgumentGui(MinionsGui parent, GuiContext.ValueSupplier context) { public ArgumentGui(MinionsGui parent, GuiContext.ValueSupplier context) {
super(parent); super(parent);
@@ -38,9 +37,7 @@ public class ArgumentGui extends MinionsGui {
this.context = context; this.context = context;
this.entry = instruction.getArguments().getEntry(parameter); this.entry = instruction.getArguments().getEntry(parameter);
if(entry != null) { this.argumentType = entry.getSupplier().getType();
this.argumentType = entry.getSupplier().getType();
}
open(); open();
} }
@@ -4,7 +4,7 @@ import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui; import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.clipboard.ClipboardItem; import io.github.skippyall.minions.clipboard.ClipboardItem;
import io.github.skippyall.minions.gui.MinionsGui; 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.gui.minion.GuiContext;
import io.github.skippyall.minions.minion.MinionListener; import io.github.skippyall.minions.minion.MinionListener;
import io.github.skippyall.minions.minion.MinionRuntime; 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.inventory.MenuType;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import java.util.List;
public class ConfigureInstructionGui extends MinionsGui implements ConfiguredInstructionListener, MinionListener { public class ConfigureInstructionGui extends MinionsGui implements ConfiguredInstructionListener, MinionListener {
private String name; private String name;
private final ConfiguredInstruction<MinionRuntime> instruction; private final ConfiguredInstruction<MinionRuntime> instruction;
@@ -53,14 +55,16 @@ public class ConfigureInstructionGui extends MinionsGui implements ConfiguredIns
gui.setSlot(6, new GuiElementBuilder(Items.ANVIL) gui.setSlot(6, new GuiElementBuilder(Items.ANVIL)
.setName(Component.translatable("minions.gui.instruction.configure.rename")) .setName(Component.translatable("minions.gui.instruction.configure.rename"))
.setCallback(() -> InstructionGui.inputInstructionName(this, context, name).thenAccept(newName -> { .setCallback(() -> InstructionGui.inputInstructionName(this, context, name).thenAccept(newName -> {
minion.getInstructionManager().setInstructionName(name, newName); if(newName != null) {
minion.getInstructionManager().setInstructionName(name, newName);
}
reopen(); reopen();
})) }))
); );
gui.setSlot(7, new GuiElementBuilder(Items.LAVA_BUCKET) gui.setSlot(7, new GuiElementBuilder(Items.LAVA_BUCKET)
.setName(Component.translatable("minions.gui.instruction.configure.delete")) .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) -> { .thenAccept((confirmed) -> {
if(confirmed) { if(confirmed) {
minion.getInstructionManager().removeInstruction(name); minion.getInstructionManager().removeInstruction(name);
@@ -84,6 +88,7 @@ public class ConfigureInstructionGui extends MinionsGui implements ConfiguredIns
}) })
); );
updateLastError();
updateRunSlot(); updateRunSlot();
gui.open(); gui.open();
} }
@@ -105,6 +110,7 @@ public class ConfigureInstructionGui extends MinionsGui implements ConfiguredIns
@Override @Override
public void onRun(ConfiguredInstruction<?> instruction) { public void onRun(ConfiguredInstruction<?> instruction) {
updateRunSlot(); updateRunSlot();
updateLastError();
} }
@Override @Override
@@ -118,16 +124,38 @@ public class ConfigureInstructionGui extends MinionsGui implements ConfiguredIns
} }
private void updateRunSlot() { private void updateRunSlot() {
if(!instruction.isRunning()) { List<Component> errors = instruction.preCheck();
gui.setSlot(26, new GuiElementBuilder(Items.ARROW) if(errors.isEmpty()) {
.setName(Component.translatable("minions.gui.instruction.run")) if (!instruction.isRunning()) {
.setCallback(() -> instruction.run(minion.getInstructionManager())) gui.setSlot(26, new GuiElementBuilder(Items.ARROW)
); .setName(Component.translatable("minions.gui.instruction.run"))
.setCallback(() -> instruction.run(minion.getInstructionManager()))
);
} else {
gui.setSlot(26, new GuiElementBuilder(Items.BARRIER)
.setName(Component.translatable("minions.gui.instruction.stop"))
.setCallback(() -> instruction.stop(minion.getInstructionManager()))
);
}
} else { } else {
gui.setSlot(26, new GuiElementBuilder(Items.BARRIER) GuiElementBuilder builder = new GuiElementBuilder(Items.RED_WOOL)
.setName(Component.translatable("minions.gui.instruction.stop")) .setName(Component.translatable("minions.gui.instruction.errors"));
.setCallback(() -> instruction.stop(minion.getInstructionManager())) 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);
} }
} }
@@ -14,7 +14,7 @@ import net.minecraft.core.RegistryAccess;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.Nullable;
public class ConverterGui extends MinionsGui { public class ConverterGui extends MinionsGui {
private @Nullable ValueConverterType<?> valueConverterType; 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) { public ConverterGui(MinionsGui parent, @Nullable ValueConverter<?,?> converter, ValueType<?> from, ValueType<?> to, ConverterList list, boolean isNew, int index) {
super(parent); super(parent);
open();
this.converter = converter; this.converter = converter;
if(converter != null) { if(converter != null) {
this.valueConverterType = converter.getType(); this.valueConverterType = converter.getType();
@@ -38,6 +37,8 @@ public class ConverterGui extends MinionsGui {
this.list = list; this.list = list;
this.isNew = isNew; this.isNew = isNew;
this.index = index; this.index = index;
open();
} }
@Override @Override
@@ -12,6 +12,7 @@ import io.github.skippyall.minions.util.TranslationUtil;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import org.jspecify.annotations.Nullable;
public class ConverterListGui extends MinionsGui { public class ConverterListGui extends MinionsGui {
private ConverterList converters; private ConverterList converters;
@@ -54,56 +55,124 @@ public class ConverterListGui extends MinionsGui {
gui.setSlot(23, new GuiElementBuilder(Items.ARROW) gui.setSlot(23, new GuiElementBuilder(Items.ARROW)
.setCallback(() -> { .setCallback(() -> {
if(page * 4 + 4 < converters.getConverters().size()) { if(page * 4 + 4 < converters.getConverters().size() + 1) {
page++; page++;
updateConverters(); updateConverters();
} }
}) })
); );
gui.setSlot(8, backButton()); gui.setSlot(26, backButton());
gui.open(); 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() { public void updateConverters() {
for(int slot = 0; slot < 18; slot++) {
gui.clearSlot(slot);
}
int lastConverter = Math.min(5, converters.getConverters().size() + 2 - page * 4); int lastConverter = Math.min(5, converters.getConverters().size() + 2 - page * 4);
for(int i = 0; i < lastConverter; i++) { for(int i = 0; i < lastConverter; i++) {
//Each page has 5 converters, but the last is displayed on the next page as well //Each page has 5 converters, but the last is displayed on the next page as well
int converterIndex = page * 4 + i; //The input element index is -1, the output element index is the list size
//without input element int converterIndex = page * 4 + i - 1;
int actualConverterIndex = converterIndex - 1;
int slot = 9 + 2 * i; int slot = 9 + 2 * i;
if(converterIndex == 0) {
gui.setSlot(slot, new GuiElementBuilder(Items.DROPPER)); if(converterIndex == converters.getConverters().size()) {
} else if(converterIndex == converters.getConverters().size() + 1) {
gui.setSlot(slot, new GuiElementBuilder(Items.HOPPER)); gui.setSlot(slot, new GuiElementBuilder(Items.HOPPER));
} else { } else {
ValueConverter<?, ?> converter = converters.getConverters().get(actualConverterIndex); ValueType<?> thisConverterOutput = getOutputTypeOfConverter(converterIndex);
ValueType<?> fromType = actualConverterIndex >= 1 ? converters.getConverters().get(actualConverterIndex - 1).getTo() : inputType; ValueType<?> nextConverterInput = getInputTypeOfConverter(converterIndex + 1);
ValueType<?> toType = actualConverterIndex < converters.getConverters().size() - 1 ? converters.getConverters().get(actualConverterIndex + 1).getFrom() : outputType;
gui.setSlot(slot, new GuiElementBuilder(GuiDisplay.getDisplayStackWithName(MinionRegistries.VALUE_CONVERTER_TYPES, converter.getType(), viewer.registryAccess())) //What should be shown at the converter's slot
.addLoreLine(converter.getDisplayText()) if (converterIndex == -1) {
.setCallback(() -> new ConverterGui(this, converter, fromType, toType, converters, false, actualConverterIndex)) gui.setSlot(slot, new GuiElementBuilder(Items.DROPPER));
); } else {
} ValueType<?> previousConverterOutput = getOutputTypeOfConverter(converterIndex - 1);
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) ValueConverter<?, ?> converter = converters.getConverters().get(converterIndex);
.setName(Component.translatable( gui.setSlot(slot, createConverterDisplay(converter, previousConverterOutput, nextConverterInput, converterIndex));
"minions.gui.instruction.converters.cast",
Component.translatable(TranslationUtil.getTranslationKey(fromType, MinionRegistries.VALUE_TYPES)), GuiElementBuilder warning = createConverterWarning(converter);
Component.translatable(TranslationUtil.getTranslationKey(toType, MinionRegistries.VALUE_TYPES)) if (warning != null) {
)) gui.setSlot(slot - 8, warning);
.setCallback(() -> new ConverterGui(this, null, fromType, toType, converters, true, actualConverterIndex + 1)) }
); }
//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, 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;
}
}
@Override @Override
protected void closeBacking() { protected void closeBacking() {
gui.close(); gui.close();
@@ -24,7 +24,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -64,7 +64,7 @@ public class InstructionGui {
ServerPlayer viewer = parent.viewer; ServerPlayer viewer = parent.viewer;
selectInstructionModuleMenu(parent, context).thenAccept(instructionType -> selectInstructionModuleMenu(parent, context).thenAccept(instructionType ->
inputInstructionName(parent, context, "Instruction").thenAccept(name -> { 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); ConfiguredInstruction<MinionRuntime> configuredInstruction = minion.getInstructionManager().createInstruction(name, instructionType);
new ConfigureInstructionGui(parent, GuiContext.Instruction.create(context, configuredInstruction, name)); 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) { public static CompletableFuture<@Nullable String> inputInstructionName(MinionsGui parent, GuiContext.Minion context, String defaultValue) {
return TextInput.inputFuture(parent, Component.translatable("minions.gui.instruction.enter_name"), defaultValue, name -> { return TextInput.input(parent, Component.translatable("minions.gui.instruction.enter_name"), defaultValue, (name, _) -> {
if (context.getMinion().getInstructionManager().hasInstruction(name)) { if (context.getMinion().getInstructionManager().hasInstruction(name)) {
return new Result.Error<>(Component.translatable("minions.gui.instruction.name_already_used")); return new Result.Error<>(Component.translatable("minions.gui.instruction.name_already_used"));
} }
@@ -163,7 +163,7 @@ public class InstructionGui {
return future; return future;
} }
public static GuiElementBuilder createInstructionElement(InstructionType<MinionRuntime> instructionType, RegistryAccess manager) { public static GuiElementBuilder createInstructionElement(@Nullable InstructionType<MinionRuntime> instructionType, RegistryAccess manager) {
GuiElementBuilder instructionBuilder; GuiElementBuilder instructionBuilder;
if (instructionType != null) { if (instructionType != null) {
instructionBuilder = new GuiElementBuilder(GuiDisplay.getDisplayStackWithName(MinionRegistries.INSTRUCTION_TYPES, instructionType, manager)); 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.Level;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType; 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.Optional;
import java.util.UUID; import java.util.UUID;
@@ -45,9 +45,9 @@ public abstract class BlockEntityMinionListener<E extends BlockEntity> implement
this.minion = null; 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) { 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)) { if (listener instanceof BlockEntityMinionListener<?> tl && tl.pos.equals(pos) && tl.worldKey.equals(world.dimension()) && clazz.isInstance(tl)) {
return clazz.cast(tl); return clazz.cast(tl);
} }
@@ -95,13 +95,13 @@ public abstract class BlockEntityMinionListener<E extends BlockEntity> implement
} }
public void add(MinecraftServer server) { 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(); MinionPersistentState.get(server).setDirty();
this.minion = (MinionFakePlayer) server.getPlayerList().getPlayer(minionUuid); this.minion = (MinionFakePlayer) server.getPlayerList().getPlayer(minionUuid);
} }
public void remove(MinecraftServer server) { 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(); 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; 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.gui.MinionLookGui;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.registration.MinionComponentTypes; 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.MinecraftServer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
@@ -17,45 +11,23 @@ import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; 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.item.context.UseOnContext;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec2; 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 {
public class MinionItem extends Item implements PolymerItem {
public MinionItem(Properties settings) { public MinionItem(Properties settings) {
super(settings); super(settings);
} }
@Override /*@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
public void appendHoverText(ItemStack stack, TooltipContext context, TooltipDisplay component, Consumer<Component> tooltip, TooltipFlag type) { public void appendHoverText(ItemStack stack, TooltipContext context, TooltipDisplay component, Consumer<Component> tooltip, TooltipFlag type) {
//MinionData data = getData(stack); //MinionData data = getData(stack);
//if(data != null) { //if(data != null) {
// tooltip.accept(Component.translatable("minions.minion_item.tooltip", data.name())); // tooltip.accept(Component.translatable("minions.minion_item.tooltip", data.name()));
//} //}
} }*/
@Override @Override
public InteractionResult use(Level world, Player user, InteractionHand hand) { public InteractionResult use(Level world, Player user, InteractionHand hand) {
@@ -79,7 +51,7 @@ public class MinionItem extends Item implements PolymerItem {
} }
public static void setData(MinecraftServer server, MinionData data, ItemStack item) { 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); 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.listener.SerializableListenerManager;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction; import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.Nullable;
public interface MinionListener extends SerializableListenerManager.SerializableListener { public interface MinionListener extends SerializableListenerManager.SerializableListener {
default void onMinionSpawn(MinionFakePlayer minion) {} 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.Level;
import net.minecraft.world.level.saveddata.SavedData; import net.minecraft.world.level.saveddata.SavedData;
import net.minecraft.world.level.saveddata.SavedDataType; import net.minecraft.world.level.saveddata.SavedDataType;
import org.jspecify.annotations.Nullable;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -31,11 +33,12 @@ public class MinionPersistentState extends SavedData {
public MinionPersistentState(List<MinionData> dataList) { public MinionPersistentState(List<MinionData> dataList) {
for (MinionData data : 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); return minionData.get(uuid);
} }
@@ -48,7 +51,8 @@ public class MinionPersistentState extends SavedData {
} }
public void updateMinionData(MinionData data) { public void updateMinionData(MinionData data) {
minionData.put(data.uuid(), data); minionData.put(data.getUuid(), data);
data.setOnDirty(this::setDirty);
setDirty(); setDirty();
} }
@@ -62,7 +66,7 @@ public class MinionPersistentState extends SavedData {
public Optional<MinionData> getMinionWithName(String name) { public Optional<MinionData> getMinionWithName(String name) {
return minionData.values().stream() return minionData.values().stream()
.filter(data -> data.name().equals(name)) .filter(data -> data.getName().equals(name))
.findFirst(); .findFirst();
} }
@@ -8,6 +8,8 @@ import io.github.skippyall.minions.gui.input.Result;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.util.StringUtil; import net.minecraft.util.StringUtil;
import org.jspecify.annotations.Nullable;
import java.util.UUID; import java.util.UUID;
import static io.github.skippyall.minions.Minions.LOGGER; import static io.github.skippyall.minions.Minions.LOGGER;
@@ -17,7 +19,7 @@ public class MinionProfileUtils {
return MinionsConfig.get().minion.minionPrefix; 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) { if(uuidMinion == null) {
uuidMinion = UUID.randomUUID(); 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.level.storage.ValueOutput;
import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3; 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.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
@NullMarked
public class MinionFakePlayer extends ServerPlayer { public class MinionFakePlayer extends ServerPlayer {
public Runnable fixStartingPosition = () -> {}; public Runnable fixStartingPosition = () -> {};
@@ -73,16 +75,16 @@ public class MinionFakePlayer extends ServerPlayer {
if(!data.isSpawned() || force) { if(!data.isSpawned() || force) {
MinecraftServer server = level.getServer(); 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))); 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) { 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()); MinionFakePlayer instance = new MinionFakePlayer(server, level, profile, ClientInformation.createDefault());
MinionPersistentState.get(server).updateMinionData(data.withSpawned(true)); data.setSpawned(true);
if(pos != null && rot != null) { if(pos != null && rot != null) {
instance.fixStartingPosition = () -> instance.snapTo(pos.x, pos.y, pos.z, rot.x, rot.y); 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() { public SerializableListenerManager<MinionListener> listeners() {
return getData().listeners(); return getData().getListeners();
} }
public void addMinionListener(MinionListener listener) { public void addMinionListener(MinionListener listener) {
@@ -174,7 +176,7 @@ public class MinionFakePlayer extends ServerPlayer {
} }
public boolean canSpawnMobs() { 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() { public boolean canDespawnMobs() {
@@ -213,7 +215,7 @@ public class MinionFakePlayer extends ServerPlayer {
})); }));
} }
MinionPersistentState.get(getServer()).updateMinionData(getData().withSpawned(false)); getData().setSpawned(false);
} }
@Override @Override
@@ -310,6 +312,7 @@ public class MinionFakePlayer extends ServerPlayer {
public void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { public void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) {
super.dropAllDeathLoot(world, damageSource); super.dropAllDeathLoot(world, damageSource);
ItemEntity entity = drop(toItemStack(world.getServer()), true, false); ItemEntity entity = drop(toItemStack(world.getServer()), true, false);
//noinspection ConstantValue (Wrong nullability of drop)
if (entity != null) { if (entity != null) {
entity.setUnlimitedLifetime(); 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.level.storage.ValueOutput;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.HitResult;
import org.jspecify.annotations.Nullable;
public class MineBlockExecution implements InstructionExecution<MinionRuntime> { public class MineBlockExecution implements InstructionExecution<MinionRuntime> {
private BlockPos currentBlock; private @Nullable BlockPos currentBlock;
private float currentBlockDamage = 0; private float currentBlockDamage = 0;
private boolean first = true; private boolean first = true;
private boolean done = false; private boolean done = false;
@@ -38,7 +39,6 @@ public class MineBlockExecution implements InstructionExecution<MinionRuntime> {
} }
if (player.blockActionRestricted(player.level(), hit.getBlockPos(), player.gameMode.getGameModeForPlayer())) { if (player.blockActionRestricted(player.level(), hit.getBlockPos(), player.gameMode.getGameModeForPlayer())) {
done = true; done = true;
return;
} }
} else { } else {
done = true; 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.inventory.MenuType;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.minion.program.supplier;
import org.jspecify.annotations.NullMarked;
@@ -4,13 +4,14 @@ import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.input.TextInput; import io.github.skippyall.minions.gui.input.TextInput;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.item.component.ResolvableProfile; import net.minecraft.world.item.component.ResolvableProfile;
import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class NameSkinProvider implements SkinProvider { public class NameSkinProvider implements SkinProvider {
@Override @Override
public CompletableFuture<ResolvableProfile> openSkinMenu(MinionsGui parent) { public CompletableFuture<@Nullable ResolvableProfile> openSkinMenu(MinionsGui parent) {
return TextInput.inputStringFuture(parent, Component.translatable("minions.gui.look.skin.name.title"), "") return TextInput.inputString(parent, Component.translatable("minions.gui.look.skin.name.title"), "")
.thenApply(name -> name != null ? ResolvableProfile.createUnresolved(name) : null); .thenApply(name -> name != null ? ResolvableProfile.createUnresolved(name) : null);
} }
@@ -3,11 +3,12 @@ package io.github.skippyall.minions.minion.skin;
import io.github.skippyall.minions.gui.MinionsGui; import io.github.skippyall.minions.gui.MinionsGui;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.item.component.ResolvableProfile; import net.minecraft.world.item.component.ResolvableProfile;
import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public interface SkinProvider { public interface SkinProvider {
CompletableFuture<ResolvableProfile> openSkinMenu(MinionsGui parent); CompletableFuture<@Nullable ResolvableProfile> openSkinMenu(MinionsGui parent);
Component getDisplayName(); Component getDisplayName();
} }
@@ -4,13 +4,14 @@ import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.input.TextInput; import io.github.skippyall.minions.gui.input.TextInput;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.item.component.ResolvableProfile; import net.minecraft.world.item.component.ResolvableProfile;
import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class UUIDSkinProvider implements SkinProvider { public class UUIDSkinProvider implements SkinProvider {
@Override @Override
public CompletableFuture<ResolvableProfile> openSkinMenu(MinionsGui parent) { public CompletableFuture<@Nullable ResolvableProfile> openSkinMenu(MinionsGui parent) {
return TextInput.inputStringFuture(parent, Component.translatable("minions.gui.look.skin.uuid.title"), "") return TextInput.inputString(parent, Component.translatable("minions.gui.look.skin.uuid.title"), "")
.thenApply(name -> name != null ? ResolvableProfile.createUnresolved(name) : null); .thenApply(name -> name != null ? ResolvableProfile.createUnresolved(name) : null);
} }
@@ -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 { 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) @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) { 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; return false;
} }
@@ -3,7 +3,7 @@ package io.github.skippyall.minions.mixins;
import io.github.skippyall.minions.mixinhelper.EntityViewMixinHelper; import io.github.skippyall.minions.mixinhelper.EntityViewMixinHelper;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.EntityGetter; 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.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg; 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.Entity;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level; 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.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
@@ -24,7 +24,7 @@ public class MinecraftServerMixin {
public List<ServerPlayer> ignoreFakePlayers(List<ServerPlayer> original) { public List<ServerPlayer> ignoreFakePlayers(List<ServerPlayer> original) {
return original.stream() return original.stream()
.filter(player -> !(player instanceof MinionFakePlayer minion .filter(player -> !(player instanceof MinionFakePlayer minion
&& !minion.getData().config().getOption(MinionConfigOptions.showInServerList))) && !minion.getData().getConfig().getOption(MinionConfigOptions.showInServerList)))
.collect(Collectors.toCollection(ArrayList::new)); .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")) @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) { 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); 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")) @ModifyReceiver(method = "canPlayerLogin", at = @At(value = "INVOKE", target = "Ljava/util/List;size()I"))
public List<ServerPlayer> noMinionCounting(List<ServerPlayer> instance) { public List<ServerPlayer> noMinionCounting(List<ServerPlayer> instance) {
return instance.stream() 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)); .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")) @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) { 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); original.call(instance, message, overlay);
} }
} }
@@ -13,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.At;
public class SleepStatusMixin { public class SleepStatusMixin {
@WrapOperation(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;isSpectator()Z")) @WrapOperation(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;isSpectator()Z"))
public boolean excludeMinions(ServerPlayer instance, Operation<Boolean> original) { 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; return true;
} else { } else {
return original.call(instance); 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.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
import net.minecraft.server.network.ServerGamePacketListenerImpl; import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jspecify.annotations.Nullable;
public class VersionSync { public class VersionSync {
public static final int NETWORK_VERSION = 1; 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) { if(context != null && context.get(PacketContext.CONNECTION).getPacketListener() instanceof ServerGamePacketListenerImpl gamePacketListener) {
return isOnClient(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.InstructionRuntime;
import io.github.skippyall.minions.program.value.ValueType; import io.github.skippyall.minions.program.value.ValueType;
import io.github.skippyall.minions.registration.MinionRegistries; 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. * 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 com.mojang.serialization.Codec;
import io.github.skippyall.minions.program.InstructionRuntime; import io.github.skippyall.minions.program.InstructionRuntime;
import io.github.skippyall.minions.program.supplier.Parameter; import io.github.skippyall.minions.program.supplier.Parameter;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@@ -22,7 +23,7 @@ public class ValueConsumerList<R extends InstructionRuntime<R>> {
this.valueConsumers = new HashMap<>(valueConsumers); 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()); ValueConsumer<?,R> argument = valueConsumers.get(parameter.name());
return argument == null ? null : argument.cast(parameter.type()); 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.InstructionRuntime;
import io.github.skippyall.minions.program.value.ValueType; import io.github.skippyall.minions.program.value.ValueType;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture; 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.registration.ValueConverters;
import io.github.skippyall.minions.util.TranslationUtil; import io.github.skippyall.minions.util.TranslationUtil;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture; 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.registration.ValueTypes;
import io.github.skippyall.minions.util.TranslationUtil; import io.github.skippyall.minions.util.TranslationUtil;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.Nullable;
public class Casts { public class Casts {
public static <F,T> @Nullable Cast<F,T> getCast(ValueType<F> from, ValueType<T> to) { 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(); 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; return null;
} }
public static <F,T> @Nullable T cast(TypedValue<F> from, ValueType<T> to) { 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) { if(cast != null) {
return cast.cast(from.value()); return cast.cast(from.value());
} else { } else {
@@ -4,13 +4,16 @@ import com.mojang.serialization.Codec;
import io.github.skippyall.minions.gui.input.Result; import io.github.skippyall.minions.gui.input.Result;
import io.github.skippyall.minions.program.value.TypedValue; import io.github.skippyall.minions.program.value.TypedValue;
import io.github.skippyall.minions.program.value.ValueType; 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 net.minecraft.network.chat.Component;
import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer;
public class ConverterList { public class ConverterList {
public static final Codec<ConverterList> CODEC = ValueConverter.CODEC.listOf().xmap(ConverterList::new, l -> l.converters); 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) { 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()); Result<I, Component> inter = Casts.castOrError(from, converter.getFrom());
if(inter instanceof Result.Error<I, Component> error) { 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()); Result<T, Component> to = converter.convert(inter.getOrThrow());
@@ -64,8 +71,37 @@ public class ConverterList {
} }
} }
public Result<@Nullable Void, Component> check() { public static @Nullable Component createConverterWarning(ValueConverter<?, ?> converter) {
return new Result.Success<>(null); 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 @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.ValueConverters;
import io.github.skippyall.minions.registration.ValueTypes; import io.github.skippyall.minions.registration.ValueTypes;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture; 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.program.value.ValueType;
import io.github.skippyall.minions.registration.MinionRegistries; import io.github.skippyall.minions.registration.MinionRegistries;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import org.jspecify.annotations.Nullable;
public interface ValueConverter<F,T> { public interface ValueConverter<F,T> {
Codec<ValueConverter<?,?>> CODEC = MinionRegistries.VALUE_CONVERTER_TYPES.byNameCodec().dispatch(ValueConverter::getType, ValueConverterType::getCodec); Codec<ValueConverter<?,?>> CODEC = MinionRegistries.VALUE_CONVERTER_TYPES.byNameCodec().dispatch(ValueConverter::getType, ValueConverterType::getCodec);
@@ -19,7 +20,7 @@ public interface ValueConverter<F,T> {
Component getDisplayText(); 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()) { if(from == getFrom() && to == getTo()) {
//noinspection unchecked //noinspection unchecked
return (ValueConverter<F2, T2>) this; return (ValueConverter<F2, T2>) this;
@@ -3,7 +3,7 @@ package io.github.skippyall.minions.program.conversion;
import com.mojang.serialization.MapCodec; import com.mojang.serialization.MapCodec;
import io.github.skippyall.minions.gui.MinionsGui; import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.program.value.ValueType; import io.github.skippyall.minions.program.value.ValueType;
import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.program.conversion;
import org.jspecify.annotations.NullMarked;
@@ -8,9 +8,13 @@ import io.github.skippyall.minions.program.supplier.Parameter;
import io.github.skippyall.minions.program.supplier.ParameterValueList; import io.github.skippyall.minions.program.supplier.ParameterValueList;
import io.github.skippyall.minions.program.supplier.ValueSupplierList; import io.github.skippyall.minions.program.supplier.ValueSupplierList;
import io.github.skippyall.minions.registration.MinionRegistries; import io.github.skippyall.minions.registration.MinionRegistries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.level.storage.ValueOutput;
import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/** /**
* Holds an instruction, its configuration and is responsible for executing the instruction * Holds an instruction, its configuration and is responsible for executing the instruction
@@ -24,6 +28,7 @@ public class ConfiguredInstruction<R extends InstructionRuntime<R>> {
private boolean paused = false; private boolean paused = false;
private SerializableListenerManager<ConfiguredInstructionListener> listeners = new SerializableListenerManager<>(); private SerializableListenerManager<ConfiguredInstructionListener> listeners = new SerializableListenerManager<>();
private List<Component> lastErrors = List.of();
private ConfiguredInstruction( private ConfiguredInstruction(
InstructionType<R> instruction, InstructionType<R> instruction,
@@ -64,8 +69,18 @@ public class ConfiguredInstruction<R extends InstructionRuntime<R>> {
return arguments; return arguments;
} }
public List<Component> preCheck() {
List<Component> errors = new ArrayList<>();
arguments.checkRun(instruction, errors::add);
return errors;
}
public boolean canRun() { public boolean canRun() {
return instruction != null && arguments != null && arguments.checkRun(instruction) == null; return preCheck().isEmpty();
}
public List<Component> getLastErrors() {
return lastErrors;
} }
public boolean isRunning() { public boolean isRunning() {
@@ -78,15 +93,21 @@ public class ConfiguredInstruction<R extends InstructionRuntime<R>> {
public void run(R minion) { public void run(R minion) {
if(canRun() && !isRunning()) { if(canRun() && !isRunning()) {
ParameterValueList resolvedArguments = arguments.resolve(minion); lastErrors = new ArrayList<>();
try { try {
execution = instruction.createExecution(resolvedArguments, minion); ParameterValueList resolvedArguments = arguments.resolve(minion, lastErrors::add);
execution.start(minion); if(lastErrors.isEmpty()) {
execution = instruction.createExecution(resolvedArguments, minion);
execution.start(minion);
}
} catch (Exception e) { } catch (Exception e) {
Minions.LOGGER.error("An error occurred while executing configured Instruction", e); Minions.LOGGER.error("An error occurred while executing configured Instruction", e);
lastErrors.add(Component.translatable("minions.gui.instruction.check.internal_error"));
} }
listeners.forEach(listener -> listener.onRun(this)); for(ConfiguredInstructionListener listener : listeners) {
listener.onRun(this);
}
} }
} }
@@ -0,0 +1,4 @@
@NullMarked
package io.github.skippyall.minions.program.instruction.execution;
import org.jspecify.annotations.NullMarked;

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