diff --git a/src/client/java/io/github/skippyall/minions/client/MinionsClient.java b/src/client/java/io/github/skippyall/minions/client/MinionsClient.java index a4b1dd1..85f64fa 100644 --- a/src/client/java/io/github/skippyall/minions/client/MinionsClient.java +++ b/src/client/java/io/github/skippyall/minions/client/MinionsClient.java @@ -2,7 +2,7 @@ package io.github.skippyall.minions.client; import eu.pb4.polymer.networking.api.client.PolymerClientNetworking; import io.github.skippyall.minions.registration.MinionBlocks; -import io.github.skippyall.minions.util.PolymerUtil; +import io.github.skippyall.minions.polymer.VersionSync; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.rendering.v1.BlockRenderLayerMap; import net.minecraft.client.render.BlockRenderLayer; @@ -12,6 +12,6 @@ public class MinionsClient implements ClientModInitializer { public void onInitializeClient() { BlockRenderLayerMap.putBlock(MinionBlocks.MINION_TRIGGER_BLOCK, BlockRenderLayer.TRANSLUCENT); - PolymerClientNetworking.registerCommonHandler(PolymerUtil.VersionSyncPayload.class, (client, handler, payload) -> {}); + PolymerClientNetworking.registerCommonHandler(VersionSync.VersionSyncPayload.class, (client, handler, payload) -> {}); } } diff --git a/src/main/java/io/github/skippyall/minions/Minions.java b/src/main/java/io/github/skippyall/minions/Minions.java index 183aaf5..1898537 100644 --- a/src/main/java/io/github/skippyall/minions/Minions.java +++ b/src/main/java/io/github/skippyall/minions/Minions.java @@ -2,13 +2,16 @@ package io.github.skippyall.minions; import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; import io.github.skippyall.minions.command.MinionsCommand; +import io.github.skippyall.minions.docs.DocsManager; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.minion.MinionPersistentState; import io.github.skippyall.minions.registration.MinionRegistration; -import io.github.skippyall.minions.util.PolymerUtil; +import io.github.skippyall.minions.polymer.VersionSync; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.resource.ResourceManagerHelper; +import net.minecraft.resource.ResourceType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,7 +26,7 @@ public class Minions implements ModInitializer { MinionRegistration.register(); - PolymerUtil.register(); + VersionSync.register(); ServerLifecycleEvents.SERVER_STARTED.register(server -> { MinionPersistentState.get(server).getMinionData().forEach((uuid, data) -> { @@ -42,5 +45,7 @@ public class Minions implements ModInitializer { });*/ PolymerResourcePackUtils.addModAssets(Minions.MOD_ID); + + ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(new DocsManager()); } } diff --git a/src/main/java/io/github/skippyall/minions/block/input/AnalogInputBlock.java b/src/main/java/io/github/skippyall/minions/block/input/AnalogInputBlock.java new file mode 100644 index 0000000..9a7d591 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/block/input/AnalogInputBlock.java @@ -0,0 +1,22 @@ +package io.github.skippyall.minions.block.input; + +import io.github.skippyall.minions.clipboard.ClipboardItem; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class AnalogInputBlock extends Block { + public AnalogInputBlock(Settings settings) { + super(settings); + } + + @Override + protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) { + player.getInventory().offer(ClipboardItem.createBlockPosReference(world, pos), true); + return ActionResult.SUCCESS; + } +} diff --git a/src/main/java/io/github/skippyall/minions/block/instruction_bound/InstructionBoundBlock.java b/src/main/java/io/github/skippyall/minions/block/instruction_bound/InstructionBoundBlock.java new file mode 100644 index 0000000..dd2d0cb --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/block/instruction_bound/InstructionBoundBlock.java @@ -0,0 +1,63 @@ +package io.github.skippyall.minions.block.instruction_bound; + +import io.github.skippyall.minions.block.miniontrigger.MinionTriggerBlockEntity; +import io.github.skippyall.minions.clipboard.InstructionClipboard; +import io.github.skippyall.minions.minion.MinionPersistentState; +import io.github.skippyall.minions.registration.MinionBlocks; +import io.github.skippyall.minions.registration.MinionComponentTypes; +import net.minecraft.block.Block; +import net.minecraft.block.BlockEntityProvider; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public abstract class InstructionBoundBlock extends Block implements BlockEntityProvider { + public InstructionBoundBlock(Settings settings) { + super(settings); + } + + protected abstract BlockEntityType> getBlockEntityType(); + + @Override + protected ActionResult onUseWithItem(ItemStack stack, BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { + if(stack.get(MinionComponentTypes.REFERENCE) instanceof InstructionClipboard instruction) { + world.getBlockEntity(pos, getBlockEntityType()).ifPresent(be -> { + be.setInstruction(instruction.selectedMinion(), instruction.selectedInstruction()); + player.playSoundToPlayer(SoundEvents.BLOCK_NOTE_BLOCK_CHIME.value(), SoundCategory.BLOCKS, 1, 1); + stack.decrement(1); + }); + return ActionResult.SUCCESS; + } + + return super.onUseWithItem(stack, state, world, pos, player, hand, hit); + } + + @Override + protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) { + if(world.isClient()) { + return ActionResult.CONSUME; + } + + world.getBlockEntity(pos, getBlockEntityType()).ifPresent(be -> { + String name = MinionPersistentState.get(world.getServer()).getMinionData(be.getMinionUuid()).name(); + player.sendMessage(Text.translatable("minions.reference.instruction.tooltip", name, be.getInstructionName()), true); + }); + return ActionResult.SUCCESS; + } + + @Override + protected void onStateReplaced(BlockState state, ServerWorld world, BlockPos pos, boolean moved) { + super.onStateReplaced(state, world, pos, moved); + world.getBlockEntity(pos, MinionBlocks.MINION_TRIGGER_BE_TYPE).ifPresent(MinionTriggerBlockEntity::removeListener); + } +} diff --git a/src/main/java/io/github/skippyall/minions/block/instruction_bound/InstructionBoundBlockEntity.java b/src/main/java/io/github/skippyall/minions/block/instruction_bound/InstructionBoundBlockEntity.java new file mode 100644 index 0000000..b04bf38 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/block/instruction_bound/InstructionBoundBlockEntity.java @@ -0,0 +1,71 @@ +package io.github.skippyall.minions.block.instruction_bound; + +import io.github.skippyall.minions.listener.BlockEntityMinionListener; +import io.github.skippyall.minions.minion.MinionRuntime; +import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; +import io.github.skippyall.minions.program.instruction.ConfiguredInstruction; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.util.math.BlockPos; + +import java.util.Optional; +import java.util.UUID; + +public abstract class InstructionBoundBlockEntity> extends BlockEntity { + protected UUID minionUuid; + protected String instructionName = ""; + + public InstructionBoundBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + + protected abstract L createListener(); + + protected abstract Class getListenerClass(); + + public void removeListener() { + L listener = getListener(); + listener.remove(world.getServer()); + } + + public void addListener() { + L listener = createListener(); + listener.add(world.getServer()); + } + + public void setInstruction(UUID minionUuid, String instructionName) { + removeListener(); + this.minionUuid = minionUuid; + this.instructionName = instructionName; + addListener(); + markDirty(); + } + + public Optional getMinion() { + if(minionUuid != null && world != null && world.getPlayerByUuid(minionUuid) instanceof MinionFakePlayer minion) { + return Optional.of(minion); + } + return Optional.empty(); + } + + public UUID getMinionUuid() { + return minionUuid; + } + + public String getInstructionName() { + return instructionName; + } + + public Optional> getInstruction(MinionFakePlayer minion) { + return Optional.ofNullable(minion.getInstructionManager().getInstruction(instructionName)); + } + + public Optional> getInstruction() { + return getMinion().flatMap(this::getInstruction); + } + + public L getListener() { + return BlockEntityMinionListener.getListener(world, pos, minionUuid, getListenerClass()); + } +} diff --git a/src/main/java/io/github/skippyall/minions/block/miniontrigger/MinionTriggerBlock.java b/src/main/java/io/github/skippyall/minions/block/miniontrigger/MinionTriggerBlock.java index d815a9a..601eb24 100644 --- a/src/main/java/io/github/skippyall/minions/block/miniontrigger/MinionTriggerBlock.java +++ b/src/main/java/io/github/skippyall/minions/block/miniontrigger/MinionTriggerBlock.java @@ -8,35 +8,27 @@ 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.block.instruction_bound.InstructionBoundBlock; +import io.github.skippyall.minions.block.instruction_bound.InstructionBoundBlockEntity; import io.github.skippyall.minions.registration.MinionBlocks; import io.github.skippyall.minions.Minions; -import io.github.skippyall.minions.minion.MinionPersistentState; -import io.github.skippyall.minions.clipboard.InstructionClipboard; -import io.github.skippyall.minions.registration.MinionComponentTypes; -import io.github.skippyall.minions.util.PolymerUtil; +import io.github.skippyall.minions.polymer.VersionSync; import net.minecraft.block.AbstractRedstoneGateBlock; import net.minecraft.block.Block; import net.minecraft.block.BlockState; -import net.minecraft.block.BlockWithEntity; import net.minecraft.block.ShapeContext; import net.minecraft.block.SideShapeType; import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; import net.minecraft.component.DataComponentTypes; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; -import net.minecraft.sound.SoundCategory; -import net.minecraft.sound.SoundEvents; import net.minecraft.state.StateManager; import net.minecraft.state.property.BooleanProperty; -import net.minecraft.text.Text; -import net.minecraft.util.ActionResult; -import net.minecraft.util.Hand; import net.minecraft.util.Identifier; -import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.shape.VoxelShape; @@ -47,7 +39,7 @@ import net.minecraft.world.block.WireOrientation; import org.jetbrains.annotations.Nullable; import xyz.nucleoid.packettweaker.PacketContext; -public class MinionTriggerBlock extends BlockWithEntity implements PolymerBlock, PolymerKeepModel, PolymerClientDecoded, BlockWithElementHolder { +public class MinionTriggerBlock extends InstructionBoundBlock implements PolymerBlock, PolymerKeepModel, PolymerClientDecoded, BlockWithElementHolder { public static final MapCodec CODEC = createCodec(MinionTriggerBlock::new); public static final BooleanProperty POWERED = BooleanProperty.of("powered"); @@ -73,44 +65,11 @@ public class MinionTriggerBlock extends BlockWithEntity implements PolymerBlock, return state.isSideSolid(world, pos, Direction.UP, SideShapeType.RIGID); } - @Override - protected void onStateReplaced(BlockState state, ServerWorld world, BlockPos pos, boolean moved) { - super.onStateReplaced(state, world, pos, moved); - world.getBlockEntity(pos, MinionBlocks.MINION_TRIGGER_BE_TYPE).ifPresent(MinionTriggerBlockEntity::removeListener); - } - @Override protected void appendProperties(StateManager.Builder builder) { builder.add(POWERED); } - @Override - protected ActionResult onUseWithItem(ItemStack stack, BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { - if(stack.get(MinionComponentTypes.REFERENCE) instanceof InstructionClipboard instruction) { - world.getBlockEntity(pos, MinionBlocks.MINION_TRIGGER_BE_TYPE).ifPresent(be -> { - be.setInstruction(instruction.selectedMinion(), instruction.selectedInstruction()); - player.playSoundToPlayer(SoundEvents.BLOCK_NOTE_BLOCK_CHIME.value(), SoundCategory.BLOCKS, 1, 1); - stack.decrement(1); - }); - return ActionResult.SUCCESS; - } - - return super.onUseWithItem(stack, state, world, pos, player, hand, hit); - } - - @Override - protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) { - if(world.isClient()) { - return ActionResult.CONSUME; - } - - world.getBlockEntity(pos, MinionBlocks.MINION_TRIGGER_BE_TYPE).ifPresent(be -> { - String name = MinionPersistentState.get(world.getServer()).getMinionData(be.getMinionUuid()).name(); - player.sendMessage(Text.translatable("minions.reference.instruction.tooltip", name, be.getInstructionName()), true); - }); - return ActionResult.SUCCESS; - } - @Override protected void neighborUpdate(BlockState state, World world, BlockPos pos, Block sourceBlock, @Nullable WireOrientation wireOrientation, boolean notify) { if(!canPlaceAt(state, world, pos)) { @@ -136,19 +95,19 @@ public class MinionTriggerBlock extends BlockWithEntity implements PolymerBlock, return world.getBlockEntity(pos, MinionBlocks.MINION_TRIGGER_BE_TYPE).map(MinionTriggerBlockEntity::getComparatorOutput).orElse(0); } - @Override - protected MapCodec getCodec() { - return CODEC; - } - @Override public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) { return new MinionTriggerBlockEntity(pos, state); } + @Override + protected BlockEntityType getBlockEntityType() { + return MinionBlocks.MINION_TRIGGER_BE_TYPE; + } + @Override public BlockState getPolymerBlockState(BlockState state, PacketContext context) { - return PolymerUtil.isOnClient(context) ? state : net.minecraft.block.Blocks.COMPARATOR.getDefaultState().with(AbstractRedstoneGateBlock.POWERED, state.get(POWERED)); + return VersionSync.isOnClient(context) ? state : net.minecraft.block.Blocks.COMPARATOR.getDefaultState().with(AbstractRedstoneGateBlock.POWERED, state.get(POWERED)); } @Override diff --git a/src/main/java/io/github/skippyall/minions/block/miniontrigger/MinionTriggerBlockEntity.java b/src/main/java/io/github/skippyall/minions/block/miniontrigger/MinionTriggerBlockEntity.java index f423f4e..74ddb45 100644 --- a/src/main/java/io/github/skippyall/minions/block/miniontrigger/MinionTriggerBlockEntity.java +++ b/src/main/java/io/github/skippyall/minions/block/miniontrigger/MinionTriggerBlockEntity.java @@ -1,5 +1,6 @@ package io.github.skippyall.minions.block.miniontrigger; +import io.github.skippyall.minions.block.instruction_bound.InstructionBoundBlockEntity; import io.github.skippyall.minions.listener.BlockEntityMinionListener; import io.github.skippyall.minions.registration.MinionBlocks; import io.github.skippyall.minions.minion.MinionRuntime; @@ -15,28 +16,19 @@ import net.minecraft.util.math.BlockPos; import java.util.Optional; import java.util.UUID; -public class MinionTriggerBlockEntity extends BlockEntity { - private UUID minionUuid; - private String instructionName = ""; - +public class MinionTriggerBlockEntity extends InstructionBoundBlockEntity { public MinionTriggerBlockEntity(BlockPos pos, BlockState state) { super(MinionBlocks.MINION_TRIGGER_BE_TYPE, pos, state); } - public void removeListener() { - MinionTriggerMinionListener.removeListener(world, pos, minionUuid, instructionName); + @Override + protected MinionTriggerMinionListener createListener() { + return new MinionTriggerMinionListener(world.getRegistryKey(), pos, minionUuid, instructionName); } - public void addListener() { - MinionTriggerMinionListener.addListener(world, pos, minionUuid, instructionName); - } - - public void setInstruction(UUID minionUuid, String instructionName) { - removeListener(); - this.minionUuid = minionUuid; - this.instructionName = instructionName; - addListener(); - markDirty(); + @Override + protected Class getListenerClass() { + return MinionTriggerMinionListener.class; } public void updatePower() { @@ -66,33 +58,6 @@ public class MinionTriggerBlockEntity extends BlockEntity { return 0; } - public Optional getMinion() { - if(minionUuid != null && world != null && world.getPlayerByUuid(minionUuid) instanceof MinionFakePlayer minion) { - return Optional.of(minion); - } - return Optional.empty(); - } - - public UUID getMinionUuid() { - return minionUuid; - } - - public String getInstructionName() { - return instructionName; - } - - public Optional> getInstruction(MinionFakePlayer minion) { - return Optional.ofNullable(minion.getInstructionManager().getInstruction(instructionName)); - } - - public Optional> getInstruction() { - return getMinion().flatMap(this::getInstruction); - } - - public MinionTriggerMinionListener getListener() { - return BlockEntityMinionListener.getListener(world, pos, minionUuid, MinionTriggerMinionListener.class); - } - @Override protected void readData(ReadView view) { minionUuid = view.read("minionUuid", Uuids.CODEC).orElse(null); diff --git a/src/main/java/io/github/skippyall/minions/block/miniontrigger/MinionTriggerMinionListener.java b/src/main/java/io/github/skippyall/minions/block/miniontrigger/MinionTriggerMinionListener.java index 6fb9216..2657093 100644 --- a/src/main/java/io/github/skippyall/minions/block/miniontrigger/MinionTriggerMinionListener.java +++ b/src/main/java/io/github/skippyall/minions/block/miniontrigger/MinionTriggerMinionListener.java @@ -36,23 +36,11 @@ public class MinionTriggerMinionListener extends BlockEntityMinionInstructionLis boolean runningCache; boolean incomingPowerCache; - private MinionTriggerMinionListener(RegistryKey worldKey, BlockPos pos, UUID minionUuid, String instructionName) { + MinionTriggerMinionListener(RegistryKey worldKey, BlockPos pos, UUID minionUuid, String instructionName) { super(worldKey, pos, minionUuid, MinionBlocks.MINION_TRIGGER_BE_TYPE); this.instructionName = Objects.requireNonNull(instructionName); } - public static void addListener(World world, BlockPos pos, UUID minion, String instructionName) { - MinionTriggerMinionListener listener = new MinionTriggerMinionListener(world.getRegistryKey(), pos, minion, instructionName); - listener.add(world.getServer()); - } - - public static void removeListener(World world, BlockPos pos, UUID minion, String instructionName) { - MinionTriggerMinionListener old = getListener(world, pos, minion, MinionTriggerMinionListener.class); - if(old != null) { - old.remove(world.getServer()); - } - } - @Override protected Map getInstructionListeners() { return Map.of(instructionName, listener); @@ -88,7 +76,7 @@ public class MinionTriggerMinionListener extends BlockEntityMinionInstructionLis } @Override - protected void add(MinecraftServer server) { + public void add(MinecraftServer server) { super.add(server); runningCache = minion.getInstructionManager().getInstruction(instructionName).isRunning(); updateComparatorsIfLoaded(server); diff --git a/src/main/java/io/github/skippyall/minions/clipboard/BlockPosClipboard.java b/src/main/java/io/github/skippyall/minions/clipboard/BlockPosClipboard.java new file mode 100644 index 0000000..fb607d6 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/clipboard/BlockPosClipboard.java @@ -0,0 +1,33 @@ +package io.github.skippyall.minions.clipboard; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.component.ComponentsAccess; +import net.minecraft.item.Item; +import net.minecraft.item.tooltip.TooltipType; +import net.minecraft.registry.RegistryKey; +import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import java.util.function.Consumer; + +public record BlockPosClipboard(RegistryKey world, BlockPos pos) implements Clipboard { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> + instance.group( + World.CODEC.fieldOf("world").forGetter(BlockPosClipboard::world), + BlockPos.CODEC.fieldOf("pos").forGetter(BlockPosClipboard::pos) + ).apply(instance, BlockPosClipboard::new) + ); + + + @Override + public MapCodec getCodec() { + return CODEC; + } + + @Override + public void appendTooltip(Item.TooltipContext context, Consumer textConsumer, TooltipType type, ComponentsAccess components) { + textConsumer.accept(Text.translatable("minions.reference.block.tooltip", pos.toString())); + } +} diff --git a/src/main/java/io/github/skippyall/minions/clipboard/ClipboardItem.java b/src/main/java/io/github/skippyall/minions/clipboard/ClipboardItem.java index 5c46618..3b11dd7 100644 --- a/src/main/java/io/github/skippyall/minions/clipboard/ClipboardItem.java +++ b/src/main/java/io/github/skippyall/minions/clipboard/ClipboardItem.java @@ -10,6 +10,8 @@ import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.item.tooltip.TooltipType; import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; import org.jetbrains.annotations.Nullable; import xyz.nucleoid.packettweaker.PacketContext; @@ -40,4 +42,10 @@ public class ClipboardItem extends Item implements PolymerItem { stack.set(MinionComponentTypes.REFERENCE, new InstructionClipboard(minion.getUuid(), instructionName, minion.getGameProfile().getName())); return stack; } + + public static ItemStack createBlockPosReference(World world, BlockPos pos) { + ItemStack stack = new ItemStack(MinionItems.REFERENCE_ITEM); + stack.set(MinionComponentTypes.REFERENCE, new BlockPosClipboard(world.getRegistryKey(), pos)); + return stack; + } } diff --git a/src/main/java/io/github/skippyall/minions/command/DocsSubcommand.java b/src/main/java/io/github/skippyall/minions/command/DocsSubcommand.java new file mode 100644 index 0000000..84956ee --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/command/DocsSubcommand.java @@ -0,0 +1,36 @@ +package io.github.skippyall.minions.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import io.github.skippyall.minions.docs.DocsManager; +import net.minecraft.command.argument.IdentifierArgumentType; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.util.Identifier; + +import java.util.concurrent.CompletableFuture; + +import static net.minecraft.server.command.CommandManager.literal; +import static net.minecraft.server.command.CommandManager.argument; + +public class DocsSubcommand { + public static final LiteralArgumentBuilder DOCS = literal("docs") + .then( + argument("docName", IdentifierArgumentType.identifier()) + .suggests(DocsSubcommand::getSuggestions) + .executes(DocsSubcommand::execute) + ); + + public static CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) { + DocsManager.getDocsEntryIds().forEach(id -> builder.suggest(id.toString())); + return CompletableFuture.completedFuture(builder.build()); + } + + public static int execute(CommandContext context) throws CommandSyntaxException { + Identifier id = IdentifierArgumentType.getIdentifier(context, "docName"); + DocsManager.showDocsEntry(context.getSource().getPlayerOrThrow(), id); + return 1; + } +} diff --git a/src/main/java/io/github/skippyall/minions/command/MinionsCommand.java b/src/main/java/io/github/skippyall/minions/command/MinionsCommand.java index 7f6daf8..b10e095 100644 --- a/src/main/java/io/github/skippyall/minions/command/MinionsCommand.java +++ b/src/main/java/io/github/skippyall/minions/command/MinionsCommand.java @@ -13,7 +13,8 @@ public class MinionsCommand { public static void register(CommandDispatcher dispatcher, CommandRegistryAccess access, CommandManager.RegistrationEnvironment environment) { LiteralArgumentBuilder builder = literal("minions") .then(SpawnSubcommand.SPAWN) - .then(ListSubcommand.LIST); + .then(ListSubcommand.LIST) + .then(DocsSubcommand.DOCS); if(MinionsConfig.get().minion.enableMobCapHacks) { builder.then(MobCapDebugSubcommand.MOB_CAP_DEBUG); diff --git a/src/main/java/io/github/skippyall/minions/docs/DocsEntry.java b/src/main/java/io/github/skippyall/minions/docs/DocsEntry.java new file mode 100644 index 0000000..9633186 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/docs/DocsEntry.java @@ -0,0 +1,30 @@ +package io.github.skippyall.minions.docs; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.github.skippyall.minions.registration.MinionRegistries; +import net.minecraft.dialog.body.DialogBody; +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.util.List; +import java.util.function.Function; + +public interface DocsEntry { + Codec CODEC = MinionRegistries.DOCS_ENTRY_TYPES.getCodec().dispatch(DocsEntry::getCodec, Function.identity()); + + Metadata getMetadata(); + + List getDialog(DynamicRegistryManager manager); + + MapCodec getCodec(); + + record Metadata(String titleKey) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> + instance.group( + Codec.STRING.fieldOf("title").forGetter(Metadata::titleKey) + ).apply(instance, Metadata::new) + ); + } +} diff --git a/src/main/java/io/github/skippyall/minions/docs/DocsManager.java b/src/main/java/io/github/skippyall/minions/docs/DocsManager.java new file mode 100644 index 0000000..38698c0 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/docs/DocsManager.java @@ -0,0 +1,145 @@ +package io.github.skippyall.minions.docs; + +import com.google.gson.JsonParseException; +import com.mojang.serialization.JsonOps; +import io.github.skippyall.minions.Minions; +import net.fabricmc.fabric.api.resource.SimpleResourceReloadListener; +import net.minecraft.dialog.AfterAction; +import net.minecraft.dialog.DialogActionButtonData; +import net.minecraft.dialog.DialogButtonData; +import net.minecraft.dialog.DialogCommonData; +import net.minecraft.dialog.action.DynamicRunCommandDialogAction; +import net.minecraft.dialog.action.SimpleDialogAction; +import net.minecraft.dialog.type.MultiActionDialog; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.resource.Resource; +import net.minecraft.resource.ResourceManager; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.ClickEvent; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.Pair; +import net.minecraft.util.StrictJsonParser; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +public class DocsManager implements SimpleResourceReloadListener, DocsTree>> { + private static Map docs; + private static DocsTree tree; + + public static DocsTree getTree() { + return tree; + } + + public static void showDocsEntry(ServerPlayerEntity player, Identifier id) { + DocsEntry entry = getDocsEntry(id); + if(entry == null) { + return; + } + + List buttons = new ArrayList<>(); + if(tree != null) { + DocsTree.DocElement element = tree.getElement(id); + if (element.previous() != null) { + Identifier previousId = element.previous().getId(); + buttons.add(getDialogButton(Text.literal("<- ").append(Text.translatable(getDocsEntry(previousId).getMetadata().titleKey())), previousId.toString())); + } + if (element.next() != null) { + Identifier nextId = element.next().getId(); + buttons.add(getDialogButton(Text.translatable(getDocsEntry(nextId).getMetadata().titleKey()).append(Text.literal(" ->")), nextId.toString())); + } + } + + buttons.add(new DialogActionButtonData( + new DialogButtonData(Text.translatable("gui.ok"), 100), + Optional.empty() + )); + + player.openDialog(RegistryEntry.of(new MultiActionDialog( + new DialogCommonData( + Text.translatable(entry.getMetadata().titleKey()), + Optional.empty(), + true, + false, + AfterAction.CLOSE, + entry.getDialog(player.getRegistryManager()), + List.of() + ), + buttons, + Optional.empty(), + 2 + ))); + } + + private static DialogActionButtonData getDialogButton(Text text, String dialogToOpen) { + return new DialogActionButtonData( + new DialogButtonData( + text, 100 + ), + Optional.of(new SimpleDialogAction(new ClickEvent.RunCommand("/minions docs " + dialogToOpen))) + ); + } + + public static DocsEntry getDocsEntry(Identifier id) { + return docs.get(id); + } + + public static Collection getDocsEntryIds() { + return docs.keySet(); + } + + @Override + public CompletableFuture, DocsTree>> load(ResourceManager resourceManager, Executor executor) { + return CompletableFuture.supplyAsync(() -> { + Map resources = resourceManager.findResources("docs", id -> id.getNamespace().equals(Minions.MOD_ID) && id.getPath().endsWith(".json")); + + final DocsTree.BranchElement[] root = {null}; + Map docsEntries = new HashMap<>(); + resources.forEach((id, resource) -> { + try(Reader reader = resource.getReader()) { + 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.of(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 Pair<>(docsEntries, tree); + } else { + return new Pair<>(docsEntries, null); + } + }, executor); + } + + @Override + public CompletableFuture apply(Pair, DocsTree> o, ResourceManager resourceManager, Executor executor) { + return CompletableFuture.supplyAsync(() -> { + docs = o.getLeft(); + tree = o.getRight(); + return null; + }); + } + + @Override + public Identifier getFabricId() { + return Identifier.of(Minions.MOD_ID, "docs"); + } +} diff --git a/src/main/java/io/github/skippyall/minions/docs/DocsTree.java b/src/main/java/io/github/skippyall/minions/docs/DocsTree.java new file mode 100644 index 0000000..a96c210 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/docs/DocsTree.java @@ -0,0 +1,146 @@ +package io.github.skippyall.minions.docs; + +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import net.minecraft.util.Identifier; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public class DocsTree { + private final BranchElement root; + private final Map entries = new HashMap<>(); + + public DocsTree(BranchElement root) { + this.root = root; + initEntries(); + } + + private void initEntries() { + DocElement element = root.first(); + do { + entries.put(element.id, element); + element = element.next(); + } while (element != null); + } + + public BranchElement getRoot() { + return root; + } + + public DocElement getElement(Identifier id) { + return entries.get(id); + } + + public static abstract sealed class Element { + private BranchElement parent; + + public BranchElement getParent() { + return parent; + } + + void setParent(BranchElement parent) { + this.parent = parent; + } + + public DocElement next() { + return parent.next(this); + } + + public DocElement previous() { + return parent.previous(this); + } + } + + public static final class DocElement extends Element { + public static final Codec CODEC = Identifier.CODEC.xmap(DocElement::new, DocElement::getId); + + private final Identifier id; + + public DocElement(Identifier element) { + this.id = element; + } + + public Identifier getId() { + return id; + } + } + + public static final class BranchElement extends Element { + public static final Codec CODEC = Codec.recursive("DocsBranch", codec -> + Codec.either(codec, DocElement.CODEC) + .xmap( + Either::unwrap, + element -> switch (element) { + case BranchElement branch -> Either.left(branch); + case DocElement doc -> Either.right(doc); + } + ).listOf() + .xmap(BranchElement::new, branch -> branch.e) + ).xmap(BranchElement::setParents, Function.identity()); + + private final List e; + + public BranchElement(List elements) { + e = elements; + } + + public DocElement first() { + return switch (e.getFirst()) { + case BranchElement branch -> branch.first(); + case DocElement doc -> doc; + }; + } + + public DocElement last() { + return switch (e.getLast()) { + case BranchElement branch -> branch.last(); + case DocElement doc -> doc; + }; + } + + public DocElement next(Element current) { + int nextIndex = e.indexOf(current) + 1; + if(nextIndex < e.size()) { + return switch (e.get(nextIndex)) { + case BranchElement branch -> branch.first(); + case DocElement doc -> doc; + }; + } else { + if(getParent() != null) { + return getParent().next(this); + } else { + return null; + } + } + } + + public DocElement previous(Element current) { + int previousIndex = e.indexOf(current) - 1; + if(previousIndex >= 0) { + return switch (e.get(previousIndex)) { + case BranchElement branch -> branch.first(); + case DocElement doc -> doc; + }; + } else { + if(getParent() != null) { + return getParent().previous(this); + } else { + return null; + } + } + } + + private BranchElement setParents() { + for(Element element : e) { + element.setParent(this); + if(element instanceof BranchElement branch) { + branch.setParents(); + } + } + return this; + } + } +} diff --git a/src/main/java/io/github/skippyall/minions/docs/ReferenceEntry.java b/src/main/java/io/github/skippyall/minions/docs/ReferenceEntry.java index 4d024b5..43b5687 100644 --- a/src/main/java/io/github/skippyall/minions/docs/ReferenceEntry.java +++ b/src/main/java/io/github/skippyall/minions/docs/ReferenceEntry.java @@ -1,14 +1,81 @@ package io.github.skippyall.minions.docs; import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.github.skippyall.minions.gui.GuiDisplay; +import net.minecraft.dialog.body.DialogBody; +import net.minecraft.dialog.body.ItemDialogBody; +import net.minecraft.dialog.body.PlainMessageDialogBody; +import net.minecraft.item.Item; +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; import net.minecraft.text.Text; import net.minecraft.text.TextCodecs; +import net.minecraft.util.Identifier; -public record ReferenceEntry(Text shortDescription, Text longDescription) { - public static final Codec CODEC = RecordCodecBuilder.create(instance -> +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public record ReferenceEntry(Metadata metadata, RegistryKey object, Text shortDescription, Text longDescription) implements DocsEntry { + private static final Codec> REGISTRY_KEY_CODEC = RecordCodecBuilder.create(instance -> instance.group( + Identifier.CODEC.fieldOf("registry").forGetter(RegistryKey::getRegistry), + Identifier.CODEC.fieldOf("value").forGetter(RegistryKey::getValue) + ).apply(instance, (registry, value) -> RegistryKey.of(RegistryKey.ofRegistry(registry), value)) + ); + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> + instance.group( + Metadata.CODEC.fieldOf("metadata").forGetter(ReferenceEntry::getMetadata), + REGISTRY_KEY_CODEC.fieldOf("object").forGetter(ReferenceEntry::object), TextCodecs.CODEC.fieldOf("shortDescription").forGetter(ReferenceEntry::shortDescription), TextCodecs.CODEC.fieldOf("longDescription").forGetter(ReferenceEntry::longDescription) ).apply(instance, ReferenceEntry::new)); + + @Override + public Metadata getMetadata() { + return metadata; + } + + @Override + public List getDialog(DynamicRegistryManager manager) { + List bodyElements = new ArrayList<>(); + + GuiDisplay display = getObjectDisplay(manager); + if(display != null) { + bodyElements.add(new ItemDialogBody(display.createItemStack(), Optional.empty(), false, false, 16, 16)); + } + bodyElements.add(new PlainMessageDialogBody(Text.translatable(object.getValue().toTranslationKey(object.getRegistry().getPath())), 200)); + bodyElements.add(new PlainMessageDialogBody(longDescription.copy().append("\n".repeat(100)), 200)); + + return bodyElements; + } + + public GuiDisplay getObjectDisplay(DynamicRegistryManager manager) { + GuiDisplay display = GuiDisplay.DEFAULT_DISPLAY; + if(object.isOf(RegistryKeys.ITEM) || object.isOf(RegistryKeys.BLOCK)) { + Item item; + if(object.isOf(RegistryKeys.ITEM)) { + item = Registries.ITEM.get(object.getValue()); + } else { + item = Registries.BLOCK.get(object.getValue()).asItem(); + } + if(item != null) { + display = new GuiDisplay.ItemBased(item); + } + } else { + Identifier displayId = object.getValue().withPrefixedPath(object.getRegistry().getPath() + "/"); + display = GuiDisplay.getGuiDisplay(displayId, manager); + } + return display; + } + + @Override + public MapCodec getCodec() { + return CODEC; + } } diff --git a/src/main/java/io/github/skippyall/minions/gui/InstructionGui.java b/src/main/java/io/github/skippyall/minions/gui/InstructionGui.java index d94da2f..c7d0ead 100644 --- a/src/main/java/io/github/skippyall/minions/gui/InstructionGui.java +++ b/src/main/java/io/github/skippyall/minions/gui/InstructionGui.java @@ -100,18 +100,22 @@ public class InstructionGui { } - public static > void configureArgumentMenu(String instructionName, ConfiguredInstruction instruction, Parameter parameter, MinionFakePlayer minion, ServerPlayerEntity player) { + public static > void configureArgumentMenu(String instructionName, ConfiguredInstruction instruction, Parameter parameter, MinionFakePlayer minion, ServerPlayerEntity player) { if (!checkInstructionExists(instructionName, instruction, minion, player)) { return; } - @Nullable A argument = instruction.getArguments().getArgument(parameter); + @Nullable ValueSupplier argument = instruction.getArguments().getArgument(parameter); if(argument == null) { configureTypeAndValue(instructionName, instruction, parameter, minion, player); return; } + configureArgumentHelper(instructionName, instruction, parameter, argument, minion, player); + } + + private static void configureArgumentHelper(String instructionName, ConfiguredInstruction instruction, Parameter parameter, ValueSupplier argument, MinionFakePlayer minion, ServerPlayerEntity player) { SimpleGui gui = new InstructionBoundSimpleGui(ScreenHandlerType.GENERIC_3X3, player, minion, instruction); ItemStack displayStack = GuiDisplay.getDisplayStack(MinionRegistries.VALUE_SUPPLIER_TYPES, argument.getType(), player.getRegistryManager()); diff --git a/src/main/java/io/github/skippyall/minions/listener/BlockEntityMinionInstructionListener.java b/src/main/java/io/github/skippyall/minions/listener/BlockEntityMinionInstructionListener.java index 9260508..02b6581 100644 --- a/src/main/java/io/github/skippyall/minions/listener/BlockEntityMinionInstructionListener.java +++ b/src/main/java/io/github/skippyall/minions/listener/BlockEntityMinionInstructionListener.java @@ -26,7 +26,7 @@ public abstract class BlockEntityMinionInstructionListener implement return false; } - protected void add(MinecraftServer server) { + public void add(MinecraftServer server) { MinionPersistentState.get(server).getMinionData(minionUuid).listeners().addListener(this); MinionPersistentState.get(server).markDirty(); this.minion = (MinionFakePlayer) server.getPlayerManager().getPlayer(minionUuid); diff --git a/src/main/java/io/github/skippyall/minions/minion/fakeplayer/EntityPlayerActionPack.java b/src/main/java/io/github/skippyall/minions/minion/fakeplayer/EntityPlayerActionPack.java index cdfda5e..3ed7e83 100644 --- a/src/main/java/io/github/skippyall/minions/minion/fakeplayer/EntityPlayerActionPack.java +++ b/src/main/java/io/github/skippyall/minions/minion/fakeplayer/EntityPlayerActionPack.java @@ -190,7 +190,7 @@ public class EntityPlayerActionPack if (onlyRideables) { entities = player.getWorld().getOtherEntities(player, player.getBoundingBox().expand(3.0D, 1.0D, 3.0D), - e -> (e instanceof MinecartEntity || e instanceof BoatEntity || e instanceof AbstractHorseEntity) && ((EntityAccessor)e).invokeCanAddPassenger(player)); + e -> (e instanceof MinecartEntity || e instanceof BoatEntity || e instanceof AbstractHorseEntity) && ((EntityAccessor)e).minions$canAddPassenger(player)); } else { diff --git a/src/main/java/io/github/skippyall/minions/minion/fakeplayer/MinionFakePlayer.java b/src/main/java/io/github/skippyall/minions/minion/fakeplayer/MinionFakePlayer.java index d9c2f04..214b384 100644 --- a/src/main/java/io/github/skippyall/minions/minion/fakeplayer/MinionFakePlayer.java +++ b/src/main/java/io/github/skippyall/minions/minion/fakeplayer/MinionFakePlayer.java @@ -23,6 +23,8 @@ import net.minecraft.entity.attribute.EntityAttributes; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.player.HungerManager; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.vehicle.AbstractBoatEntity; +import net.minecraft.entity.vehicle.BoatEntity; import net.minecraft.item.ItemStack; import net.minecraft.network.DisconnectionInfo; import net.minecraft.network.NetworkSide; @@ -210,6 +212,21 @@ public class MinionFakePlayer extends ServerPlayerEntity { } + @Override + public boolean startRiding(Entity entityToRide, boolean force) { + if (super.startRiding(entityToRide, force)) { + // from ClientPacketListener.handleSetEntityPassengersPacket + if (entityToRide instanceof AbstractBoatEntity) { + this.lastYaw = entityToRide.getYaw(); + this.setYaw(entityToRide.getYaw()); + this.setHeadYaw(entityToRide.getHeadYaw()); + } + return true; + } else { + return false; + } + } + private void shakeOff() { if (getVehicle() instanceof PlayerEntity) stopRiding(); diff --git a/src/main/java/io/github/skippyall/minions/instruction/ActionExecution.java b/src/main/java/io/github/skippyall/minions/minion/program/instruction/ActionExecution.java similarity index 96% rename from src/main/java/io/github/skippyall/minions/instruction/ActionExecution.java rename to src/main/java/io/github/skippyall/minions/minion/program/instruction/ActionExecution.java index 2363df7..4e764ff 100644 --- a/src/main/java/io/github/skippyall/minions/instruction/ActionExecution.java +++ b/src/main/java/io/github/skippyall/minions/minion/program/instruction/ActionExecution.java @@ -1,4 +1,4 @@ -package io.github.skippyall.minions.instruction; +package io.github.skippyall.minions.minion.program.instruction; import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack; diff --git a/src/main/java/io/github/skippyall/minions/instruction/MineBlockExecution.java b/src/main/java/io/github/skippyall/minions/minion/program/instruction/MineBlockExecution.java similarity index 98% rename from src/main/java/io/github/skippyall/minions/instruction/MineBlockExecution.java rename to src/main/java/io/github/skippyall/minions/minion/program/instruction/MineBlockExecution.java index 9d71cb6..ad6a86d 100644 --- a/src/main/java/io/github/skippyall/minions/instruction/MineBlockExecution.java +++ b/src/main/java/io/github/skippyall/minions/minion/program/instruction/MineBlockExecution.java @@ -1,5 +1,5 @@ //partially code from https://github.com/gnembon/fabric-carpet (EntityPlayerActionPack) -package io.github.skippyall.minions.instruction; +package io.github.skippyall.minions.minion.program.instruction; import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack; diff --git a/src/main/java/io/github/skippyall/minions/instruction/inventory/SwapItemExecution.java b/src/main/java/io/github/skippyall/minions/minion/program/instruction/inventory/SwapItemExecution.java similarity index 98% rename from src/main/java/io/github/skippyall/minions/instruction/inventory/SwapItemExecution.java rename to src/main/java/io/github/skippyall/minions/minion/program/instruction/inventory/SwapItemExecution.java index 83ea5ba..58d2980 100644 --- a/src/main/java/io/github/skippyall/minions/instruction/inventory/SwapItemExecution.java +++ b/src/main/java/io/github/skippyall/minions/minion/program/instruction/inventory/SwapItemExecution.java @@ -1,4 +1,4 @@ -package io.github.skippyall.minions.instruction.inventory; +package io.github.skippyall.minions.minion.program.instruction.inventory; import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; diff --git a/src/main/java/io/github/skippyall/minions/instruction/move/AbstractTurnExecution.java b/src/main/java/io/github/skippyall/minions/minion/program/instruction/move/AbstractTurnExecution.java similarity index 95% rename from src/main/java/io/github/skippyall/minions/instruction/move/AbstractTurnExecution.java rename to src/main/java/io/github/skippyall/minions/minion/program/instruction/move/AbstractTurnExecution.java index e5781a7..6a51e4d 100644 --- a/src/main/java/io/github/skippyall/minions/instruction/move/AbstractTurnExecution.java +++ b/src/main/java/io/github/skippyall/minions/minion/program/instruction/move/AbstractTurnExecution.java @@ -1,4 +1,4 @@ -package io.github.skippyall.minions.instruction.move; +package io.github.skippyall.minions.minion.program.instruction.move; import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.program.instruction.InstructionExecution; diff --git a/src/main/java/io/github/skippyall/minions/instruction/move/ContinuousWalkExecution.java b/src/main/java/io/github/skippyall/minions/minion/program/instruction/move/ContinuousWalkExecution.java similarity index 90% rename from src/main/java/io/github/skippyall/minions/instruction/move/ContinuousWalkExecution.java rename to src/main/java/io/github/skippyall/minions/minion/program/instruction/move/ContinuousWalkExecution.java index 7d4d379..34b973d 100644 --- a/src/main/java/io/github/skippyall/minions/instruction/move/ContinuousWalkExecution.java +++ b/src/main/java/io/github/skippyall/minions/minion/program/instruction/move/ContinuousWalkExecution.java @@ -1,4 +1,4 @@ -package io.github.skippyall.minions.instruction.move; +package io.github.skippyall.minions.minion.program.instruction.move; import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.program.instruction.InstructionExecution; diff --git a/src/main/java/io/github/skippyall/minions/instruction/move/TurnDirection.java b/src/main/java/io/github/skippyall/minions/minion/program/instruction/move/TurnDirection.java similarity index 95% rename from src/main/java/io/github/skippyall/minions/instruction/move/TurnDirection.java rename to src/main/java/io/github/skippyall/minions/minion/program/instruction/move/TurnDirection.java index 17208d7..17b0015 100644 --- a/src/main/java/io/github/skippyall/minions/instruction/move/TurnDirection.java +++ b/src/main/java/io/github/skippyall/minions/minion/program/instruction/move/TurnDirection.java @@ -1,4 +1,4 @@ -package io.github.skippyall.minions.instruction.move; +package io.github.skippyall.minions.minion.program.instruction.move; import com.mojang.serialization.Codec; import io.github.skippyall.minions.gui.Displayable; diff --git a/src/main/java/io/github/skippyall/minions/instruction/move/TurnExecution.java b/src/main/java/io/github/skippyall/minions/minion/program/instruction/move/TurnExecution.java similarity index 93% rename from src/main/java/io/github/skippyall/minions/instruction/move/TurnExecution.java rename to src/main/java/io/github/skippyall/minions/minion/program/instruction/move/TurnExecution.java index 12445ca..ec9b077 100644 --- a/src/main/java/io/github/skippyall/minions/instruction/move/TurnExecution.java +++ b/src/main/java/io/github/skippyall/minions/minion/program/instruction/move/TurnExecution.java @@ -1,4 +1,4 @@ -package io.github.skippyall.minions.instruction.move; +package io.github.skippyall.minions.minion.program.instruction.move; import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.program.supplier.ValueSupplierList; diff --git a/src/main/java/io/github/skippyall/minions/instruction/move/TurnVectorExecution.java b/src/main/java/io/github/skippyall/minions/minion/program/instruction/move/TurnVectorExecution.java similarity index 95% rename from src/main/java/io/github/skippyall/minions/instruction/move/TurnVectorExecution.java rename to src/main/java/io/github/skippyall/minions/minion/program/instruction/move/TurnVectorExecution.java index ae01c50..c445ac3 100644 --- a/src/main/java/io/github/skippyall/minions/instruction/move/TurnVectorExecution.java +++ b/src/main/java/io/github/skippyall/minions/minion/program/instruction/move/TurnVectorExecution.java @@ -1,4 +1,4 @@ -package io.github.skippyall.minions.instruction.move; +package io.github.skippyall.minions.minion.program.instruction.move; import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.program.supplier.Parameter; diff --git a/src/main/java/io/github/skippyall/minions/instruction/move/WalkExecution.java b/src/main/java/io/github/skippyall/minions/minion/program/instruction/move/WalkExecution.java similarity index 96% rename from src/main/java/io/github/skippyall/minions/instruction/move/WalkExecution.java rename to src/main/java/io/github/skippyall/minions/minion/program/instruction/move/WalkExecution.java index 92a0529..ba90653 100644 --- a/src/main/java/io/github/skippyall/minions/instruction/move/WalkExecution.java +++ b/src/main/java/io/github/skippyall/minions/minion/program/instruction/move/WalkExecution.java @@ -1,4 +1,4 @@ -package io.github.skippyall.minions.instruction.move; +package io.github.skippyall.minions.minion.program.instruction.move; import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.program.instruction.InstructionExecution; diff --git a/src/main/java/io/github/skippyall/minions/minion/program/supplier/AnalogInputSupplier.java b/src/main/java/io/github/skippyall/minions/minion/program/supplier/AnalogInputSupplier.java new file mode 100644 index 0000000..0f7888a --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/minion/program/supplier/AnalogInputSupplier.java @@ -0,0 +1,100 @@ +package io.github.skippyall.minions.minion.program.supplier; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import eu.pb4.sgui.api.elements.GuiElementBuilder; +import eu.pb4.sgui.api.gui.SimpleGui; +import io.github.skippyall.minions.clipboard.BlockPosClipboard; +import io.github.skippyall.minions.minion.MinionRuntime; +import io.github.skippyall.minions.program.supplier.ValueSupplier; +import io.github.skippyall.minions.program.supplier.ValueSupplierType; +import io.github.skippyall.minions.program.value.ValueType; +import io.github.skippyall.minions.registration.MinionBlocks; +import io.github.skippyall.minions.registration.MinionComponentTypes; +import io.github.skippyall.minions.registration.MinionItems; +import io.github.skippyall.minions.registration.ValueSuppliers; +import io.github.skippyall.minions.registration.ValueTypes; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.RegistryKey; +import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.CompletableFuture; + +public class AnalogInputSupplier implements ValueSupplier { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> + instance.group( + World.CODEC.fieldOf("analogInputWorld").forGetter(s -> s.analogInputWorld), + BlockPos.CODEC.fieldOf("analogInputPos").forGetter(s -> s.analogInputPos) + ).apply(instance, AnalogInputSupplier::new)); + + private final RegistryKey analogInputWorld; + private final BlockPos analogInputPos; + + public AnalogInputSupplier(RegistryKey analogInputWorld, BlockPos analogInputPos) { + this.analogInputWorld = analogInputWorld; + this.analogInputPos = analogInputPos; + } + + @Override + public Long resolve(MinionRuntime minion) { + World world = minion.getMinion().getServer().getWorld(analogInputWorld); + if(world != null && world.isPosLoaded(analogInputPos) && world.getBlockState(analogInputPos).isOf(MinionBlocks.ANALOG_INPUT_BLOCK)) { + return (long) world.getReceivedRedstonePower(analogInputPos); + } else { + return 0L; + } + } + + @Override + public ValueType getValueType() { + return ValueTypes.LONG; + } + + @Override + public ValueSupplierType getType() { + return ValueSuppliers.ANALOG_INPUT; + } + + @Override + public Text getDisplayText() { + return Text.translatable("value_supplier_type.minions.analog_input.display", analogInputPos.toString(), analogInputWorld.getValue()); + } + + public static class AnalogInputSupplierType extends ValueSupplierType { + @Override + public Codec> getCodec(ValueType type) { + if(type == ValueTypes.LONG) { + return ValueSupplier.castCodec(CODEC, ValueTypes.LONG, type); + } + return null; + } + + @Override + public CompletableFuture> openConfiguration(ServerPlayerEntity player, ValueType valueType, @Nullable ValueSupplier previous) { + if(valueType != ValueTypes.LONG) { + return CompletableFuture.failedFuture(new IllegalArgumentException("Value type " + valueType + " not allowed")); + } + + CompletableFuture> future = new CompletableFuture<>(); + + SimpleGui gui = new SimpleGui(ScreenHandlerType.GENERIC_3X3, player, false); + gui.setTitle(Text.translatable("value_supplier_type.minions.analog_input")); + + gui.setSlot(4, new GuiElementBuilder(MinionItems.REFERENCE_ITEM) + .setCallback(() -> { + ItemStack cursor = player.currentScreenHandler.getCursorStack(); + if(cursor.isOf(MinionItems.REFERENCE_ITEM) && cursor.get(MinionComponentTypes.REFERENCE) instanceof BlockPosClipboard pos) { + future.complete(new AnalogInputSupplier(pos.world(), pos.pos()).cast(valueType)); + } + }) + .setItemName(Text.translatable("value_supplier_type.minions.analog_input.config.click_with_reference")) + ); + return future; + } + } +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/EntityAccessor.java b/src/main/java/io/github/skippyall/minions/mixins/EntityAccessor.java index 6746f5f..06eaa10 100644 --- a/src/main/java/io/github/skippyall/minions/mixins/EntityAccessor.java +++ b/src/main/java/io/github/skippyall/minions/mixins/EntityAccessor.java @@ -4,8 +4,13 @@ import net.minecraft.entity.Entity; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Invoker; +import java.util.stream.Stream; + @Mixin(Entity.class) public interface EntityAccessor { @Invoker("canAddPassenger") - boolean invokeCanAddPassenger(Entity other); + boolean minions$canAddPassenger(Entity other); + + @Invoker("streamIntoPassengers") + Stream minions$streamIntoPassengers(); } diff --git a/src/main/java/io/github/skippyall/minions/mixins/PistonMovingBlockEntityMixin.java b/src/main/java/io/github/skippyall/minions/mixins/PistonMovingBlockEntityMixin.java new file mode 100644 index 0000000..6aef1b4 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/PistonMovingBlockEntityMixin.java @@ -0,0 +1,42 @@ +package io.github.skippyall.minions.mixins; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; +import net.minecraft.block.Blocks; +import net.minecraft.block.entity.PistonBlockEntity; +import net.minecraft.block.piston.PistonBehavior; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(PistonBlockEntity.class) +public abstract class PistonMovingBlockEntityMixin { + @WrapOperation(method = "pushEntities", at = @At( + value = "INVOKE", + target = "Lnet/minecraft/entity/Entity;getPistonBehavior()Lnet/minecraft/block/piston/PistonBehavior;" + )) + private static PistonBehavior moveFakePlayers(Entity entity, Operation original, World world, BlockPos pos, float f, PistonBlockEntity pistonBlockEntity) + { + if (entity instanceof MinionFakePlayer && pistonBlockEntity.getPushedBlock().isOf(Blocks.SLIME_BLOCK)) + { + Vec3d vec3d = entity.getVelocity(); + double x = vec3d.x; + double y = vec3d.y; + double z = vec3d.z; + Direction direction = pistonBlockEntity.getMovementDirection(); + switch (direction.getAxis()) { + case X -> x = direction.getOffsetX(); + case Y -> y = direction.getOffsetY(); + case Z -> z = direction.getOffsetZ(); + } + + entity.setVelocity(x, y, z); + } + return original.call(entity); + } +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/SpawnHelperMixin.java b/src/main/java/io/github/skippyall/minions/mixins/SpawnHelperMixin.java index a2a31fc..5af310a 100644 --- a/src/main/java/io/github/skippyall/minions/mixins/SpawnHelperMixin.java +++ b/src/main/java/io/github/skippyall/minions/mixins/SpawnHelperMixin.java @@ -1,20 +1,21 @@ package io.github.skippyall.minions.mixins; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; +import io.github.skippyall.minions.mixinhelper.EntityViewMixinHelper; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.world.SpawnHelper; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; @Mixin(SpawnHelper.class) public class SpawnHelperMixin { - @Redirect(method = "spawnEntitiesInChunk(Lnet/minecraft/entity/SpawnGroup;Lnet/minecraft/server/world/ServerWorld;Lnet/minecraft/world/chunk/Chunk;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/SpawnHelper$Checker;Lnet/minecraft/world/SpawnHelper$Runner;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerWorld;getClosestPlayer(DDDDZ)Lnet/minecraft/entity/player/PlayerEntity;")) - private static PlayerEntity checkMobSpawningMinion(ServerWorld instance, double x, double y, double z, double maxDistance, boolean b) { - return instance.getClosestPlayer(x, y, z, maxDistance, EntityPredicates.EXCEPT_SPECTATOR.and(entity -> { + @WrapOperation(method = "spawnEntitiesInChunk(Lnet/minecraft/entity/SpawnGroup;Lnet/minecraft/server/world/ServerWorld;Lnet/minecraft/world/chunk/Chunk;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/SpawnHelper$Checker;Lnet/minecraft/world/SpawnHelper$Runner;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerWorld;getClosestPlayer(DDDDZ)Lnet/minecraft/entity/player/PlayerEntity;")) + private static PlayerEntity checkMobSpawningMinion(ServerWorld world, double x, double y, double z, double maxDistance, boolean ignoreCreative, Operation original) { + EntityViewMixinHelper.ADDITIONAL_PREDICATE.set(entity -> { if(entity instanceof ServerPlayerEntity player) { if(player instanceof MinionFakePlayer minion) { return minion.canSpawnMobs(); @@ -22,6 +23,9 @@ public class SpawnHelperMixin { return true; } return false; - })); + }); + PlayerEntity player = original.call(world, x, y, z, maxDistance, ignoreCreative); + EntityViewMixinHelper.ADDITIONAL_PREDICATE.remove(); + return player; } } diff --git a/src/main/java/io/github/skippyall/minions/mixins/TickRateManagerMixin.java b/src/main/java/io/github/skippyall/minions/mixins/TickRateManagerMixin.java new file mode 100644 index 0000000..ca75b7f --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/TickRateManagerMixin.java @@ -0,0 +1,33 @@ +package io.github.skippyall.minions.mixins; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.world.tick.TickManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(TickManager.class) +public abstract class TickRateManagerMixin { + @Shadow + public abstract boolean shouldTick(); + + @ModifyReturnValue(method = "shouldSkipTick", at = @At("TAIL")) + private boolean handler(boolean alreadyFrozen, Entity entity) { + if (alreadyFrozen) return true; + if (shouldTick()) return false; + + return !isActualPlayer(entity) && // not carrying players + ((EntityAccessor) entity) + .minions$streamIntoPassengers() + .noneMatch(TickRateManagerMixin::isActualPlayer); + } + + @Unique + private static boolean isActualPlayer(Entity e) { + return e instanceof PlayerEntity && !(e instanceof MinionFakePlayer); + } +} diff --git a/src/main/java/io/github/skippyall/minions/util/PolymerUtil.java b/src/main/java/io/github/skippyall/minions/polymer/VersionSync.java similarity index 92% rename from src/main/java/io/github/skippyall/minions/util/PolymerUtil.java rename to src/main/java/io/github/skippyall/minions/polymer/VersionSync.java index 8818db2..0d54323 100644 --- a/src/main/java/io/github/skippyall/minions/util/PolymerUtil.java +++ b/src/main/java/io/github/skippyall/minions/polymer/VersionSync.java @@ -1,4 +1,4 @@ -package io.github.skippyall.minions.util; +package io.github.skippyall.minions.polymer; import eu.pb4.polymer.networking.api.PolymerNetworking; import eu.pb4.polymer.networking.api.server.PolymerServerNetworking; @@ -9,7 +9,7 @@ import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.util.Identifier; import xyz.nucleoid.packettweaker.PacketContext; -public class PolymerUtil { +public class VersionSync { public static final int NETWORK_VERSION = 1; public static boolean isOnClient(PacketContext context) { @@ -29,7 +29,7 @@ public class PolymerUtil { @Override public Id getId() { - return null; + return PACKET_ID; } } diff --git a/src/main/java/io/github/skippyall/minions/program/supplier/Parameter.java b/src/main/java/io/github/skippyall/minions/program/supplier/Parameter.java index 7226dab..f57ee4b 100644 --- a/src/main/java/io/github/skippyall/minions/program/supplier/Parameter.java +++ b/src/main/java/io/github/skippyall/minions/program/supplier/Parameter.java @@ -2,6 +2,7 @@ package io.github.skippyall.minions.program.supplier; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.github.skippyall.minions.program.value.SimpleValueType; import io.github.skippyall.minions.registration.MinionRegistries; import io.github.skippyall.minions.program.value.ValueType; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/io/github/skippyall/minions/program/supplier/ValueSupplier.java b/src/main/java/io/github/skippyall/minions/program/supplier/ValueSupplier.java index cad224c..1e56ee9 100644 --- a/src/main/java/io/github/skippyall/minions/program/supplier/ValueSupplier.java +++ b/src/main/java/io/github/skippyall/minions/program/supplier/ValueSupplier.java @@ -1,6 +1,7 @@ package io.github.skippyall.minions.program.supplier; import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; import io.github.skippyall.minions.registration.MinionRegistries; import io.github.skippyall.minions.program.InstructionRuntime; import io.github.skippyall.minions.program.value.ValueType; @@ -31,14 +32,33 @@ public interface ValueSupplier> { } } + /** + * WARNING: If originalType is not the type of the value suppliers from the codec, this will leak wrong generics! + */ + static , A extends ValueSupplier, C extends ValueSupplier> @Nullable Codec castCodec(Codec codec, ValueType originalType, ValueType newType) { + if(originalType == newType) { + //noinspection unchecked + return (Codec) codec; + } else { + return null; + } + } + static > Codec> createArgumentCodec(Codec> codec) { return codec.dispatch( "type", ValueSupplier::getType, type -> - MinionRegistries.VALUE_TYPES.getCodec().>dispatch( - ValueSupplier::getValueType, - valueType -> type.getCodec(valueType).fieldOf("valueType") + MinionRegistries.VALUE_TYPES.getCodec().>partialDispatch( + "type", + s -> DataResult.success(s.getValueType()), + valueType -> { + if(type.getCodec(valueType) != null) { + return DataResult.success(type.getCodec(valueType).fieldOf("valueType")); + } else { + return DataResult.error(() -> "Supplier type " + type + "not available for value type " + valueType); + } + } ).fieldOf("valueType") ); } diff --git a/src/main/java/io/github/skippyall/minions/program/supplier/ValueSupplierList.java b/src/main/java/io/github/skippyall/minions/program/supplier/ValueSupplierList.java index 0a9844d..522881e 100644 --- a/src/main/java/io/github/skippyall/minions/program/supplier/ValueSupplierList.java +++ b/src/main/java/io/github/skippyall/minions/program/supplier/ValueSupplierList.java @@ -2,6 +2,8 @@ package io.github.skippyall.minions.program.supplier; import com.mojang.serialization.Codec; import io.github.skippyall.minions.program.InstructionRuntime; +import io.github.skippyall.minions.program.value.Cast; +import io.github.skippyall.minions.program.value.Casts; import java.util.ArrayList; import java.util.Collection; @@ -22,17 +24,29 @@ public class ValueSupplierList> { this.arguments = new HashMap<>(arguments); } - public T getValue(Parameter parameter, R runtime) { - ValueSupplier valueSupplier = getArgument(parameter); - return valueSupplier != null ? valueSupplier.resolve(runtime) : null; + public T getValue(Parameter parameter, R runtime) { + //noinspection unchecked + ValueSupplier valueSupplier = (ValueSupplier) getArgument(parameter); + if(valueSupplier == null) { + return null; + } + + if(valueSupplier.getValueType() == parameter.type()) { + return valueSupplier.cast(parameter.type()).resolve(runtime); + } else { + Cast cast = Casts.getCast(valueSupplier.getValueType(), parameter.type()); + if(cast != null) { + return cast.cast(valueSupplier.resolve(runtime)); + } + } + return null; } - public > A getArgument(Parameter parameter) { - ValueSupplier valueSupplier = arguments.get(parameter.name()); - return valueSupplier == null ? null : valueSupplier.cast(parameter.type()); + public ValueSupplier getArgument(Parameter parameter) { + return arguments.get(parameter.name()); } - public void setArgument(Parameter parameter, ValueSupplier valueSupplier) { + public void setArgument(Parameter parameter, ValueSupplier valueSupplier) { arguments.put(parameter.name(), valueSupplier); onChange(parameter); } diff --git a/src/main/java/io/github/skippyall/minions/program/value/Cast.java b/src/main/java/io/github/skippyall/minions/program/value/Cast.java new file mode 100644 index 0000000..55f7638 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/program/value/Cast.java @@ -0,0 +1,77 @@ +package io.github.skippyall.minions.program.value; + +public class Cast { + private final ValueType from; + private final ValueType to; + + private final Caster caster; + + private final boolean lossy, canFail; + + public Cast(ValueType from, ValueType to, Caster caster) { + this(from, to, caster, false, false); + } + + private Cast(ValueType from, ValueType to, Caster caster, boolean lossy, boolean canFail) { + this.from = from; + this.to = to; + this.caster = caster; + + this.lossy = lossy; + this.canFail = canFail; + } + + public T cast(F from) { + return caster.cast(from); + } + + public ValueType getFrom() { + return from; + } + + public ValueType getTo() { + return to; + } + + public boolean isLossy() { + return lossy; + } + + public boolean canFail() { + return canFail; + } + + public static class CastCrafter { + private final ValueType from; + private final ValueType to; + + private final Caster caster; + + private boolean lossy, canFail; + + public CastCrafter(ValueType from, ValueType to, Caster caster) { + this.from = from; + this.to = to; + this.caster = caster; + } + + public CastCrafter lossy() { + lossy = true; + return this; + } + + public CastCrafter canFail() { + canFail = true; + return this; + } + + public Cast craftCast() { + return new Cast<>(from, to, caster, lossy, canFail); + } + } + + @FunctionalInterface + public interface Caster { + T cast(F from); + } +} diff --git a/src/main/java/io/github/skippyall/minions/program/value/Casts.java b/src/main/java/io/github/skippyall/minions/program/value/Casts.java new file mode 100644 index 0000000..1cd0724 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/program/value/Casts.java @@ -0,0 +1,36 @@ +package io.github.skippyall.minions.program.value; + +import io.github.skippyall.minions.registration.ValueTypes; +import org.jetbrains.annotations.Nullable; + +public class Casts { + public static @Nullable Cast getCast(ValueType from, ValueType to) { + if(from == to) { + //noinspection unchecked + return new Cast<>(from, to, v -> (T)v); + } + if(from == ValueTypes.LONG && to == ValueTypes.DOUBLE) { + //noinspection unchecked + return (Cast) new Cast<>(ValueTypes.LONG, ValueTypes.DOUBLE, (v) -> (double)v); + } + if(from == ValueTypes.DOUBLE && to == ValueTypes.LONG) { + //noinspection unchecked + return (Cast) new Cast.CastCrafter<>(ValueTypes.DOUBLE, ValueTypes.LONG, (v) -> (long)(double)v).lossy().craftCast(); + } + if((from == ValueTypes.DOUBLE || from == ValueTypes.LONG) && to == ValueTypes.STRING) { + //noinspection unchecked + return (Cast) new Cast.CastCrafter<>(from, ValueTypes.STRING, String::valueOf).craftCast(); + } + + return null; + } + + public static boolean canCastSafely(ValueType from, ValueType to) { + if(from == to) { + return true; + } else { + Cast cast = getCast(from, to); + return cast != null && !cast.canFail() && !cast.isLossy(); + } + } +} diff --git a/src/main/java/io/github/skippyall/minions/program/value/LongValueType.java b/src/main/java/io/github/skippyall/minions/program/value/LongValueType.java new file mode 100644 index 0000000..2b937a5 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/program/value/LongValueType.java @@ -0,0 +1,29 @@ +package io.github.skippyall.minions.program.value; + +import com.mojang.serialization.Codec; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; + +import java.util.concurrent.CompletableFuture; + +public class LongValueType implements ValueType { + @Override + public CompletableFuture openValueDialog(ServerPlayerEntity player, Long previousValue) { + return null; + } + + @Override + public Text getDisplayText(Long value) { + return null; + } + + @Override + public Codec codec() { + return null; + } + + @Override + public Long defaultValue() { + return 0L; + } +} diff --git a/src/main/java/io/github/skippyall/minions/program/value/SimpleValueType.java b/src/main/java/io/github/skippyall/minions/program/value/SimpleValueType.java new file mode 100644 index 0000000..efce1dd --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/program/value/SimpleValueType.java @@ -0,0 +1,22 @@ +package io.github.skippyall.minions.program.value; + +import com.mojang.serialization.Codec; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; +import java.util.function.Function; + +public record SimpleValueType(Codec codec, T defaultValue, BiFunction> valueDialogOpener, Function textDisplay) implements ValueType { + @Override + public CompletableFuture openValueDialog(ServerPlayerEntity player, T previousValue) { + return valueDialogOpener.apply(player, previousValue); + } + + @Override + public Text getDisplayText(T value) { + return textDisplay.apply(value); + } +} diff --git a/src/main/java/io/github/skippyall/minions/program/value/ValueType.java b/src/main/java/io/github/skippyall/minions/program/value/ValueType.java index 6ae41e2..75d5c3f 100644 --- a/src/main/java/io/github/skippyall/minions/program/value/ValueType.java +++ b/src/main/java/io/github/skippyall/minions/program/value/ValueType.java @@ -3,17 +3,18 @@ package io.github.skippyall.minions.program.value; import com.mojang.serialization.Codec; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; import java.util.concurrent.CompletableFuture; import java.util.function.BiFunction; import java.util.function.Function; -public record ValueType(Codec codec, T defaultValue, BiFunction> valueDialogOpener, Function textDisplay) { - public CompletableFuture openValueDialog(ServerPlayerEntity player, T previousValue) { - return valueDialogOpener.apply(player, previousValue); - } +public interface ValueType { + CompletableFuture openValueDialog(ServerPlayerEntity player, T previousValue); - public Text getDisplayText(T value) { - return textDisplay.apply(value); - } + Text getDisplayText(T value); + + Codec codec(); + + T defaultValue(); } diff --git a/src/main/java/io/github/skippyall/minions/program/value/conversion/ValueConverter.java b/src/main/java/io/github/skippyall/minions/program/value/conversion/ValueConverter.java new file mode 100644 index 0000000..ca6fce2 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/program/value/conversion/ValueConverter.java @@ -0,0 +1,13 @@ +package io.github.skippyall.minions.program.value.conversion; + +import io.github.skippyall.minions.program.value.ValueType; + +public interface ValueConverter { + T convert(F from); + + ValueType getFrom(); + + ValueType getTo(); + + ValueConverterType getType(); +} diff --git a/src/main/java/io/github/skippyall/minions/program/value/conversion/ValueConverterType.java b/src/main/java/io/github/skippyall/minions/program/value/conversion/ValueConverterType.java new file mode 100644 index 0000000..52db014 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/program/value/conversion/ValueConverterType.java @@ -0,0 +1,18 @@ +package io.github.skippyall.minions.program.value.conversion; + +import com.mojang.serialization.Codec; +import io.github.skippyall.minions.program.value.ValueType; +import net.minecraft.server.network.ServerPlayerEntity; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.CompletableFuture; + +public interface ValueConverterType> { + Codec getCodec(ValueType from, ValueType to); + + boolean isSupportedFromType(ValueType type); + + boolean isSupportedToType(ValueType type); + + CompletableFuture configure(ServerPlayerEntity player, ValueType from, ValueType to, @Nullable C old); +} diff --git a/src/main/java/io/github/skippyall/minions/registration/ClipboardTypes.java b/src/main/java/io/github/skippyall/minions/registration/ClipboardTypes.java index a90d79d..7eb4e4a 100644 --- a/src/main/java/io/github/skippyall/minions/registration/ClipboardTypes.java +++ b/src/main/java/io/github/skippyall/minions/registration/ClipboardTypes.java @@ -2,6 +2,7 @@ package io.github.skippyall.minions.registration; import com.mojang.serialization.MapCodec; import io.github.skippyall.minions.Minions; +import io.github.skippyall.minions.clipboard.BlockPosClipboard; import io.github.skippyall.minions.clipboard.InstructionClipboard; import io.github.skippyall.minions.clipboard.Clipboard; import net.minecraft.registry.Registry; @@ -14,5 +15,6 @@ public class ClipboardTypes { static void register() { register("instruction", InstructionClipboard.CODEC); + register("position", BlockPosClipboard.CODEC); } } diff --git a/src/main/java/io/github/skippyall/minions/registration/DocsEntryTypes.java b/src/main/java/io/github/skippyall/minions/registration/DocsEntryTypes.java new file mode 100644 index 0000000..ee084a4 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/registration/DocsEntryTypes.java @@ -0,0 +1,12 @@ +package io.github.skippyall.minions.registration; + +import io.github.skippyall.minions.Minions; +import io.github.skippyall.minions.docs.ReferenceEntry; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + +public class DocsEntryTypes { + public static void register() { + Registry.register(MinionRegistries.DOCS_ENTRY_TYPES, Identifier.of(Minions.MOD_ID, "reference_entry"), ReferenceEntry.CODEC); + } +} diff --git a/src/main/java/io/github/skippyall/minions/registration/Instructions.java b/src/main/java/io/github/skippyall/minions/registration/Instructions.java index bb114d8..92b1440 100644 --- a/src/main/java/io/github/skippyall/minions/registration/Instructions.java +++ b/src/main/java/io/github/skippyall/minions/registration/Instructions.java @@ -1,17 +1,17 @@ package io.github.skippyall.minions.registration; -import io.github.skippyall.minions.instruction.inventory.SwapItemExecution; +import io.github.skippyall.minions.minion.program.instruction.inventory.SwapItemExecution; import io.github.skippyall.minions.program.instruction.InstructionExecution; import io.github.skippyall.minions.program.instruction.InstructionType; import io.github.skippyall.minions.Minions; import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack; -import io.github.skippyall.minions.instruction.ActionExecution; -import io.github.skippyall.minions.instruction.MineBlockExecution; -import io.github.skippyall.minions.instruction.move.ContinuousWalkExecution; -import io.github.skippyall.minions.instruction.move.TurnExecution; -import io.github.skippyall.minions.instruction.move.TurnVectorExecution; -import io.github.skippyall.minions.instruction.move.WalkExecution; +import io.github.skippyall.minions.minion.program.instruction.ActionExecution; +import io.github.skippyall.minions.minion.program.instruction.MineBlockExecution; +import io.github.skippyall.minions.minion.program.instruction.move.ContinuousWalkExecution; +import io.github.skippyall.minions.minion.program.instruction.move.TurnExecution; +import io.github.skippyall.minions.minion.program.instruction.move.TurnVectorExecution; +import io.github.skippyall.minions.minion.program.instruction.move.WalkExecution; import io.github.skippyall.minions.program.supplier.Parameter; import net.minecraft.registry.Registry; import net.minecraft.util.Identifier; diff --git a/src/main/java/io/github/skippyall/minions/registration/MinionBlocks.java b/src/main/java/io/github/skippyall/minions/registration/MinionBlocks.java index 72e8cb1..06c2b9b 100644 --- a/src/main/java/io/github/skippyall/minions/registration/MinionBlocks.java +++ b/src/main/java/io/github/skippyall/minions/registration/MinionBlocks.java @@ -2,12 +2,14 @@ package io.github.skippyall.minions.registration; import eu.pb4.polymer.core.api.block.PolymerBlockUtils; import io.github.skippyall.minions.Minions; +import io.github.skippyall.minions.block.input.AnalogInputBlock; import io.github.skippyall.minions.block.miniontrigger.MinionTriggerBlock; import io.github.skippyall.minions.block.miniontrigger.MinionTriggerBlockEntity; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; import net.minecraft.block.AbstractBlock; import net.minecraft.block.entity.BlockEntityType; import net.minecraft.block.piston.PistonBehavior; +import net.minecraft.network.packet.CustomPayload; import net.minecraft.registry.Registries; import net.minecraft.registry.Registry; import net.minecraft.registry.RegistryKey; @@ -35,6 +37,15 @@ public class MinionBlocks { FabricBlockEntityTypeBuilder.create(MinionTriggerBlockEntity::new, MINION_TRIGGER_BLOCK).build() ); + public static final Identifier ANALOG_INPUT_BLOCK_ID = Identifier.of(Minions.MOD_ID, "analog_input"); + public static final AnalogInputBlock ANALOG_INPUT_BLOCK = Registry.register( + Registries.BLOCK, + ANALOG_INPUT_BLOCK_ID, + new AnalogInputBlock(AbstractBlock.Settings.create() + .registryKey(RegistryKey.of(RegistryKeys.BLOCK, ANALOG_INPUT_BLOCK_ID)) + ) + ); + public static void register() { PolymerBlockUtils.registerBlockEntity(MINION_TRIGGER_BE_TYPE); } diff --git a/src/main/java/io/github/skippyall/minions/registration/MinionConfigOptions.java b/src/main/java/io/github/skippyall/minions/registration/MinionConfigOptions.java index c379636..1fc2a4a 100644 --- a/src/main/java/io/github/skippyall/minions/registration/MinionConfigOptions.java +++ b/src/main/java/io/github/skippyall/minions/registration/MinionConfigOptions.java @@ -8,13 +8,13 @@ import net.minecraft.util.Identifier; import static io.github.skippyall.minions.minion.MinionConfig.booleanOption; public class MinionConfigOptions { - public static final MinionConfig.Option showInServerList = register(booleanOption(id("showInServerList"), false)); - public static final MinionConfig.Option showInTabList = register(booleanOption(id("showInTabList"), false)); - public static final MinionConfig.Option sendLoginMessage = register(booleanOption(id("sendLoginMessage"), false)); - public static final MinionConfig.Option sendLogoutMessage = register(booleanOption(id("sendLogoutMessage"), false)); - public static final MinionConfig.Option countForSleeping = register(booleanOption(id("countForSleeping"), false)); - public static final MinionConfig.Option countForPlayerLimit = register(booleanOption(id("countForPlayerLimit"), false)); - public static final MinionConfig.Option spawnAndDespawnMobs = register(booleanOption(id("spawnAndDespawnMobs"), false)); + public static final MinionConfig.Option showInServerList = register(booleanOption(id("show_in_server_list"), false)); + public static final MinionConfig.Option showInTabList = register(booleanOption(id("show_in_tab_list"), false)); + public static final MinionConfig.Option sendLoginMessage = register(booleanOption(id("send_login_message"), false)); + public static final MinionConfig.Option sendLogoutMessage = register(booleanOption(id("send_logout_message"), false)); + public static final MinionConfig.Option countForSleeping = register(booleanOption(id("count_for_sleeping"), false)); + public static final MinionConfig.Option countForPlayerLimit = register(booleanOption(id("count_for_player_limit"), false)); + public static final MinionConfig.Option spawnAndDespawnMobs = register(booleanOption(id("spawn_and_despawn_mobs"), false)); private static MinionConfig.Option register(MinionConfig.Option option) { return Registry.register(MinionRegistries.MINION_CONFIG_OPTIONS, option.key(), option); @@ -23,4 +23,6 @@ public class MinionConfigOptions { private static Identifier id(String name) { return Identifier.of(Minions.MOD_ID, name); } + + public static void register() {} } diff --git a/src/main/java/io/github/skippyall/minions/registration/MinionRegistration.java b/src/main/java/io/github/skippyall/minions/registration/MinionRegistration.java index 62de12b..780748e 100644 --- a/src/main/java/io/github/skippyall/minions/registration/MinionRegistration.java +++ b/src/main/java/io/github/skippyall/minions/registration/MinionRegistration.java @@ -5,10 +5,12 @@ public class MinionRegistration { MinionRegistries.register(); ClipboardTypes.register(); + DocsEntryTypes.register(); GuiDisplayTypes.register(); Instructions.register(); MinionBlocks.register(); MinionComponentTypes.register(); + MinionConfigOptions.register(); MinionItems.register(); MinionListeners.register(); SkinProviders.register(); diff --git a/src/main/java/io/github/skippyall/minions/registration/MinionRegistries.java b/src/main/java/io/github/skippyall/minions/registration/MinionRegistries.java index 7a8d8df..00ff630 100644 --- a/src/main/java/io/github/skippyall/minions/registration/MinionRegistries.java +++ b/src/main/java/io/github/skippyall/minions/registration/MinionRegistries.java @@ -3,6 +3,7 @@ package io.github.skippyall.minions.registration; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import io.github.skippyall.minions.Minions; +import io.github.skippyall.minions.docs.DocsEntry; import io.github.skippyall.minions.docs.ReferenceEntry; import io.github.skippyall.minions.gui.GuiDisplay; import io.github.skippyall.minions.minion.MinionConfig; @@ -35,9 +36,10 @@ public class MinionRegistries { public static final Registry> CLIPBOARD_TYPES = registry("clipboard_type"); public static final Registry SPECIAL_ABILITIES = registry("special_ability"); public static final Registry> MINION_CONFIG_OPTIONS = registry("minion_config_option"); + public static final Registry> DOCS_ENTRY_TYPES = registry("docs_entry_type"); public static final RegistryKey> GUI_DISPLAY = key("gui_display"); - public static final RegistryKey> REFERENCE_ENTRY = key("reference_entry"); + public static final RegistryKey> DOCS_ENTRY = key("docs_entry"); private static Registry registry(String id) { return FabricRegistryBuilder.createSimple(key(id)).attribute(RegistryAttribute.OPTIONAL).buildAndRegister(); @@ -49,6 +51,5 @@ public class MinionRegistries { public static void register() { DynamicRegistries.register(GUI_DISPLAY, GuiDisplay.CODEC); - DynamicRegistries.register(REFERENCE_ENTRY, ReferenceEntry.CODEC); } } diff --git a/src/main/java/io/github/skippyall/minions/registration/ValueSuppliers.java b/src/main/java/io/github/skippyall/minions/registration/ValueSuppliers.java index b7ea886..acd3fa9 100644 --- a/src/main/java/io/github/skippyall/minions/registration/ValueSuppliers.java +++ b/src/main/java/io/github/skippyall/minions/registration/ValueSuppliers.java @@ -1,5 +1,6 @@ package io.github.skippyall.minions.registration; +import io.github.skippyall.minions.minion.program.supplier.AnalogInputSupplier; import io.github.skippyall.minions.program.supplier.FixedValueSupplierType; import io.github.skippyall.minions.program.supplier.ValueSupplierType; import io.github.skippyall.minions.Minions; @@ -9,6 +10,7 @@ import net.minecraft.util.Identifier; public class ValueSuppliers { public static final FixedValueSupplierType FIXED_VALUE_SUPPLIER_TYPE = register("fixed", new FixedValueSupplierType<>()); + public static final AnalogInputSupplier.AnalogInputSupplierType ANALOG_INPUT = register("analog_input", new AnalogInputSupplier.AnalogInputSupplierType()); public static > T register(String id, T type) { return Registry.register(MinionRegistries.VALUE_SUPPLIER_TYPES, Identifier.of(Minions.MOD_ID, id), type); diff --git a/src/main/java/io/github/skippyall/minions/registration/ValueTypes.java b/src/main/java/io/github/skippyall/minions/registration/ValueTypes.java index 8e8fb34..c8344c5 100644 --- a/src/main/java/io/github/skippyall/minions/registration/ValueTypes.java +++ b/src/main/java/io/github/skippyall/minions/registration/ValueTypes.java @@ -1,11 +1,12 @@ package io.github.skippyall.minions.registration; import com.mojang.serialization.Codec; +import io.github.skippyall.minions.program.value.SimpleValueType; import io.github.skippyall.minions.program.value.ValueType; import io.github.skippyall.minions.Minions; import io.github.skippyall.minions.gui.input.ChoiceInput; import io.github.skippyall.minions.gui.input.TextInput; -import io.github.skippyall.minions.instruction.move.TurnDirection; +import io.github.skippyall.minions.minion.program.instruction.move.TurnDirection; import net.minecraft.registry.Registry; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; @@ -79,7 +80,7 @@ public class ValueTypes { return Registry.register( MinionRegistries.VALUE_TYPES, identifier, - new ValueType<>( + new SimpleValueType<>( codec, defaultValue, valueDialogOpener, diff --git a/src/main/resources/data/minions/docs/test.json b/src/main/resources/data/minions/docs/test.json new file mode 100644 index 0000000..ec99fdc --- /dev/null +++ b/src/main/resources/data/minions/docs/test.json @@ -0,0 +1,12 @@ +{ + "type": "minions:reference_entry", + "metadata": { + "title": "Hi" + }, + "object": { + "registry": "minions:value_type", + "value": "minions:float" + }, + "shortDescription": "Whatever floats your goat", + "longDescription": "A decimal value" +} \ No newline at end of file diff --git a/src/main/resources/data/minions/docs/tree.json b/src/main/resources/data/minions/docs/tree.json new file mode 100644 index 0000000..8634a5f --- /dev/null +++ b/src/main/resources/data/minions/docs/tree.json @@ -0,0 +1,3 @@ +[ + "minions:test" +] \ No newline at end of file diff --git a/src/main/resources/data/minions/lang/en_us.json b/src/main/resources/data/minions/lang/en_us.json index 7ac216a..cdfcce3 100644 --- a/src/main/resources/data/minions/lang/en_us.json +++ b/src/main/resources/data/minions/lang/en_us.json @@ -55,6 +55,7 @@ "minions.command.minion.not_present": "This minion does not exist", "minions.reference.instruction.tooltip": "Linked to instruction %s in %s", + "minions.reference.block.tooltip": "Linked to block at %s", "instruction_type.minions.walk": "Walk", "instruction_type.minions.walk.description": "Walk forward a specified amount of blocks", @@ -70,8 +71,11 @@ "value_type.minions.string": "Text", "value_type.minions.turn_direction": "Turn Direction", - "value_supplier_type.minions.fixed": "Value", + "value_supplier_type.minions.fixed": "Fixed Value", "value_supplier_type.minions.fixed.display": "Fixed Value: %s", + "value_supplier_type.minions.analog_input": "Analog Input", + "value_supplier_type.minions.analog_input.display": "Analog Input at %s in %s", + "value_supplier_type.minions.analog_input.config.click_with_reference": "Click here with a position clipboard", "item.minions.attack_module": "Attack Module", "item.minions.interact_module": "Interact Module", diff --git a/src/main/resources/minions.mixins.json b/src/main/resources/minions.mixins.json index 6d38d5b..36b0b78 100644 --- a/src/main/resources/minions.mixins.json +++ b/src/main/resources/minions.mixins.json @@ -9,20 +9,22 @@ "EntityAccessor", "EntityMixin", "EntityViewMixin", - "compat.universal_graves.GraveMixin", "MinecraftServerMixin", "MobEntityMixin", + "PistonMovingBlockEntityMixin", "PlayerListEntryS2CPacket$EntryMixin", "PlayerListMixin", "PlayerMixin", "ServerPlayNetworkHandlerMixin", "SleepManagerMixin", "SpawnHelperMixin", + "TickRateManagerMixin", "antimobcap.ChunkLevelManager$DistanceFromNearestPlayerTrackerMixin", "antimobcap.ChunkLevelManagerMixin", "antimobcap.ChunkPosDistanceLevelPropagatorMixin", "antimobcap.ServerChunkManagerAccessor", - "antimobcap.ServerChunkManagerMixin" + "antimobcap.ServerChunkManagerMixin", + "compat.universal_graves.GraveMixin" ], "client": [], "server": [],