From 135131b448b556360eba9a962379f225b914446c Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Mon, 15 Jun 2020 11:15:20 -0400 Subject: [PATCH] Chest Boats; More Features In The Difficulty Stages; The Diviner; Advancements --- README.md | 10 +- .../java/com/thebrokenrail/twine/Twine.java | 16 ++ .../twine/advancement/ChestBoatCriterion.java | 28 +++ .../advancement/EnderChestBoatCriterion.java | 28 +++ .../twine/component/StageDataComponent.java | 20 +- .../entity/ExplodeArtificialBlockGoal.java | 64 ++++++ .../twine/entity/FollowPassiveEntityGoal.java | 4 +- .../twine/item/BackpackItem.java | 17 -- .../thebrokenrail/twine/item/DivinerItem.java | 46 ++++ .../twine/mixin/CriteriaHook.java | 14 ++ .../twine/mixin/MixinBoatEntity.java | 216 ++++++++++++++++++ .../twine/mixin/MixinBoatEntityRenderer.java | 41 ++++ .../MixinClientPlayerInteractionManager.java | 30 +++ .../twine/mixin/MixinMobEntity.java | 6 +- .../mixin/MixinServerPlayNetworkHandler.java | 25 ++ .../twine/mixin/MixinServerPlayerEntity.java | 32 ++- .../twine/util/BoatInventory.java | 30 +++ .../thebrokenrail/twine/util/BoatUtil.java | 42 ++++ .../util/EnderChestInventoryWrapper.java | 73 ++++++ .../resources/assets/twine/lang/en_us.json | 32 ++- .../assets/twine/models/item/diviner.json | 6 + .../assets/twine/textures/item/diviner.png | Bin 0 -> 6382 bytes .../twine/textures/item/diviner.png.mcmeta | 6 + .../data/twine/advancements/chest_boat.json | 19 ++ .../data/twine/advancements/diviner.json | 26 +++ .../twine/advancements/ender_chest_boat.json | 19 ++ .../twine/advancements/large_backpack.json | 26 +++ .../data/twine/advancements/root.json | 28 +++ .../twine/advancements/small_backpack.json | 31 +++ .../resources/data/twine/recipes/diviner.json | 20 ++ .../twine/tags/blocks/artificial_blocks.json | 22 ++ src/main/resources/fabric.mod.json | 2 +- src/main/resources/twine.mixins.json | 7 + 33 files changed, 941 insertions(+), 45 deletions(-) create mode 100644 src/main/java/com/thebrokenrail/twine/advancement/ChestBoatCriterion.java create mode 100644 src/main/java/com/thebrokenrail/twine/advancement/EnderChestBoatCriterion.java create mode 100644 src/main/java/com/thebrokenrail/twine/entity/ExplodeArtificialBlockGoal.java create mode 100644 src/main/java/com/thebrokenrail/twine/item/DivinerItem.java create mode 100644 src/main/java/com/thebrokenrail/twine/mixin/CriteriaHook.java create mode 100644 src/main/java/com/thebrokenrail/twine/mixin/MixinBoatEntity.java create mode 100644 src/main/java/com/thebrokenrail/twine/mixin/MixinBoatEntityRenderer.java create mode 100644 src/main/java/com/thebrokenrail/twine/mixin/MixinClientPlayerInteractionManager.java create mode 100644 src/main/java/com/thebrokenrail/twine/mixin/MixinServerPlayNetworkHandler.java create mode 100644 src/main/java/com/thebrokenrail/twine/util/BoatInventory.java create mode 100644 src/main/java/com/thebrokenrail/twine/util/BoatUtil.java create mode 100644 src/main/java/com/thebrokenrail/twine/util/EnderChestInventoryWrapper.java create mode 100644 src/main/resources/assets/twine/models/item/diviner.json create mode 100644 src/main/resources/assets/twine/textures/item/diviner.png create mode 100644 src/main/resources/assets/twine/textures/item/diviner.png.mcmeta create mode 100644 src/main/resources/data/twine/advancements/chest_boat.json create mode 100644 src/main/resources/data/twine/advancements/diviner.json create mode 100644 src/main/resources/data/twine/advancements/ender_chest_boat.json create mode 100644 src/main/resources/data/twine/advancements/large_backpack.json create mode 100644 src/main/resources/data/twine/advancements/root.json create mode 100644 src/main/resources/data/twine/advancements/small_backpack.json create mode 100644 src/main/resources/data/twine/recipes/diviner.json create mode 100644 src/main/resources/data/twine/tags/blocks/artificial_blocks.json diff --git a/README.md b/README.md index e4a0719..532b715 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,11 @@ Glowing Obsidian heals monsters and hurts everything else. It naturally generate Redstone +## Chest Boats +Right-Click a Boat with a Chest to place it in the Boat, you can also use an Ender Chest. Shift-Right-Click the Boat to open the Chest. You can also open the Chest inside the Boat by opening your inventory. + ## Difficulty Stages +Each player has a "personal" stage of an area, the highest online player's difficulty stage in an area will be the "effective" difficulty stage of that area. ### Stage 1 - Normal Gameplay @@ -39,6 +43,7 @@ Glowing Obsidian heals monsters and hurts everything else. It naturally generate ### Stage 4 - Villages Kick You Out (Using Iron Golems) +- Creepers Target Artificial Blocks ### Stage 5 - Neural Mobs Are Always Hostile @@ -46,4 +51,7 @@ Glowing Obsidian heals monsters and hurts everything else. It naturally generate ### Stage 6 ### Technical Details -Each player has a 6-element long list in their data. Each element in the list has a chunk position and time value. Every tick the player will first, find the stage element its current position is in, and increase the time value of that stage element. If the time value when increased is over 24,000 ticks the next stage element will have its chunk set to the players current position, have its time value reset, the old stage element will have the next stage element's old chunk, and have its time value reset. To determine if a player is in a stage element, it checks if the player is within 16 chunks of the stage element's chunk, it starts searching at the last element, ending at the first. The "global" stage element of a chunk can be found by searching every player for their stage element for the chunk and then picking the highest value. \ No newline at end of file +Each player has a 6-element long list in their data. Each element in the list has a chunk position and time value. Every tick the player will first, find the stage element its current position is in, and increase the time value of that stage element. If the time value when increased is over 24,000 ticks the next stage element will have its chunk set to the players current position, have its time value reset, the old stage element will have the next stage element's old chunk, and have its time value reset. To determine if a player is in a stage element, it checks if the player is within 16 chunks of the stage element's chunk, it starts searching at the last element, ending at the first. The "effective" stage element of a chunk can be found by searching every **online** player for their stage element for the chunk and then picking the highest value. + +## Credits +Thanks to ```sirikaros``` for the backpack textures! \ No newline at end of file diff --git a/src/main/java/com/thebrokenrail/twine/Twine.java b/src/main/java/com/thebrokenrail/twine/Twine.java index 10e96ce..21c1250 100644 --- a/src/main/java/com/thebrokenrail/twine/Twine.java +++ b/src/main/java/com/thebrokenrail/twine/Twine.java @@ -1,16 +1,23 @@ package com.thebrokenrail.twine; +import com.thebrokenrail.twine.advancement.ChestBoatCriterion; +import com.thebrokenrail.twine.advancement.EnderChestBoatCriterion; import com.thebrokenrail.twine.block.GlowingObsidianBlock; import com.thebrokenrail.twine.item.BackpackItem; +import com.thebrokenrail.twine.item.DivinerItem; +import com.thebrokenrail.twine.mixin.CriteriaHook; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.client.itemgroup.FabricItemGroupBuilder; import net.fabricmc.fabric.api.container.ContainerProviderRegistry; +import net.fabricmc.fabric.api.tag.TagRegistry; +import net.minecraft.block.Block; import net.minecraft.item.BlockItem; import net.minecraft.item.Item; import net.minecraft.item.ItemGroup; import net.minecraft.item.ItemStack; import net.minecraft.screen.GenericContainerScreenHandler; import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.tag.Tag; import net.minecraft.util.Hand; import net.minecraft.util.Identifier; import net.minecraft.util.registry.Registry; @@ -26,9 +33,16 @@ public class Twine implements ModInitializer { public static final Item SMALL_BACKPACK = new BackpackItem(false); public static final Item LARGE_BACKPACK = new BackpackItem(true); + public static final Item DIVINER = new DivinerItem(); + + public static final Tag ARTIFICIAL_BLOCKS_TAG = TagRegistry.block(new Identifier(NAMESPACE, "artificial_blocks")); + public static final int STAGE_COUNT = 6; public static final int STAGE_TIME = 24000; + public static ChestBoatCriterion CHEST_BOAT_CRITERION = CriteriaHook.callRegister(new ChestBoatCriterion()); + public static EnderChestBoatCriterion ENDER_CHEST_BOAT_CRITERION = CriteriaHook.callRegister(new EnderChestBoatCriterion()); + @Override public void onInitialize() { ContainerProviderRegistry.INSTANCE.registerFactory(BACKPACK_SCREEN, (i, identifier, playerEntity, buf) -> { @@ -41,6 +55,8 @@ public class Twine implements ModInitializer { Registry.register(Registry.ITEM, new Identifier(NAMESPACE, "small_backpack"), SMALL_BACKPACK); Registry.register(Registry.ITEM, new Identifier(NAMESPACE, "large_backpack"), LARGE_BACKPACK); + Registry.register(Registry.ITEM, new Identifier(NAMESPACE, "diviner"), DIVINER); + Registry.register(Registry.BLOCK, new Identifier(NAMESPACE, "glowing_obsidian"), GLOWING_OBSIDIAN); Registry.register(Registry.ITEM, new Identifier(NAMESPACE, "glowing_obsidian"), new BlockItem(GLOWING_OBSIDIAN, new Item.Settings().group(ITEM_GROUP))); } diff --git a/src/main/java/com/thebrokenrail/twine/advancement/ChestBoatCriterion.java b/src/main/java/com/thebrokenrail/twine/advancement/ChestBoatCriterion.java new file mode 100644 index 0000000..1e79d1e --- /dev/null +++ b/src/main/java/com/thebrokenrail/twine/advancement/ChestBoatCriterion.java @@ -0,0 +1,28 @@ +package com.thebrokenrail.twine.advancement; + +import com.google.gson.JsonObject; +import com.thebrokenrail.twine.Twine; +import net.minecraft.advancement.criterion.AbstractCriterion; +import net.minecraft.advancement.criterion.AbstractCriterionConditions; +import net.minecraft.predicate.entity.AdvancementEntityPredicateDeserializer; +import net.minecraft.predicate.entity.EntityPredicate; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; + +public class ChestBoatCriterion extends AbstractCriterion { + private static final Identifier ID = new Identifier(Twine.NAMESPACE, "chest_boat"); + + public void trigger(ServerPlayerEntity player) { + this.test(player, conditions -> true); + } + + @Override + protected AbstractCriterionConditions conditionsFromJson(JsonObject obj, EntityPredicate.Extended playerPredicate, AdvancementEntityPredicateDeserializer predicateDeserializer) { + return new AbstractCriterionConditions(ID, playerPredicate) {}; + } + + @Override + public Identifier getId() { + return ID; + } +} diff --git a/src/main/java/com/thebrokenrail/twine/advancement/EnderChestBoatCriterion.java b/src/main/java/com/thebrokenrail/twine/advancement/EnderChestBoatCriterion.java new file mode 100644 index 0000000..2acee1d --- /dev/null +++ b/src/main/java/com/thebrokenrail/twine/advancement/EnderChestBoatCriterion.java @@ -0,0 +1,28 @@ +package com.thebrokenrail.twine.advancement; + +import com.google.gson.JsonObject; +import com.thebrokenrail.twine.Twine; +import net.minecraft.advancement.criterion.AbstractCriterion; +import net.minecraft.advancement.criterion.AbstractCriterionConditions; +import net.minecraft.predicate.entity.AdvancementEntityPredicateDeserializer; +import net.minecraft.predicate.entity.EntityPredicate; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; + +public class EnderChestBoatCriterion extends AbstractCriterion { + private static final Identifier ID = new Identifier(Twine.NAMESPACE, "ender_chest_boat"); + + public void trigger(ServerPlayerEntity player) { + this.test(player, conditions -> true); + } + + @Override + protected AbstractCriterionConditions conditionsFromJson(JsonObject obj, EntityPredicate.Extended playerPredicate, AdvancementEntityPredicateDeserializer predicateDeserializer) { + return new AbstractCriterionConditions(ID, playerPredicate) {}; + } + + @Override + public Identifier getId() { + return ID; + } +} diff --git a/src/main/java/com/thebrokenrail/twine/component/StageDataComponent.java b/src/main/java/com/thebrokenrail/twine/component/StageDataComponent.java index ba4f468..ea8e6e7 100644 --- a/src/main/java/com/thebrokenrail/twine/component/StageDataComponent.java +++ b/src/main/java/com/thebrokenrail/twine/component/StageDataComponent.java @@ -7,6 +7,7 @@ import net.minecraft.nbt.Tag; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.ChunkPos; import net.minecraft.world.PersistentState; +import net.minecraft.world.World; import java.util.*; @@ -43,15 +44,26 @@ public class StageDataComponent extends PersistentState { return 0; } - public int findStageOfChunk(ChunkPos pos) { - Collection values = data.values(); + public int findEffectiveStageOfChunk(ServerWorld world, ChunkPos pos) { int stage = 0; - for (StageData[] value : values) { - stage = Math.max(stage, findStageOfChunk(pos, value)); + for (Map.Entry entry : data.entrySet()) { + if (world.getPlayerByUuid(entry.getKey()) != null) { + stage = Math.max(stage, findStageOfChunk(pos, entry.getValue())); + } } return stage; } + public long findEffectiveTimeOfChunk(ServerWorld world, ChunkPos pos) { + long time = 0; + for (Map.Entry entry : data.entrySet()) { + if (world.getPlayerByUuid(entry.getKey()) != null) { + time = Math.max(time, entry.getValue()[findStageOfChunk(pos, entry.getValue())].time); + } + } + return time; + } + public static StageDataComponent getFromWorld(ServerWorld world) { return world.getPersistentStateManager().getOrCreate(() -> new StageDataComponent(STAGE_DATA_ID), STAGE_DATA_ID); } diff --git a/src/main/java/com/thebrokenrail/twine/entity/ExplodeArtificialBlockGoal.java b/src/main/java/com/thebrokenrail/twine/entity/ExplodeArtificialBlockGoal.java new file mode 100644 index 0000000..c33e03a --- /dev/null +++ b/src/main/java/com/thebrokenrail/twine/entity/ExplodeArtificialBlockGoal.java @@ -0,0 +1,64 @@ +package com.thebrokenrail.twine.entity; + +import com.thebrokenrail.twine.Twine; +import com.thebrokenrail.twine.component.StageDataComponent; +import net.minecraft.block.BlockState; +import net.minecraft.entity.ai.goal.MoveToTargetPosGoal; +import net.minecraft.entity.mob.CreeperEntity; +import net.minecraft.entity.mob.MobEntityWithAi; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.WorldView; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkStatus; + +public class ExplodeArtificialBlockGoal extends MoveToTargetPosGoal { + public ExplodeArtificialBlockGoal(MobEntityWithAi mob) { + super(mob, 1d, 24, 4); + } + + @Override + protected boolean isTargetPos(WorldView world, BlockPos pos) { + Chunk chunk = world.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.FULL, false); + if (chunk == null) { + return false; + } else { + return chunk.getBlockState(pos).isOpaqueFullCube(world, pos) && chunk.getBlockState(pos.up()).isAir() && chunk.getBlockState(pos.up(2)).isAir() && (isArtificial(world, chunk, pos) || isArtificial(world, chunk, pos.up().north()) || isArtificial(world, chunk, pos.up().south()) || isArtificial(world, chunk, pos.up().east()) || isArtificial(world, chunk, pos.up().west())); + } + } + + private boolean isArtificial(WorldView world, Chunk chunk, BlockPos pos) { + ChunkPos newPos = new ChunkPos(pos.getX() >> 4, pos.getZ() >> 4); + if (!chunk.getPos().equals(newPos)) { + chunk = world.getChunk(newPos.x, newPos.z, ChunkStatus.FULL, false); + if (chunk == null) { + return false; + } + } + BlockState state = chunk.getBlockState(pos); + return state != null && (state.isIn(Twine.ARTIFICIAL_BLOCKS_TAG) || state.getLuminance() > 12); + } + + @Override + public double getDesiredSquaredDistanceToTarget() { + return 2d; + } + + @Override + public void tick() { + super.tick(); + if (this.hasReached() && mob instanceof CreeperEntity) { + ((CreeperEntity) mob).setIgnited(); + } + } + + @Override + public boolean canStart() { + StageDataComponent component = StageDataComponent.getFromWorld((ServerWorld) mob.getEntityWorld()); + ChunkPos chunkPos = new ChunkPos(mob.getBlockPos()); + int stage = component.findEffectiveStageOfChunk((ServerWorld) mob.getEntityWorld(), chunkPos); + + return stage >= 3 && super.canStart(); + } +} diff --git a/src/main/java/com/thebrokenrail/twine/entity/FollowPassiveEntityGoal.java b/src/main/java/com/thebrokenrail/twine/entity/FollowPassiveEntityGoal.java index 1a302cc..415133e 100644 --- a/src/main/java/com/thebrokenrail/twine/entity/FollowPassiveEntityGoal.java +++ b/src/main/java/com/thebrokenrail/twine/entity/FollowPassiveEntityGoal.java @@ -16,7 +16,7 @@ public class FollowPassiveEntityGoal extends FollowTargetGoal { super(mob, LivingEntity.class, 10, false, false, entity -> { StageDataComponent component = StageDataComponent.getFromWorld((ServerWorld) mob.getEntityWorld()); ChunkPos chunkPos = new ChunkPos(mob.getBlockPos()); - int stage = component.findStageOfChunk(chunkPos); + int stage = component.findEffectiveStageOfChunk((ServerWorld) mob.getEntityWorld(), chunkPos); return entity instanceof PassiveEntity || (entity instanceof PlayerEntity && stage >= 4); }); @@ -25,7 +25,7 @@ public class FollowPassiveEntityGoal extends FollowTargetGoal { public boolean canStart() { StageDataComponent component = StageDataComponent.getFromWorld((ServerWorld) mob.getEntityWorld()); ChunkPos chunkPos = new ChunkPos(mob.getBlockPos()); - int stage = component.findStageOfChunk(chunkPos); + int stage = component.findEffectiveStageOfChunk((ServerWorld) mob.getEntityWorld(), chunkPos); return mob instanceof HostileEntity && (!(mob instanceof Angerable) || stage >= 4) && stage >= 2 && super.canStart(); } diff --git a/src/main/java/com/thebrokenrail/twine/item/BackpackItem.java b/src/main/java/com/thebrokenrail/twine/item/BackpackItem.java index bb7eca3..0b9c4dc 100644 --- a/src/main/java/com/thebrokenrail/twine/item/BackpackItem.java +++ b/src/main/java/com/thebrokenrail/twine/item/BackpackItem.java @@ -1,22 +1,17 @@ package com.thebrokenrail.twine.item; import com.thebrokenrail.twine.Twine; -import com.thebrokenrail.twine.component.StageDataComponent; import net.fabricmc.fabric.api.container.ContainerProviderRegistry; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.inventory.Inventories; import net.minecraft.inventory.Inventory; import net.minecraft.item.DyeableItem; import net.minecraft.item.Item; -import net.minecraft.item.ItemGroup; import net.minecraft.item.ItemStack; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.text.LiteralText; import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; import net.minecraft.util.TypedActionResult; import net.minecraft.util.collection.DefaultedList; -import net.minecraft.util.math.ChunkPos; import net.minecraft.world.World; public class BackpackItem extends Item implements DyeableItem { @@ -116,18 +111,6 @@ public class BackpackItem extends Item implements DyeableItem { buf.writeBoolean(large); buf.writeEnumConstant(hand); }); - - StageDataComponent component = StageDataComponent.getFromWorld((ServerWorld) world); - ChunkPos chunkPos = new ChunkPos(user.getBlockPos()); - - int stage = component.findStageOfChunk(chunkPos); - user.sendMessage(new LiteralText("Global Stage: " + stage), false); - - StageDataComponent.StageData[] personalData = component.getData(user.getUuid()); - int personalStage = StageDataComponent.findStageOfChunk(chunkPos, personalData); - user.sendMessage(new LiteralText("Personal Stage: " + personalStage), false); - user.sendMessage(new LiteralText("Personal Center Chunk X: " + personalData[personalStage].x + " Personal Center Chunk Z: " + personalData[personalStage].z), false); - user.sendMessage(new LiteralText("Time To Personal Stage Increase: " + (Twine.STAGE_TIME - personalData[personalStage].time) + " Ticks"), false); } return new TypedActionResult<>(ActionResult.CONSUME, stack); } diff --git a/src/main/java/com/thebrokenrail/twine/item/DivinerItem.java b/src/main/java/com/thebrokenrail/twine/item/DivinerItem.java new file mode 100644 index 0000000..5827222 --- /dev/null +++ b/src/main/java/com/thebrokenrail/twine/item/DivinerItem.java @@ -0,0 +1,46 @@ +package com.thebrokenrail.twine.item; + +import com.thebrokenrail.twine.Twine; +import com.thebrokenrail.twine.component.StageDataComponent; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.SwordItem; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.LiteralText; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Formatting; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.World; + +public class DivinerItem extends Item { + public DivinerItem() { + super(new Settings().group(Twine.ITEM_GROUP).maxDamage(6)); + } + + @Override + public TypedActionResult use(World world, PlayerEntity user, Hand hand) { + ItemStack stack = user.getStackInHand(hand); + if (!world.isClient()) { + StageDataComponent component = StageDataComponent.getFromWorld((ServerWorld) world); + ChunkPos chunkPos = new ChunkPos(user.getBlockPos()); + + int stage = component.findEffectiveStageOfChunk((ServerWorld) world, chunkPos); + + StageDataComponent.StageData[] personalData = component.getData(user.getUuid()); + int effectiveStage = StageDataComponent.findStageOfChunk(chunkPos, personalData); + + user.sendMessage(new TranslatableText("chat.twine.diviner_info_1", new LiteralText(String.valueOf(stage + 1)).formatted(Formatting.WHITE)).formatted(Formatting.YELLOW), false); + user.sendMessage(new TranslatableText("chat.twine.diviner_info_2", (stage + 1 >= Twine.STAGE_COUNT ? new TranslatableText("chat.twine.diviner_info_never") : new LiteralText(String.valueOf(Twine.STAGE_TIME - component.findEffectiveTimeOfChunk((ServerWorld) world, chunkPos)))).formatted(Formatting.WHITE)).formatted(Formatting.YELLOW), false); + user.sendMessage(new TranslatableText("chat.twine.diviner_info_3", new LiteralText(String.valueOf(effectiveStage + 1)).formatted(Formatting.WHITE)).formatted(Formatting.YELLOW), false); + user.sendMessage(new TranslatableText("chat.twine.diviner_info_4", new LiteralText(String.valueOf(personalData[effectiveStage].x << 4)).formatted(Formatting.WHITE), new LiteralText(String.valueOf(personalData[effectiveStage].z << 4)).formatted(Formatting.WHITE)).formatted(Formatting.YELLOW), false); + user.sendMessage(new TranslatableText("chat.twine.diviner_info_5", (effectiveStage + 1 >= Twine.STAGE_COUNT ? new TranslatableText("chat.twine.diviner_info_never") : new LiteralText(String.valueOf(Twine.STAGE_TIME - personalData[effectiveStage].time))).formatted(Formatting.WHITE)).formatted(Formatting.YELLOW), false); + + stack.damage(1, user, e -> e.sendToolBreakStatus(hand)); + } + return new TypedActionResult<>(ActionResult.SUCCESS, stack); + } +} diff --git a/src/main/java/com/thebrokenrail/twine/mixin/CriteriaHook.java b/src/main/java/com/thebrokenrail/twine/mixin/CriteriaHook.java new file mode 100644 index 0000000..c46d76e --- /dev/null +++ b/src/main/java/com/thebrokenrail/twine/mixin/CriteriaHook.java @@ -0,0 +1,14 @@ +package com.thebrokenrail.twine.mixin; + +import net.minecraft.advancement.criterion.Criteria; +import net.minecraft.advancement.criterion.Criterion; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(Criteria.class) +public interface CriteriaHook { + @Invoker + static > T callRegister(T object) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/thebrokenrail/twine/mixin/MixinBoatEntity.java b/src/main/java/com/thebrokenrail/twine/mixin/MixinBoatEntity.java new file mode 100644 index 0000000..2410004 --- /dev/null +++ b/src/main/java/com/thebrokenrail/twine/mixin/MixinBoatEntity.java @@ -0,0 +1,216 @@ +package com.thebrokenrail.twine.mixin; + +import com.thebrokenrail.twine.Twine; +import com.thebrokenrail.twine.advancement.ChestBoatCriterion; +import com.thebrokenrail.twine.util.BoatInventory; +import com.thebrokenrail.twine.util.BoatUtil; +import com.thebrokenrail.twine.util.EnderChestInventoryWrapper; +import net.minecraft.block.BlockState; +import net.minecraft.block.EnderChestBlock; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.entity.Entity; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.vehicle.BoatEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.screen.SimpleNamedScreenHandlerFactory; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.GameRules; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.List; + +@Mixin(BoatEntity.class) +public class MixinBoatEntity implements BoatUtil { + @Inject(at = @At("RETURN"), method = "initDataTracker") + public void initDataTracker(CallbackInfo info) { + ((BoatEntity) (Object) this).getDataTracker().startTracking(BoatUtil.HAS_CHEST, BoatChestMode.NONE.name()); + } + + @Inject(at = @At("HEAD"), method = "canAddPassenger", cancellable = true) + public void canAddPassenger(Entity passenger, CallbackInfoReturnable info) { + if (((BoatEntity) (Object) this).getPassengerList().size() > 0 && hasChest() != BoatChestMode.NONE) { + info.setReturnValue(false); + } + } + + @Redirect(at = @At(value = "INVOKE", target = "Ljava/util/List;size()I"), method = "updatePassengerPosition") + public int updatePassengerPosition(List list) { + if (hasChest() != BoatChestMode.NONE) { + return list.size() + 1; + } else { + return list.size(); + } + } + + @Inject(at = @At("HEAD"), method = "interact", cancellable = true) + public void interact(PlayerEntity player, Hand hand, CallbackInfoReturnable info) { + BoatChestMode hasChest = hasChest(); + ItemStack stack = player.getStackInHand(hand); + if ((stack.getItem() == Items.CHEST || stack.getItem() == Items.ENDER_CHEST) && hasChest == BoatChestMode.NONE) { + List passengers = ((BoatEntity) (Object) this).getPassengerList(); + for (int i = 1; i < passengers.size(); i++) { + passengers.get(i).stopRiding(); + } + if (!player.getEntityWorld().isClient()) { + if (stack.getItem() == Items.CHEST) { + Twine.CHEST_BOAT_CRITERION.trigger((ServerPlayerEntity) player); + setHasChest(BoatChestMode.CHEST); + } else { + Twine.ENDER_CHEST_BOAT_CRITERION.trigger((ServerPlayerEntity) player); + setHasChest(BoatChestMode.ENDER_CHEST); + } + updateInventory(); + } + if (!player.isCreative()) { + stack.decrement(1); + } + info.setReturnValue(ActionResult.SUCCESS); + } else if (player.isSneaking()) { + if (!player.getEntityWorld().isClient()) { + openInventory(player); + } + info.setReturnValue(ActionResult.SUCCESS); + } + } + + @Unique + private void setHasChest(BoatChestMode mode) { + ((BoatEntity) (Object) this).getDataTracker().set(BoatUtil.HAS_CHEST, mode.name()); + } + + @Override + public Item getChestItem() { + switch (hasChest()) { + case CHEST: { + return Items.CHEST; + } + case ENDER_CHEST: { + return Items.ENDER_CHEST; + } + default: { + return Items.AIR; + } + } + } + + @Unique + private void dropItemsOnDestroy() { + if (((BoatEntity) (Object) this).getEntityWorld().getGameRules().getBoolean(GameRules.DO_ENTITY_DROPS)) { + ((BoatEntity) (Object) this).dropStack(new ItemStack(getChestItem())); + dropItems(); + } + } + + @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/vehicle/BoatEntity;remove()V"), method = "damage") + public void damage(DamageSource source, float amount, CallbackInfoReturnable info) { + dropItemsOnDestroy(); + } + + @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/vehicle/BoatEntity;remove()V"), method = "fall") + public void fall(double heightDifference, boolean onGround, BlockState landedState, BlockPos landedPosition, CallbackInfo info) { + dropItemsOnDestroy(); + } + + @Unique + private BoatInventory items; + + @Unique + private void updateInventory() { + items = hasChest() == BoatChestMode.CHEST ? new BoatInventory((BoatEntity) (Object) this, 27) : null; + } + + @Unique + private void dropItems() { + if (items != null) { + for (int i = 0; i < items.size(); ++i) { + ItemStack itemStack = items.getStack(i); + if (!itemStack.isEmpty() && !EnchantmentHelper.hasVanishingCurse(itemStack)) { + ((BoatEntity) (Object) this).dropStack(itemStack); + } + } + } + } + + @Inject(at = @At("RETURN"), method = "writeCustomDataToTag") + public void writeCustomDataToTag(CompoundTag tag, CallbackInfo info) { + BoatChestMode hasChest = hasChest(); + tag.putString("HasChest", hasChest.name()); + if (hasChest == BoatChestMode.CHEST) { + ListTag listTag = new ListTag(); + + for (int i = 2; i < this.items.size(); ++i) { + ItemStack itemStack = this.items.getStack(i); + if (!itemStack.isEmpty()) { + CompoundTag compoundTag = new CompoundTag(); + compoundTag.putByte("Slot", (byte)i); + itemStack.toTag(compoundTag); + listTag.add(compoundTag); + } + } + + tag.put("Items", listTag); + } + } + + @Inject(at = @At("RETURN"), method = "readCustomDataFromTag") + public void readCustomDataFromTag(CompoundTag tag, CallbackInfo info) { + BoatChestMode hasChest; + try { + hasChest = BoatChestMode.valueOf(tag.getString("HasChest")); + } catch (IllegalArgumentException e) { + hasChest = BoatChestMode.NONE; + } + setHasChest(hasChest); + updateInventory(); + if (hasChest == BoatChestMode.CHEST) { + ListTag listTag = tag.getList("Items", 10); + + for (int i = 0; i < listTag.size(); ++i) { + CompoundTag compoundTag = listTag.getCompound(i); + int j = compoundTag.getByte("Slot") & 255; + if (j >= 2 && j < items.size()) { + items.setStack(j, ItemStack.fromTag(compoundTag)); + } + } + } + } + + @Override + public BoatChestMode hasChest() { + try { + return BoatChestMode.valueOf(((BoatEntity) (Object) this).getDataTracker().get(BoatUtil.HAS_CHEST)); + } catch (IllegalArgumentException e) { + return BoatChestMode.NONE; + } + } + + @Override + public Inventory getInventory() { + return items; + } + + @Override + public void openInventory(PlayerEntity player) { + if (hasChest() == BoatUtil.BoatChestMode.CHEST) { + player.openHandledScreen(new SimpleNamedScreenHandlerFactory((i, playerInventory, playerEntity) -> GenericContainerScreenHandler.createGeneric9x3(i, playerInventory, getInventory()), ((BoatEntity) (Object) this).getDisplayName())); + } else { + player.openHandledScreen(new SimpleNamedScreenHandlerFactory((i, playerInventory, playerEntity) -> GenericContainerScreenHandler.createGeneric9x3(i, playerInventory, new EnderChestInventoryWrapper((BoatEntity) (Object) this, player.getEnderChestInventory())), EnderChestBlock.CONTAINER_NAME)); + } + } +} diff --git a/src/main/java/com/thebrokenrail/twine/mixin/MixinBoatEntityRenderer.java b/src/main/java/com/thebrokenrail/twine/mixin/MixinBoatEntityRenderer.java new file mode 100644 index 0000000..803b325 --- /dev/null +++ b/src/main/java/com/thebrokenrail/twine/mixin/MixinBoatEntityRenderer.java @@ -0,0 +1,41 @@ +package com.thebrokenrail.twine.mixin; + +import com.thebrokenrail.twine.util.BoatUtil; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.block.Block; +import net.minecraft.block.BlockRenderType; +import net.minecraft.block.BlockState; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.OverlayTexture; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.BoatEntityRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.client.util.math.Vector3f; +import net.minecraft.entity.vehicle.BoatEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Environment(EnvType.CLIENT) +@Mixin(BoatEntityRenderer.class) +public class MixinBoatEntityRenderer { + @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;pop()V"), method = "render") + public void render(BoatEntity boatEntity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo info) { + BlockState blockState = Block.getBlockFromItem(((BoatUtil) boatEntity).getChestItem()).getDefaultState(); + if (blockState.getRenderType() != BlockRenderType.INVISIBLE) { + matrixStack.push(); + matrixStack.multiply(Vector3f.POSITIVE_X.getDegreesQuaternion(180.0f)); + matrixStack.scale(0.75f, 0.75f, 0.75f); + matrixStack.translate(-1d, -0.25f, 0.5d); + matrixStack.multiply(Vector3f.POSITIVE_Y.getDegreesQuaternion(90.0f)); + renderBlock(blockState, matrixStack, vertexConsumerProvider, i); + matrixStack.pop(); + } + } + + private void renderBlock(BlockState state, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i) { + MinecraftClient.getInstance().getBlockRenderManager().renderBlockAsEntity(state, matrixStack, vertexConsumerProvider, i, OverlayTexture.DEFAULT_UV); + } +} diff --git a/src/main/java/com/thebrokenrail/twine/mixin/MixinClientPlayerInteractionManager.java b/src/main/java/com/thebrokenrail/twine/mixin/MixinClientPlayerInteractionManager.java new file mode 100644 index 0000000..851ec23 --- /dev/null +++ b/src/main/java/com/thebrokenrail/twine/mixin/MixinClientPlayerInteractionManager.java @@ -0,0 +1,30 @@ +package com.thebrokenrail.twine.mixin; + +import com.thebrokenrail.twine.util.BoatUtil; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.entity.vehicle.BoatEntity; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Environment(EnvType.CLIENT) +@Mixin(ClientPlayerInteractionManager.class) +public class MixinClientPlayerInteractionManager { + @Shadow + @Final + private MinecraftClient client; + + @Inject(at = @At("HEAD"), method = "hasRidingInventory", cancellable = true) + public void hasRidingInventory(CallbackInfoReturnable info) { + assert client.player != null; + if (client.player.hasVehicle() && client.player.getVehicle() instanceof BoatEntity && ((BoatUtil) client.player.getVehicle()).hasChest() != BoatUtil.BoatChestMode.NONE) { + info.setReturnValue(true); + } + } +} diff --git a/src/main/java/com/thebrokenrail/twine/mixin/MixinMobEntity.java b/src/main/java/com/thebrokenrail/twine/mixin/MixinMobEntity.java index 834150e..199cdfc 100644 --- a/src/main/java/com/thebrokenrail/twine/mixin/MixinMobEntity.java +++ b/src/main/java/com/thebrokenrail/twine/mixin/MixinMobEntity.java @@ -1,6 +1,7 @@ package com.thebrokenrail.twine.mixin; import com.thebrokenrail.twine.component.StageDataComponent; +import com.thebrokenrail.twine.entity.ExplodeArtificialBlockGoal; import com.thebrokenrail.twine.entity.FollowPassiveEntityGoal; import com.thebrokenrail.twine.entity.StandOnGlowingObsidian; import net.minecraft.entity.EntityType; @@ -36,13 +37,16 @@ public class MixinMobEntity { if (this instanceof Monster) { if ((Object) this instanceof MobEntityWithAi) { goalSelector.add(1, new StandOnGlowingObsidian((MobEntityWithAi) (Object) this)); + if ((Object) this instanceof CreeperEntity) { + goalSelector.add(3, new ExplodeArtificialBlockGoal((MobEntityWithAi) (Object) this)); + } } targetSelector.add(2, new FollowPassiveEntityGoal((MobEntity) (Object) this)); } else if ((Object) this instanceof IronGolemEntity) { targetSelector.add(3, new FollowTargetGoal<>((IronGolemEntity) (Object) this, PlayerEntity.class, 10, true, false, entity -> { StageDataComponent component = StageDataComponent.getFromWorld((ServerWorld) ((IronGolemEntity) (Object) this).getEntityWorld()); ChunkPos chunkPos = new ChunkPos(((IronGolemEntity) (Object) this).getBlockPos()); - int stage = component.findStageOfChunk(chunkPos); + int stage = component.findEffectiveStageOfChunk((ServerWorld) (((IronGolemEntity) (Object) this).getEntityWorld()), chunkPos); return stage >= 3 && !((IronGolemEntity) (Object) this).isPlayerCreated(); })); diff --git a/src/main/java/com/thebrokenrail/twine/mixin/MixinServerPlayNetworkHandler.java b/src/main/java/com/thebrokenrail/twine/mixin/MixinServerPlayNetworkHandler.java new file mode 100644 index 0000000..a73d50c --- /dev/null +++ b/src/main/java/com/thebrokenrail/twine/mixin/MixinServerPlayNetworkHandler.java @@ -0,0 +1,25 @@ +package com.thebrokenrail.twine.mixin; + +import com.thebrokenrail.twine.util.BoatUtil; +import net.minecraft.entity.vehicle.BoatEntity; +import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ServerPlayNetworkHandler.class) +public class MixinServerPlayNetworkHandler { + @Shadow + public ServerPlayerEntity player; + + @Inject(at = @At("TAIL"), method = "onClientCommand") + public void onClientCommand(ClientCommandC2SPacket packet, CallbackInfo info) { + if (packet.getMode() == ClientCommandC2SPacket.Mode.OPEN_INVENTORY && player.hasVehicle() && player.getVehicle() instanceof BoatEntity && ((BoatUtil) player.getVehicle()).hasChest() != BoatUtil.BoatChestMode.NONE) { + ((BoatUtil) player.getVehicle()).openInventory(player); + } + } +} diff --git a/src/main/java/com/thebrokenrail/twine/mixin/MixinServerPlayerEntity.java b/src/main/java/com/thebrokenrail/twine/mixin/MixinServerPlayerEntity.java index f926ffb..f61ca7b 100644 --- a/src/main/java/com/thebrokenrail/twine/mixin/MixinServerPlayerEntity.java +++ b/src/main/java/com/thebrokenrail/twine/mixin/MixinServerPlayerEntity.java @@ -31,26 +31,24 @@ public abstract class MixinServerPlayerEntity { StageDataComponent.StageData[] data = component.getData(((ServerPlayerEntity) (Object) this).getUuid()); int stage = StageDataComponent.findStageOfChunk(getChunkPos(), data); StageDataComponent.StageData stageObj = data[stage]; - if (lastTime != -1) { - long diff = Math.max(0, getServerWorld().getTime() - lastTime); - if (diff > 5) { - System.out.println(diff); + if (stage + 1 < Twine.STAGE_COUNT) { + if (lastTime != -1L) { + stageObj.time = stageObj.time + Math.max(0, getServerWorld().getTime() - lastTime); + } + if (stageObj.time >= Twine.STAGE_TIME) { + StageDataComponent.StageData old = data[stage + 1]; + if (old != null) { + old.time = 0; + } + StageDataComponent.StageData newData = new StageDataComponent.StageData(); + ChunkPos pos = getChunkPos(); + newData.x = pos.x; + newData.z = pos.z; + data[stage + 1] = newData; + data[stage] = old; } - stageObj.time = stageObj.time + Math.max(0, getServerWorld().getTime() - lastTime); } lastTime = getServerWorld().getTime(); - if (stageObj.time >= 24000 && stage + 1 < Twine.STAGE_COUNT) { - StageDataComponent.StageData old = data[stage + 1]; - if (old != null) { - old.time = 0; - } - StageDataComponent.StageData newData = new StageDataComponent.StageData(); - ChunkPos pos = getChunkPos(); - newData.x = pos.x; - newData.z = pos.z; - data[stage + 1] = newData; - data[stage] = old; - } component.markDirty(); } } diff --git a/src/main/java/com/thebrokenrail/twine/util/BoatInventory.java b/src/main/java/com/thebrokenrail/twine/util/BoatInventory.java new file mode 100644 index 0000000..905e17c --- /dev/null +++ b/src/main/java/com/thebrokenrail/twine/util/BoatInventory.java @@ -0,0 +1,30 @@ +package com.thebrokenrail.twine.util; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.sound.SoundEvents; + +public class BoatInventory extends SimpleInventory { + private final Entity entity; + + public BoatInventory(Entity entity, int size) { + super(size); + this.entity = entity; + } + + @Override + public boolean canPlayerUse(PlayerEntity player) { + return BoatUtil.canReachEntity(player, entity) && entity.isAlive() && ((BoatUtil) entity).hasChest() == BoatUtil.BoatChestMode.CHEST && super.canPlayerUse(player); + } + + @Override + public void onOpen(PlayerEntity player) { + BoatUtil.playSound(entity, SoundEvents.BLOCK_CHEST_OPEN); + } + + @Override + public void onClose(PlayerEntity player) { + BoatUtil.playSound(entity, SoundEvents.BLOCK_CHEST_CLOSE); + } +} \ No newline at end of file diff --git a/src/main/java/com/thebrokenrail/twine/util/BoatUtil.java b/src/main/java/com/thebrokenrail/twine/util/BoatUtil.java new file mode 100644 index 0000000..3f8c389 --- /dev/null +++ b/src/main/java/com/thebrokenrail/twine/util/BoatUtil.java @@ -0,0 +1,42 @@ +package com.thebrokenrail.twine.util; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.entity.data.TrackedDataHandlerRegistry; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.vehicle.BoatEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.Item; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; +import net.minecraft.util.math.Vec3d; + +import java.util.Objects; + +public interface BoatUtil { + TrackedData HAS_CHEST = DataTracker.registerData(BoatEntity.class, TrackedDataHandlerRegistry.STRING); + + Item getChestItem(); + + BoatChestMode hasChest(); + + Inventory getInventory(); + + void openInventory(PlayerEntity player); + + static boolean canReachEntity(PlayerEntity player, Entity entity) { + return player.squaredDistanceTo(entity.getPos().getX(), entity.getPos().getY(), entity.getPos().getZ()) <= 64d; + } + + enum BoatChestMode { + ENDER_CHEST, + CHEST, + NONE + } + + static void playSound(Entity vehicle, SoundEvent sound) { + Vec3d pos = vehicle.getPos(); + vehicle.getEntityWorld().playSound(null, pos.getX(), pos.getY(), pos.getZ(), sound, SoundCategory.BLOCKS, 0.5F, vehicle.getEntityWorld().random.nextFloat() * 0.1F + 0.9F); + } +} diff --git a/src/main/java/com/thebrokenrail/twine/util/EnderChestInventoryWrapper.java b/src/main/java/com/thebrokenrail/twine/util/EnderChestInventoryWrapper.java new file mode 100644 index 0000000..03140b9 --- /dev/null +++ b/src/main/java/com/thebrokenrail/twine/util/EnderChestInventoryWrapper.java @@ -0,0 +1,73 @@ +package com.thebrokenrail.twine.util; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.EnderChestInventory; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.sound.SoundEvents; + +public class EnderChestInventoryWrapper implements Inventory { + private final EnderChestInventory inventory; + private final Entity entity; + + public EnderChestInventoryWrapper(Entity entity, EnderChestInventory inventory) { + this.entity = entity; + this.inventory = inventory; + } + + @Override + public int size() { + return inventory.size(); + } + + @Override + public boolean isEmpty() { + return inventory.isEmpty(); + } + + @Override + public ItemStack getStack(int slot) { + return inventory.getStack(slot); + } + + @Override + public ItemStack removeStack(int slot, int amount) { + return inventory.removeStack(slot, amount); + } + + @Override + public ItemStack removeStack(int slot) { + return inventory.removeStack(slot); + } + + @Override + public void setStack(int slot, ItemStack stack) { + inventory.setStack(slot, stack); + } + + @Override + public void markDirty() { + inventory.markDirty(); + } + + @Override + public boolean canPlayerUse(PlayerEntity player) { + return BoatUtil.canReachEntity(player, entity) && entity.isAlive() && ((BoatUtil) entity).hasChest() == BoatUtil.BoatChestMode.ENDER_CHEST; + } + + @Override + public void onOpen(PlayerEntity player) { + BoatUtil.playSound(entity, SoundEvents.BLOCK_ENDER_CHEST_OPEN); + } + + @Override + public void onClose(PlayerEntity player) { + BoatUtil.playSound(entity, SoundEvents.BLOCK_ENDER_CHEST_CLOSE); + } + + @Override + public void clear() { + inventory.clear(); + } +} diff --git a/src/main/resources/assets/twine/lang/en_us.json b/src/main/resources/assets/twine/lang/en_us.json index 42e19a2..2e6c05f 100644 --- a/src/main/resources/assets/twine/lang/en_us.json +++ b/src/main/resources/assets/twine/lang/en_us.json @@ -1,6 +1,34 @@ { + "itemGroup.twine.item_group": "Twine", + "item.twine.large_backpack": "Large Backpack", "item.twine.small_backpack": "Small Backpack", - "itemGroup.twine.item_group": "Twine", - "block.twine.glowing_obsidian": "Glowing Obsidian" + + "block.twine.glowing_obsidian": "Glowing Obsidian", + + "advancements.twine.root.title": "Twine", + "advancements.twine.root.description": "A simple survival mod for Minecraft encouraging a nomadic lifestyle", + + "advancements.twine.chest_boat.title": "TEAM SWAMP!", + "advancements.twine.chest_boat.description": "Put a Chest in a Boat", + + "advancements.twine.ender_chest_boat.title": "Why not?", + "advancements.twine.ender_chest_boat.description": "Put an Ender Chest in a Boat", + + "advancements.twine.small_backpack.title": "On the go!", + "advancements.twine.small_backpack.description": "Construct a Small Backpack with a Crafting Table", + + "advancements.twine.large_backpack.title": "Go big or go home!", + "advancements.twine.large_backpack.description": "Construct a Large Backpack with a Smithing Table by combing a Small Backpack and a Gold Ingot", + + "advancements.twine.diviner.title": "Divination", + "advancements.twine.diviner.description": "Construct a Diviner with a Crafting Table", + + "item.twine.diviner": "Diviner", + "chat.twine.diviner_info_1": "Effective Stage Of Area: %s", + "chat.twine.diviner_info_2": "Effective Stage Of Area Increases In: %s Ticks", + "chat.twine.diviner_info_3": "Personal Stage Of Area: %s", + "chat.twine.diviner_info_4": "Personal Center Of Area: X: %s Z: %s", + "chat.twine.diviner_info_5": "Personal Stage Of Area Increases In: %s Ticks", + "chat.twine.diviner_info_never": "Never" } \ No newline at end of file diff --git a/src/main/resources/assets/twine/models/item/diviner.json b/src/main/resources/assets/twine/models/item/diviner.json new file mode 100644 index 0000000..c3431cd --- /dev/null +++ b/src/main/resources/assets/twine/models/item/diviner.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "twine:item/diviner" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/twine/textures/item/diviner.png b/src/main/resources/assets/twine/textures/item/diviner.png new file mode 100644 index 0000000000000000000000000000000000000000..659d237eb6aaa93dbe68e7366e6ce39392045815 GIT binary patch literal 6382 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/twine/textures/item/diviner.png.mcmeta b/src/main/resources/assets/twine/textures/item/diviner.png.mcmeta new file mode 100644 index 0000000..1d13ee1 --- /dev/null +++ b/src/main/resources/assets/twine/textures/item/diviner.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 64 + } +} \ No newline at end of file diff --git a/src/main/resources/data/twine/advancements/chest_boat.json b/src/main/resources/data/twine/advancements/chest_boat.json new file mode 100644 index 0000000..e139705 --- /dev/null +++ b/src/main/resources/data/twine/advancements/chest_boat.json @@ -0,0 +1,19 @@ +{ + "display": { + "icon": { + "item": "minecraft:oak_boat" + }, + "title": { + "translate": "advancements.twine.chest_boat.title" + }, + "description": { + "translate": "advancements.twine.chest_boat.description" + } + }, + "parent": "twine:root", + "criteria": { + "chest_boat": { + "trigger": "twine:chest_boat" + } + } +} \ No newline at end of file diff --git a/src/main/resources/data/twine/advancements/diviner.json b/src/main/resources/data/twine/advancements/diviner.json new file mode 100644 index 0000000..3bf9665 --- /dev/null +++ b/src/main/resources/data/twine/advancements/diviner.json @@ -0,0 +1,26 @@ +{ + "display": { + "icon": { + "item": "twine:diviner" + }, + "title": { + "translate": "advancements.twine.diviner.title" + }, + "description": { + "translate": "advancements.twine.diviner.description" + } + }, + "parent": "twine:root", + "criteria": { + "chest_boat": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "twine:diviner" + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/data/twine/advancements/ender_chest_boat.json b/src/main/resources/data/twine/advancements/ender_chest_boat.json new file mode 100644 index 0000000..702ff7c --- /dev/null +++ b/src/main/resources/data/twine/advancements/ender_chest_boat.json @@ -0,0 +1,19 @@ +{ + "display": { + "icon": { + "item": "minecraft:oak_boat" + }, + "title": { + "translate": "advancements.twine.ender_chest_boat.title" + }, + "description": { + "translate": "advancements.twine.ender_chest_boat.description" + } + }, + "parent": "twine:chest_boat", + "criteria": { + "chest_boat": { + "trigger": "twine:ender_chest_boat" + } + } +} \ No newline at end of file diff --git a/src/main/resources/data/twine/advancements/large_backpack.json b/src/main/resources/data/twine/advancements/large_backpack.json new file mode 100644 index 0000000..f49bfc2 --- /dev/null +++ b/src/main/resources/data/twine/advancements/large_backpack.json @@ -0,0 +1,26 @@ +{ + "display": { + "icon": { + "item": "twine:large_backpack" + }, + "title": { + "translate": "advancements.twine.large_backpack.title" + }, + "description": { + "translate": "advancements.twine.large_backpack.description" + } + }, + "parent": "twine:small_backpack", + "criteria": { + "chest_boat": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "twine:large_backpack" + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/data/twine/advancements/root.json b/src/main/resources/data/twine/advancements/root.json new file mode 100644 index 0000000..f0837d0 --- /dev/null +++ b/src/main/resources/data/twine/advancements/root.json @@ -0,0 +1,28 @@ +{ + "display": { + "icon": { + "item": "twine:glowing_obsidian" + }, + "title": { + "translate": "advancements.twine.root.title" + }, + "description": { + "translate": "advancements.twine.root.description" + }, + "background": "minecraft:textures/gui/advancements/backgrounds/stone.png", + "show_toast": false, + "announce_to_chat": false + }, + "rewards": { + "recipes": [ + "twine:glowing_obsidian", + "twine:small_backpack", + "twine:diviner" + ] + }, + "criteria": { + "tick": { + "trigger": "minecraft:tick" + } + } +} \ No newline at end of file diff --git a/src/main/resources/data/twine/advancements/small_backpack.json b/src/main/resources/data/twine/advancements/small_backpack.json new file mode 100644 index 0000000..63ad009 --- /dev/null +++ b/src/main/resources/data/twine/advancements/small_backpack.json @@ -0,0 +1,31 @@ +{ + "display": { + "icon": { + "item": "twine:small_backpack" + }, + "title": { + "translate": "advancements.twine.small_backpack.title" + }, + "description": { + "translate": "advancements.twine.small_backpack.description" + } + }, + "parent": "twine:root", + "rewards": { + "recipes": [ + "twine:large_backpack" + ] + }, + "criteria": { + "chest_boat": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "twine:small_backpack" + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/data/twine/recipes/diviner.json b/src/main/resources/data/twine/recipes/diviner.json new file mode 100644 index 0000000..0b24571 --- /dev/null +++ b/src/main/resources/data/twine/recipes/diviner.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + " I ", + "IEI", + " I " + ], + "key": { + "I": { + "item": "minecraft:iron_ingot" + }, + "E": { + "item": "minecraft:ender_pearl" + } + }, + "result": { + "item": "twine:small_diviner", + "count": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/data/twine/tags/blocks/artificial_blocks.json b/src/main/resources/data/twine/tags/blocks/artificial_blocks.json new file mode 100644 index 0000000..d39dd30 --- /dev/null +++ b/src/main/resources/data/twine/tags/blocks/artificial_blocks.json @@ -0,0 +1,22 @@ +{ + "replace": false, + "values": [ + "#minecraft:doors", + "#minecraft:fences", + "#minecraft:fence_gates", + "#minecraft:planks", + "#minecraft:signs", + "#minecraft:slabs", + "#minecraft:stairs", + "#minecraft:trapdoors", + "#minecraft:walls", + "#minecraft:wool", + "#minecraft:anvil", + "#minecraft:beacon_base_blocks", + "#minecraft:beds", + "#minecraft:shulker_boxes", + "#minecraft:stone_bricks", + "minecraft:nether_portal", + "minecraft:cobblestone" + ] +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 8247ee7..e72e9cf 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -13,7 +13,7 @@ "issues": "https://gitea.thebrokenrail.com/TheBrokenRail/Twine/issues" }, "license": "MIT", - "icon": "assets/twine/icon.png", + "icon": "assets/twine/textures/block/glowing_obsidian.png", "environment": "*", "mixins": [ "twine.mixins.json" diff --git a/src/main/resources/twine.mixins.json b/src/main/resources/twine.mixins.json index 6bf73e6..fac15a1 100644 --- a/src/main/resources/twine.mixins.json +++ b/src/main/resources/twine.mixins.json @@ -4,11 +4,18 @@ "package": "com.thebrokenrail.twine.mixin", "compatibilityLevel": "JAVA_8", "mixins": [ + "CriteriaHook", + "MixinBoatEntity", "MixinDefaultBiomeFeatures", "MixinMobEntity", "MixinServerPlayerEntity", + "MixinServerPlayNetworkHandler", "MixinSlot" ], + "client": [ + "MixinBoatEntityRenderer", + "MixinClientPlayerInteractionManager" + ], "injectors": { "defaultRequire": 1 }