Chest Boats; More Features In The Difficulty Stages; The Diviner; Advancements
Twine/pipeline/head This commit looks good Details

This commit is contained in:
TheBrokenRail 2020-06-15 11:15:20 -04:00
parent c4a2e8e529
commit 135131b448
33 changed files with 941 additions and 45 deletions

View File

@ -27,7 +27,11 @@ Glowing Obsidian heals monsters and hurts everything else. It naturally generate
<tr><th></th><th>Redstone</th><th></th></tr>
</table>
## 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.
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!

View File

@ -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<Block> 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)));
}

View File

@ -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<AbstractCriterionConditions> {
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;
}
}

View File

@ -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<AbstractCriterionConditions> {
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;
}
}

View File

@ -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<StageData[]> 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<UUID, StageData[]> 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<UUID, StageData[]> 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);
}

View File

@ -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();
}
}

View File

@ -16,7 +16,7 @@ public class FollowPassiveEntityGoal extends FollowTargetGoal<LivingEntity> {
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<LivingEntity> {
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();
}

View File

@ -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);
}

View File

@ -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<ItemStack> 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);
}
}

View File

@ -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 extends Criterion<?>> T callRegister(T object) {
throw new UnsupportedOperationException();
}
}

View File

@ -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<Boolean> 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<Entity> 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<ActionResult> info) {
BoatChestMode hasChest = hasChest();
ItemStack stack = player.getStackInHand(hand);
if ((stack.getItem() == Items.CHEST || stack.getItem() == Items.ENDER_CHEST) && hasChest == BoatChestMode.NONE) {
List<Entity> 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<Boolean> 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));
}
}
}

View File

@ -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);
}
}

View File

@ -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<Boolean> 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);
}
}
}

View File

@ -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();
}));

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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<String> 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);
}
}

View File

@ -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();
}
}

View File

@ -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"
}

View File

@ -0,0 +1,6 @@
{
"parent": "minecraft:item/handheld",
"textures": {
"layer0": "twine:item/diviner"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 64
}
}

View File

@ -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"
}
}
}

View File

@ -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"
}
]
}
}
}
}

View File

@ -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"
}
}
}

View File

@ -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"
}
]
}
}
}
}

View File

@ -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"
}
}
}

View File

@ -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"
}
]
}
}
}
}

View File

@ -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
}
}

View File

@ -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"
]
}

View File

@ -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"

View File

@ -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
}