From b53cb798302c575560084d5a4a3939bf43a6289f Mon Sep 17 00:00:00 2001 From: Blake Rain Date: Mon, 27 Nov 2023 07:51:29 +0000 Subject: [PATCH 1/3] chore: refactor function --- src/main/java/net/banutama/utamacraft/block/ModBlocks.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/banutama/utamacraft/block/ModBlocks.java b/src/main/java/net/banutama/utamacraft/block/ModBlocks.java index 5d9e248..77a1678 100644 --- a/src/main/java/net/banutama/utamacraft/block/ModBlocks.java +++ b/src/main/java/net/banutama/utamacraft/block/ModBlocks.java @@ -19,7 +19,7 @@ public class ModBlocks { DeferredRegister.create(ForgeRegistries.BLOCKS, Utamacraft.MOD_ID); public static final RegistryObject ETHEREAL_GLASS = - registerBlock("ethereal_glass", () -> new EtherealGlass()); + registerBlock("ethereal_glass", EtherealGlass::new); private static RegistryObject registerBlock(String name, Supplier block) { RegistryObject registered_block = BLOCKS.register(name, block); -- 2.45.2 From 30a924e5ed4941d9b67edc7d72ab4c305b085085 Mon Sep 17 00:00:00 2001 From: Blake Rain Date: Mon, 27 Nov 2023 08:37:08 +0000 Subject: [PATCH 2/3] feat: add dependency on CC:Tweaked --- build.gradle | 23 ++++++++++++++++++++++- gradle.properties | 5 +++++ src/main/resources/META-INF/mods.toml | 11 ++++++++--- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index c04a4fb..f6687dd 100644 --- a/build.gradle +++ b/build.gradle @@ -38,6 +38,11 @@ minecraft { // Default run configurations. // These can be tweaked, removed, or duplicated as needed. runs { + configureEach { + property 'mixin.env.remapRefMap', 'true' + property 'mixin.env.refMapRemappingFile', "${buildDir}/createSrgToMcp/output.srg" + } + client { workingDirectory project.file('run') @@ -123,6 +128,13 @@ repositories { // flatDir { // dir 'libs' // } + + maven { + url "https://squiddev.cc/maven/" + content { + includeGroup("org.squiddev") + } + } } dependencies { @@ -142,6 +154,14 @@ dependencies { // For more info... // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html // http://www.gradle.org/docs/current/userguide/dependency_management.html + + // Vanilla (i.e. for multi-loader systems) +// compileOnly("cc.tweaked:cc-tweaked-${minecraft_version}-common-api:${cct_version}") + + // Forge Gradle +// compileOnly("cc.tweaked:cc-tweaked-${minecraft_version}-core-api:${cct_version}") +// compileOnly(fg.deobf("cc.tweaked:cc-tweaked-${minecraft_version}-forge-api:${cct_version}")) + implementation fg.deobf("org.squiddev:cc-tweaked-${minecraft_version}:${cct_version}") } // This task will expand all declared properties from Gradle (gradle.properties) in the specified resource targets. @@ -158,7 +178,8 @@ tasks.named('processResources', ProcessResources).configure { mod_license: mod_license, mod_version: mod_version, mod_authors: mod_authors, - mod_description: mod_description + mod_description: mod_description, + cct_version: cct_version ] inputs.properties replaceProperties diff --git a/gradle.properties b/gradle.properties index 093f922..e5cfdac 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,6 +35,11 @@ mapping_channel=official # This must match the format required by the mapping channel. mapping_version=1.19.2 +## Dependency Properties + +# The version of CC:Tweaked we're building against +cct_version=1.101.3 + ## Mod Properties # The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index f88b91c..49f9085 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -46,7 +46,6 @@ authors="${mod_authors}" #optional # The description text for the mod (multi line!) (#mandatory) description='''${mod_description}''' -# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. [[dependencies.${mod_id}]] #optional # the modid of the dependency modId="forge" #mandatory @@ -58,11 +57,17 @@ description='''${mod_description}''' ordering="NONE" # Side this dependency is applied on - BOTH, CLIENT or SERVER side="BOTH" -# Here's another dependency + [[dependencies.${mod_id}]] modId="minecraft" mandatory=true -# This version range declares a minimum of the current minecraft version up to but not including the next major version versionRange="${minecraft_version_range}" ordering="NONE" side="BOTH" + +[[dependencies.${mod_id}]] +modId = "computercraft" +mandatory = true +versionRange = "[${cct_version},)" +ordering = "NONE" +side = "BOTH" \ No newline at end of file -- 2.45.2 From 55c5016610d2c208fe52eed78426a4b5a822a705 Mon Sep 17 00:00:00 2001 From: Blake Rain Date: Mon, 27 Nov 2023 16:05:48 +0000 Subject: [PATCH 3/3] feat: add player-use peripheral (closes #8) --- .../banutama/utamacraft/CCRegistration.java | 35 ++++ .../net/banutama/utamacraft/Utamacraft.java | 3 +- .../utamacraft/client/ClientRegistry.java | 18 ++ .../computercraft/BoundMethod.java | 49 +++++ .../computercraft/PeripheralProvider.java | 17 ++ .../peripheral/BasePeripheral.java | 72 +++++++ .../peripheral/BasePeripheralOwner.java | 4 + .../BlockEntityPeripheralOwner.java | 5 + .../peripheral/PlayerPeripheral.java | 36 ++++ .../peripheral/PocketPeripheralOwner.java | 4 + .../peripheral/TurtlePeripheralOwner.java | 18 ++ .../turtles/PeripheralTurtleUpgrade.java | 27 +++ .../turtles/TurtlePlayerCache.java | 52 +++++ .../turtles/TurtlePlayerUpgrade.java | 24 +++ .../banutama/utamacraft/item/BaseItem.java | 10 + .../utamacraft/item/ModCreativeModeTab.java | 3 +- .../banutama/utamacraft/item/ModItems.java | 7 +- .../utamacraft/item/PlayerPeripheralItem.java | 30 +++ .../utamacraft/util/FakeGameProfile.java | 16 ++ .../utamacraft/util/SimpleFakePlayer.java | 192 ++++++++++++++++++ .../assets/utamacraft/lang/en_us.json | 6 +- .../models/item/player_peripheral.json | 6 + .../textures/item/player_peripheral.png | Bin 0 -> 2059 bytes .../turtle_upgrades/player_turtle.json | 4 + .../utamacraft/recipes/player_peripheral.json | 23 +++ textures/player_peripheral.afdesign | Bin 0 -> 32402 bytes 26 files changed, 654 insertions(+), 7 deletions(-) create mode 100644 src/main/java/net/banutama/utamacraft/CCRegistration.java create mode 100644 src/main/java/net/banutama/utamacraft/client/ClientRegistry.java create mode 100644 src/main/java/net/banutama/utamacraft/integrations/computercraft/BoundMethod.java create mode 100644 src/main/java/net/banutama/utamacraft/integrations/computercraft/PeripheralProvider.java create mode 100644 src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/BasePeripheral.java create mode 100644 src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/BasePeripheralOwner.java create mode 100644 src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/BlockEntityPeripheralOwner.java create mode 100644 src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/PlayerPeripheral.java create mode 100644 src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/PocketPeripheralOwner.java create mode 100644 src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/TurtlePeripheralOwner.java create mode 100644 src/main/java/net/banutama/utamacraft/integrations/computercraft/turtles/PeripheralTurtleUpgrade.java create mode 100644 src/main/java/net/banutama/utamacraft/integrations/computercraft/turtles/TurtlePlayerCache.java create mode 100644 src/main/java/net/banutama/utamacraft/integrations/computercraft/turtles/TurtlePlayerUpgrade.java create mode 100644 src/main/java/net/banutama/utamacraft/item/BaseItem.java create mode 100644 src/main/java/net/banutama/utamacraft/item/PlayerPeripheralItem.java create mode 100644 src/main/java/net/banutama/utamacraft/util/FakeGameProfile.java create mode 100644 src/main/java/net/banutama/utamacraft/util/SimpleFakePlayer.java create mode 100644 src/main/resources/assets/utamacraft/models/item/player_peripheral.json create mode 100644 src/main/resources/assets/utamacraft/textures/item/player_peripheral.png create mode 100644 src/main/resources/data/utamacraft/computercraft/turtle_upgrades/player_turtle.json create mode 100644 src/main/resources/data/utamacraft/recipes/player_peripheral.json create mode 100644 textures/player_peripheral.afdesign diff --git a/src/main/java/net/banutama/utamacraft/CCRegistration.java b/src/main/java/net/banutama/utamacraft/CCRegistration.java new file mode 100644 index 0000000..e69fec8 --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/CCRegistration.java @@ -0,0 +1,35 @@ +package net.banutama.utamacraft; + +import com.mojang.logging.LogUtils; +import dan200.computercraft.api.ForgeComputerCraftAPI; +import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser; +import net.banutama.utamacraft.integrations.computercraft.PeripheralProvider; +import net.banutama.utamacraft.integrations.computercraft.turtles.TurtlePlayerUpgrade; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.RegistryObject; +import org.slf4j.Logger; + +/** + * CC:Tweaked registration + */ +public class CCRegistration { + + public static final DeferredRegister> TURTLE_SERIALIZERS = + DeferredRegister.create(TurtleUpgradeSerialiser.REGISTRY_ID, Utamacraft.MOD_ID); + + public static final RegistryObject> PLAYER_TURTLE = + TURTLE_SERIALIZERS.register(ID.PLAYER_TURTLE.getPath(), () -> TurtleUpgradeSerialiser.simpleWithCustomItem(TurtlePlayerUpgrade::new)); + + public static PeripheralProvider peripheralProvider = new PeripheralProvider(); + + public static void register(IEventBus bus) { + TURTLE_SERIALIZERS.register(bus); + ForgeComputerCraftAPI.registerPeripheralProvider(peripheralProvider); + } + + public static class ID { + public static final ResourceLocation PLAYER_TURTLE = new ResourceLocation(Utamacraft.MOD_ID, "player_turtle"); + } +} diff --git a/src/main/java/net/banutama/utamacraft/Utamacraft.java b/src/main/java/net/banutama/utamacraft/Utamacraft.java index 4e2bdae..3d366fb 100644 --- a/src/main/java/net/banutama/utamacraft/Utamacraft.java +++ b/src/main/java/net/banutama/utamacraft/Utamacraft.java @@ -28,6 +28,7 @@ public class Utamacraft { ModItems.register(bus); ModBlocks.register(bus); + CCRegistration.register(bus); bus.addListener(this::commonSetup); MinecraftForge.EVENT_BUS.register(this); @@ -37,7 +38,7 @@ public class Utamacraft { } @Mod.EventBusSubscriber(modid = MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) - public static class ClientModEvents { + public static class ModEvents { @SubscribeEvent public static void onClientSetup(FMLClientSetupEvent event) { } diff --git a/src/main/java/net/banutama/utamacraft/client/ClientRegistry.java b/src/main/java/net/banutama/utamacraft/client/ClientRegistry.java new file mode 100644 index 0000000..044122c --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/client/ClientRegistry.java @@ -0,0 +1,18 @@ +package net.banutama.utamacraft.client; + +import dan200.computercraft.api.client.ComputerCraftAPIClient; +import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; +import net.banutama.utamacraft.CCRegistration; +import net.banutama.utamacraft.Utamacraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; + +@Mod.EventBusSubscriber(modid = Utamacraft.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) +public class ClientRegistry { + @SubscribeEvent + public static void onClientSetup(FMLClientSetupEvent event) { + ComputerCraftAPIClient.registerTurtleUpgradeModeller(CCRegistration.PLAYER_TURTLE.get(), TurtleUpgradeModeller.flatItem()); + } +} diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/BoundMethod.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/BoundMethod.java new file mode 100644 index 0000000..f788502 --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/BoundMethod.java @@ -0,0 +1,49 @@ +package net.banutama.utamacraft.integrations.computercraft; + +import dan200.computercraft.api.lua.*; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.core.asm.NamedMethod; +import dan200.computercraft.core.asm.PeripheralMethod; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class BoundMethod { + private final Object target; + private final String name; + private final PeripheralMethod method; + + public BoundMethod(@NotNull Object target, @NotNull NamedMethod method) { + this.target = target; + this.name = method.getName(); + this.method = method.getMethod(); + } + + @NotNull + public String getName() { + return name; + } + + @NotNull + public MethodResult apply(@NotNull IComputerAccess computer, @NotNull ILuaContext context, @NotNull IArguments arguments) throws LuaException { + return method.apply(target, context, computer, arguments); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof BoundMethod boundMethod)) { + return false; + } + + return target.equals(boundMethod.target) && name.equals(boundMethod.name) && method.equals(boundMethod.method); + } + + @Override + public int hashCode() { + return Objects.hash(target, name, method); + } +} diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/PeripheralProvider.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/PeripheralProvider.java new file mode 100644 index 0000000..f7fe02a --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/PeripheralProvider.java @@ -0,0 +1,17 @@ +package net.banutama.utamacraft.integrations.computercraft; + +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IPeripheralProvider; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraftforge.common.util.LazyOptional; +import org.jetbrains.annotations.NotNull; + +public class PeripheralProvider implements IPeripheralProvider { + @NotNull + @Override + public LazyOptional getPeripheral(@NotNull Level world, @NotNull BlockPos pos, @NotNull Direction side) { + return LazyOptional.empty(); + } +} diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/BasePeripheral.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/BasePeripheral.java new file mode 100644 index 0000000..2d5b755 --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/BasePeripheral.java @@ -0,0 +1,72 @@ +package net.banutama.utamacraft.integrations.computercraft.peripheral; + +import dan200.computercraft.api.lua.IArguments; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.lua.MethodResult; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IDynamicPeripheral; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.core.asm.PeripheralMethod; +import net.banutama.utamacraft.integrations.computercraft.BoundMethod; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +public abstract class BasePeripheral implements IPeripheral, IDynamicPeripheral { + protected final String type; + protected final BasePeripheralOwner owner; + protected final List methods; + protected final Set computers = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + protected BasePeripheral(String type, BasePeripheralOwner owner) { + this.type = type; + this.owner = owner; + this.methods = PeripheralMethod.GENERATOR.getMethods(this.getClass()).stream().map(named -> new BoundMethod(this, named)).collect(Collectors.toList()); + } + + @NotNull + @Override + public String @NotNull [] getMethodNames() { + return methods.stream().map(BoundMethod::getName).toArray(String[]::new); + } + + @NotNull + @Override + public MethodResult callMethod(@NotNull IComputerAccess computer, @NotNull ILuaContext context, int method, @NotNull IArguments arguments) throws LuaException { + return methods.get(method).apply(computer, context, arguments); + } + + @NotNull + @Override + public String getType() { + return type; + } + + @Override + public void attach(@NotNull IComputerAccess computer) { + this.computers.add(computer); + } + + @Override + public void detach(@NotNull IComputerAccess computer) { + this.computers.remove(computer); + } + + @Nullable + @Override + public Object getTarget() { + return owner; + } + + @Override + public boolean equals(@Nullable IPeripheral other) { + return Objects.equals(this, other); + } +} diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/BasePeripheralOwner.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/BasePeripheralOwner.java new file mode 100644 index 0000000..421696d --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/BasePeripheralOwner.java @@ -0,0 +1,4 @@ +package net.banutama.utamacraft.integrations.computercraft.peripheral; + +public class BasePeripheralOwner { +} diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/BlockEntityPeripheralOwner.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/BlockEntityPeripheralOwner.java new file mode 100644 index 0000000..57de81b --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/BlockEntityPeripheralOwner.java @@ -0,0 +1,5 @@ +package net.banutama.utamacraft.integrations.computercraft.peripheral; + +public class BlockEntityPeripheralOwner extends BasePeripheralOwner { + // TODO: After we add PeripheralBlockEntity we want to add it to a field and constructor here. +} diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/PlayerPeripheral.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/PlayerPeripheral.java new file mode 100644 index 0000000..2dd9876 --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/PlayerPeripheral.java @@ -0,0 +1,36 @@ +package net.banutama.utamacraft.integrations.computercraft.peripheral; + +import com.mojang.logging.LogUtils; +import dan200.computercraft.api.lua.LuaFunction; +import dan200.computercraft.api.lua.MethodResult; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.TurtleSide; +import net.banutama.utamacraft.integrations.computercraft.turtles.TurtlePlayerCache; +import net.minecraft.world.InteractionResult; +import org.slf4j.Logger; + +public class PlayerPeripheral extends BasePeripheral { + public static final String PERIPHERAL_TYPE = "player"; + private static final Logger LOGGER = LogUtils.getLogger(); + + protected PlayerPeripheral(BasePeripheralOwner owner) { + super(PERIPHERAL_TYPE, owner); + } + + public PlayerPeripheral(ITurtleAccess turtle, TurtleSide side) { + this(new TurtlePeripheralOwner(turtle, side)); + } + + @LuaFunction(mainThread = true) + public final MethodResult use() { + if (!(owner instanceof TurtlePeripheralOwner turtleOwner)) { + LOGGER.info("Owner of this PlayerPeripheral is not a TurtlePeripheralOwner"); + return MethodResult.of(); + } + + InteractionResult result = TurtlePlayerCache.withPlayer(turtleOwner.getTurtle(), + player -> player.use(5, true, false, null)); + + return MethodResult.of(result.name()); + } +} diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/PocketPeripheralOwner.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/PocketPeripheralOwner.java new file mode 100644 index 0000000..850c852 --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/PocketPeripheralOwner.java @@ -0,0 +1,4 @@ +package net.banutama.utamacraft.integrations.computercraft.peripheral; + +public class PocketPeripheralOwner extends BasePeripheralOwner { +} diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/TurtlePeripheralOwner.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/TurtlePeripheralOwner.java new file mode 100644 index 0000000..c46b29a --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/TurtlePeripheralOwner.java @@ -0,0 +1,18 @@ +package net.banutama.utamacraft.integrations.computercraft.peripheral; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.TurtleSide; + +public class TurtlePeripheralOwner extends BasePeripheralOwner { + private final ITurtleAccess turtle; + private final TurtleSide side; + + public TurtlePeripheralOwner(ITurtleAccess turtle, TurtleSide side) { + this.turtle = turtle; + this.side = side; + } + + public ITurtleAccess getTurtle() { + return turtle; + } +} diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/turtles/PeripheralTurtleUpgrade.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/turtles/PeripheralTurtleUpgrade.java new file mode 100644 index 0000000..871531d --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/turtles/PeripheralTurtleUpgrade.java @@ -0,0 +1,27 @@ +package net.banutama.utamacraft.integrations.computercraft.turtles; + +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.turtle.AbstractTurtleUpgrade; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.api.turtle.TurtleUpgradeType; +import net.banutama.utamacraft.integrations.computercraft.peripheral.BasePeripheral; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class PeripheralTurtleUpgrade extends AbstractTurtleUpgrade { + + protected PeripheralTurtleUpgrade(ResourceLocation id, ItemStack item) { + super(id, TurtleUpgradeType.PERIPHERAL, String.format("turtle.utamacraft.%s", id.getPath()), item); + } + + protected abstract T buildPeripheral(@NotNull ITurtleAccess turtle, @NotNull TurtleSide side); + + @Nullable + @Override + public IPeripheral createPeripheral(@NotNull ITurtleAccess turtle, @NotNull TurtleSide side) { + return buildPeripheral(turtle, side); + } +} diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/turtles/TurtlePlayerCache.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/turtles/TurtlePlayerCache.java new file mode 100644 index 0000000..cb86aae --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/turtles/TurtlePlayerCache.java @@ -0,0 +1,52 @@ +package net.banutama.utamacraft.integrations.computercraft.turtles; + +import com.mojang.authlib.GameProfile; +import dan200.computercraft.api.turtle.ITurtleAccess; +import net.banutama.utamacraft.util.FakeGameProfile; +import net.banutama.utamacraft.util.SimpleFakePlayer; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Vec3i; +import net.minecraft.server.level.ServerLevel; + +import java.util.WeakHashMap; +import java.util.function.Function; + +public class TurtlePlayerCache { + private static final WeakHashMap PLAYERS = new WeakHashMap<>(); + + private static SimpleFakePlayer getPlayerFor(ITurtleAccess turtle, GameProfile profile) { + SimpleFakePlayer player = PLAYERS.get(turtle); + if (player == null) { + player = new SimpleFakePlayer((ServerLevel)turtle.getLevel(), profile); + PLAYERS.put(turtle, player); + } + + return player; + } + + public static T withPlayer(ITurtleAccess turtle, Function callback) { + GameProfile profile = turtle.getOwningPlayer(); + if (profile == null) { + profile = new FakeGameProfile(); + } + + SimpleFakePlayer player = getPlayerFor(turtle, profile); + BlockPos position = turtle.getPosition(); + Direction direction = turtle.getDirection(); + + float pitch = direction == Direction.UP ? -90.0f : direction == Direction.DOWN ? 90.0f : 0.0f; + float yaw = direction == Direction.SOUTH ? 0.0f : direction == Direction.WEST ? 90.0f : direction == Direction.NORTH ? 180.0f : -90.0f; + + Vec3i normal = direction.getNormal(); + Direction.Axis axis = direction.getAxis(); + Direction.AxisDirection axis_dir = direction.getAxisDirection(); + + double x = (axis == Direction.Axis.X && axis_dir == Direction.AxisDirection.NEGATIVE) ? -0.5 : (0.5 * normal.getX() / 1.9); + double y = 0.5 + normal.getY() / 1.9; + double z = (axis == Direction.Axis.Z && axis_dir == Direction.AxisDirection.NEGATIVE) ? -0.5 : (0.5 + normal.getZ() / 1.9); + + player.moveTo(position.getX() + x, position.getY() + y, position.getZ() + z, yaw, pitch); + return callback.apply(player); + } +} diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/turtles/TurtlePlayerUpgrade.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/turtles/TurtlePlayerUpgrade.java new file mode 100644 index 0000000..d61368a --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/turtles/TurtlePlayerUpgrade.java @@ -0,0 +1,24 @@ +package net.banutama.utamacraft.integrations.computercraft.turtles; + +import com.mojang.logging.LogUtils; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.TurtleSide; +import net.banutama.utamacraft.integrations.computercraft.peripheral.PlayerPeripheral; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; + +public class TurtlePlayerUpgrade extends PeripheralTurtleUpgrade { + private static final Logger LOGGER = LogUtils.getLogger(); + + public TurtlePlayerUpgrade(ResourceLocation id, ItemStack item) { + super(id, item); + } + + @Override + protected PlayerPeripheral buildPeripheral(@NotNull ITurtleAccess turtle, @NotNull TurtleSide side) { + LOGGER.info("Building PlayerPeripheral for TurtlePlayerUpgrade"); + return new PlayerPeripheral(turtle, side); + } +} diff --git a/src/main/java/net/banutama/utamacraft/item/BaseItem.java b/src/main/java/net/banutama/utamacraft/item/BaseItem.java new file mode 100644 index 0000000..118a4a1 --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/item/BaseItem.java @@ -0,0 +1,10 @@ +package net.banutama.utamacraft.item; + +import net.minecraft.world.item.Item; +import org.jetbrains.annotations.NotNull; + +public abstract class BaseItem extends Item { + public BaseItem(@NotNull Properties properties) { + super(properties.tab(ModCreativeModeTab.TAB)); + } +} diff --git a/src/main/java/net/banutama/utamacraft/item/ModCreativeModeTab.java b/src/main/java/net/banutama/utamacraft/item/ModCreativeModeTab.java index e3cc5b4..b27f1db 100644 --- a/src/main/java/net/banutama/utamacraft/item/ModCreativeModeTab.java +++ b/src/main/java/net/banutama/utamacraft/item/ModCreativeModeTab.java @@ -3,7 +3,6 @@ package net.banutama.utamacraft.item; import net.banutama.utamacraft.Utamacraft; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.ItemStack; -import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import org.jetbrains.annotations.NotNull; @@ -11,7 +10,7 @@ import org.jetbrains.annotations.NotNull; public class ModCreativeModeTab { public static final CreativeModeTab TAB = new CreativeModeTab("utamacraft_tab") { @Override - public ItemStack makeIcon() { + public @NotNull ItemStack makeIcon() { return new ItemStack(ModItems.LOGO.get()); } }; diff --git a/src/main/java/net/banutama/utamacraft/item/ModItems.java b/src/main/java/net/banutama/utamacraft/item/ModItems.java index 3f45ec1..110028c 100644 --- a/src/main/java/net/banutama/utamacraft/item/ModItems.java +++ b/src/main/java/net/banutama/utamacraft/item/ModItems.java @@ -11,8 +11,11 @@ public class ModItems { public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, Utamacraft.MOD_ID); - public static final RegistryObject LOGO = ITEMS.register("utamacraft_logo", - () -> new Item(new Item.Properties())); + public static final RegistryObject LOGO = + ITEMS.register("utamacraft_logo", () -> new Item(new Item.Properties())); + + public static final RegistryObject PLAYER_PERIPHERAL = + ITEMS.register("player_peripheral", PlayerPeripheralItem::new); public static void register(IEventBus eventBus) { ITEMS.register(eventBus); diff --git a/src/main/java/net/banutama/utamacraft/item/PlayerPeripheralItem.java b/src/main/java/net/banutama/utamacraft/item/PlayerPeripheralItem.java new file mode 100644 index 0000000..5ba14cb --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/item/PlayerPeripheralItem.java @@ -0,0 +1,30 @@ +package net.banutama.utamacraft.item; + +import dan200.computercraft.shared.Registry; +import net.banutama.utamacraft.CCRegistration; +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class PlayerPeripheralItem extends BaseItem { + public PlayerPeripheralItem() { + super(new Item.Properties().stacksTo(16)); + } + + @Override + public void fillItemCategory(@NotNull CreativeModeTab group, @NotNull NonNullList items) { + super.fillItemCategory(group, items); + + if (allowedIn(group)) { + ItemStack turtle_stack = new ItemStack(Registry.ModItems.TURTLE_NORMAL.get()); + turtle_stack.getOrCreateTag().putString("RightUpgrade", CCRegistration.ID.PLAYER_TURTLE.toString()); + items.add(turtle_stack); + + ItemStack advanced_turtle_stack = new ItemStack(Registry.ModItems.TURTLE_ADVANCED.get()); + advanced_turtle_stack.getOrCreateTag().putString("RightUpgrade", CCRegistration.ID.PLAYER_TURTLE.toString()); + items.add(advanced_turtle_stack); + } + } +} diff --git a/src/main/java/net/banutama/utamacraft/util/FakeGameProfile.java b/src/main/java/net/banutama/utamacraft/util/FakeGameProfile.java new file mode 100644 index 0000000..73c2f67 --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/util/FakeGameProfile.java @@ -0,0 +1,16 @@ +package net.banutama.utamacraft.util; + +import com.mojang.authlib.GameProfile; +import net.banutama.utamacraft.Utamacraft; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +public class FakeGameProfile extends GameProfile { + private static final UUID GAME_PROFILE_UUID = UUID.nameUUIDFromBytes("Utamacraft".getBytes(StandardCharsets.UTF_8)); + private static final String GAME_PROFILE_NAME = String.format("[%s]", Utamacraft.MOD_ID); + + public FakeGameProfile() { + super(GAME_PROFILE_UUID, GAME_PROFILE_NAME); + } +} diff --git a/src/main/java/net/banutama/utamacraft/util/SimpleFakePlayer.java b/src/main/java/net/banutama/utamacraft/util/SimpleFakePlayer.java new file mode 100644 index 0000000..e80329f --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/util/SimpleFakePlayer.java @@ -0,0 +1,192 @@ +package net.banutama.utamacraft.util; + +import com.mojang.authlib.GameProfile; +import net.banutama.utamacraft.Utamacraft; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.phys.*; +import net.minecraftforge.common.util.FakePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Predicate; + +public class SimpleFakePlayer extends FakePlayer { + private static WeakReference INSTANCE; + + public SimpleFakePlayer(ServerLevel world, GameProfile profile) { + super(world, profile); + } + + public SimpleFakePlayer(ServerLevel world) { + this(world, new FakeGameProfile()); + } + + public static R withFakePlayer(ServerLevel world, Function consumer) { + SimpleFakePlayer player = INSTANCE == null ? null : INSTANCE.get(); + if (player == null) { + player = new SimpleFakePlayer(world); + INSTANCE = new WeakReference<>(player); + } + + return consumer.apply(player); + } + + public HitResult findHit(int range, boolean skipEntity, boolean skipBlock, Predicate entityFilter) { + Vec3 origin = new Vec3(getX(), getY(), getZ()); + Vec3 look = getLookAngle(); + Vec3 target = new Vec3(origin.x + look.x * range, origin.y + look.y * range, origin.z + look.z * range); + ClipContext traceContext = new ClipContext(origin, target, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this); + Vec3 directionVec = traceContext.getFrom().subtract(traceContext.getTo()); + Direction traceDirection = Direction.getNearest(directionVec.x, directionVec.y, directionVec.z); + + HitResult blockHit = null; + if (skipBlock) { + Vec3 to = traceContext.getTo(); + blockHit = BlockHitResult.miss(to, traceDirection, new BlockPos(to)); + } else { + blockHit = BlockGetter.traverseBlocks(traceContext.getFrom(), traceContext.getTo(), traceContext, (clipContext, pos) -> { + if (level.isEmptyBlock(pos)) { + return null; + } + + return new BlockHitResult(new Vec3(pos.getX(), pos.getY(), pos.getZ()), traceDirection, pos, false); + }, clipContext -> BlockHitResult.miss(clipContext.getTo(), traceDirection, new BlockPos(clipContext.getTo()))); + } + + if (skipEntity) { + return blockHit; + } + + List entities = + level.getEntities( + this, + this.getBoundingBox() + .expandTowards(look.x * range, look.y * range, look.z * range) + .inflate(1.0, 1.0, 1.0), + EntitySelector.NO_SPECTATORS); + + LivingEntity closestEntity = null; + Vec3 closestVec = null; + double closestDistance = 0.0; + + for (Entity entityHit : entities) { + if (!(entityHit instanceof LivingEntity living)) { + continue; + } + + if (entityFilter != null && !entityFilter.test(entityHit)) { + continue; + } + + AABB box = entityHit.getBoundingBox().inflate(entityHit.getPickRadius() + 0.5); + Optional clipResult = box.clip(origin, target); + + if (box.contains(origin)) { + if (closestDistance >= 0.0) { + closestEntity = living; + closestVec = clipResult.orElse(origin); + closestDistance = 0.0; + } + } else if (clipResult.isPresent()) { + Vec3 clipVec = clipResult.get(); + double distance = origin.distanceTo(clipVec); + + if (distance < closestDistance || closestDistance == 0.0) { + if (entityHit == entityHit.getRootVehicle()) { + if (closestDistance == 0.0) { + closestEntity = living; + closestVec = clipVec; + } + } else { + closestEntity = living; + closestVec = clipVec; + closestDistance = distance; + } + } + } + } + + if (closestEntity != null && + closestDistance <= range && + (blockHit.getType() == HitResult.Type.MISS || + distanceToSqr(blockHit.getLocation()) > closestDistance * closestDistance)) { + return new EntityHitResult(closestEntity, closestVec); + } else { + return blockHit; + } + } + + public InteractionResult use(int range, boolean skipEntity, boolean skipBlock, Predicate entityFilter) { + HitResult hit = findHit(range, skipEntity, skipBlock, entityFilter); + if (hit instanceof BlockHitResult blockHit) { + InteractionResult res = + gameMode.useItemOn(this, level, getMainHandItem(), InteractionHand.MAIN_HAND, blockHit); + if (res.consumesAction()) { + return res; + } + + return gameMode.useItem(this, level, getMainHandItem(), InteractionHand.MAIN_HAND); + } else if (hit instanceof EntityHitResult) { + // TODO: Interact with an entity? + return InteractionResult.FAIL; + } else { + return InteractionResult.FAIL; + } + } + + // + // Overrides to compensate for FakePlayer + // + + @Override + public boolean canBeAffected(@NotNull MobEffectInstance effect) { + return false; + } + + @Override + public boolean canHarmPlayer(Player player) { + return true; + } + + @Override + public boolean startRiding(@NotNull Entity entity, boolean b) { + return false; + } + + @Override + public @NotNull OptionalInt openMenu(@Nullable MenuProvider provider) { + return OptionalInt.empty(); + } + + @Override + public float getStandingEyeHeight(@NotNull Pose pose, @NotNull EntityDimensions dimensions) { + return 0.0f; + } + + @Override + public double getEyeY() { + return getY() + 0.2; + } + + @Override + public float getAttackStrengthScale(float f) { + return 1.0f; + } +} diff --git a/src/main/resources/assets/utamacraft/lang/en_us.json b/src/main/resources/assets/utamacraft/lang/en_us.json index f5eeb1b..445fe9a 100644 --- a/src/main/resources/assets/utamacraft/lang/en_us.json +++ b/src/main/resources/assets/utamacraft/lang/en_us.json @@ -1,6 +1,8 @@ { + "block.utamacraft.ethereal_glass": "Ethereal Glass", + "item.utamacraft.player_peripheral": "Player peripheral", "item.utamacraft.utamacraft_logo": "Utamacraft", "itemGroup.utamacraft_tab": "Utamacraft", - "block.utamacraft.ethereal_glass": "Ethereal Glass", - "tooltip.utamacraft.ethereal_glass": "Glass that is not solid to players" + "tooltip.utamacraft.ethereal_glass": "Glass that is not solid to players", + "turtle.utamacraft.player_turtle": "Player" } diff --git a/src/main/resources/assets/utamacraft/models/item/player_peripheral.json b/src/main/resources/assets/utamacraft/models/item/player_peripheral.json new file mode 100644 index 0000000..94003da --- /dev/null +++ b/src/main/resources/assets/utamacraft/models/item/player_peripheral.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "utamacraft:item/player_peripheral" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/utamacraft/textures/item/player_peripheral.png b/src/main/resources/assets/utamacraft/textures/item/player_peripheral.png new file mode 100644 index 0000000000000000000000000000000000000000..4c7615f53dde53637640fe705c5e471cd46ad6ef GIT binary patch literal 2059 zcmah~eM}Q)7(ZroSVyKqHa0iC9Lr#0d+kBdp1nG35m3qsVgPaC(Y|d@uD#pcm9{E= zfQZJg;U7+&GjYq@qT*MUuuL|Q`7vA;4a6mGsN2k(n^HF$BRYk>cZEVlw@cpZz3=aN ze$Vr~&qp`d3Ud=wuc#0NNyyK$6vN-~@`@h=-&4Z15w@d)@mlc-SmsQ&h4s4c5 zWekvzzyl;24R~0uNCqfO;U(d>e2inLA|h2&Seey^nmGZWdW~MA#Y`&HC^(#Cv1QJH zGTc#^TatJZ$Nhf4#-E|#1Q)I|7!0_UzzISPCDdZjE71Y9S4@#9A{-VV+69J}7|x5z zoOBiElPC;>I6CN=hmVqb#ewMIJ>UVF$8{PlK3tf0a}p=Ixu>W^;Sq`sd(;T;6IjK6 zhaCqj@BptQLU~<`ArN4OVvcc3Mn=#}jPohtGBQFO=EiD`Iut`c7`4X}8TxcB ze4@fu&cQf?PZHK=kXrr1AwgJ44qBrBM>LuiQWlK30t@&*{TR|Qb1Wx7m!Rhvisd0F z-)y!CoReW8CKl(;M)PybSvo^jmYUG$6j?c2q>Tvxw#>r7REw|@DNL937+C%kP_aE9 zHXt+*A(Zn;=6h%tC}kXyJIXb5UI4tV#{oGjtWR!(1zrZ`kx=X_I%Pu`g-s{$VP?ul zKyqWDMu(l07>NbSF4Ab(tQ6Trwk#ue7s=9I7lj4X4&bDHtc00(mJR};k_WIU-9VsO zBQCck%M4H{yFo4^9%IO;R4tjuh!Q6RhuNMF-i@`bbT^7BHA~WVIcy5!1229p`p^1A_KjIE!6(!_^HGU%BR zAK`l>@L}@_>P61r+=BxYk4{jyiOyi)g>@^y37aQf1_%;Y$G{34QF#cG*5qrtnqFU& ze0J6(?1Oz@*cP=ii+1i<*qv4}Tiv|#&Q0g?p2i=q_tdt0-aWM?eQtNJ?__0Lv(P-T zXMj8MJyj_H>DR&fnh?Hhvg>^+;~#(leh;SzVCzX=CccbKMW?I(s+O7w)?8 zP11q{&$NlE;x1F@NcrDq>=*afhAPErXA_cGYx~Fh_x-*$wV}S+@cplzUuyJ8@4RSA z3U&Qf*WR*ya(Z~<=?-F5`N7=wxFap9^p2@p798ExnbCen=-w$Ue)QGWyDzwmGiRn8 zt!N!ruTAVN+EKL|F^ze-=~Cmf_@iSV&FO1PVGv!bE3Wb3?w z^^@Ok+PCCxKUcZ=)O#(9F8|TEF12gL%{6D#X-U>__(JBqT-&|)moCli`!nvj6B`#F zbcbqrPunGsv$3b`{u`3fT|_-dxwzq{#IXTt&Gw-C_`2(-h4U-#8NP1(_RAF)lWv8w z5AXJ?Ye&(n>)MsZm3!fTgTL90)>;#Ivhw=eX|PicYvQGd^sz5Q21uRS=tW9b9s z^u*T2zGfBh4xXw^7RrgrR~wy89nwi-cN>2&n$T`B}U OK=N}6Er(_=srwg>^U^Z_ literal 0 HcmV?d00001 diff --git a/src/main/resources/data/utamacraft/computercraft/turtle_upgrades/player_turtle.json b/src/main/resources/data/utamacraft/computercraft/turtle_upgrades/player_turtle.json new file mode 100644 index 0000000..63ce426 --- /dev/null +++ b/src/main/resources/data/utamacraft/computercraft/turtle_upgrades/player_turtle.json @@ -0,0 +1,4 @@ +{ + "type": "utamacraft:player_turtle", + "item": "utamacraft:player_peripheral" +} \ No newline at end of file diff --git a/src/main/resources/data/utamacraft/recipes/player_peripheral.json b/src/main/resources/data/utamacraft/recipes/player_peripheral.json new file mode 100644 index 0000000..d9931a0 --- /dev/null +++ b/src/main/resources/data/utamacraft/recipes/player_peripheral.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "GRG", + "R.R", + "GRG" + ], + "key": { + "G": { + "item": "minecraft:gold_ingot" + }, + "R": { + "item": "minecraft:redstone" + }, + ".": { + "item": "minecraft:ender_eye" + } + }, + "result": { + "item": "utamacraft:player_peripheral", + "count": 1 + } +} diff --git a/textures/player_peripheral.afdesign b/textures/player_peripheral.afdesign new file mode 100644 index 0000000000000000000000000000000000000000..a3450b6dae192503d6d3af01505b2315e26e4be2 GIT binary patch literal 32402 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 H*oFTe3@x9k literal 0 HcmV?d00001 -- 2.45.2