diff --git a/build.gradle.kts b/build.gradle.kts index 804b9798b..ebe82d5b1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,6 +34,7 @@ tasks.withType { } minecraft { + accessWidener("src/main/resources/galaxy-tweak.accesswidener") } dependencies { diff --git a/src/main/java/one/oktw/galaxy/mixin/interfaces/FakeEntity.java b/src/main/java/one/oktw/galaxy/mixin/interfaces/FakeEntity.java new file mode 100644 index 000000000..e76f875ab --- /dev/null +++ b/src/main/java/one/oktw/galaxy/mixin/interfaces/FakeEntity.java @@ -0,0 +1,23 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.mixin.interfaces; + +public interface FakeEntity { + void setFake(boolean value); +} diff --git a/src/main/java/one/oktw/galaxy/mixin/tweak/MixinCustomBlockEntity_BarrierBlock.java b/src/main/java/one/oktw/galaxy/mixin/tweak/MixinCustomBlockEntity_BarrierBlock.java index eeef7c56d..b5af4de59 100644 --- a/src/main/java/one/oktw/galaxy/mixin/tweak/MixinCustomBlockEntity_BarrierBlock.java +++ b/src/main/java/one/oktw/galaxy/mixin/tweak/MixinCustomBlockEntity_BarrierBlock.java @@ -18,23 +18,37 @@ package one.oktw.galaxy.mixin.tweak; -import net.minecraft.block.AbstractBlock; import net.minecraft.block.BarrierBlock; +import net.minecraft.block.Block; import net.minecraft.block.BlockEntityProvider; import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntityTicker; import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; import net.minecraft.screen.ScreenHandler; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.ItemScatterer; +import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; import net.minecraft.world.World; +import net.minecraft.world.WorldAccess; import one.oktw.galaxy.block.CustomBlock; import one.oktw.galaxy.block.CustomBlockEntityTicker; +import one.oktw.galaxy.block.listener.CustomBlockClickListener; +import one.oktw.galaxy.block.listener.CustomBlockNeighborUpdateListener; +import one.oktw.galaxy.block.listener.CustomBlockTickListener; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; +import java.util.Random; + @Mixin(BarrierBlock.class) -public abstract class MixinCustomBlockEntity_BarrierBlock extends AbstractBlock implements BlockEntityProvider { +public abstract class MixinCustomBlockEntity_BarrierBlock extends Block implements BlockEntityProvider { public MixinCustomBlockEntity_BarrierBlock(Settings settings) { super(settings); } @@ -60,4 +74,54 @@ public int getComparatorOutput(BlockState state, World world, BlockPos pos) { public BlockEntityTicker getTicker(World world, BlockState state, BlockEntityType type) { return new CustomBlockEntityTicker<>(); } + + @Override + public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { + BlockEntity entity = world.getBlockEntity(pos); + if (entity instanceof CustomBlockClickListener) return ((CustomBlockClickListener) entity).onClick(player, hand, hit); + return super.onUse(state, world, pos, player, hand, hit); + } + + @Override + public void neighborUpdate(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean notify) { + BlockEntity entity = world.getBlockEntity(pos); + if (entity instanceof CustomBlockNeighborUpdateListener) ((CustomBlockNeighborUpdateListener) entity).onNeighborUpdate(false); + super.neighborUpdate(state, world, pos, block, fromPos, notify); + } + + @Override + public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState newState, WorldAccess world, BlockPos pos, BlockPos posFrom) { + BlockEntity entity = world.getBlockEntity(pos); + if (entity instanceof CustomBlockNeighborUpdateListener) ((CustomBlockNeighborUpdateListener) entity).onNeighborUpdate(true); + return super.getStateForNeighborUpdate(state, direction, newState, world, pos, posFrom); + } + + @Override + public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) { + if (!state.isOf(newState.getBlock())) { + BlockEntity blockEntity = world.getBlockEntity(pos); + if (blockEntity instanceof Inventory) { + ItemScatterer.spawn(world, pos, (Inventory) blockEntity); + world.updateComparators(pos, this); + } + + super.onStateReplaced(state, world, pos, newState, moved); + } + } + + @Override + public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { + BlockEntity entity = world.getBlockEntity(pos); + if (entity instanceof CustomBlockTickListener) ((CustomBlockTickListener) entity).scheduledTick(); + + super.scheduledTick(state, world, pos, random); + } + + @Override + public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { + BlockEntity entity = world.getBlockEntity(pos); + if (entity instanceof CustomBlockTickListener) ((CustomBlockTickListener) entity).randomTick(); + + super.randomTick(state, world, pos, random); + } } diff --git a/src/main/java/one/oktw/galaxy/mixin/tweak/MixinCustomBlockEntity_Clone.java b/src/main/java/one/oktw/galaxy/mixin/tweak/MixinCustomBlockEntity_Clone.java new file mode 100644 index 000000000..c05673ad2 --- /dev/null +++ b/src/main/java/one/oktw/galaxy/mixin/tweak/MixinCustomBlockEntity_Clone.java @@ -0,0 +1,49 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.mixin.tweak; + +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.block.pattern.CachedBlockPosition; +import net.minecraft.server.command.CloneCommand; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockBox; +import net.minecraft.util.math.BlockPos; +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.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import java.util.Deque; +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; + +@Mixin(CloneCommand.class) +public class MixinCustomBlockEntity_Clone { + @Inject(method = "execute", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerWorld;setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;I)Z", ordinal = 3), locals = LocalCapture.CAPTURE_FAILSOFT) + private static void hackClone(ServerCommandSource source, BlockPos begin, BlockPos end, BlockPos destination, Predicate filter, CloneCommand.Mode mode, CallbackInfoReturnable cir, BlockBox blockBox, BlockPos blockPos, BlockBox blockBox2, ServerWorld serverWorld, List list, List list2, List list3, Deque deque, BlockPos blockPos2, List list4, List list5, int m, Iterator var19, CloneCommand.BlockInfo blockInfo) { + // Workaround clone override custom block NBT + if (blockInfo.state.getBlock() == Blocks.BARRIER) { + serverWorld.setBlockState(blockInfo.pos, Blocks.AIR.getDefaultState(), Block.NO_REDRAW | Block.FORCE_STATE); + } + } +} diff --git a/src/main/java/one/oktw/galaxy/mixin/tweak/MixinCustomBlockEntity_Structure.java b/src/main/java/one/oktw/galaxy/mixin/tweak/MixinCustomBlockEntity_Structure.java index e9eab5bfd..10425aa93 100644 --- a/src/main/java/one/oktw/galaxy/mixin/tweak/MixinCustomBlockEntity_Structure.java +++ b/src/main/java/one/oktw/galaxy/mixin/tweak/MixinCustomBlockEntity_Structure.java @@ -19,6 +19,7 @@ package one.oktw.galaxy.mixin.tweak; import com.mojang.datafixers.util.Pair; +import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.fluid.FluidState; @@ -44,6 +45,8 @@ public class MixinCustomBlockEntity_Structure { at = @At(value = "INVOKE", target = "Lnet/minecraft/world/ServerWorldAccess;setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;I)Z", ordinal = 1), locals = LocalCapture.CAPTURE_FAILSOFT) private void hackPlace(ServerWorldAccess world, BlockPos pos, BlockPos blockPos, StructurePlacementData placementData, Random random, int i, CallbackInfoReturnable cir, List list, BlockBox blockBox, List list2, List list3, List> list4, int j, int k, int l, int m, int n, int o, List list5, Iterator var19, Structure.StructureBlockInfo structureBlockInfo, BlockPos blockPos2, FluidState fluidState, BlockState blockState) { // Workaround structure barrier bug - if (structureBlockInfo.state.getBlock() == Blocks.BARRIER) world.setBlockState(blockPos2, Blocks.AIR.getDefaultState(), 20); + if (structureBlockInfo.state.getBlock() == Blocks.BARRIER) { + world.setBlockState(blockPos2, Blocks.AIR.getDefaultState(), Block.NO_REDRAW | Block.FORCE_STATE); + } } } diff --git a/src/main/java/one/oktw/galaxy/mixin/tweak/MixinFakeItem_ItemEntity.java b/src/main/java/one/oktw/galaxy/mixin/tweak/MixinFakeItem_ItemEntity.java new file mode 100644 index 000000000..d778db4f2 --- /dev/null +++ b/src/main/java/one/oktw/galaxy/mixin/tweak/MixinFakeItem_ItemEntity.java @@ -0,0 +1,68 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.mixin.tweak; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.ItemEntity; +import net.minecraft.entity.MovementType; +import net.minecraft.util.collection.ReusableStream; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import one.oktw.galaxy.mixin.interfaces.FakeEntity; +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; + +@Mixin(ItemEntity.class) +public abstract class MixinFakeItem_ItemEntity extends Entity implements FakeEntity { + private boolean fake = false; + + public MixinFakeItem_ItemEntity(EntityType type, World world) { + super(type, world); + } + + @Override + public void setFake(boolean value) { + noClip = value; + fake = value; + } + + @Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/ItemEntity;getVelocity()Lnet/minecraft/util/math/Vec3d;", ordinal = 0, shift = At.Shift.BEFORE), cancellable = true) + private void fakeTick(CallbackInfo ci) { + if (fake) { + Vec3d velocity = getVelocity(); + Vec3d adjustedVelocity = adjustMovementForCollisions(velocity, getBoundingBox(), new ReusableStream<>(world.getBlockCollisions(this, getBoundingBox().stretch(velocity)))); + // Tick velocity + move(MovementType.SELF, velocity); + + // Send velocity to client + if (!adjustedVelocity.equals(velocity)) { + velocityDirty = true; + velocityModified = true; + } + if ((this.age + this.getId()) % 4 == 0) { + velocityModified = true; + } + + ci.cancel(); + } + } +} diff --git a/src/main/java/one/oktw/galaxy/mixin/tweak/MixinOptimizeContainer_AvailableSlots2.java b/src/main/java/one/oktw/galaxy/mixin/tweak/MixinOptimizeContainer_AvailableSlots2.java new file mode 100644 index 000000000..a8a4efaa9 --- /dev/null +++ b/src/main/java/one/oktw/galaxy/mixin/tweak/MixinOptimizeContainer_AvailableSlots2.java @@ -0,0 +1,42 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.mixin.tweak; + +import net.minecraft.inventory.DoubleInventory; +import net.minecraft.inventory.Inventory; +import one.oktw.galaxy.mixin.interfaces.InventoryAvailableSlots; +import org.spongepowered.asm.mixin.Mixin; + +import java.util.Arrays; + +@Mixin(DoubleInventory.class) +public abstract class MixinOptimizeContainer_AvailableSlots2 implements InventoryAvailableSlots, Inventory { + private int[] availableSlots; + + @Override + public int[] getAvailableSlots() { + if (availableSlots == null) { + int[] array = new int[size()]; + Arrays.setAll(array, i -> i); + availableSlots = array; + } + + return availableSlots; + } +} diff --git a/src/main/java/one/oktw/galaxy/mixin/tweak/MixinPipe_ComparatorBlock.java b/src/main/java/one/oktw/galaxy/mixin/tweak/MixinPipe_ComparatorBlock.java new file mode 100644 index 000000000..58a538d28 --- /dev/null +++ b/src/main/java/one/oktw/galaxy/mixin/tweak/MixinPipe_ComparatorBlock.java @@ -0,0 +1,38 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.mixin.tweak; + +import net.minecraft.block.ComparatorBlock; +import net.minecraft.entity.decoration.ItemFrameEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; +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.CallbackInfoReturnable; + +@Mixin(ComparatorBlock.class) +public class MixinPipe_ComparatorBlock { + @Inject(method = "getAttachedItemFrame", at = @At("RETURN"), cancellable = true) + private void ignoreInvulnerableItemFrame(World world, Direction facing, BlockPos pos, CallbackInfoReturnable cir) { + ItemFrameEntity entity = cir.getReturnValue(); + if (entity != null && entity.isInvulnerable()) cir.setReturnValue(null); + } +} diff --git a/src/main/kotlin/one/oktw/galaxy/block/CustomBlock.kt b/src/main/kotlin/one/oktw/galaxy/block/CustomBlock.kt index 41c75ea22..1f778aa86 100644 --- a/src/main/kotlin/one/oktw/galaxy/block/CustomBlock.kt +++ b/src/main/kotlin/one/oktw/galaxy/block/CustomBlock.kt @@ -51,6 +51,7 @@ open class CustomBlock(final override val identifier: Identifier, val baseBlock: val TELEPORTER_CORE_BASIC = registry.register(ModelCustomBlock("teleporter_core_basic", CustomBlockItem.TELEPORTER_CORE_BASIC.createItemStack())) val TELEPORTER_CORE_ADVANCE = registry.register(ModelCustomBlock("teleporter_core_advance", CustomBlockItem.TELEPORTER_CORE_ADVANCE.createItemStack())) val TELEPORTER_FRAME = registry.register(ModelCustomBlock("teleporter_frame", CustomBlockItem.TELEPORTER_FRAME.createItemStack())) + val PIPE = registry.register(PipeBlock("pipe", CustomBlockItem.PIPE.createItemStack())) } open fun toItem(): CustomBlockItem? = null diff --git a/src/main/kotlin/one/oktw/galaxy/block/DummyBlock.kt b/src/main/kotlin/one/oktw/galaxy/block/DummyBlock.kt index de964d7d9..20bcbf2e9 100644 --- a/src/main/kotlin/one/oktw/galaxy/block/DummyBlock.kt +++ b/src/main/kotlin/one/oktw/galaxy/block/DummyBlock.kt @@ -18,10 +18,10 @@ package one.oktw.galaxy.block -import net.minecraft.util.Identifier import net.minecraft.util.math.BlockPos import one.oktw.galaxy.block.entity.DummyBlockEntity +import one.oktw.galaxy.item.CustomBlockItem -class DummyBlock : CustomBlock(Identifier("galaxy", "block/dummy")) { +class DummyBlock : ModelCustomBlock("dummy", CustomBlockItem.DUMMY.createItemStack()) { override fun createBlockEntity(pos: BlockPos) = DummyBlockEntity(blockEntityType, pos) } diff --git a/src/main/kotlin/one/oktw/galaxy/block/ModelCustomBlock.kt b/src/main/kotlin/one/oktw/galaxy/block/ModelCustomBlock.kt index 889c74e4f..d58446cb7 100644 --- a/src/main/kotlin/one/oktw/galaxy/block/ModelCustomBlock.kt +++ b/src/main/kotlin/one/oktw/galaxy/block/ModelCustomBlock.kt @@ -27,7 +27,7 @@ import one.oktw.galaxy.block.entity.ModelCustomBlockEntity import one.oktw.galaxy.item.CustomBlockItem import one.oktw.galaxy.item.CustomItemHelper -class ModelCustomBlock(identifier: Identifier, private val modelItem: ItemStack) : CustomBlock(identifier, BARRIER) { +open class ModelCustomBlock(identifier: Identifier, protected val modelItem: ItemStack) : CustomBlock(identifier, BARRIER) { constructor(id: String, modelItem: ItemStack) : this(Identifier("galaxy", "block/$id"), modelItem) override fun toItem() = modelItem.let { CustomItemHelper.getItem(it) as? CustomBlockItem } diff --git a/src/main/kotlin/one/oktw/galaxy/block/PipeBlock.kt b/src/main/kotlin/one/oktw/galaxy/block/PipeBlock.kt new file mode 100644 index 000000000..2d140f39d --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/block/PipeBlock.kt @@ -0,0 +1,30 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.block + +import net.minecraft.item.ItemStack +import net.minecraft.util.math.BlockPos +import one.oktw.galaxy.block.entity.CustomBlockEntity +import one.oktw.galaxy.block.entity.PipeBlockEntity + +class PipeBlock(id: String, modelItem: ItemStack) : ModelCustomBlock(id, modelItem) { + override fun createBlockEntity(pos: BlockPos): CustomBlockEntity { + return PipeBlockEntity(blockEntityType, pos, modelItem) + } +} diff --git a/src/main/kotlin/one/oktw/galaxy/block/entity/CustomBlockEntity.kt b/src/main/kotlin/one/oktw/galaxy/block/entity/CustomBlockEntity.kt index c8652c4f1..0711bc839 100644 --- a/src/main/kotlin/one/oktw/galaxy/block/entity/CustomBlockEntity.kt +++ b/src/main/kotlin/one/oktw/galaxy/block/entity/CustomBlockEntity.kt @@ -21,9 +21,22 @@ package one.oktw.galaxy.block.entity import net.minecraft.block.Blocks import net.minecraft.block.entity.BlockEntity import net.minecraft.block.entity.BlockEntityType +import net.minecraft.nbt.NbtCompound import net.minecraft.util.math.BlockPos // BlockEntity need extend open class CustomBlockEntity(type: BlockEntityType<*>, pos: BlockPos) : BlockEntity(type, pos, Blocks.BARRIER.defaultState) { fun getId() = BlockEntityType.getId(type)!! + + override fun readNbt(nbt: NbtCompound) { + super.readNbt(nbt) + readCopyableData(nbt) + } + + /** + * Read custom data from NBT, it will use on block clone. + * + * Also call by [readNbt]. + */ + open fun readCopyableData(nbt: NbtCompound) = Unit } diff --git a/src/main/kotlin/one/oktw/galaxy/block/entity/DummyBlockEntity.kt b/src/main/kotlin/one/oktw/galaxy/block/entity/DummyBlockEntity.kt index f2a3b73c2..7f915a79f 100644 --- a/src/main/kotlin/one/oktw/galaxy/block/entity/DummyBlockEntity.kt +++ b/src/main/kotlin/one/oktw/galaxy/block/entity/DummyBlockEntity.kt @@ -25,12 +25,16 @@ import net.minecraft.util.math.BlockPos import one.oktw.galaxy.block.CustomBlock class DummyBlockEntity(type: BlockEntityType<*>, pos: BlockPos) : CustomBlockEntity(type, pos) { - override fun readNbt(tag: NbtCompound) { - super.readNbt(tag) - tag.getString("id")?.let(Identifier::tryParse)?.let(CustomBlock.registry::get)?.let { + override fun readNbt(nbt: NbtCompound) { + super.readNbt(nbt) + nbt.getString("id")?.let(Identifier::tryParse)?.let(CustomBlock.registry::get)?.let { if (it != CustomBlock.DUMMY) { world?.removeBlockEntity(pos) - world?.addBlockEntity(it.createBlockEntity(pos)) + + val realBlockEntity = it.createBlockEntity(pos) + realBlockEntity.readCopyableData(nbt) + + world?.addBlockEntity(realBlockEntity) } } } diff --git a/src/main/kotlin/one/oktw/galaxy/block/entity/ModelCustomBlockEntity.kt b/src/main/kotlin/one/oktw/galaxy/block/entity/ModelCustomBlockEntity.kt index 91e5bdce4..813c1b1ac 100644 --- a/src/main/kotlin/one/oktw/galaxy/block/entity/ModelCustomBlockEntity.kt +++ b/src/main/kotlin/one/oktw/galaxy/block/entity/ModelCustomBlockEntity.kt @@ -33,7 +33,7 @@ import java.util.* open class ModelCustomBlockEntity(type: BlockEntityType<*>, pos: BlockPos, private val modelItem: ItemStack) : CustomBlockEntity(type, pos), CustomBlockTickListener { companion object { - private val armorStandTag = NbtCompound().apply { + private val armorStandNbt = NbtCompound().apply { putString("id", "minecraft:armor_stand") putBoolean("Invisible", true) putBoolean("Invulnerable", true) @@ -56,15 +56,15 @@ open class ModelCustomBlockEntity(type: BlockEntityType<*>, pos: BlockPos, priva } } - override fun readNbt(tag: NbtCompound) { - super.readNbt(tag) - entityUUID = (tag.get("GalaxyData") as? NbtCompound)?.getUuid("ModelEntity") ?: return + override fun readNbt(nbt: NbtCompound) { + super.readNbt(nbt) + entityUUID = (nbt.get("GalaxyData") as? NbtCompound)?.getUuid("ModelEntity") ?: return } - override fun writeNbt(tag: NbtCompound): NbtCompound { - super.writeNbt(tag) - entityUUID?.let { tag.put("GalaxyData", NbtCompound().apply { putUuid("ModelEntity", it) }) } - return tag + override fun writeNbt(nbt: NbtCompound): NbtCompound { + super.writeNbt(nbt) + entityUUID?.let { nbt.put("GalaxyData", NbtCompound().apply { putUuid("ModelEntity", it) }) } + return nbt } override fun markRemoved() { @@ -73,7 +73,7 @@ open class ModelCustomBlockEntity(type: BlockEntityType<*>, pos: BlockPos, priva } private fun spawnEntity() { - val entity: ArmorStandEntity = EntityType.getEntityFromNbt(armorStandTag, world).get() as ArmorStandEntity + val entity: ArmorStandEntity = EntityType.getEntityFromNbt(armorStandNbt, world).get() as ArmorStandEntity entity.refreshPositionAndAngles(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, 0.0F, 0.0F) entity.equipStack(EquipmentSlot.HEAD, modelItem) entity.addScoreboardTag("BLOCK") diff --git a/src/main/kotlin/one/oktw/galaxy/block/entity/PipeBlockEntity.kt b/src/main/kotlin/one/oktw/galaxy/block/entity/PipeBlockEntity.kt new file mode 100644 index 000000000..fe47316f1 --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/block/entity/PipeBlockEntity.kt @@ -0,0 +1,664 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.block.entity + +import com.google.common.collect.MapMaker +import net.fabricmc.fabric.api.util.NbtType +import net.minecraft.block.Block +import net.minecraft.block.Blocks +import net.minecraft.block.entity.BlockEntityType +import net.minecraft.entity.ItemEntity +import net.minecraft.entity.decoration.ItemFrameEntity +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.inventory.SidedInventory +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtList +import net.minecraft.server.world.ServerWorld +import net.minecraft.text.Text +import net.minecraft.util.ActionResult +import net.minecraft.util.Hand +import net.minecraft.util.ItemScatterer +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Vec3d +import one.oktw.galaxy.block.listener.CustomBlockClickListener +import one.oktw.galaxy.block.listener.CustomBlockNeighborUpdateListener +import one.oktw.galaxy.block.pipe.* +import one.oktw.galaxy.block.pipe.PipeSideMode.* +import one.oktw.galaxy.item.CustomItemHelper +import one.oktw.galaxy.item.PipeModelItem +import one.oktw.galaxy.item.PipeModelItem.Companion.PIPE_PORT_EXPORT +import one.oktw.galaxy.item.PipeModelItem.Companion.PIPE_PORT_IMPORT +import one.oktw.galaxy.item.PipeModelItem.Companion.PIPE_PORT_STORAGE +import one.oktw.galaxy.item.Tool +import one.oktw.galaxy.mixin.interfaces.FakeEntity +import one.oktw.galaxy.util.getOrCreateSubNbt +import java.util.* +import kotlin.math.max +import kotlin.math.min + +open class PipeBlockEntity(type: BlockEntityType<*>, pos: BlockPos, modelItem: ItemStack) : ModelCustomBlockEntity(type, pos, modelItem), + CustomBlockClickListener, + CustomBlockNeighborUpdateListener, + SidedInventory { + companion object { + private val POSITIVE_DIRECTION = Direction.values().filter { it.direction == Direction.AxisDirection.POSITIVE } + } + + private val pipeIO = EnumMap(Direction::class.java) + private val sideEntity = EnumMap(Direction::class.java) + private val queue = LinkedList() + private val connectedPipe = MapMaker().weakValues().concurrencyLevel(1).makeMap() + private val connectedIO = WeakHashMap>() + private var pressureCache: Int? = null + private var showingItemEntity: UUID? = null + private var showingProgress: PipeShowProgress? = null + private var redstone = 0 + private var needUpdatePipeConnect = true + + /** + * Push [ItemTransferPacket] into pipe queue. + */ + fun pushItem(packet: ItemTransferPacket, side: Direction): Boolean { + pressureCache = null + packet.progress = 0 + + return queue.offer(packet).also { if (showingItemEntity == null) showItem(packet, side) } + } + + fun tryMoveShowEntity(uuid: UUID): Boolean { + val world = (world as ServerWorld) + val entity = world.getEntity(uuid) as? ItemEntity ?: return false + return if (entity.stack == showingProgress?.packet?.item) { + showingItemEntity?.let { world.getEntity(it)?.discard() } + showingItemEntity = entity.uuid + entity.setPosition(Vec3d(pos.x + 0.5, pos.y + 0.15, pos.z + 0.5).let { + when (showingProgress!!.from) { + Direction.DOWN -> it.subtract(0.0, 0.5, 0.0) + Direction.UP -> it.add(0.0, 0.5, 0.0) + Direction.NORTH -> it.subtract(0.0, 0.0, 0.5) + Direction.SOUTH -> it.add(0.0, 0.0, 0.5) + Direction.WEST -> it.subtract(0.5, 0.0, 0.0) + Direction.EAST -> it.add(0.5, 0.0, 0.0) + } + }) + entity.velocity = when (showingProgress!!.from) { + Direction.DOWN -> Vec3d(0.0, 0.05, 0.0) + Direction.UP -> Vec3d(0.0, -0.05, 0.0) + Direction.NORTH -> Vec3d(0.0, 0.0, 0.05) + Direction.SOUTH -> Vec3d(0.0, 0.0, -0.05) + Direction.WEST -> Vec3d(0.05, 0.00, 0.0) + Direction.EAST -> Vec3d(-0.05, 0.00, 0.0) + } + true + } else false + } + + /** + * Get pipe I/O mode. + */ + fun getMode(side: Direction): PipeSideMode { + return this.pipeIO[side]?.mode ?: NONE + } + + fun getConnectedIO(exclude: Direction? = null): Map { + return HashMap(pipeIO.size + connectedIO.size).apply { + pipeIO.values.forEach { put(it, 0) } + connectedIO.forEach { (io, info) -> + info.minOfOrNull { if (it.key == exclude) Int.MAX_VALUE else it.value }?.let { if (it != Int.MAX_VALUE) put(io, it) } + } + } + } + + fun getPressure(): Int { + return queue.count { packet -> !pipeIO.values.any { it is PipeSideExport && it.canExport(packet.item) } }.also { pressureCache = it } + } + + override fun tick() { + super.tick() + if (queue.isEmpty() && showingItemEntity !== null) { + (world as ServerWorld).getEntity(showingItemEntity)?.discard() + showingItemEntity = null + showingProgress = null + this.markDirty() + } + + if (needUpdatePipeConnect) { + updatePipeConnect() + needUpdatePipeConnect = false + } + + if (queue.isNotEmpty()) this.markDirty() + + // Tick IO CoolDown and cleanup removed IO + pipeIO.entries.removeIf { (_, io) -> io.removed || io.tick() != Unit } + + val transferBuffer = queue.filterTo(ArrayList()) { packet -> min(packet.progress + 1, 20).also { packet.progress = it } == 20 } + if (transferBuffer.isNotEmpty()) { + val pushed = ArrayList(transferBuffer.size) + val exportIO by lazy { connectedIO.filterKeys { it is PipeSideExport && !it.isFull() } } + val sortedPipes by lazy { + connectedPipe.entries.sortedBy { (side, pipe) -> + var distance = Int.MAX_VALUE + exportIO.forEach { (_, info) -> + if (info[side] ?: Int.MAX_VALUE < distance) info[side]?.let { distance = it } + if (distance == 1) return@sortedBy pipe.getPressure() * 1000 + distance + } + + if (distance == Int.MAX_VALUE) Int.MAX_VALUE else pipe.getPressure() * 1000 + distance + } + } + var selfPressure = getPressure() + + transferBuffer.forEach transfer@{ packet -> + // Push request item + if (packet.destination != null) { + connectedIO.filterKeys { it.id == packet.destination }.values + .flatMapTo(HashSet()) { it.keys.mapNotNull(connectedPipe::get) } + .sortedBy { it.getPressure() } + .forEach { pipe -> + if (pipe.pushItem(packet, connectedPipe.entries.first { it.value == pipe }.key.opposite)) { + tryMoveShowEntityTo(packet, pipe) + pushed.add(packet) + selfPressure-- + return@transfer + } + } + } + + // Output + if (pipeIO.values.any { it.output(packet.item).isEmpty }) { + pushed.add(packet) + return@transfer + } + + // Low pressure and have export first + sortedPipes.forEach { (side, pipe) -> + if (pipe.getPressure() < selfPressure && + exportIO.any { (io, info) -> info.contains(side) && (io as PipeSideExport).canExport(packet.item) } && + pipe.pushItem(packet, side.opposite) + ) { + tryMoveShowEntityTo(packet, pipe) + pushed.add(packet) + selfPressure-- + return@transfer + } + } + + // Just push to low pressure pipe + sortedPipes.forEach { (side, pipe) -> + if (pipe.getPressure() < selfPressure && pipe.pushItem(packet, side.opposite)) { + tryMoveShowEntityTo(packet, pipe) + pushed.add(packet) + selfPressure-- + return@transfer + } + } + + // Retry + packet.progress = 0 + } + + if (queue.removeAll(pushed)) this.markDirty() + } + + // Input + if (queue.size < 54) { + for ((side, io) in pipeIO) { + io.input().let { + if (!it.isEmpty) { + val packet = ItemTransferPacket(io.id, it).also(queue::add) + pressureCache = null + if (showingItemEntity == null) showItem(packet, side) + } + } + if (queue.size >= 54) break + } + } + + if (queue.isNotEmpty()) this.markDirty() + + tickShowingItem() + + // TODO show item + } + + override fun markRemoved() { + super.markRemoved() + val world = world as ServerWorld + sideEntity.values.forEach { world.getEntity(it)?.discard() } + showingItemEntity?.let { world.getEntity(it)?.discard() } + queue.clear() + connectedPipe.clear() + connectedIO.clear() + } + + override fun readNbt(nbt: NbtCompound) { + super.readNbt(nbt) + val pipeData = nbt.getCompound("GalaxyData").getCompound("PipeData") + + if (sideEntity.isNotEmpty() && world is ServerWorld) { + sideEntity.values.forEach { (world as ServerWorld).getEntity(it)?.discard() } + sideEntity.clear() + } + + pipeData.getCompound("SideEntity").run { keys.forEach { sideEntity[Direction.valueOf(it)] = getUuid(it) } } + + queue.clear() + pipeData.getList("Queue", NbtType.COMPOUND).mapTo(queue) { ItemTransferPacket.createFromNbt(it as NbtCompound) } + + showingItemEntity?.let { (world as? ServerWorld)?.getEntity(it)?.discard() } + showingItemEntity = null + showingProgress = null + if (pipeData.containsUuid("showingEntity")) showingItemEntity = pipeData.getUuid("showingEntity") + } + + override fun readCopyableData(nbt: NbtCompound) { + super.readCopyableData(nbt) + + pipeIO.clear() + nbt.getCompound("GalaxyData").getCompound("PipeData").getCompound("IO").run { + keys.forEach { side -> + val direction = Direction.valueOf(side) + PipeSide.createFromNbt(this@PipeBlockEntity, direction, getCompound(side))?.let { pipeIO[direction] = it } + } + } + } + + override fun writeNbt(nbt: NbtCompound): NbtCompound { + super.writeNbt(nbt) + + nbt.getOrCreateSubNbt("GalaxyData").getOrCreateSubNbt("PipeData").apply { + put("Queue", queue.mapTo(NbtList()) { it.toNbt(NbtCompound()) }) + put("IO", NbtCompound().apply { pipeIO.forEach { (k, v) -> if (v.mode != NONE) put(k.name, v.writeNbt(NbtCompound())) } }) + put("SideEntity", NbtCompound().apply { sideEntity.forEach { (k, v) -> putUuid(k.name, v) } }) + showingItemEntity?.let { putUuid("showingEntity", it) } + } + + return nbt + } + + override fun onClick(player: PlayerEntity, hand: Hand, hit: BlockHitResult): ActionResult { + val item = player.getStackInHand(hand) + val direction = hit.side + val customItem = CustomItemHelper.getItem(item) + + if (customItem == Tool.WRENCH) { + val dropPos = pos.offset(hit.side) + when (getMode(direction)) { + IMPORT -> PIPE_PORT_IMPORT.createItemStack() + EXPORT -> PIPE_PORT_EXPORT.createItemStack() + STORAGE -> PIPE_PORT_STORAGE.createItemStack() + else -> null + }?.let { + ItemScatterer.spawn(world, dropPos.x.toDouble(), dropPos.y.toDouble(), dropPos.z.toDouble(), it) + setSideMode(direction, NONE) + return ActionResult.SUCCESS + } + + return ActionResult.PASS + } + + if (pipeIO.contains(direction)) return ActionResult.PASS + when (customItem) { + PIPE_PORT_EXPORT -> setSideMode(direction, EXPORT) + PIPE_PORT_IMPORT -> setSideMode(direction, IMPORT) + PIPE_PORT_STORAGE -> setSideMode(direction, STORAGE) + else -> return ActionResult.PASS + } + if (!player.isCreative) item.decrement(1) + + return ActionResult.SUCCESS + } + + override fun onNeighborUpdate(direct: Boolean) { + if (direct) { + updatePipeConnect() + needUpdatePipeConnect = true // Also update on next tick + } else { + redstone = world!!.getReceivedRedstonePower(pos) + } + } + + override fun scheduledTick() { + updateIOInfo() + } + + override fun clear() { + queue.clear() + Direction.values().forEach { setSideMode(it, NONE) } + } + + override fun size(): Int { + return 60 // queue 54 + io 6 + } + + override fun isEmpty() = false + + override fun getStack(slot: Int): ItemStack { + return if (slot < 54) { + queue.getOrNull(slot)?.item ?: ItemStack.EMPTY + } else { + when (pipeIO[Direction.byId(slot - 54)]?.mode) { + IMPORT -> PIPE_PORT_IMPORT.createItemStack() + EXPORT -> PIPE_PORT_EXPORT.createItemStack() + STORAGE -> PIPE_PORT_STORAGE.createItemStack() + else -> ItemStack.EMPTY + } + } + } + + override fun removeStack(slot: Int, amount: Int): ItemStack { + return ItemStack.EMPTY +// return if (slot < 54) { +// if (slot > queue.size || amount <= 0) ItemStack.EMPTY else { +// val item = queue.removeAt(slot) +// val remove = item.item.split(amount) +// if (item.item.count > 0) queue.push(item) +// return remove +// } +// } else { +// val direction = Direction.byId(slot - 54) +// val mode = side.remove(direction)?.mode +// +// if (mode != null) { +// val world = world as ServerWorld +// sideEntity[direction]?.let(world::getEntity)?.discard() +// } +// +// when (mode) { +// IMPORT -> PIPE_PORT_IMPORT.createItemStack() +// EXPORT -> PIPE_PORT_EXPORT.createItemStack() +// STORAGE -> PIPE_PORT_STORAGE.createItemStack() +// else -> ItemStack.EMPTY +// } +// } + } + + override fun removeStack(slot: Int): ItemStack { + return ItemStack.EMPTY // Not support +// return if (slot < 54) { +// if (slot >= queue.size) ItemStack.EMPTY else queue.removeAt(slot).item +// } else { +// val direction = Direction.byId(slot - 54) +// val mode = side.remove(direction)?.mode +// +// if (mode != null) { +// val world = world as ServerWorld +// sideEntity[direction]?.let(world::getEntity)?.discard() +// } +// +// when (mode) { +// IMPORT -> PIPE_PORT_IMPORT.createItemStack() +// EXPORT -> PIPE_PORT_EXPORT.createItemStack() +// STORAGE -> PIPE_PORT_STORAGE.createItemStack() +// else -> ItemStack.EMPTY +// } +// } + } + + override fun setStack(slot: Int, stack: ItemStack) { + // Not support + } + + override fun canPlayerUse(player: PlayerEntity?) = false + + // Not support IO + override fun getAvailableSlots(side: Direction?) = intArrayOf() + + override fun canInsert(slot: Int, stack: ItemStack, dir: Direction?) = false + + override fun canExtract(slot: Int, stack: ItemStack, dir: Direction) = false + + private fun setSideMode(side: Direction, mode: PipeSideMode) { + val world = world as ServerWorld + + if (pipeIO[side]?.mode == mode) return + + if (mode == NONE) { + this.pipeIO.remove(side)?.let { + it.remove() + // Remove IO entity + sideEntity.remove(side)?.let(world::getEntity)?.discard() + + // Reconnect pipe + updatePipeConnect() + cachedState.updateNeighbors(world, pos, Block.NOTIFY_LISTENERS) + } + } else { + val io = when (mode) { + IMPORT -> PipeSideImport(this, side) + EXPORT -> PipeSideExport(this, side) + STORAGE -> PipeSideStorage(this, side) + NONE -> throw IllegalStateException() + } + this.pipeIO[side] = io + spawnSideEntity(side, mode) + } + + this.markDirty() + + // Update connected pipes + connectedPipe.keys.forEach { world.blockTickScheduler.schedule(pos.offset(it), Blocks.BARRIER, 1) } + } + + private fun updatePipeConnect() { + val world = (world as ServerWorld) + val updateQueue = ArrayList(6) + for (direction in Direction.values()) { + val connectPipe = world.getBlockEntity(pos.offset(direction)) as? PipeBlockEntity + val sideMode = pipeIO[direction]?.mode + if (connectPipe != null && sideMode == null && connectPipe.getMode(direction.opposite) == NONE) { + if (connectPipe != connectedPipe[direction]) { + connectedPipe[direction] = connectPipe + updateQueue += direction + } + + // Connect pipe + if (direction in POSITIVE_DIRECTION && sideEntity[direction] == null) spawnSideEntity(direction, NONE) + } else { + connectedPipe.remove(direction)?.let { updateQueue += direction } + + // Disconnect pipe + if (sideMode == null) { + sideEntity.remove(direction)?.let(world::getEntity)?.discard() + } else if (sideEntity[direction] == null) { // TODO move check and respawn IO entity to tick + spawnSideEntity(direction, sideMode) + } + } + } + + updateQueue.forEach(::updateIOInfo) + } + + private fun updateIOInfo(from: Direction) { + val newIO = connectedPipe[from]?.getConnectedIO(from.opposite)?.filterNot { (io, _) -> io.removed || pipeIO.containsValue(io) } + val oldIO = connectedIO.filterValues { it.contains(from) } + + if (newIO?.equals(oldIO.mapValues { it.value.values.minOrNull()?.dec() }) == true) return + + val removedIO = oldIO.filter { newIO?.contains(it.key) != true } + + var updated = false + removedIO.filterValues { it.minByOrNull { (_, value) -> value }?.key == from || it.remove(from).run { isEmpty } }.keys + .let { if (connectedIO.keys.removeAll(it)) updated = true } + + newIO?.forEach { (io, distance) -> + connectedIO.getOrPut(io) { + updated = true + EnumMap(net.minecraft.util.math.Direction::class.java) + }.let { if (it.put(from, distance + 1) != distance + 1) updated = true } + } + + // Update connected pipes + if (updated) { + connectedPipe.keys.forEach { + if (it != from) world!!.blockTickScheduler.schedule(pos.offset(it), Blocks.BARRIER, 1) + } + } + } + + private fun updateIOInfo() { + val removedIO = HashMap>() + var updated = false + + for (side in Direction.values()) { + val newIO = connectedPipe[side]?.getConnectedIO(side.opposite)?.filterNot { (io, _) -> io.removed || pipeIO.containsValue(io) } + val oldIO = connectedIO.filterValues { it.contains(side) } + + if (newIO?.equals(oldIO.mapValues { it.value.values.minOrNull()?.dec() }) == true) continue + + oldIO.forEach { (io, _) -> if (newIO?.contains(io) != true) removedIO.getOrPut(io) { EnumSet.noneOf(Direction::class.java) }.add(side) } + + newIO?.forEach { (io, distance) -> + connectedIO.getOrPut(io) { EnumMap(net.minecraft.util.math.Direction::class.java) } + .let { if (it.put(side, distance + 1) != distance + 1) updated = true } + } + } + + removedIO.filter { (io, side) -> + connectedIO[io]?.minByOrNull { it.value }?.key?.let(side::contains) == true || connectedIO[io]?.keys?.apply { removeAll(side) }?.isEmpty() == true + }.keys.let { if (connectedIO.keys.removeAll(it)) updated = true } + + // Update connected pipes + if (updated) { + connectedPipe.keys.forEach { world!!.blockTickScheduler.schedule(pos.offset(it), Blocks.BARRIER, 1) } + } + } + + private fun spawnSideEntity(side: Direction, mode: PipeSideMode) { + val world = world as ServerWorld + sideEntity[side]?.let(world::getEntity)?.discard() + + val entity = ItemFrameEntity(world, pos, side.opposite).apply { + readCustomDataFromNbt(NbtCompound().also(this::writeCustomDataToNbt).apply { putBoolean("Fixed", true) }) + isInvisible = true + isInvulnerable = true + isSilent = true + addScoreboardTag("PIPE") + heldItemStack = when (mode) { + NONE -> PipeModelItem.PIPE_EXTENDED.createItemStack() + IMPORT -> PIPE_PORT_IMPORT.createItemStack() + EXPORT -> PIPE_PORT_EXPORT.createItemStack() + STORAGE -> PIPE_PORT_STORAGE.createItemStack() + } + } + if (world.spawnEntity(entity)) sideEntity[side] = entity.uuid + this.markDirty() + } + + private fun tickShowingItem() { + val entity = showingItemEntity?.let { (world as ServerWorld).getEntity(it) as? ItemEntity } + val progress = showingProgress + + if (entity == null || progress == null || !queue.contains(progress.packet)) { + entity?.discard() + showingItemEntity = null + showingProgress = null + this.markDirty() + return + } + + if (progress.to != null && progress.packet.progress < 10) { // Retry + when (progress.to) { + Direction.DOWN -> entity.setVelocity(0.0, 0.05, 0.0) + Direction.UP -> entity.setVelocity(0.0, -0.05, 0.0) + Direction.NORTH -> entity.setVelocity(0.0, 0.0, 0.05) + Direction.SOUTH -> entity.setVelocity(0.0, 0.0, -0.05) + Direction.WEST -> entity.setVelocity(0.05, 0.00, 0.0) + Direction.EAST -> entity.setVelocity(-0.05, 0.00, 0.0) + } + + progress.to = null + } + + // Tick progress + if (progress.progress < 10 || progress.to != null) progress.progress++ else progress.progress-- + entity.setCovetedItem() // prevent despawn + (entity as FakeEntity).setFake(true) + + // Find next pipe + if (progress.progress == 10) { + entity.setPosition(pos.x + 0.5, pos.y + 0.15, pos.z + 0.5) + val to = pipeIO.entries.firstOrNull { (_, io) -> io is PipeSideExport && io.canExport(progress.packet.item) }?.key + ?: connectedPipe.entries.minByOrNull { (side, pipe) -> + connectedIO.entries.minOfOrNull { (io, info) -> + (if (io is PipeSideExport && info.contains(side) && io.canExport(progress.packet.item)) info[side] else null) ?: Int.MAX_VALUE + }?.let { if (it == Int.MAX_VALUE) null else max(pipe.getPressure() - 1, 0) * 1000 + it } ?: Int.MAX_VALUE + }?.key ?: return run { + entity.setVelocity(0.0, 0.0, 0.0) + entity.setPosition(pos.x + 0.5, pos.y + 0.15, pos.z + 0.5) + } + + progress.to = to + when (progress.to) { + Direction.DOWN -> entity.setVelocity(0.0, -0.05, 0.0) + Direction.UP -> entity.setVelocity(0.0, 0.05, 0.0) + Direction.NORTH -> entity.setVelocity(0.0, 0.0, -0.05) + Direction.SOUTH -> entity.setVelocity(0.0, 0.0, 0.05) + Direction.WEST -> entity.setVelocity(-0.05, 0.00, 0.0) + Direction.EAST -> entity.setVelocity(0.05, 0.00, 0.0) + } + } + } + + private fun showItem(packet: ItemTransferPacket, from: Direction) { + if (showingItemEntity != null) return + + val startPos = Vec3d(pos.x + 0.5, pos.y + 0.15, pos.z + 0.5).let { + when (from) { + Direction.DOWN -> it.subtract(0.0, 0.5, 0.0) + Direction.UP -> it.add(0.0, 0.5, 0.0) + Direction.NORTH -> it.subtract(0.0, 0.0, 0.5) + Direction.SOUTH -> it.add(0.0, 0.0, 0.5) + Direction.WEST -> it.subtract(0.5, 0.0, 0.0) + Direction.EAST -> it.add(0.5, 0.0, 0.0) + } + } + val velocity = when (from) { + Direction.DOWN -> Vec3d(0.0, 0.05, 0.0) + Direction.UP -> Vec3d(0.0, -0.05, 0.0) + Direction.NORTH -> Vec3d(0.0, 0.0, 0.05) + Direction.SOUTH -> Vec3d(0.0, 0.0, -0.05) + Direction.WEST -> Vec3d(0.05, 0.00, 0.0) + Direction.EAST -> Vec3d(-0.05, 0.00, 0.0) + } + + val entity = ItemEntity(world, startPos.x, startPos.y, startPos.z, packet.item, velocity.x, velocity.y, velocity.z) // TODO check need copy item + entity.setNoGravity(true) + entity.setPickupDelayInfinite() + (entity as FakeEntity).setFake(true) + if ((world as ServerWorld).tryLoadEntity(entity)) { + showingItemEntity = entity.uuid + showingProgress = PipeShowProgress(packet, from) + this.markDirty() + } + } + + private fun tryMoveShowEntityTo(packet: ItemTransferPacket, pipe: PipeBlockEntity) { + if (showingProgress?.packet != packet) return + + showingItemEntity?.let { if (!pipe.tryMoveShowEntity(it)) (world as ServerWorld).getEntity(it)?.discard() } + showingItemEntity = null + showingProgress = null + this.markDirty() + } +} diff --git a/src/main/kotlin/one/oktw/galaxy/block/listener/CustomBlockClickListener.kt b/src/main/kotlin/one/oktw/galaxy/block/listener/CustomBlockClickListener.kt new file mode 100644 index 000000000..d73ad89b8 --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/block/listener/CustomBlockClickListener.kt @@ -0,0 +1,28 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.block.listener + +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.util.ActionResult +import net.minecraft.util.Hand +import net.minecraft.util.hit.BlockHitResult + +interface CustomBlockClickListener { + fun onClick(player: PlayerEntity, hand: Hand, hit: BlockHitResult): ActionResult +} diff --git a/src/main/kotlin/one/oktw/galaxy/block/listener/CustomBlockNeighborUpdateListener.kt b/src/main/kotlin/one/oktw/galaxy/block/listener/CustomBlockNeighborUpdateListener.kt new file mode 100644 index 000000000..e7b15464f --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/block/listener/CustomBlockNeighborUpdateListener.kt @@ -0,0 +1,23 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.block.listener + +interface CustomBlockNeighborUpdateListener { + fun onNeighborUpdate(direct: Boolean) +} diff --git a/src/main/kotlin/one/oktw/galaxy/block/listener/CustomBlockTickListener.kt b/src/main/kotlin/one/oktw/galaxy/block/listener/CustomBlockTickListener.kt index 8e4583f2f..6c61e7d44 100644 --- a/src/main/kotlin/one/oktw/galaxy/block/listener/CustomBlockTickListener.kt +++ b/src/main/kotlin/one/oktw/galaxy/block/listener/CustomBlockTickListener.kt @@ -20,4 +20,8 @@ package one.oktw.galaxy.block.listener interface CustomBlockTickListener { fun tick() + + fun scheduledTick() {} + + fun randomTick() {} } diff --git a/src/main/kotlin/one/oktw/galaxy/block/pipe/ItemTransferPacket.kt b/src/main/kotlin/one/oktw/galaxy/block/pipe/ItemTransferPacket.kt new file mode 100644 index 000000000..0bb745d19 --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/block/pipe/ItemTransferPacket.kt @@ -0,0 +1,49 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.block.pipe + +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtCompound +import java.util.* + +data class ItemTransferPacket( + val source: UUID, + val item: ItemStack, + val destination: UUID? = null, + var progress: Int = 0 +) { + companion object { + fun createFromNbt(nbt: NbtCompound): ItemTransferPacket { + val uuid = nbt.getUuid("source") + val item = (nbt.get("item") as NbtCompound).let(ItemStack::fromNbt) + val destination = if (nbt.containsUuid("destination")) nbt.getUuid("destination") else null + val progress = nbt.getInt("progress") + + return ItemTransferPacket(uuid, item, destination).also { it.progress = progress } + } + } + + fun toNbt(nbt: NbtCompound): NbtCompound { + nbt.putUuid("source", source) + nbt.put("item", item.writeNbt(NbtCompound())) + nbt.putInt("progress", progress) + destination?.let { nbt.putUuid("destination", it) } + return nbt + } +} diff --git a/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeShowProgress.kt b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeShowProgress.kt new file mode 100644 index 000000000..074029ae8 --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeShowProgress.kt @@ -0,0 +1,25 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.block.pipe + +import net.minecraft.util.math.Direction + +data class PipeShowProgress(val packet: ItemTransferPacket, val from: Direction, var to: Direction? = null) { + var progress: Int = 0 +} diff --git a/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSide.kt b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSide.kt new file mode 100644 index 000000000..69367270f --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSide.kt @@ -0,0 +1,68 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.block.pipe + +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtCompound +import net.minecraft.util.math.Direction +import one.oktw.galaxy.block.entity.PipeBlockEntity +import one.oktw.galaxy.block.pipe.PipeSideMode.* +import java.util.* + +abstract class PipeSide(protected val pipe: PipeBlockEntity, protected val side: Direction, val id: UUID = UUID.randomUUID(), open val mode: PipeSideMode) { + companion object { + fun createFromNbt(pipe: PipeBlockEntity, side: Direction, nbt: NbtCompound): PipeSide? { + val mode = try { + valueOf(nbt.getString("mode")) + } catch (e: IllegalArgumentException) { + null + } + return when (mode) { + STORAGE -> PipeSideStorage(pipe, side, nbt.getUuid("id")) + IMPORT -> PipeSideImport(pipe, side, nbt.getUuid("id")) + EXPORT -> PipeSideExport(pipe, side, nbt.getUuid("id")) + else -> null + } + } + } + + var removed = false + private set + + fun remove() { + removed = true + } + + open fun writeNbt(nbt: NbtCompound): NbtCompound { + nbt.putUuid("id", id) + nbt.putString("mode", mode.name) + + return nbt + } + + open fun tick() = Unit + + open fun input(): ItemStack = ItemStack.EMPTY + + open fun output(item: ItemStack): ItemStack = item + + override fun toString(): String { + return "PipeIO(id=$id, mode=$mode)" + } +} diff --git a/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSideExport.kt b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSideExport.kt new file mode 100644 index 000000000..7fd681272 --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSideExport.kt @@ -0,0 +1,81 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.block.pipe + +import net.minecraft.inventory.Inventory +import net.minecraft.inventory.SidedInventory +import net.minecraft.item.ItemStack +import net.minecraft.server.world.ServerWorld +import net.minecraft.util.math.Direction +import one.oktw.galaxy.block.entity.PipeBlockEntity +import one.oktw.galaxy.block.pipe.PipeUtil.canMergeWith +import one.oktw.galaxy.block.pipe.PipeUtil.getAvailableSlots +import java.lang.Integer.min +import java.lang.ref.WeakReference +import java.util.* + +open class PipeSideExport(pipe: PipeBlockEntity, side: Direction, id: UUID = UUID.randomUUID()) : PipeSide(pipe, side, id, PipeSideMode.EXPORT) { + private var inventoryCache = WeakReference(null) + private var fullCache: Boolean? = false + + override fun tick() { + // Null cache + inventoryCache = WeakReference(null) + fullCache = null + } + + fun canExport(item: ItemStack): Boolean { + val inventory = inventoryCache.get() ?: getInventory()?.also { inventoryCache = WeakReference(it) } ?: return false + + return inventory.getAvailableSlots(side.opposite).any { slot -> + inventory.isValid(slot, item) && + (inventory as? SidedInventory)?.canInsert(slot, item, side.opposite) != false && + inventory.getStack(slot).let { it.isEmpty || it.count < it.maxCount && it.canMergeWith(item) } + } + } + + fun isFull(): Boolean { + return fullCache ?: (inventoryCache.get() ?: getInventory())?.also { inventoryCache = WeakReference(it) }?.let { inventory -> + inventory.getAvailableSlots(side.opposite).all { slot -> inventory.getStack(slot).let { !it.isStackable || it.count >= it.maxCount } } + }?.also { fullCache = it } ?: true + } + + override fun output(item: ItemStack): ItemStack { + val inventory = getInventory() ?: return item + + inventory.getAvailableSlots(side.opposite).forEach { + if (item.isEmpty) return ItemStack.EMPTY + if (!inventory.isValid(it, item) || (inventory as? SidedInventory)?.canInsert(it, item, side.opposite) == false) return@forEach + + val stack = inventory.getStack(it) + if (stack.isEmpty) { + inventory.setStack(it, item) + return ItemStack.EMPTY + } else if (item.canMergeWith(stack)) { + val i = min(item.count, stack.maxCount - stack.count) + stack.increment(i) + item.decrement(i) + } + } + + return item.also { if (!it.isEmpty) fullCache = true } + } + + private fun getInventory() = PipeUtil.getInventory(pipe.world as ServerWorld, pipe.pos.offset(side)) +} diff --git a/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSideImport.kt b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSideImport.kt new file mode 100644 index 000000000..ecda007dd --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSideImport.kt @@ -0,0 +1,53 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.block.pipe + +import net.minecraft.inventory.SidedInventory +import net.minecraft.item.ItemStack +import net.minecraft.server.world.ServerWorld +import net.minecraft.util.math.Direction +import one.oktw.galaxy.block.entity.PipeBlockEntity +import one.oktw.galaxy.block.pipe.PipeUtil.getAvailableSlots +import java.lang.Integer.min +import java.util.* + +class PipeSideImport(pipe: PipeBlockEntity, side: Direction, id: UUID = UUID.randomUUID()) : PipeSide(pipe, side, id, PipeSideMode.IMPORT) { + var coolDown = 0 + + override fun tick() { + if (coolDown == 0) return + coolDown -= min(coolDown, 2) // TODO speed upgrade + } + + override fun input(): ItemStack { + if (coolDown > 0) return ItemStack.EMPTY + + val inventory = PipeUtil.getInventory(pipe.world as ServerWorld, pipe.pos.offset(side)) ?: return ItemStack.EMPTY + + inventory.getAvailableSlots(side.opposite).forEach { + val itemStack = inventory.getStack(it) + if (!itemStack.isEmpty && (inventory !is SidedInventory || inventory.canExtract(it, itemStack, side.opposite))) { + coolDown += 20 + return inventory.removeStack(it, 1) // TODO stack upgrade + } + } + + return ItemStack.EMPTY + } +} diff --git a/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSideMode.kt b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSideMode.kt new file mode 100644 index 000000000..132a462c9 --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSideMode.kt @@ -0,0 +1,26 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.block.pipe + +enum class PipeSideMode { + NONE, + IMPORT, + EXPORT, + STORAGE +} diff --git a/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSideStorage.kt b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSideStorage.kt new file mode 100644 index 000000000..0f138d386 --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeSideStorage.kt @@ -0,0 +1,32 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.block.pipe + +import net.minecraft.item.ItemStack +import net.minecraft.util.math.Direction +import one.oktw.galaxy.block.entity.PipeBlockEntity +import java.util.* + +class PipeSideStorage(pipe: PipeBlockEntity, side: Direction, id: UUID = UUID.randomUUID()) : PipeSideExport(pipe, side, id) { + override val mode = PipeSideMode.STORAGE + + fun requestItem(itemStack: ItemStack) { + // TODO + } +} diff --git a/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeUtil.kt b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeUtil.kt new file mode 100644 index 000000000..053dc9c5c --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/block/pipe/PipeUtil.kt @@ -0,0 +1,60 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.block.pipe + +import net.minecraft.block.ChestBlock +import net.minecraft.block.InventoryProvider +import net.minecraft.block.entity.ChestBlockEntity +import net.minecraft.inventory.Inventory +import net.minecraft.inventory.SidedInventory +import net.minecraft.item.ItemStack +import net.minecraft.server.world.ServerWorld +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import one.oktw.galaxy.mixin.interfaces.InventoryAvailableSlots + +object PipeUtil { + fun Inventory.getAvailableSlots(side: Direction): IntArray { + if (this is SidedInventory) return this.getAvailableSlots(side) + if (this is InventoryAvailableSlots) return (this as InventoryAvailableSlots).availableSlots + + return (0 until this.size()).toList().toIntArray() + } + + fun ItemStack.canMergeWith(item: ItemStack): Boolean { + return when { + this.item !== item.item -> false + this.damage != item.damage -> false + this.count > this.maxCount -> false + else -> ItemStack.areTagsEqual(this, item) + } + } + + fun getInventory(world: ServerWorld, pos: BlockPos): Inventory? { + return when (val blockEntity = world.getBlockEntity(pos)) { + is InventoryProvider -> blockEntity.getInventory(world.getBlockState(pos), world, pos) + is ChestBlockEntity -> { + val blockState = world.getBlockState(pos) + ChestBlock.getInventory(blockState.block as ChestBlock, blockState, world, pos, true) + } + is Inventory -> blockEntity + else -> null + } + } +} diff --git a/src/main/kotlin/one/oktw/galaxy/item/CustomBlockItem.kt b/src/main/kotlin/one/oktw/galaxy/item/CustomBlockItem.kt index 8c71537d3..f29c5570d 100644 --- a/src/main/kotlin/one/oktw/galaxy/item/CustomBlockItem.kt +++ b/src/main/kotlin/one/oktw/galaxy/item/CustomBlockItem.kt @@ -28,12 +28,14 @@ import one.oktw.galaxy.block.CustomBlock class CustomBlockItem private constructor(private val id: String, modelData: Int, private val name: String?) : CustomItem(Identifier("galaxy", "item/block/$id"), COMMAND_BLOCK, modelData) { companion object { + val DUMMY = registry.register(CustomBlockItem("dummy", 9999999, "block.DUMMY")) val HT_CRAFTING_TABLE = registry.register(CustomBlockItem("ht_crafting_table", 1010100, "block.HT_CRAFTING_TABLE")) val ELEVATOR = registry.register(CustomBlockItem("elevator", 1010200, "block.ELEVATOR")) val ANGEL_BLOCK = registry.register(CustomBlockItem("angel_block", 1010400, "block.ANGEL_BLOCK")) val TELEPORTER_CORE_BASIC = registry.register(CustomBlockItem("teleporter_core_basic", 1010300, "block.TELEPORTER")) val TELEPORTER_CORE_ADVANCE = registry.register(CustomBlockItem("teleporter_core_advance", 1010301, "block.TELEPORTER_ADVANCED")) val TELEPORTER_FRAME = registry.register(CustomBlockItem("teleporter_frame", 1010302, "block.TELEPORTER_FRAME")) + val PIPE = registry.register(CustomBlockItem("pipe", 1010500, "block.PIPE")) } fun getBlock(): CustomBlock { diff --git a/src/main/kotlin/one/oktw/galaxy/item/CustomItem.kt b/src/main/kotlin/one/oktw/galaxy/item/CustomItem.kt index a65fddfc4..74a90e476 100644 --- a/src/main/kotlin/one/oktw/galaxy/item/CustomItem.kt +++ b/src/main/kotlin/one/oktw/galaxy/item/CustomItem.kt @@ -39,20 +39,21 @@ abstract class CustomItem(override val identifier: Identifier, private val baseI Upgrade Weapon CustomBlockItem + PipeModelItem } } open val cacheable = true private lateinit var cacheItemStack: ItemStack - abstract fun getName(): Text? + open fun getName(): Text? = null - open fun writeCustomNbt(tag: NbtCompound) { - tag.putString("CustomItemIdentifier", identifier.toString()) + open fun writeCustomNbt(nbt: NbtCompound) { + nbt.putString("CustomItemIdentifier", identifier.toString()) } - open fun readCustomNbt(tag: NbtCompound): CustomItem { - require(tag.getString("CustomItemIdentifier") == identifier.toString()) + open fun readCustomNbt(nbt: NbtCompound): CustomItem { + require(nbt.getString("CustomItemIdentifier") == identifier.toString()) return this } @@ -60,7 +61,7 @@ abstract class CustomItem(override val identifier: Identifier, private val baseI open fun createItemStack(): ItemStack { if (cacheable && this::cacheItemStack.isInitialized) return cacheItemStack.copy() - val itemStack = ItemStack(baseItem).apply { + return ItemStack(baseItem).apply { orCreateTag.apply { putInt("HideFlags", ItemStack.TooltipSection.values().map(ItemStack.TooltipSection::getFlag).reduce { acc, i -> acc or i }) // ALL putInt("CustomModelData", modelData) @@ -69,8 +70,6 @@ abstract class CustomItem(override val identifier: Identifier, private val baseI } setCustomName(this@CustomItem.getName()) writeCustomNbt(getOrCreateSubTag("GalaxyData")) - } - - return if (cacheable) itemStack.also { cacheItemStack = it } else itemStack + }.also { if (cacheable) cacheItemStack = it.copy() } } } diff --git a/src/main/kotlin/one/oktw/galaxy/item/PipeModelItem.kt b/src/main/kotlin/one/oktw/galaxy/item/PipeModelItem.kt new file mode 100644 index 000000000..30b4d2abb --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/item/PipeModelItem.kt @@ -0,0 +1,37 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.item + +import net.minecraft.item.Items +import net.minecraft.text.Text +import net.minecraft.text.TranslatableText +import net.minecraft.util.Formatting +import net.minecraft.util.Identifier + +class PipeModelItem private constructor(id: String, modelData: Int, private val name: String) : + CustomItem(Identifier("galaxy", "item/pipe/$id"), Items.COMMAND_BLOCK, modelData) { + companion object { + val PIPE_EXTENDED = registry.register(PipeModelItem("pipe_extended", 1010501, "pipe.extended")) + val PIPE_PORT_EXPORT = registry.register(PipeModelItem("pipe_port_export", 1010502, "pipe.export")) + val PIPE_PORT_IMPORT = registry.register(PipeModelItem("pipe_port_import", 1010503, "pipe.import")) + val PIPE_PORT_STORAGE = registry.register(PipeModelItem("pipe_port_storage", 1010504, "pipe.storage")) + } + + override fun getName(): Text? = name.let(::TranslatableText).styled { it.withColor(Formatting.WHITE).withItalic(false) } +} diff --git a/src/main/kotlin/one/oktw/galaxy/item/Tool.kt b/src/main/kotlin/one/oktw/galaxy/item/Tool.kt index f7a7868a7..48333b1f2 100644 --- a/src/main/kotlin/one/oktw/galaxy/item/Tool.kt +++ b/src/main/kotlin/one/oktw/galaxy/item/Tool.kt @@ -36,8 +36,8 @@ class Tool private constructor(id: String, modelData: Int, private val name: Str override fun getName(): Text = TranslatableText(name).styled { it.withColor(Formatting.WHITE).withItalic(false) } - override fun writeCustomNbt(tag: NbtCompound) { - super.writeCustomNbt(tag) - tag.put("ToolData", NbtCompound().apply { putUuid("id", MathHelper.randomUuid()) }) + override fun writeCustomNbt(nbt: NbtCompound) { + super.writeCustomNbt(nbt) + nbt.put("ToolData", NbtCompound().apply { putUuid("id", MathHelper.randomUuid()) }) } } diff --git a/src/main/kotlin/one/oktw/galaxy/item/Weapon.kt b/src/main/kotlin/one/oktw/galaxy/item/Weapon.kt index d5ef1078c..0776229c6 100644 --- a/src/main/kotlin/one/oktw/galaxy/item/Weapon.kt +++ b/src/main/kotlin/one/oktw/galaxy/item/Weapon.kt @@ -59,8 +59,8 @@ class Weapon private constructor(id: String, modelData: Int, private val name: S override fun getName(): Text = TranslatableText(name).styled { it.withColor(Formatting.WHITE).withItalic(false) } - override fun writeCustomNbt(tag: NbtCompound) { - super.writeCustomNbt(tag) - tag.put("WeaponData", NbtCompound().apply { putUuid("id", MathHelper.randomUuid()) }) + override fun writeCustomNbt(nbt: NbtCompound) { + super.writeCustomNbt(nbt) + nbt.put("WeaponData", NbtCompound().apply { putUuid("id", MathHelper.randomUuid()) }) } } diff --git a/src/main/kotlin/one/oktw/galaxy/util/CustomRegistry.kt b/src/main/kotlin/one/oktw/galaxy/util/CustomRegistry.kt index 74f555397..710697e55 100644 --- a/src/main/kotlin/one/oktw/galaxy/util/CustomRegistry.kt +++ b/src/main/kotlin/one/oktw/galaxy/util/CustomRegistry.kt @@ -20,7 +20,6 @@ package one.oktw.galaxy.util import net.minecraft.util.Identifier import java.util.* -import kotlin.collections.HashMap open class CustomRegistry { private val registry = HashMap() diff --git a/src/main/kotlin/one/oktw/galaxy/util/NbtCompoundExt.kt b/src/main/kotlin/one/oktw/galaxy/util/NbtCompoundExt.kt new file mode 100644 index 000000000..405ae6764 --- /dev/null +++ b/src/main/kotlin/one/oktw/galaxy/util/NbtCompoundExt.kt @@ -0,0 +1,25 @@ +/* + * OKTW Galaxy Project + * Copyright (C) 2018-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package one.oktw.galaxy.util + +import net.minecraft.nbt.NbtCompound + +fun NbtCompound.getOrCreateSubNbt(key: String): NbtCompound { + return if (contains(key, 10)) getCompound(key) else NbtCompound().also { put(key, it) } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 009a75b4e..2739ac54c 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -25,6 +25,7 @@ "accessor.mixin.json", "recipe.mixin.json" ], + "accessWidener": "galaxy-tweak.accesswidener", "depends": { "minecraft": "1.17.x", "fabricloader": ">=0.8.8" diff --git a/src/main/resources/galaxy-tweak.accesswidener b/src/main/resources/galaxy-tweak.accesswidener new file mode 100644 index 000000000..06599bf24 --- /dev/null +++ b/src/main/resources/galaxy-tweak.accesswidener @@ -0,0 +1,5 @@ +accessWidener v1 named + +# CloneCommand +accessible class net/minecraft/server/command/CloneCommand$Mode +accessible class net/minecraft/server/command/CloneCommand$BlockInfo diff --git a/src/main/resources/tweak.mixin.json b/src/main/resources/tweak.mixin.json index db3daf168..e285c3f1b 100644 --- a/src/main/resources/tweak.mixin.json +++ b/src/main/resources/tweak.mixin.json @@ -5,15 +5,19 @@ "mixins": [ "MixinAsyncChunk_ThreadedAnvilChunkStorage", "MixinCustomBlockEntity_BarrierBlock", + "MixinCustomBlockEntity_Clone", "MixinCustomBlockEntity_Structure", + "MixinFakeItem_ItemEntity", "MixinFixBeacon_BeaconBlockEntity", "MixinMapExistingChunk_FilledMapItem", "MixinOneSpawnChunk_MinecraftServer", "MixinOneSpawnChunk_ServerWorld", "MixinOptimizeArmorStand_ArmorStandEntity", "MixinOptimizeContainer_AvailableSlots", + "MixinOptimizeContainer_AvailableSlots2", "MixinOptimizeContainer_HopperBlockEntity", "MixinOptimizeContainer_LootableContainerBlockEntity", + "MixinPipe_ComparatorBlock", "MixinRCON_RconBase", "MixinRCON_RconClient", "MixinThrownCountdown_Entity",