From 0f9cb916cdb4af6d74e299d8392770ebe2dee605 Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Sat, 5 Oct 2019 19:00:43 +0200 Subject: [PATCH] Advanced Logistics Headstart - Added Logistical Controllers - Added Logistical Casings - Added the Logistical Index - Added a Logistical Dial - Started laying out logic for Logistical Networks --- build.gradle | 6 +- .../java/com/simibubi/create/AllBlocks.java | 18 +- .../com/simibubi/create/AllContainers.java | 8 +- .../java/com/simibubi/create/AllEntities.java | 61 ++ .../java/com/simibubi/create/AllItems.java | 21 + .../java/com/simibubi/create/AllKeys.java | 4 +- .../java/com/simibubi/create/AllPackets.java | 6 +- .../com/simibubi/create/AllTileEntities.java | 33 +- src/main/java/com/simibubi/create/Create.java | 10 + .../com/simibubi/create/CreateClient.java | 1 + .../com/simibubi/create/CreateItemGroup.java | 2 +- src/main/java/com/simibubi/create/Events.java | 5 +- .../com/simibubi/create/ScreenResources.java | 70 +- .../foundation/block/ProperStairsBlock.java | 2 +- .../foundation/block/TileEntityExtension.java | 7 + .../gui/widgets/InterpolatedChasingValue.java | 26 + .../gui/widgets/InterpolatedValue.java | 20 + .../type/CombinedCountedItemsList.java | 36 + .../foundation/type/CountedItemsList.java | 182 +++++ .../foundation/utility/ColorHelper.java | 15 + .../create/foundation/utility/ItemHelper.java | 30 +- .../foundation/utility/TessellatorHelper.java | 7 +- .../contraptions/CachedBufferReloader.java | 2 + .../receivers/DrillTileEntity.java | 2 +- .../partialWindows/WindowInABlockBlock.java | 2 +- .../WindowInABlockTileEntity.java | 11 + .../modules/logistics/block/IExtractor.java | 22 +- .../block/{ => belts}/BeltFunnelBlock.java | 17 +- .../{ => belts}/BeltFunnelTileEntity.java | 37 +- .../{ => belts}/EntityDetectorBlock.java | 3 +- .../{ => belts}/EntityDetectorTileEntity.java | 3 +- .../EntityDetectorTileEntityRenderer.java | 3 +- .../block/{ => belts}/ExtractorBlock.java | 4 +- .../{ => belts}/ExtractorTileEntity.java | 4 +- .../ExtractorTileEntityRenderer.java | 4 +- .../{ => belts}/LinkedExtractorBlock.java | 3 +- .../LinkedExtractorTileEntity.java | 5 +- .../LinkedExtractorTileEntityRenderer.java | 5 +- .../{ => inventories}/FlexcrateBlock.java | 2 +- .../{ => inventories}/FlexcrateContainer.java | 2 +- .../{ => inventories}/FlexcrateScreen.java | 2 +- .../FlexcrateTileEntity.java | 2 +- .../logistics/entity/CardboardBoxEntity.java | 291 ++++++++ .../entity/CardboardBoxEntityRenderer.java | 76 +++ .../logistics/item/CardboardBoxItem.java | 128 ++++ .../management/LogisticalDialItem.java | 89 +++ .../management/LogisticalNetwork.java | 79 +++ .../management/LogisticalNetworkHandler.java | 50 ++ .../base/IncrementalInventoryUpdate.java | 11 + .../management/base/LogisticalActor.java | 36 + .../base/LogisticalCasingBlock.java | 261 +++++++ .../base/LogisticalCasingTileEntity.java | 131 ++++ .../base/LogisticalControllerBlock.java | 263 +++++++ .../base/LogisticalControllerItem.java | 31 + .../base/LogisticalControllerTileEntity.java | 156 +++++ ...ogisticalControllerTileEntityRenderer.java | 94 +++ ...gisticalInventoryControllerTileEntity.java | 402 +++++++++++ .../management/base/LogisticalTask.java | 45 ++ .../base/LogisticalTileEntityExtension.java | 13 + .../controller/CalculationTileEntity.java | 17 + .../controller/RequestTileEntity.java | 31 + .../controller/StorageTileEntity.java | 27 + .../controller/SupplyTileEntity.java | 22 + .../controller/TransactionsTileEntity.java | 12 + .../index/IndexContainerUpdatePacket.java | 95 +++ .../management/index/IndexOrderRequest.java | 72 ++ .../index/LogisticalIndexBlock.java | 157 +++++ .../index/LogisticalIndexContainer.java | 63 ++ .../index/LogisticalIndexScreen.java | 645 ++++++++++++++++++ .../index/LogisticalIndexTileEntity.java | 214 ++++++ .../packet/ConfigureFlexcratePacket.java | 2 +- .../create/blockstates/logistical_casing.json | 20 + .../blockstates/logistical_controller.json | 23 + .../logistical_controller_indicator.json | 20 + .../create/blockstates/logistical_index.json | 14 + .../resources/assets/create/lang/en_us.json | 4 + .../models/block/logistical_casing.json | 50 ++ .../models/block/logistical_casing_end.json | 89 +++ .../block/logistical_casing_middle.json | 76 +++ .../models/block/logistical_casing_start.json | 89 +++ .../models/block/logistical_controller.json | 22 + ...ogistical_controller_icon_calculation.json | 28 + .../logistical_controller_icon_request.json | 28 + .../logistical_controller_icon_storage.json | 28 + .../logistical_controller_icon_supply.json | 28 + ...gistical_controller_icon_transactions.json | 6 + .../create/models/block/logistical_index.json | 77 +++ .../models/item/cardboard_box_1410.json | 23 + .../models/item/cardboard_box_1416.json | 23 + .../models/item/cardboard_box_1612.json | 23 + .../models/item/cardboard_box_1616.json | 23 + .../create/models/item/logistical_casing.json | 3 + .../models/item/logistical_controller.json | 26 + .../logistical_controller_calculation.json | 38 ++ .../item/logistical_controller_request.json | 38 ++ .../item/logistical_controller_storage.json | 38 ++ .../item/logistical_controller_supply.json | 38 ++ .../logistical_controller_transactions.json | 6 + .../create/models/item/logistical_dial.json | 7 + .../create/models/item/logistical_index.json | 20 + ...CEPT_logistical_transaction_controller.pdn | Bin 0 -> 4351 bytes .../textures/block/logistical_controller.png | Bin 0 -> 615 bytes .../textures/block/logistical_icons_1.png | Bin 0 -> 367 bytes .../textures/block/logistical_icons_2.png | Bin 0 -> 308 bytes .../textures/block/logistical_index_side.png | Bin 0 -> 517 bytes .../block/logistical_index_side_overlay.png | Bin 0 -> 376 bytes .../textures/block/redstone_antenna.png | Bin 297 -> 333 bytes .../block/redstone_antenna_powered.png | Bin 324 -> 394 bytes .../assets/create/textures/gui/index.pdn | Bin 0 -> 60617 bytes .../assets/create/textures/gui/index.png | Bin 0 -> 11259 bytes .../assets/create/textures/gui/widgets.png | Bin 4492 -> 2795 bytes .../textures/item/cardboard_box_1410.png | Bin 0 -> 1044 bytes .../textures/item/cardboard_box_1416.png | Bin 0 -> 1141 bytes .../textures/item/cardboard_box_1612.png | Bin 0 -> 1109 bytes .../textures/item/cardboard_box_1616.png | Bin 0 -> 1036 bytes .../textures/item/cardboard_box_particle.png | Bin 0 -> 406 bytes .../create/textures/item/logistical_dial.png | Bin 0 -> 438 bytes .../textures/item/logistical_dial_overlay.png | Bin 0 -> 285 bytes 118 files changed, 5035 insertions(+), 91 deletions(-) create mode 100644 src/main/java/com/simibubi/create/AllEntities.java create mode 100644 src/main/java/com/simibubi/create/foundation/block/TileEntityExtension.java create mode 100644 src/main/java/com/simibubi/create/foundation/gui/widgets/InterpolatedChasingValue.java create mode 100644 src/main/java/com/simibubi/create/foundation/gui/widgets/InterpolatedValue.java create mode 100644 src/main/java/com/simibubi/create/foundation/type/CombinedCountedItemsList.java create mode 100644 src/main/java/com/simibubi/create/foundation/type/CountedItemsList.java rename src/main/java/com/simibubi/create/modules/logistics/block/{ => belts}/BeltFunnelBlock.java (87%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => belts}/BeltFunnelTileEntity.java (77%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => belts}/EntityDetectorBlock.java (98%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => belts}/EntityDetectorTileEntity.java (87%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => belts}/EntityDetectorTileEntityRenderer.java (87%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => belts}/ExtractorBlock.java (96%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => belts}/ExtractorTileEntity.java (93%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => belts}/ExtractorTileEntityRenderer.java (81%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => belts}/LinkedExtractorBlock.java (95%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => belts}/LinkedExtractorTileEntity.java (92%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => belts}/LinkedExtractorTileEntityRenderer.java (78%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => inventories}/FlexcrateBlock.java (97%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => inventories}/FlexcrateContainer.java (97%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => inventories}/FlexcrateScreen.java (98%) rename src/main/java/com/simibubi/create/modules/logistics/block/{ => inventories}/FlexcrateTileEntity.java (98%) create mode 100644 src/main/java/com/simibubi/create/modules/logistics/entity/CardboardBoxEntity.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/entity/CardboardBoxEntityRenderer.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/item/CardboardBoxItem.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/LogisticalDialItem.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/LogisticalNetwork.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/LogisticalNetworkHandler.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/base/IncrementalInventoryUpdate.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalActor.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalCasingBlock.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalCasingTileEntity.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerBlock.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerItem.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerTileEntity.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerTileEntityRenderer.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalInventoryControllerTileEntity.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalTask.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalTileEntityExtension.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/controller/CalculationTileEntity.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/controller/RequestTileEntity.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/controller/StorageTileEntity.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/controller/SupplyTileEntity.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/controller/TransactionsTileEntity.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/index/IndexContainerUpdatePacket.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/index/IndexOrderRequest.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexBlock.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexContainer.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexScreen.java create mode 100644 src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexTileEntity.java create mode 100644 src/main/resources/assets/create/blockstates/logistical_casing.json create mode 100644 src/main/resources/assets/create/blockstates/logistical_controller.json create mode 100644 src/main/resources/assets/create/blockstates/logistical_controller_indicator.json create mode 100644 src/main/resources/assets/create/blockstates/logistical_index.json create mode 100644 src/main/resources/assets/create/models/block/logistical_casing.json create mode 100644 src/main/resources/assets/create/models/block/logistical_casing_end.json create mode 100644 src/main/resources/assets/create/models/block/logistical_casing_middle.json create mode 100644 src/main/resources/assets/create/models/block/logistical_casing_start.json create mode 100644 src/main/resources/assets/create/models/block/logistical_controller.json create mode 100644 src/main/resources/assets/create/models/block/logistical_controller_icon_calculation.json create mode 100644 src/main/resources/assets/create/models/block/logistical_controller_icon_request.json create mode 100644 src/main/resources/assets/create/models/block/logistical_controller_icon_storage.json create mode 100644 src/main/resources/assets/create/models/block/logistical_controller_icon_supply.json create mode 100644 src/main/resources/assets/create/models/block/logistical_controller_icon_transactions.json create mode 100644 src/main/resources/assets/create/models/block/logistical_index.json create mode 100644 src/main/resources/assets/create/models/item/cardboard_box_1410.json create mode 100644 src/main/resources/assets/create/models/item/cardboard_box_1416.json create mode 100644 src/main/resources/assets/create/models/item/cardboard_box_1612.json create mode 100644 src/main/resources/assets/create/models/item/cardboard_box_1616.json create mode 100644 src/main/resources/assets/create/models/item/logistical_casing.json create mode 100644 src/main/resources/assets/create/models/item/logistical_controller.json create mode 100644 src/main/resources/assets/create/models/item/logistical_controller_calculation.json create mode 100644 src/main/resources/assets/create/models/item/logistical_controller_request.json create mode 100644 src/main/resources/assets/create/models/item/logistical_controller_storage.json create mode 100644 src/main/resources/assets/create/models/item/logistical_controller_supply.json create mode 100644 src/main/resources/assets/create/models/item/logistical_controller_transactions.json create mode 100644 src/main/resources/assets/create/models/item/logistical_dial.json create mode 100644 src/main/resources/assets/create/models/item/logistical_index.json create mode 100644 src/main/resources/assets/create/textures/block/CONCEPT_logistical_transaction_controller.pdn create mode 100644 src/main/resources/assets/create/textures/block/logistical_controller.png create mode 100644 src/main/resources/assets/create/textures/block/logistical_icons_1.png create mode 100644 src/main/resources/assets/create/textures/block/logistical_icons_2.png create mode 100644 src/main/resources/assets/create/textures/block/logistical_index_side.png create mode 100644 src/main/resources/assets/create/textures/block/logistical_index_side_overlay.png create mode 100644 src/main/resources/assets/create/textures/gui/index.pdn create mode 100644 src/main/resources/assets/create/textures/gui/index.png create mode 100644 src/main/resources/assets/create/textures/item/cardboard_box_1410.png create mode 100644 src/main/resources/assets/create/textures/item/cardboard_box_1416.png create mode 100644 src/main/resources/assets/create/textures/item/cardboard_box_1612.png create mode 100644 src/main/resources/assets/create/textures/item/cardboard_box_1616.png create mode 100644 src/main/resources/assets/create/textures/item/cardboard_box_particle.png create mode 100644 src/main/resources/assets/create/textures/item/logistical_dial.png create mode 100644 src/main/resources/assets/create/textures/item/logistical_dial_overlay.png diff --git a/build.gradle b/build.gradle index afa76c8a1..415ead953 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ archivesBaseName = 'create' sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' minecraft { - mappings channel: 'snapshot', version: '20190917-1.14.3' + mappings channel: 'snapshot', version: '20190927-1.14.3' runs { client { @@ -71,12 +71,12 @@ repositories { } dependencies { - minecraft 'net.minecraftforge:forge:1.14.4-28.1.6' + minecraft 'net.minecraftforge:forge:1.14.4-28.1.22' // compile against the JEI API but do not include it at runtime compileOnly fg.deobf("mezz.jei:jei-1.14.4:6.0.0.10:api") // at runtime, use the full JEI jar - runtimeOnly fg.deobf("mezz.jei:jei-1.14.4:6.0.0.10") + runtimeOnly fg.deobf("mezz.jei:jei-1.14.4:6.0.0.13") } jar { diff --git a/src/main/java/com/simibubi/create/AllBlocks.java b/src/main/java/com/simibubi/create/AllBlocks.java index b5864318a..04795b8b5 100644 --- a/src/main/java/com/simibubi/create/AllBlocks.java +++ b/src/main/java/com/simibubi/create/AllBlocks.java @@ -37,15 +37,19 @@ import com.simibubi.create.modules.curiosities.symmetry.block.CrossPlaneSymmetry import com.simibubi.create.modules.curiosities.symmetry.block.PlaneSymmetryBlock; import com.simibubi.create.modules.curiosities.symmetry.block.TriplePlaneSymmetryBlock; import com.simibubi.create.modules.gardens.CocoaLogBlock; -import com.simibubi.create.modules.logistics.block.BeltFunnelBlock; -import com.simibubi.create.modules.logistics.block.EntityDetectorBlock; -import com.simibubi.create.modules.logistics.block.ExtractorBlock; -import com.simibubi.create.modules.logistics.block.FlexcrateBlock; -import com.simibubi.create.modules.logistics.block.LinkedExtractorBlock; import com.simibubi.create.modules.logistics.block.RedstoneBridgeBlock; import com.simibubi.create.modules.logistics.block.StockswitchBlock; +import com.simibubi.create.modules.logistics.block.belts.BeltFunnelBlock; +import com.simibubi.create.modules.logistics.block.belts.EntityDetectorBlock; +import com.simibubi.create.modules.logistics.block.belts.ExtractorBlock; +import com.simibubi.create.modules.logistics.block.belts.LinkedExtractorBlock; import com.simibubi.create.modules.logistics.block.diodes.FlexpeaterBlock; import com.simibubi.create.modules.logistics.block.diodes.PulseRepeaterBlock; +import com.simibubi.create.modules.logistics.block.inventories.FlexcrateBlock; +import com.simibubi.create.modules.logistics.management.base.LogisticalCasingBlock; +import com.simibubi.create.modules.logistics.management.base.LogisticalControllerBlock; +import com.simibubi.create.modules.logistics.management.base.LogisticalControllerBlock.LogisticalControllerIndicatorBlock; +import com.simibubi.create.modules.logistics.management.index.LogisticalIndexBlock; import com.simibubi.create.modules.palettes.GlassPaneBlock; import com.simibubi.create.modules.schematics.block.CreativeCrateBlock; import com.simibubi.create.modules.schematics.block.SchematicTableBlock; @@ -125,6 +129,10 @@ public enum AllBlocks { PULSE_REPEATER(new PulseRepeaterBlock()), FLEXPEATER(new FlexpeaterBlock()), FLEXPEATER_INDICATOR(new RenderUtilityBlock()), + LOGISTICAL_CASING(new LogisticalCasingBlock()), + LOGISTICAL_CONTROLLER(new LogisticalControllerBlock()), + LOGISTICAL_CONTROLLER_INDICATOR(new LogisticalControllerIndicatorBlock()), + LOGISTICAL_INDEX(new LogisticalIndexBlock()), __CURIOSITIES__(), SYMMETRY_PLANE(new PlaneSymmetryBlock()), diff --git a/src/main/java/com/simibubi/create/AllContainers.java b/src/main/java/com/simibubi/create/AllContainers.java index fb0153905..1b4fdc896 100644 --- a/src/main/java/com/simibubi/create/AllContainers.java +++ b/src/main/java/com/simibubi/create/AllContainers.java @@ -1,7 +1,9 @@ package com.simibubi.create; -import com.simibubi.create.modules.logistics.block.FlexcrateContainer; -import com.simibubi.create.modules.logistics.block.FlexcrateScreen; +import com.simibubi.create.modules.logistics.block.inventories.FlexcrateContainer; +import com.simibubi.create.modules.logistics.block.inventories.FlexcrateScreen; +import com.simibubi.create.modules.logistics.management.index.LogisticalIndexContainer; +import com.simibubi.create.modules.logistics.management.index.LogisticalIndexScreen; import com.simibubi.create.modules.schematics.block.SchematicTableContainer; import com.simibubi.create.modules.schematics.block.SchematicTableScreen; import com.simibubi.create.modules.schematics.block.SchematicannonContainer; @@ -29,6 +31,7 @@ public enum AllContainers { SCHEMATIC_TABLE(SchematicTableContainer::new), SCHEMATICANNON(SchematicannonContainer::new), FLEXCRATE(FlexcrateContainer::new), + LOGISTICAL_INDEX(LogisticalIndexContainer::new), ; @@ -54,6 +57,7 @@ public enum AllContainers { bind(SCHEMATIC_TABLE, SchematicTableScreen::new); bind(SCHEMATICANNON, SchematicannonScreen::new); bind(FLEXCRATE, FlexcrateScreen::new); + bind(LOGISTICAL_INDEX, LogisticalIndexScreen::new); } @OnlyIn(Dist.CLIENT) diff --git a/src/main/java/com/simibubi/create/AllEntities.java b/src/main/java/com/simibubi/create/AllEntities.java new file mode 100644 index 000000000..8cb707c2a --- /dev/null +++ b/src/main/java/com/simibubi/create/AllEntities.java @@ -0,0 +1,61 @@ +package com.simibubi.create; + +import java.util.function.Function; + +import com.simibubi.create.modules.logistics.entity.CardboardBoxEntity; +import com.simibubi.create.modules.logistics.entity.CardboardBoxEntityRenderer; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityClassification; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.EntityType.Builder; +import net.minecraft.entity.EntityType.IFactory; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.fml.client.registry.RenderingRegistry; + +public enum AllEntities { + + CARDBOARD_BOX(CardboardBoxEntity::new, 30, 3, CardboardBoxEntity::build), + + ; + + private IFactory factory; + private int range; + private int updateFrequency; + private Function, EntityType.Builder> propertyBuilder; + private EntityClassification group; + + public EntityType type; + + private AllEntities(IFactory factory, int range, int updateFrequency, + Function, EntityType.Builder> propertyBuilder) { + this.factory = factory; + this.range = range; + this.updateFrequency = updateFrequency; + this.propertyBuilder = propertyBuilder; + } + + public static void register(final RegistryEvent.Register> event) { + for (AllEntities entity : values()) { + String id = entity.name().toLowerCase(); + ResourceLocation resourceLocation = new ResourceLocation(Create.ID, id); + Builder builder = EntityType.Builder.create(entity.factory, entity.group) + .setTrackingRange(entity.range).setUpdateInterval(entity.updateFrequency) + .setShouldReceiveVelocityUpdates(true); + if (entity.propertyBuilder != null) + builder = entity.propertyBuilder.apply(builder); + entity.type = builder.build(id).setRegistryName(resourceLocation); + event.getRegistry().register(entity.type); + } + + } + + @OnlyIn(value = Dist.CLIENT) + public static void registerRenderers() { + RenderingRegistry.registerEntityRenderingHandler(CardboardBoxEntity.class, CardboardBoxEntityRenderer::new); + } + +} diff --git a/src/main/java/com/simibubi/create/AllItems.java b/src/main/java/com/simibubi/create/AllItems.java index 57502af0a..3f886bdb6 100644 --- a/src/main/java/com/simibubi/create/AllItems.java +++ b/src/main/java/com/simibubi/create/AllItems.java @@ -10,6 +10,10 @@ import com.simibubi.create.modules.curiosities.placementHandgun.BuilderGunItemRe import com.simibubi.create.modules.curiosities.symmetry.SymmetryWandItem; import com.simibubi.create.modules.curiosities.symmetry.client.SymmetryWandItemRenderer; import com.simibubi.create.modules.gardens.TreeFertilizerItem; +import com.simibubi.create.modules.logistics.item.CardboardBoxItem; +import com.simibubi.create.modules.logistics.management.LogisticalDialItem; +import com.simibubi.create.modules.logistics.management.base.LogisticalControllerBlock.Type; +import com.simibubi.create.modules.logistics.management.base.LogisticalControllerItem; import com.simibubi.create.modules.schematics.item.SchematicAndQuillItem; import com.simibubi.create.modules.schematics.item.SchematicItem; @@ -76,6 +80,19 @@ public enum AllItems { DOUGH(ingredient()), PROPELLER(ingredient()), + __LOGISTICS__(), + CARDBOARD_BOX_1616(new CardboardBoxItem(standardItemProperties())), + CARDBOARD_BOX_1612(new CardboardBoxItem(standardItemProperties())), + CARDBOARD_BOX_1416(new CardboardBoxItem(standardItemProperties())), + CARDBOARD_BOX_1410(new CardboardBoxItem(standardItemProperties())), + + LOGISTICAL_DIAL(new LogisticalDialItem(standardItemProperties())), + LOGISTICAL_CONTROLLER_SUPPLY(new LogisticalControllerItem(standardItemProperties(), Type.SUPPLY)), + LOGISTICAL_CONTROLLER_REQUEST(new LogisticalControllerItem(standardItemProperties(), Type.REQUEST)), + LOGISTICAL_CONTROLLER_STORAGE(new LogisticalControllerItem(standardItemProperties(), Type.STORAGE)), + LOGISTICAL_CONTROLLER_CALCULATION(new LogisticalControllerItem(standardItemProperties(), Type.CALCULATION)), + LOGISTICAL_CONTROLLER_TRANSACTIONS(new LogisticalControllerItem(standardItemProperties(), Type.TRANSACTIONS)), + ; private static class CategoryTracker { @@ -129,6 +146,10 @@ public enum AllItems { public boolean typeOf(ItemStack stack) { return stack.getItem() == item; } + + public ItemStack asStack() { + return new ItemStack(item); + } // Client diff --git a/src/main/java/com/simibubi/create/AllKeys.java b/src/main/java/com/simibubi/create/AllKeys.java index ee857e4f6..ffb5b4367 100644 --- a/src/main/java/com/simibubi/create/AllKeys.java +++ b/src/main/java/com/simibubi/create/AllKeys.java @@ -9,7 +9,7 @@ import net.minecraftforge.fml.client.registry.ClientRegistry; public enum AllKeys { - TOOL_MENU("toolmenu", GLFW.GLFW_KEY_LEFT_ALT), + TOOL_MENU("toolmenu", GLFW.GLFW_KEY_LEFT_ALT), ACTIVATE_TOOL("", GLFW.GLFW_KEY_LEFT_CONTROL), ; @@ -30,7 +30,7 @@ public enum AllKeys { key.keybind = new KeyBinding(key.description, key.key, Create.NAME); if (!key.modifiable) continue; - + ClientRegistry.registerKeyBinding(key.keybind); } } diff --git a/src/main/java/com/simibubi/create/AllPackets.java b/src/main/java/com/simibubi/create/AllPackets.java index 4cda41e0c..7dc2d2c6d 100644 --- a/src/main/java/com/simibubi/create/AllPackets.java +++ b/src/main/java/com/simibubi/create/AllPackets.java @@ -11,6 +11,8 @@ import com.simibubi.create.modules.contraptions.receivers.constructs.ConfigureCh import com.simibubi.create.modules.curiosities.placementHandgun.BuilderGunBeamPacket; import com.simibubi.create.modules.curiosities.symmetry.SymmetryEffectPacket; import com.simibubi.create.modules.logistics.block.diodes.ConfigureFlexpeaterPacket; +import com.simibubi.create.modules.logistics.management.index.IndexContainerUpdatePacket; +import com.simibubi.create.modules.logistics.management.index.IndexOrderRequest; import com.simibubi.create.modules.logistics.packet.ConfigureFlexcratePacket; import com.simibubi.create.modules.logistics.packet.ConfigureStockswitchPacket; import com.simibubi.create.modules.schematics.packet.ConfigureSchematicannonPacket; @@ -35,10 +37,12 @@ public enum AllPackets { CONFIGURE_FLEXPEATER(ConfigureFlexpeaterPacket.class, ConfigureFlexpeaterPacket::new), PLACE_SCHEMATIC(SchematicPlacePacket.class, SchematicPlacePacket::new), UPLOAD_SCHEMATIC(SchematicUploadPacket.class, SchematicUploadPacket::new), - + INDEX_ORDER_REQUEST(IndexOrderRequest.class, IndexOrderRequest::new), + // Server to Client SYMMETRY_EFFECT(SymmetryEffectPacket.class, SymmetryEffectPacket::new), BEAM_EFFECT(BuilderGunBeamPacket.class, BuilderGunBeamPacket::new), + INDEX_CONTAINER_UPDATE(IndexContainerUpdatePacket.class, IndexContainerUpdatePacket::new), ; diff --git a/src/main/java/com/simibubi/create/AllTileEntities.java b/src/main/java/com/simibubi/create/AllTileEntities.java index 966e1955e..82ae2d708 100644 --- a/src/main/java/com/simibubi/create/AllTileEntities.java +++ b/src/main/java/com/simibubi/create/AllTileEntities.java @@ -30,19 +30,28 @@ import com.simibubi.create.modules.contraptions.relays.SplitShaftTileEntityRende import com.simibubi.create.modules.contraptions.relays.belt.BeltTileEntity; import com.simibubi.create.modules.contraptions.relays.belt.BeltTileEntityRenderer; import com.simibubi.create.modules.curiosities.partialWindows.WindowInABlockTileEntity; -import com.simibubi.create.modules.logistics.block.BeltFunnelTileEntity; -import com.simibubi.create.modules.logistics.block.EntityDetectorTileEntity; -import com.simibubi.create.modules.logistics.block.EntityDetectorTileEntityRenderer; -import com.simibubi.create.modules.logistics.block.ExtractorTileEntity; -import com.simibubi.create.modules.logistics.block.ExtractorTileEntityRenderer; -import com.simibubi.create.modules.logistics.block.FlexcrateTileEntity; -import com.simibubi.create.modules.logistics.block.LinkedExtractorTileEntity; -import com.simibubi.create.modules.logistics.block.LinkedExtractorTileEntityRenderer; import com.simibubi.create.modules.logistics.block.LinkedTileEntityRenderer; import com.simibubi.create.modules.logistics.block.RedstoneBridgeTileEntity; import com.simibubi.create.modules.logistics.block.StockswitchTileEntity; +import com.simibubi.create.modules.logistics.block.belts.BeltFunnelTileEntity; +import com.simibubi.create.modules.logistics.block.belts.EntityDetectorTileEntity; +import com.simibubi.create.modules.logistics.block.belts.EntityDetectorTileEntityRenderer; +import com.simibubi.create.modules.logistics.block.belts.ExtractorTileEntity; +import com.simibubi.create.modules.logistics.block.belts.ExtractorTileEntityRenderer; +import com.simibubi.create.modules.logistics.block.belts.LinkedExtractorTileEntity; +import com.simibubi.create.modules.logistics.block.belts.LinkedExtractorTileEntityRenderer; import com.simibubi.create.modules.logistics.block.diodes.FlexpeaterTileEntity; import com.simibubi.create.modules.logistics.block.diodes.FlexpeaterTileEntityRenderer; +import com.simibubi.create.modules.logistics.block.inventories.FlexcrateTileEntity; +import com.simibubi.create.modules.logistics.management.base.LogisticalCasingTileEntity; +import com.simibubi.create.modules.logistics.management.base.LogisticalControllerTileEntity; +import com.simibubi.create.modules.logistics.management.base.LogisticalControllerTileEntityRenderer; +import com.simibubi.create.modules.logistics.management.controller.CalculationTileEntity; +import com.simibubi.create.modules.logistics.management.controller.RequestTileEntity; +import com.simibubi.create.modules.logistics.management.controller.StorageTileEntity; +import com.simibubi.create.modules.logistics.management.controller.SupplyTileEntity; +import com.simibubi.create.modules.logistics.management.controller.TransactionsTileEntity; +import com.simibubi.create.modules.logistics.management.index.LogisticalIndexTileEntity; import com.simibubi.create.modules.schematics.block.SchematicTableTileEntity; import com.simibubi.create.modules.schematics.block.SchematicannonRenderer; import com.simibubi.create.modules.schematics.block.SchematicannonTileEntity; @@ -95,6 +104,13 @@ public enum AllTileEntities { BELT_FUNNEL(BeltFunnelTileEntity::new, AllBlocks.BELT_FUNNEL), ENTITY_DETECTOR(EntityDetectorTileEntity::new, AllBlocks.ENTITY_DETECTOR), FLEXPEATER(FlexpeaterTileEntity::new, AllBlocks.FLEXPEATER), + LOGISTICAL_CASING(LogisticalCasingTileEntity::new, AllBlocks.LOGISTICAL_CASING), + LOGISTICAL_SUPPLY_CONTROLLER(SupplyTileEntity::new, AllBlocks.LOGISTICAL_CONTROLLER), + LOGISTICAL_REQUEST_CONTROLLER(RequestTileEntity::new, AllBlocks.LOGISTICAL_CONTROLLER), + LOGISTICAL_STORAGE_CONTROLLER(StorageTileEntity::new, AllBlocks.LOGISTICAL_CONTROLLER), + LOGISTICAL_CALCULATION_CONTROLLER(CalculationTileEntity::new, AllBlocks.LOGISTICAL_CONTROLLER), + LOGISTICAL_TRANSATIONS_CONTROLLER(TransactionsTileEntity::new, AllBlocks.LOGISTICAL_CONTROLLER), + LOGISTICAL_INDEX(LogisticalIndexTileEntity::new, AllBlocks.LOGISTICAL_INDEX), // Curiosities WINDOW_IN_A_BLOCK(WindowInABlockTileEntity::new, AllBlocks.WINDOW_IN_A_BLOCK), @@ -148,6 +164,7 @@ public enum AllTileEntities { bind(EntityDetectorTileEntity.class, new EntityDetectorTileEntityRenderer()); bind(MechanicalPressTileEntity.class, new MechanicalPressTileEntityRenderer()); bind(FlexpeaterTileEntity.class, new FlexpeaterTileEntityRenderer()); + bind(LogisticalControllerTileEntity.class, new LogisticalControllerTileEntityRenderer()); } @OnlyIn(Dist.CLIENT) diff --git a/src/main/java/com/simibubi/create/Create.java b/src/main/java/com/simibubi/create/Create.java index 6fdf530b7..c22e66c3e 100644 --- a/src/main/java/com/simibubi/create/Create.java +++ b/src/main/java/com/simibubi/create/Create.java @@ -6,9 +6,11 @@ import org.apache.logging.log4j.Logger; import com.simibubi.create.modules.ModuleLoadedCondition; import com.simibubi.create.modules.contraptions.receivers.constructs.MovingConstructHandler; import com.simibubi.create.modules.logistics.FrequencyHandler; +import com.simibubi.create.modules.logistics.management.LogisticalNetworkHandler; import com.simibubi.create.modules.schematics.ServerSchematicLoader; import net.minecraft.block.Block; +import net.minecraft.entity.EntityType; import net.minecraft.item.Item; import net.minecraft.item.ItemGroup; import net.minecraft.item.crafting.IRecipeSerializer; @@ -35,6 +37,7 @@ public class Create { public static ServerSchematicLoader schematicReceiver; public static FrequencyHandler frequencyHandler; public static MovingConstructHandler constructHandler; + public static LogisticalNetworkHandler logisticalNetworkHandler; public static ModConfig config; @@ -48,6 +51,8 @@ public class Create { schematicReceiver = new ServerSchematicLoader(); frequencyHandler = new FrequencyHandler(); constructHandler = new MovingConstructHandler(); + logisticalNetworkHandler = new LogisticalNetworkHandler(); + CraftingHelper.register(new ModuleLoadedCondition.Serializer()); AllPackets.registerPackets(); } @@ -67,6 +72,11 @@ public class Create { public static void registerRecipes(RegistryEvent.Register> event) { AllRecipes.register(event); } + + @SubscribeEvent + public static void registerEntities(final RegistryEvent.Register> event) { + AllEntities.register(event); + } @SubscribeEvent public static void createConfigs(ModConfig.ModConfigEvent event) { diff --git a/src/main/java/com/simibubi/create/CreateClient.java b/src/main/java/com/simibubi/create/CreateClient.java index 1b1323e44..7005ecdf6 100644 --- a/src/main/java/com/simibubi/create/CreateClient.java +++ b/src/main/java/com/simibubi/create/CreateClient.java @@ -54,6 +54,7 @@ public class CreateClient { AllTileEntities.registerRenderers(); AllItems.registerColorHandlers(); AllBlocks.registerColorHandlers(); + AllEntities.registerRenderers(); IResourceManager resourceManager = Minecraft.getInstance().getResourceManager(); if (resourceManager instanceof IReloadableResourceManager) diff --git a/src/main/java/com/simibubi/create/CreateItemGroup.java b/src/main/java/com/simibubi/create/CreateItemGroup.java index 4fdd1e99c..b15e55879 100644 --- a/src/main/java/com/simibubi/create/CreateItemGroup.java +++ b/src/main/java/com/simibubi/create/CreateItemGroup.java @@ -15,7 +15,7 @@ public final class CreateItemGroup extends ItemGroup { @Override public ItemStack createIcon() { - return new ItemStack(AllItems.SYMMETRY_WAND.get()); + return new ItemStack(AllBlocks.COGWHEEL.get()); } @Override diff --git a/src/main/java/com/simibubi/create/Events.java b/src/main/java/com/simibubi/create/Events.java index 368b1c013..145592020 100644 --- a/src/main/java/com/simibubi/create/Events.java +++ b/src/main/java/com/simibubi/create/Events.java @@ -45,6 +45,7 @@ public class Events { IWorld world = event.getWorld(); Create.frequencyHandler.onLoadWorld(world); Create.constructHandler.onLoadWorld(world); + Create.logisticalNetworkHandler.onLoadWorld(world); } @SubscribeEvent @@ -52,6 +53,7 @@ public class Events { IWorld world = event.getWorld(); Create.frequencyHandler.onUnloadWorld(world); Create.constructHandler.onUnloadWorld(world); + Create.logisticalNetworkHandler.onUnloadWorld(world); } @SubscribeEvent @@ -81,7 +83,8 @@ public class Events { if (AllBlocks.WINDOW_IN_A_BLOCK.typeOf(blockState)) return; - world.setBlockState(pos, AllBlocks.WINDOW_IN_A_BLOCK.get().getDefaultState()); + BlockState defaultState = AllBlocks.WINDOW_IN_A_BLOCK.get().getDefaultState(); + world.setBlockState(pos, defaultState); TileEntity te = world.getTileEntity(pos); if (te != null && te instanceof WindowInABlockTileEntity) { WindowInABlockTileEntity wte = (WindowInABlockTileEntity) te; diff --git a/src/main/java/com/simibubi/create/ScreenResources.java b/src/main/java/com/simibubi/create/ScreenResources.java index ede2380d6..38baf178c 100644 --- a/src/main/java/com/simibubi/create/ScreenResources.java +++ b/src/main/java/com/simibubi/create/ScreenResources.java @@ -5,16 +5,16 @@ import net.minecraft.client.gui.AbstractGui; import net.minecraft.util.ResourceLocation; public enum ScreenResources { - + // Inventories PLAYER_INVENTORY("player_inventory.png", 176, 108), WAND_SYMMETRY("wand_symmetry.png", 207, 58), PLACEMENT_GUN("placement_handgun.png", 217, 70), - + SCHEMATIC_TABLE("schematic_table.png", 207, 89), SCHEMATIC_TABLE_PROGRESS("schematic_table.png", 209, 0, 24, 17), SCHEMATIC("schematic.png", 207, 95), - + SCHEMATICANNON("schematicannon.png", 247, 161), SCHEMATICANNON_PROGRESS("schematicannon.png", 0, 161, 121, 16), SCHEMATICANNON_PROGRESS_2("schematicannon.png", 122, 161, 16, 15), @@ -23,7 +23,7 @@ public enum ScreenResources { FLEXCRATE("flex_crate_and_stockpile_switch.png", 125, 129), FLEXCRATE_LOCKED_SLOT("flex_crate_and_stockpile_switch.png", 138, 0, 18, 18), - + STOCKSWITCH("flex_crate_and_stockpile_switch.png", 0, 129, 205, 93), STOCKSWITCH_INTERVAL("flex_crate_and_stockpile_switch.png", 0, 222, 198, 17), STOCKSWITCH_INTERVAL_END("flex_crate_and_stockpile_switch.png", 0, 239, 198, 17), @@ -31,14 +31,36 @@ public enum ScreenResources { STOCKSWITCH_CURSOR_OFF("flex_crate_and_stockpile_switch.png", 226, 129, 8, 21), STOCKSWITCH_BOUND_LEFT("flex_crate_and_stockpile_switch.png", 234, 129, 7, 21), STOCKSWITCH_BOUND_RIGHT("flex_crate_and_stockpile_switch.png", 241, 129, 7, 21), - + + // Logistical Index + INDEX_TOP("index.png", 41, 0, 174, 22), + INDEX_TOP_TRIM("index.png", 41, 22, 174, 6), + INDEX_MIDDLE("index.png", 41, 28, 183, 178), + INDEX_BOTTOM_TRIM("index.png", 41, 206, 174, 6), + INDEX_BOTTOM("index.png", 41, 212, 181, 44), + INDEX_SCROLLER_TOP("index.png", 224, 31, 10, 6), + INDEX_SCROLLER_MIDDLE("index.png", 224, 37, 10, 6), + INDEX_SCROLLER_BOTTOM("index.png", 224, 43, 10, 6), + INDEX_TAB("index.png", 0, 55, 22, 22), + INDEX_TAB_ACTIVE("index.png", 0, 77, 22, 22), + INDEX_SEARCH("index.png", 0, 99, 28, 19), + INDEX_SEARCH_OVERLAY("widgets.png", 0, 81, 176, 20), + + SLOT_FRAME("index.png", 0, 118, 18, 18), + SLOT_INNER("index.png", 18, 118, 18, 18), + DISABLED_SLOT_FRAME("index.png", 0, 136, 18, 18), + DISABLED_SLOT_INNER("index.png", 18, 136, 18, 18), + CRAFTY_SLOT_FRAME("index.png", 0, 154, 18, 18), + CRAFTY_SLOT_INNER("index.png", 18, 154, 18, 18), + SELECTED_SLOT_INNER("index.png", 18, 172, 18, 18), + // JEI CRUSHING_RECIPE("recipes1.png", 177, 109), FAN_RECIPE("recipes1.png", 0, 128, 177, 109), BLOCKZAPPER_UPGRADE_RECIPE("recipes2.png", 144, 66), PRESSER_RECIPE("recipes2.png", 0, 108, 177, 109), WASHING_RECIPE("recipes3.png", 177, 109), - + // Widgets PALETTE_BUTTON("palette_picker.png", 0, 236, 20, 20), TEXT_INPUT("widgets.png", 0, 28, 194, 47), @@ -51,9 +73,9 @@ public enum ScreenResources { INDICATOR_YELLOW("widgets.png", 18, 23, 18, 5), INDICATOR_RED("widgets.png", 36, 23, 18, 5), GRAY("background.png", 0, 0, 16, 16), - + BLUEPRINT_SLOT("widgets.png", 90, 0, 24, 24), - + // Icons ICON_NONE("icons.png", 16, 16, 16, 16), ICON_ADD("icons.png", 16, 16), @@ -61,28 +83,28 @@ public enum ScreenResources { ICON_3x3("icons.png", 32, 0, 16, 16), ICON_TARGET("icons.png", 48, 0, 16, 16), ICON_CONFIRM("icons.png", 0, 16, 16, 16), - + ICON_OPEN_FOLDER("icons.png", 32, 16, 16, 16), ICON_REFRESH("icons.png", 48, 16, 16, 16), - + ICON_DONT_REPLACE("icons.png", 0, 32, 16, 16), ICON_REPLACE_SOLID("icons.png", 16, 32, 16, 16), ICON_REPLACE_ANY("icons.png", 32, 32, 16, 16), ICON_REPLACE_EMPTY("icons.png", 48, 32, 16, 16), - + ICON_TOOL_DEPLOY("icons.png", 0, 48, 16, 16), ICON_SKIP_MISSING("icons.png", 16, 48, 16, 16), ICON_SKIP_TILES("icons.png", 32, 48, 16, 16), - + ICON_TOOL_MOVE_XZ("icons.png", 0, 64, 16, 16), ICON_TOOL_MOVE_Y("icons.png", 16, 64, 16, 16), ICON_TOOL_ROTATE("icons.png", 32, 64, 16, 16), ICON_TOOL_MIRROR("icons.png", 48, 64, 16, 16), - + ICON_PLAY("icons.png", 0, 80, 16, 16), ICON_PAUSE("icons.png", 16, 80, 16, 16), ICON_STOP("icons.png", 32, 80, 16, 16), - + ICON_PATTERN_SOLID("icons.png", 0, 96, 16, 16), ICON_PATTERN_CHECKERED("icons.png", 16, 96, 16, 16), ICON_PATTERN_CHECKERED_INVERSED("icons.png", 32, 96, 16, 16), @@ -91,29 +113,31 @@ public enum ScreenResources { ICON_PATTERN_CHANCE_75("icons.png", 16, 112, 16, 16), ICON_FOLLOW_DIAGONAL("icons.png", 32, 112, 16, 16), ICON_FOLLOW_MATERIAL("icons.png", 48, 112, 16, 16), - + ; - + public static final int FONT_COLOR = 0x575F7A; - + public final ResourceLocation location; public int width, height; public int startX, startY; - + private ScreenResources(String location, int width, int height) { this(location, 0, 0, width, height); } - + private ScreenResources(String location, int startX, int startY, int width, int height) { this.location = new ResourceLocation(Create.ID, "textures/gui/" + location); - this.width = width; this.height = height; - this.startX = startX; this.startY = startY; + this.width = width; + this.height = height; + this.startX = startX; + this.startY = startY; } - + public void bind() { Minecraft.getInstance().getTextureManager().bindTexture(location); } - + public void draw(AbstractGui screen, int i, int j) { bind(); screen.blit(i, j, startX, startY, width, height); diff --git a/src/main/java/com/simibubi/create/foundation/block/ProperStairsBlock.java b/src/main/java/com/simibubi/create/foundation/block/ProperStairsBlock.java index 5efbf5045..35892d1fd 100644 --- a/src/main/java/com/simibubi/create/foundation/block/ProperStairsBlock.java +++ b/src/main/java/com/simibubi/create/foundation/block/ProperStairsBlock.java @@ -6,7 +6,7 @@ import net.minecraft.block.StairsBlock; public class ProperStairsBlock extends StairsBlock { public ProperStairsBlock(Block block) { - super(block.getDefaultState(), Properties.from(block)); + super(() -> block.getDefaultState(), Properties.from(block)); } } diff --git a/src/main/java/com/simibubi/create/foundation/block/TileEntityExtension.java b/src/main/java/com/simibubi/create/foundation/block/TileEntityExtension.java new file mode 100644 index 000000000..7a99d10ab --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/block/TileEntityExtension.java @@ -0,0 +1,7 @@ +package com.simibubi.create.foundation.block; + +import net.minecraft.tileentity.TileEntity; + +public abstract class TileEntityExtension { + +} diff --git a/src/main/java/com/simibubi/create/foundation/gui/widgets/InterpolatedChasingValue.java b/src/main/java/com/simibubi/create/foundation/gui/widgets/InterpolatedChasingValue.java new file mode 100644 index 000000000..e18b5f6d4 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/gui/widgets/InterpolatedChasingValue.java @@ -0,0 +1,26 @@ +package com.simibubi.create.foundation.gui.widgets; + +public class InterpolatedChasingValue extends InterpolatedValue { + + float speed = 0.5f; + float target = 0; + float eps = 1 / 4096f; + + public void tick() { + float diff = target - value; + if (Math.abs(diff) < eps) + return; + set(value + (diff) * speed); + } + + public InterpolatedChasingValue withSpeed(float speed) { + this.speed = speed; + return this; + } + + public InterpolatedChasingValue target(float target) { + this.target = target; + return this; + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/gui/widgets/InterpolatedValue.java b/src/main/java/com/simibubi/create/foundation/gui/widgets/InterpolatedValue.java new file mode 100644 index 000000000..6739f39af --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/gui/widgets/InterpolatedValue.java @@ -0,0 +1,20 @@ +package com.simibubi.create.foundation.gui.widgets; + +import net.minecraft.util.math.MathHelper; + +public class InterpolatedValue { + + public float value = 0; + public float lastValue = 0; + + public InterpolatedValue set(float value) { + lastValue = this.value; + this.value = value; + return this; + } + + public float get(float partialTicks) { + return MathHelper.lerp(partialTicks, lastValue, value); + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/type/CombinedCountedItemsList.java b/src/main/java/com/simibubi/create/foundation/type/CombinedCountedItemsList.java new file mode 100644 index 000000000..c020e360e --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/type/CombinedCountedItemsList.java @@ -0,0 +1,36 @@ +package com.simibubi.create.foundation.type; + +import java.util.HashMap; +import java.util.Map; + +public class CombinedCountedItemsList { + + protected Map lists = new HashMap<>(); + protected CountedItemsList combined = new CountedItemsList(); + boolean combinedListDirty = true; + + public void add(T key, CountedItemsList list) { + lists.put(key, list); + combinedListDirty = true; + } + + public void remove(T key) { + lists.remove(key); + combinedListDirty = true; + } + + public void clear() { + lists.clear(); + combinedListDirty = true; + } + + public CountedItemsList get() { + if (combinedListDirty) { + combined = new CountedItemsList(); + lists.values().forEach(list -> list.getFlattenedList().forEach(combined::add)); + combinedListDirty = false; + } + return combined; + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/type/CountedItemsList.java b/src/main/java/com/simibubi/create/foundation/type/CountedItemsList.java new file mode 100644 index 000000000..bb63a5e58 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/type/CountedItemsList.java @@ -0,0 +1,182 @@ +package com.simibubi.create.foundation.type; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Set; + +import com.google.common.collect.Sets; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.ItemHandlerHelper; + +public class CountedItemsList { + + Map> items = new HashMap<>(); + + Collection flattenedList = new PriorityQueue<>(); + boolean flattenedListDirty = true; + + public CountedItemsList() { + } + + public CountedItemsList(IItemHandler inventory) { + for (int slot = 0; slot < inventory.getSlots(); slot++) + add(inventory.extractItem(slot, inventory.getSlotLimit(slot), true)); + } + + public List getStacksToUpdate(CountedItemsList newList) { + List changes = new ArrayList<>(); + for (Item key : Sets.union(items.keySet(), newList.items.keySet())) { + Set currentSet = items.get(key); + Set newSet = newList.items.get(key); + + if (currentSet == null) { + changes.addAll(newSet); + continue; + } + if (newSet == null) { + currentSet.forEach(entry -> changes.add(new ItemStackEntry(entry.stack, 0))); + continue; + } + + Set remainderNew = new HashSet<>(newSet); + OuterLoop: for (ItemStackEntry entry : currentSet) { + for (ItemStackEntry newEntry : newSet) { + if (!entry.matches(newEntry.stack)) + continue; + remainderNew.remove(newEntry); + if (entry.amount != newEntry.amount) + changes.add(newEntry); + continue OuterLoop; + } + changes.add(new ItemStackEntry(entry.stack, 0)); + } + changes.addAll(remainderNew); + } + + return changes; + } + + public void add(ItemStack stack) { + add(stack, stack.getCount()); + } + + public void add(ItemStackEntry entry) { + add(entry.stack, entry.amount); + } + + public void add(ItemStack stack, int amount) { + if (stack.isEmpty()) + return; + + Set stackSet = getOrCreateItemSet(stack); + for (ItemStackEntry entry : stackSet) { + if (!entry.matches(stack)) + continue; + entry.amount += amount; + return; + } + stackSet.add(new ItemStackEntry(stack, amount)); + flattenedListDirty = true; + } + + public boolean contains(ItemStack stack) { + return getItemCount(stack) != 0; + } + + public int getItemCount(ItemStack stack) { + Set stackSet = getItemSet(stack); + if (stackSet == null) + return 0; + for (ItemStackEntry entry : stackSet) { + if (!entry.matches(stack)) + continue; + return entry.amount; + } + return 0; + } + + public void setItemCount(ItemStack stack, int amount) { + remove(stack); + add(stack, amount); + } + + public void remove(ItemStack stack) { + Set stackSet = getItemSet(stack); + if (stackSet == null) + return; + + for (Iterator iterator = stackSet.iterator(); iterator.hasNext();) { + ItemStackEntry entry = iterator.next(); + if (entry.matches(stack)) { + iterator.remove(); + flattenedListDirty = true; + return; + } + } + } + + public Collection getFlattenedList() { + if (flattenedListDirty) { + flattenedList.clear(); + items.values().forEach(set -> flattenedList.addAll(set)); + flattenedListDirty = false; + } + return flattenedList; + } + + private Set getItemSet(ItemStack stack) { + return items.get(stack.getItem()); + } + + private Set getOrCreateItemSet(ItemStack stack) { + if (!items.containsKey(stack.getItem())) + items.put(stack.getItem(), new HashSet<>()); + return getItemSet(stack); + } + + public class ItemStackEntry implements Comparable { + public ItemStack stack; + public int amount; + + public ItemStackEntry(ItemStack stack) { + this(stack, stack.getCount()); + } + + public ItemStackEntry(CompoundNBT nbt) { + this(ItemStack.read(nbt.getCompound("Item")), nbt.getInt("Amount")); + } + + public ItemStackEntry(ItemStack stack, int amount) { + this.stack = stack.copy(); + this.amount = amount; + } + + public boolean matches(ItemStack other) { + return ItemHandlerHelper.canItemStacksStack(other, stack); + } + + public CompoundNBT serializeNBT() { + CompoundNBT nbt = new CompoundNBT(); + nbt.put("Item", stack.serializeNBT()); + nbt.putInt("Amount", amount); + return nbt; + } + + @Override + public int compareTo(ItemStackEntry o) { + return amount - o.amount; + } + + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/utility/ColorHelper.java b/src/main/java/com/simibubi/create/foundation/utility/ColorHelper.java index 8b48f2738..d987cbf87 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/ColorHelper.java +++ b/src/main/java/com/simibubi/create/foundation/utility/ColorHelper.java @@ -1,5 +1,7 @@ package com.simibubi.create.foundation.utility; +import com.mojang.blaze3d.platform.GlStateManager; + public class ColorHelper { public static int rainbowColor(int timeStep) { @@ -39,4 +41,17 @@ public class ColorHelper { return color; } + public static void glColor(int color) { + color = mixColors(color, 0xFFFFFF, .5f); + int r = (color >> 16); + int g = (color >> 8) & 0xFF; + int b = color & 0xFF; + + GlStateManager.color4f(r / 256f, g / 256f, b / 256f, 1); + } + + public static void glResetColor() { + GlStateManager.color4f(1, 1, 1, 1); + } + } diff --git a/src/main/java/com/simibubi/create/foundation/utility/ItemHelper.java b/src/main/java/com/simibubi/create/foundation/utility/ItemHelper.java index 79557ac17..ae91f1ba7 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/ItemHelper.java +++ b/src/main/java/com/simibubi/create/foundation/utility/ItemHelper.java @@ -4,6 +4,8 @@ import java.util.ArrayList; import java.util.List; import net.minecraft.item.ItemStack; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.ItemHandlerHelper; public class ItemHelper { @@ -11,13 +13,37 @@ public class ItemHelper { List stacks = new ArrayList<>(); ItemStack result = out.copy(); result.setCount(in.getCount() * out.getCount()); - + while (result.getCount() > result.getMaxStackSize()) { stacks.add(result.split(result.getMaxStackSize())); } - + stacks.add(result); return stacks; } + public static void addToList(ItemStack stack, List stacks) { + for (ItemStack s : stacks) { + if (!ItemHandlerHelper.canItemStacksStack(stack, s)) + continue; + int transferred = Math.min(s.getMaxStackSize() - s.getCount(), stack.getCount()); + s.grow(transferred); + stack.shrink(transferred); + } + if (stack.getCount() > 0) + stacks.add(stack); + } + + public static boolean isSameInventory(IItemHandler h1, IItemHandler h2) { + if (h1 == null || h2 == null) + return false; + if (h1.getSlots() != h2.getSlots()) + return false; + for (int slot = 0; slot < h1.getSlots(); slot++) { + if (h1.getStackInSlot(slot) != h2.getStackInSlot(slot)) + return false; + } + return true; + } + } diff --git a/src/main/java/com/simibubi/create/foundation/utility/TessellatorHelper.java b/src/main/java/com/simibubi/create/foundation/utility/TessellatorHelper.java index ecb0f521e..052442c47 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/TessellatorHelper.java +++ b/src/main/java/com/simibubi/create/foundation/utility/TessellatorHelper.java @@ -9,6 +9,7 @@ import net.minecraft.client.renderer.ActiveRenderInfo; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; @@ -30,7 +31,11 @@ public class TessellatorHelper { } public static void begin() { - Tessellator.getInstance().getBuffer().begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + begin(DefaultVertexFormats.POSITION_TEX); + } + + public static void begin(VertexFormat format) { + Tessellator.getInstance().getBuffer().begin(GL11.GL_QUADS, format); } public static void draw() { diff --git a/src/main/java/com/simibubi/create/modules/contraptions/CachedBufferReloader.java b/src/main/java/com/simibubi/create/modules/contraptions/CachedBufferReloader.java index 713c83f90..6f1fb9c9a 100644 --- a/src/main/java/com/simibubi/create/modules/contraptions/CachedBufferReloader.java +++ b/src/main/java/com/simibubi/create/modules/contraptions/CachedBufferReloader.java @@ -4,6 +4,7 @@ import com.simibubi.create.modules.contraptions.base.KineticTileEntityRenderer; import com.simibubi.create.modules.contraptions.receivers.constructs.MechanicalBearingTileEntityRenderer; import com.simibubi.create.modules.contraptions.receivers.constructs.MechanicalPistonTileEntityRenderer; import com.simibubi.create.modules.logistics.block.diodes.FlexpeaterTileEntityRenderer; +import com.simibubi.create.modules.logistics.management.base.LogisticalControllerTileEntityRenderer; import net.minecraft.client.resources.ReloadListener; import net.minecraft.profiler.IProfiler; @@ -22,6 +23,7 @@ public class CachedBufferReloader extends ReloadListener { MechanicalPistonTileEntityRenderer.invalidateCache(); MechanicalBearingTileEntityRenderer.invalidateCache(); FlexpeaterTileEntityRenderer.invalidateCache(); + LogisticalControllerTileEntityRenderer.invalidateCache(); } diff --git a/src/main/java/com/simibubi/create/modules/contraptions/receivers/DrillTileEntity.java b/src/main/java/com/simibubi/create/modules/contraptions/receivers/DrillTileEntity.java index 8aa1da827..597087a7b 100644 --- a/src/main/java/com/simibubi/create/modules/contraptions/receivers/DrillTileEntity.java +++ b/src/main/java/com/simibubi/create/modules/contraptions/receivers/DrillTileEntity.java @@ -29,7 +29,7 @@ public class DrillTileEntity extends KineticTileEntity implements ITickableTileE private static final AtomicInteger NEXT_DRILL_ID = new AtomicInteger(); - private static DamageSource damageSourceDrill = new DamageSource("create.drill").setDamageBypassesArmor(); + public static DamageSource damageSourceDrill = new DamageSource("create.drill").setDamageBypassesArmor(); private int ticksUntilNextProgress; private int destroyProgress; private int drillId = -NEXT_DRILL_ID.incrementAndGet(); diff --git a/src/main/java/com/simibubi/create/modules/curiosities/partialWindows/WindowInABlockBlock.java b/src/main/java/com/simibubi/create/modules/curiosities/partialWindows/WindowInABlockBlock.java index 1b5b76059..08ea23e6b 100644 --- a/src/main/java/com/simibubi/create/modules/curiosities/partialWindows/WindowInABlockBlock.java +++ b/src/main/java/com/simibubi/create/modules/curiosities/partialWindows/WindowInABlockBlock.java @@ -171,7 +171,7 @@ public class WindowInABlockBlock extends PaneBlock public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context) { WindowInABlockTileEntity tileEntity = getTileEntity(worldIn, pos); if (tileEntity == null) - return VoxelShapes.empty(); + return makeCuboidShape(7, 0, 7, 9, 16, 9); VoxelShape shape1 = tileEntity.getPartialBlock().getShape(worldIn, pos, context); VoxelShape shape2 = tileEntity.getWindowBlock().getShape(worldIn, pos, context); return VoxelShapes.or(shape1, shape2); diff --git a/src/main/java/com/simibubi/create/modules/curiosities/partialWindows/WindowInABlockTileEntity.java b/src/main/java/com/simibubi/create/modules/curiosities/partialWindows/WindowInABlockTileEntity.java index 0a5ca65bf..0f3e5ba0c 100644 --- a/src/main/java/com/simibubi/create/modules/curiosities/partialWindows/WindowInABlockTileEntity.java +++ b/src/main/java/com/simibubi/create/modules/curiosities/partialWindows/WindowInABlockTileEntity.java @@ -12,17 +12,27 @@ import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.NBTUtil; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.client.model.data.IModelData; import net.minecraftforge.client.model.data.ModelDataMap; +import net.minecraftforge.fml.DistExecutor; public class WindowInABlockTileEntity extends SyncedTileEntity { private BlockState partialBlock = Blocks.AIR.getDefaultState(); private BlockState windowBlock = Blocks.AIR.getDefaultState(); + + @OnlyIn(value = Dist.CLIENT) private IModelData modelData; public WindowInABlockTileEntity() { super(AllTileEntities.WINDOW_IN_A_BLOCK.type); + DistExecutor.runWhenOn(Dist.CLIENT, () -> this::initDataMap); + } + + @OnlyIn(value = Dist.CLIENT) + private void initDataMap() { modelData = new ModelDataMap.Builder().withInitial(WINDOW_BLOCK, Blocks.AIR.getDefaultState()) .withInitial(PARTIAL_BLOCK, Blocks.AIR.getDefaultState()) .withInitial(WindowInABlockModel.POSITION, BlockPos.ZERO).build(); @@ -57,6 +67,7 @@ public class WindowInABlockTileEntity extends SyncedTileEntity { markDirty(); } + @OnlyIn(value = Dist.CLIENT) @Override public IModelData getModelData() { modelData.setData(WindowInABlockModel.PARTIAL_BLOCK, partialBlock); diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/IExtractor.java b/src/main/java/com/simibubi/create/modules/logistics/block/IExtractor.java index 074ebd75f..bcb69d095 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/IExtractor.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/IExtractor.java @@ -1,11 +1,17 @@ package com.simibubi.create.modules.logistics.block; +import static net.minecraft.state.properties.BlockStateProperties.HORIZONTAL_FACING; + import com.simibubi.create.CreateConfig; import com.simibubi.create.foundation.utility.VecHelper; +import com.simibubi.create.modules.logistics.entity.CardboardBoxEntity; +import com.simibubi.create.modules.logistics.item.CardboardBoxItem; +import net.minecraft.entity.Entity; import net.minecraft.entity.item.ItemEntity; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.ITickableTileEntity; +import net.minecraft.util.Direction; import net.minecraft.util.SoundCategory; import net.minecraft.util.SoundEvents; import net.minecraft.util.math.AxisAlignedBB; @@ -168,10 +174,20 @@ public interface IExtractor extends ITickableTileEntity, IInventoryManipulator { if (!simulate && hasEnoughItems) { World world = getWorld(); Vec3d pos = VecHelper.getCenterOf(getPos()).add(0, -0.5f, 0); - ItemEntity entityIn = new ItemEntity(world, pos.x, pos.y, pos.z, extracting); - entityIn.setMotion(Vec3d.ZERO); + Entity entityIn = null; + + if (extracting.getItem() instanceof CardboardBoxItem) { + Direction face = getWorld().getBlockState(getPos()).get(HORIZONTAL_FACING).getOpposite(); + entityIn = new CardboardBoxEntity(world, pos, extracting, face); + world.playSound(null, getPos(), SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.BLOCKS, .25f, .05f); + + } else { + entityIn = new ItemEntity(world, pos.x, pos.y, pos.z, extracting); + entityIn.setMotion(Vec3d.ZERO); + world.playSound(null, getPos(), SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.BLOCKS, .125f, .1f); + } + world.addEntity(entityIn); - world.playSound(null, getPos(), SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.BLOCKS, .125f, .1f); } return extracting; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/BeltFunnelBlock.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/BeltFunnelBlock.java similarity index 87% rename from src/main/java/com/simibubi/create/modules/logistics/block/BeltFunnelBlock.java rename to src/main/java/com/simibubi/create/modules/logistics/block/belts/BeltFunnelBlock.java index b591fad53..c3b32e0f3 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/BeltFunnelBlock.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/BeltFunnelBlock.java @@ -1,4 +1,4 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.belts; import java.util.Arrays; import java.util.List; @@ -11,6 +11,8 @@ import com.simibubi.create.modules.contraptions.relays.belt.AllBeltAttachments.B import com.simibubi.create.modules.contraptions.relays.belt.AllBeltAttachments.IBeltAttachment; import com.simibubi.create.modules.contraptions.relays.belt.BeltBlock; import com.simibubi.create.modules.contraptions.relays.belt.BeltBlock.Slope; +import com.simibubi.create.modules.logistics.block.IInventoryManipulator; +import com.simibubi.create.modules.logistics.entity.CardboardBoxEntity; import com.simibubi.create.modules.contraptions.relays.belt.BeltTileEntity; import net.minecraft.block.Block; @@ -35,10 +37,8 @@ import net.minecraft.world.World; public class BeltFunnelBlock extends HorizontalBlock implements IBeltAttachment, IWithTileEntity { - public static final VoxelShape - SHAPE_NORTH = makeCuboidShape(3, -4, -1, 13, 8, 5), - SHAPE_SOUTH = makeCuboidShape(3, -4, 11, 13, 8, 17), - SHAPE_WEST = makeCuboidShape(-1, -4, 3, 5, 8, 13), + public static final VoxelShape SHAPE_NORTH = makeCuboidShape(3, -4, -1, 13, 8, 5), + SHAPE_SOUTH = makeCuboidShape(3, -4, 11, 13, 8, 17), SHAPE_WEST = makeCuboidShape(-1, -4, 3, 5, 8, 13), SHAPE_EAST = makeCuboidShape(11, -4, 3, 17, 8, 13); public BeltFunnelBlock() { @@ -135,15 +135,16 @@ public class BeltFunnelBlock extends HorizontalBlock implements IBeltAttachment, @Override public boolean handleEntity(BeltTileEntity te, Entity entity, BeltAttachmentState state) { - if (!(entity instanceof ItemEntity)) + boolean isItem = entity instanceof ItemEntity; + if (!isItem && !(entity instanceof CardboardBoxEntity)) return false; boolean slope = te.getBlockState().get(BeltBlock.SLOPE) != Slope.HORIZONTAL; - if (entity.getPositionVec().distanceTo(VecHelper.getCenterOf(te.getPos())) > (slope ? .6f : .4f)) + if (isItem && entity.getPositionVec().distanceTo(VecHelper.getCenterOf(te.getPos())) > (slope ? .6f : .4f)) return false; entity.setMotion(Vec3d.ZERO); withTileEntityDo(te.getWorld(), state.attachmentPos, funnelTE -> { - funnelTE.tryToInsert((ItemEntity) entity); + funnelTE.tryToInsert(entity); }); return true; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/BeltFunnelTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/BeltFunnelTileEntity.java similarity index 77% rename from src/main/java/com/simibubi/create/modules/logistics/block/BeltFunnelTileEntity.java rename to src/main/java/com/simibubi/create/modules/logistics/block/belts/BeltFunnelTileEntity.java index d568c2ef2..9e1e2f16c 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/BeltFunnelTileEntity.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/BeltFunnelTileEntity.java @@ -1,8 +1,11 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.belts; import com.simibubi.create.AllTileEntities; import com.simibubi.create.foundation.block.SyncedTileEntity; +import com.simibubi.create.modules.logistics.block.IInventoryManipulator; +import com.simibubi.create.modules.logistics.entity.CardboardBoxEntity; +import net.minecraft.entity.Entity; import net.minecraft.entity.item.ItemEntity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; @@ -33,12 +36,12 @@ public class BeltFunnelTileEntity extends SyncedTileEntity implements ITickableT waitingForInventorySpace = compound.getBoolean("Waiting"); super.read(compound); } - + @Override public void onLoad() { initialize = true; } - + @Override public void readClientUpdate(CompoundNBT tag) { super.readClientUpdate(tag); @@ -61,7 +64,7 @@ public class BeltFunnelTileEntity extends SyncedTileEntity implements ITickableT public LazyOptional getInventory() { return inventory; } - + @Override public void tick() { if (initialize && hasWorld()) { @@ -69,7 +72,7 @@ public class BeltFunnelTileEntity extends SyncedTileEntity implements ITickableT initialize = false; } } - + @Override public void setInventory(LazyOptional inventory) { this.inventory = inventory; @@ -83,13 +86,18 @@ public class BeltFunnelTileEntity extends SyncedTileEntity implements ITickableT sendData(); } - public void tryToInsert(ItemEntity entity) { + public void tryToInsert(Entity entity) { if (!inventory.isPresent()) return; if (waitingForInventorySpace) return; - ItemStack stack = entity.getItem().copy(); + ItemStack stack = null; + if (entity instanceof ItemEntity) + stack = ((ItemEntity) entity).getItem().copy(); + if (entity instanceof CardboardBoxEntity) + stack = ((CardboardBoxEntity) entity).getBox().copy(); + IItemHandler inv = inventory.orElse(null); for (int slot = 0; slot < inv.getSlots(); slot++) { stack = inv.insertItem(slot, stack, world.isRemote); @@ -97,12 +105,12 @@ public class BeltFunnelTileEntity extends SyncedTileEntity implements ITickableT if (!world.isRemote) { entity.remove(); world.playSound(null, pos, SoundEvents.ENTITY_GENERIC_EAT, SoundCategory.BLOCKS, .125f, 1f); - } - else { + } else { Vec3i directionVec = getBlockState().get(BlockStateProperties.HORIZONTAL_FACING).getDirectionVec(); - float xSpeed = directionVec.getX() * 1/8f; - float zSpeed = directionVec.getZ() * 1/8f; - world.addParticle(new ItemParticleData(ParticleTypes.ITEM, entity.getItem()), entity.posX, entity.posY, entity.posZ, xSpeed, 1/6f, zSpeed); + float xSpeed = directionVec.getX() * 1 / 8f; + float zSpeed = directionVec.getZ() * 1 / 8f; + world.addParticle(new ItemParticleData(ParticleTypes.ITEM, stack), entity.posX, + entity.posY, entity.posZ, xSpeed, 1 / 6f, zSpeed); } return; } @@ -111,8 +119,9 @@ public class BeltFunnelTileEntity extends SyncedTileEntity implements ITickableT waitingForInventorySpace = true; sendData(); - if (!stack.equals(entity.getItem(), false)) - entity.setItem(stack); + if (entity instanceof ItemEntity) + if (!stack.equals(((ItemEntity) entity).getItem(), false)) + ((ItemEntity) entity).setItem(stack); } diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/EntityDetectorBlock.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/EntityDetectorBlock.java similarity index 98% rename from src/main/java/com/simibubi/create/modules/logistics/block/EntityDetectorBlock.java rename to src/main/java/com/simibubi/create/modules/logistics/block/belts/EntityDetectorBlock.java index d6b9e8d7f..48225c77b 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/EntityDetectorBlock.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/EntityDetectorBlock.java @@ -1,4 +1,4 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.belts; import java.util.ArrayList; import java.util.Arrays; @@ -14,6 +14,7 @@ import com.simibubi.create.modules.contraptions.relays.belt.AllBeltAttachments.I import com.simibubi.create.modules.contraptions.relays.belt.BeltBlock; import com.simibubi.create.modules.contraptions.relays.belt.BeltBlock.Part; import com.simibubi.create.modules.contraptions.relays.belt.BeltBlock.Slope; +import com.simibubi.create.modules.logistics.block.IBlockWithFilter; import com.simibubi.create.modules.contraptions.relays.belt.BeltTileEntity; import net.minecraft.block.Block; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/EntityDetectorTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/EntityDetectorTileEntity.java similarity index 87% rename from src/main/java/com/simibubi/create/modules/logistics/block/EntityDetectorTileEntity.java rename to src/main/java/com/simibubi/create/modules/logistics/block/belts/EntityDetectorTileEntity.java index 5866141b6..518dbc9e7 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/EntityDetectorTileEntity.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/EntityDetectorTileEntity.java @@ -1,7 +1,8 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.belts; import com.simibubi.create.AllTileEntities; import com.simibubi.create.foundation.block.SyncedTileEntity; +import com.simibubi.create.modules.logistics.block.IHaveFilter; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/EntityDetectorTileEntityRenderer.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/EntityDetectorTileEntityRenderer.java similarity index 87% rename from src/main/java/com/simibubi/create/modules/logistics/block/EntityDetectorTileEntityRenderer.java rename to src/main/java/com/simibubi/create/modules/logistics/block/belts/EntityDetectorTileEntityRenderer.java index c824f1f10..008a8f023 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/EntityDetectorTileEntityRenderer.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/EntityDetectorTileEntityRenderer.java @@ -1,6 +1,7 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.belts; import com.mojang.blaze3d.platform.GLX; +import com.simibubi.create.modules.logistics.block.FilteredTileEntityRenderer; import net.minecraft.client.renderer.tileentity.TileEntityRenderer; import net.minecraft.state.properties.BlockStateProperties; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/ExtractorBlock.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/ExtractorBlock.java similarity index 96% rename from src/main/java/com/simibubi/create/modules/logistics/block/ExtractorBlock.java rename to src/main/java/com/simibubi/create/modules/logistics/block/belts/ExtractorBlock.java index 18635e0b7..2644ea845 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/ExtractorBlock.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/ExtractorBlock.java @@ -1,9 +1,11 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.belts; import java.util.ArrayList; import java.util.List; import com.simibubi.create.foundation.utility.VecHelper; +import com.simibubi.create.modules.logistics.block.IBlockWithFilter; +import com.simibubi.create.modules.logistics.block.IExtractor; import net.minecraft.block.Block; import net.minecraft.block.BlockState; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/ExtractorTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/ExtractorTileEntity.java similarity index 93% rename from src/main/java/com/simibubi/create/modules/logistics/block/ExtractorTileEntity.java rename to src/main/java/com/simibubi/create/modules/logistics/block/belts/ExtractorTileEntity.java index 4d59e6d4a..d6a3e8855 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/ExtractorTileEntity.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/ExtractorTileEntity.java @@ -1,8 +1,10 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.belts; import com.simibubi.create.AllTileEntities; import com.simibubi.create.CreateConfig; import com.simibubi.create.foundation.block.SyncedTileEntity; +import com.simibubi.create.modules.logistics.block.IExtractor; +import com.simibubi.create.modules.logistics.block.IHaveFilter; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/ExtractorTileEntityRenderer.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/ExtractorTileEntityRenderer.java similarity index 81% rename from src/main/java/com/simibubi/create/modules/logistics/block/ExtractorTileEntityRenderer.java rename to src/main/java/com/simibubi/create/modules/logistics/block/belts/ExtractorTileEntityRenderer.java index 3146efe77..45df232bf 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/ExtractorTileEntityRenderer.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/ExtractorTileEntityRenderer.java @@ -1,4 +1,6 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.belts; + +import com.simibubi.create.modules.logistics.block.FilteredTileEntityRenderer; import net.minecraft.client.renderer.tileentity.TileEntityRenderer; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/LinkedExtractorBlock.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/LinkedExtractorBlock.java similarity index 95% rename from src/main/java/com/simibubi/create/modules/logistics/block/LinkedExtractorBlock.java rename to src/main/java/com/simibubi/create/modules/logistics/block/belts/LinkedExtractorBlock.java index 6774d518d..5316d6e69 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/LinkedExtractorBlock.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/LinkedExtractorBlock.java @@ -1,4 +1,4 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.belts; import java.util.ArrayList; import java.util.List; @@ -6,6 +6,7 @@ import java.util.List; import org.apache.commons.lang3.tuple.Pair; import com.simibubi.create.foundation.utility.VecHelper; +import com.simibubi.create.modules.logistics.block.IBlockWithFrequency; import net.minecraft.block.Block; import net.minecraft.block.BlockState; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/LinkedExtractorTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/LinkedExtractorTileEntity.java similarity index 92% rename from src/main/java/com/simibubi/create/modules/logistics/block/LinkedExtractorTileEntity.java rename to src/main/java/com/simibubi/create/modules/logistics/block/belts/LinkedExtractorTileEntity.java index d2badee55..1d8b5b88c 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/LinkedExtractorTileEntity.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/LinkedExtractorTileEntity.java @@ -1,10 +1,13 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.belts; import static net.minecraft.state.properties.BlockStateProperties.POWERED; import com.simibubi.create.AllTileEntities; import com.simibubi.create.CreateConfig; import com.simibubi.create.modules.logistics.IReceiveWireless; +import com.simibubi.create.modules.logistics.block.IExtractor; +import com.simibubi.create.modules.logistics.block.IHaveFilter; +import com.simibubi.create.modules.logistics.block.LinkedTileEntity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/LinkedExtractorTileEntityRenderer.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/LinkedExtractorTileEntityRenderer.java similarity index 78% rename from src/main/java/com/simibubi/create/modules/logistics/block/LinkedExtractorTileEntityRenderer.java rename to src/main/java/com/simibubi/create/modules/logistics/block/belts/LinkedExtractorTileEntityRenderer.java index e066e8579..328ea47cd 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/LinkedExtractorTileEntityRenderer.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/LinkedExtractorTileEntityRenderer.java @@ -1,4 +1,7 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.belts; + +import com.simibubi.create.modules.logistics.block.FilteredTileEntityRenderer; +import com.simibubi.create.modules.logistics.block.LinkedTileEntityRenderer; import net.minecraft.client.renderer.tileentity.TileEntityRenderer; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/FlexcrateBlock.java b/src/main/java/com/simibubi/create/modules/logistics/block/inventories/FlexcrateBlock.java similarity index 97% rename from src/main/java/com/simibubi/create/modules/logistics/block/FlexcrateBlock.java rename to src/main/java/com/simibubi/create/modules/logistics/block/inventories/FlexcrateBlock.java index d2053d06f..e264502d7 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/FlexcrateBlock.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/inventories/FlexcrateBlock.java @@ -1,4 +1,4 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.inventories; import net.minecraft.block.Block; import net.minecraft.block.BlockState; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/FlexcrateContainer.java b/src/main/java/com/simibubi/create/modules/logistics/block/inventories/FlexcrateContainer.java similarity index 97% rename from src/main/java/com/simibubi/create/modules/logistics/block/FlexcrateContainer.java rename to src/main/java/com/simibubi/create/modules/logistics/block/inventories/FlexcrateContainer.java index 0f1d05eef..55fd0d0eb 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/FlexcrateContainer.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/inventories/FlexcrateContainer.java @@ -1,4 +1,4 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.inventories; import com.simibubi.create.AllContainers; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/FlexcrateScreen.java b/src/main/java/com/simibubi/create/modules/logistics/block/inventories/FlexcrateScreen.java similarity index 98% rename from src/main/java/com/simibubi/create/modules/logistics/block/FlexcrateScreen.java rename to src/main/java/com/simibubi/create/modules/logistics/block/inventories/FlexcrateScreen.java index d148a057b..602b04fc7 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/FlexcrateScreen.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/inventories/FlexcrateScreen.java @@ -1,4 +1,4 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.inventories; import static com.simibubi.create.ScreenResources.FLEXCRATE; import static com.simibubi.create.ScreenResources.PLAYER_INVENTORY; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/FlexcrateTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/block/inventories/FlexcrateTileEntity.java similarity index 98% rename from src/main/java/com/simibubi/create/modules/logistics/block/FlexcrateTileEntity.java rename to src/main/java/com/simibubi/create/modules/logistics/block/inventories/FlexcrateTileEntity.java index 320223d0c..3deb8380b 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/FlexcrateTileEntity.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/inventories/FlexcrateTileEntity.java @@ -1,4 +1,4 @@ -package com.simibubi.create.modules.logistics.block; +package com.simibubi.create.modules.logistics.block.inventories; import com.simibubi.create.AllTileEntities; import com.simibubi.create.foundation.block.SyncedTileEntity; diff --git a/src/main/java/com/simibubi/create/modules/logistics/entity/CardboardBoxEntity.java b/src/main/java/com/simibubi/create/modules/logistics/entity/CardboardBoxEntity.java new file mode 100644 index 000000000..10534d495 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/entity/CardboardBoxEntity.java @@ -0,0 +1,291 @@ +package com.simibubi.create.modules.logistics.entity; + +import java.util.Collections; + +import javax.annotation.Nullable; + +import com.simibubi.create.AllEntities; +import com.simibubi.create.AllItems; +import com.simibubi.create.foundation.utility.VecHelper; +import com.simibubi.create.modules.contraptions.receivers.DrillTileEntity; +import com.simibubi.create.modules.logistics.item.CardboardBoxItem; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntitySize; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.Pose; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.item.ItemEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.projectile.AbstractArrowEntity; +import net.minecraft.inventory.EquipmentSlotType; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.network.IPacket; +import net.minecraft.network.PacketBuffer; +import net.minecraft.particles.ItemParticleData; +import net.minecraft.particles.ParticleTypes; +import net.minecraft.util.DamageSource; +import net.minecraft.util.Direction; +import net.minecraft.util.HandSide; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.SoundEvents; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; +import net.minecraftforge.fml.network.FMLPlayMessages.SpawnEntity; +import net.minecraftforge.fml.network.NetworkHooks; + +public class CardboardBoxEntity extends LivingEntity implements IEntityAdditionalSpawnData { + + public ItemStack box; + + public int extractorAnimationProgress; + public Direction extractorSide; + + @SuppressWarnings("unchecked") + public CardboardBoxEntity(EntityType entityTypeIn, World worldIn) { + super((EntityType) entityTypeIn, worldIn); + } + + protected CardboardBoxEntity(World worldIn, double x, double y, double z) { + this(AllEntities.CARDBOARD_BOX.type, worldIn); + this.setPosition(x, y, z); + this.recalculateSize(); + this.rotationYaw = this.rand.nextFloat() * 360.0F; + } + + public CardboardBoxEntity(World worldIn, Vec3d pos, ItemStack stack, Direction extractionDirection) { + this(worldIn, pos.x, pos.y, pos.z); + this.setBox(stack); + this.extractedFrom(extractionDirection); + } + + public static EntityType.Builder build(EntityType.Builder builder) { + @SuppressWarnings("unchecked") + EntityType.Builder boxBuilder = (EntityType.Builder) builder; + return boxBuilder.setCustomClientFactory(CardboardBoxEntity::spawn).size(1, 1); + } + + protected void registerAttributes() { + super.registerAttributes(); + this.getAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(5.0D); + } + + private void extractedFrom(Direction side) { + extractorSide = side; + extractorAnimationProgress = 20; + } + + @Override + public void tick() { + if (extractorAnimationProgress > -1) { + extractorAnimationProgress--; + return; + } + super.tick(); + } + + @Override + public EntitySize getSize(Pose poseIn) { + if (box == null) + return super.getSize(poseIn); + if (AllItems.CARDBOARD_BOX_1410.typeOf(box)) + return new EntitySize(14 / 16f, 10 / 16f, true); + if (AllItems.CARDBOARD_BOX_1416.typeOf(box)) + return new EntitySize(14 / 16f, 1f, true); + if (AllItems.CARDBOARD_BOX_1612.typeOf(box)) + return new EntitySize(1f, 12 / 16f, true); + if (AllItems.CARDBOARD_BOX_1616.typeOf(box)) + return new EntitySize(1f, 1f, true); + + return super.getSize(poseIn); + } + + public static CardboardBoxEntity spawn(SpawnEntity spawnEntity, World world) { + return new CardboardBoxEntity(world, 0, 0, 0); + } + + public ItemStack getBox() { + return box; + } + + public void setBox(ItemStack box) { + this.box = box.copy(); + recalculateSize(); + } + + public AxisAlignedBB getCollisionBoundingBox() { + return this.getBoundingBox(); + } + + public boolean attackEntityFrom(DamageSource source, float amount) { + if (world.isRemote || !this.isAlive()) + return false; + + if (DamageSource.OUT_OF_WORLD.equals(source)) { + this.remove(); + return false; + } + + if (DamageSource.FALL.equals(source)) + return false; + + if (this.isInvulnerableTo(source)) + return false; + + if (source.isExplosion()) { + this.destroy(source); + this.remove(); + return false; + } + + if (DamageSource.IN_FIRE.equals(source)) { + if (this.isBurning()) { + this.takeDamage(source, 0.15F); + } else { + this.setFire(5); + } + return false; + } + + if (DamageSource.ON_FIRE.equals(source) && this.getHealth() > 0.5F) { + this.takeDamage(source, 4.0F); + return false; + } + + boolean wasShot = source.getImmediateSource() instanceof AbstractArrowEntity; + boolean shotCanPierce = wasShot && ((AbstractArrowEntity) source.getImmediateSource()).getPierceLevel() > 0; + + if (source.getTrueSource() instanceof PlayerEntity + && !((PlayerEntity) source.getTrueSource()).abilities.allowEdit) + return false; + + this.destroy(source); + this.remove(); + return shotCanPierce; + } + + private void takeDamage(DamageSource source, float amount) { + float hp = this.getHealth(); + hp = hp - amount; + if (hp <= 0.5F) { + this.destroy(source); + this.remove(); + } else { + this.setHealth(hp); + } + } + + private void destroy(DamageSource source) { + this.world.playSound((PlayerEntity) null, this.posX, this.posY, this.posZ, SoundEvents.ENTITY_ARMOR_STAND_BREAK, + this.getSoundCategory(), 1.0F, 1.0F); + this.spawnDrops(source); + } + + @Override + protected void spawnDrops(DamageSource source) { + super.spawnDrops(source); + for (ItemStack stack : CardboardBoxItem.getContents(box)) { + ItemEntity entityIn = new ItemEntity(world, posX, posY, posZ, stack); + world.addEntity(entityIn); + if (DrillTileEntity.damageSourceDrill.equals(source)) + entityIn.setMotion(Vec3d.ZERO); + } + } + + @Override + public void remove(boolean keepData) { + if (world.isRemote) { + for (int i = 0; i < 20; i++) { + Vec3d pos = VecHelper.offsetRandomly(this.getPositionVector(), world.rand, .5f); + Vec3d motion = Vec3d.ZERO; + world.addParticle(new ItemParticleData(ParticleTypes.ITEM, box), pos.x, pos.y, pos.z, motion.x, + motion.y, motion.z); + } + } + super.remove(keepData); + } + + @Override + protected void registerData() { + super.registerData(); + } + + @Override + public void readAdditional(CompoundNBT compound) { + super.readAdditional(compound); + box = ItemStack.read(compound.getCompound("Box")); + if (compound.contains("Direction")) + extractedFrom(Direction.byIndex(compound.getInt("Direction"))); + } + + @Override + public void writeAdditional(CompoundNBT compound) { + super.writeAdditional(compound); + compound.put("Box", box.serializeNBT()); + if (extractorSide != null) + compound.putInt("Direction", extractorSide.getIndex()); + } + + @Override + public IPacket createSpawnPacket() { + return NetworkHooks.getEntitySpawningPacket(this); + } + + @Override + public Iterable getArmorInventoryList() { + return Collections.emptyList(); + } + + @Override + public ItemStack getItemStackFromSlot(EquipmentSlotType slotIn) { + if (slotIn == EquipmentSlotType.MAINHAND) + return getBox(); + return ItemStack.EMPTY; + } + + @Override + public void setItemStackToSlot(EquipmentSlotType slotIn, ItemStack stack) { + if (slotIn == EquipmentSlotType.MAINHAND) + setBox(stack); + } + + @Override + public HandSide getPrimaryHand() { + return HandSide.LEFT; + } + + @Override + public void writeSpawnData(PacketBuffer buffer) { + buffer.writeItemStack(getBox()); + boolean sidePresent = extractorSide != null; + buffer.writeBoolean(sidePresent); + if (sidePresent) + buffer.writeInt(extractorSide.getIndex()); + } + + @Override + public void readSpawnData(PacketBuffer additionalData) { + setBox(additionalData.readItemStack()); + if (additionalData.readBoolean()) + extractedFrom(Direction.byIndex(additionalData.readInt())); + } + + protected SoundEvent getFallSound(int heightIn) { + return SoundEvents.ENTITY_ARMOR_STAND_FALL; + } + + @Nullable + protected SoundEvent getHurtSound(DamageSource damageSourceIn) { + return SoundEvents.ENTITY_ARMOR_STAND_HIT; + } + + @Nullable + protected SoundEvent getDeathSound() { + return SoundEvents.ENTITY_ARMOR_STAND_BREAK; + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/entity/CardboardBoxEntityRenderer.java b/src/main/java/com/simibubi/create/modules/logistics/entity/CardboardBoxEntityRenderer.java new file mode 100644 index 000000000..395e352d5 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/entity/CardboardBoxEntityRenderer.java @@ -0,0 +1,76 @@ +package com.simibubi.create.modules.logistics.entity; + +import com.mojang.blaze3d.platform.GlStateManager; + +import net.minecraft.block.Blocks; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.culling.ICamera; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.EntityRendererManager; +import net.minecraft.client.renderer.model.IBakedModel; +import net.minecraft.client.renderer.texture.AtlasTexture; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; + +public class CardboardBoxEntityRenderer extends EntityRenderer { + + public CardboardBoxEntityRenderer(EntityRendererManager renderManager) { + super(renderManager); + } + + @Override + protected ResourceLocation getEntityTexture(CardboardBoxEntity entity) { + return null; + } + + @Override + public boolean shouldRender(CardboardBoxEntity livingEntity, ICamera camera, double camX, double camY, + double camZ) { + return super.shouldRender(livingEntity, camera, camX, camY, camZ); + } + + @Override + public void renderMultipass(CardboardBoxEntity entityIn, double x, double y, double z, float entityYaw, + float partialTicks) { + super.renderMultipass(entityIn, x, y, z, entityYaw, partialTicks); + } + + @Override + public void doRender(CardboardBoxEntity entity, double x, double y, double z, float entityYaw, float partialTicks) { + IBakedModel model = getModelForBox(entity); + if (model == null) + return; + + bindTexture(AtlasTexture.LOCATION_BLOCKS_TEXTURE); + GlStateManager.pushMatrix(); + GlStateManager.translated(x, y, z); + + if (entity.extractorSide != null && entity.extractorAnimationProgress > 0) { + float time = entity.extractorAnimationProgress - partialTicks; + float scale = 1; + if (time > 5) { + scale = MathHelper.lerp(((time - 10) / 10), .3f, .25f); + } else { + float step = time / 5; + scale = MathHelper.lerp(step * step * step, 1, .3f); + } + GlStateManager.scaled(scale, scale, scale); + } + + GlStateManager.rotated(entity.rotationYaw, 0, 1, 0); + GlStateManager.translated(-.5, 0, .5); + + Minecraft.getInstance().getBlockRendererDispatcher().getBlockModelRenderer().renderModelBrightness(model, + Blocks.AIR.getDefaultState(), 1, false); + GlStateManager.popMatrix(); + + super.doRender(entity, x, y, z, entityYaw, partialTicks); + } + + public IBakedModel getModelForBox(CardboardBoxEntity entity) { + if (entity.getBox() == null || entity.getBox().isEmpty()) + return null; + return Minecraft.getInstance().getItemRenderer().getModelWithOverrides(entity.getBox()); + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/item/CardboardBoxItem.java b/src/main/java/com/simibubi/create/modules/logistics/item/CardboardBoxItem.java new file mode 100644 index 000000000..4f0c78616 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/item/CardboardBoxItem.java @@ -0,0 +1,128 @@ +package com.simibubi.create.modules.logistics.item; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.ItemStackHelper; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.ActionResult; +import net.minecraft.util.ActionResultType; +import net.minecraft.util.Hand; +import net.minecraft.util.NonNullList; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.StringTextComponent; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.TranslationTextComponent; +import net.minecraft.world.World; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.common.util.Constants; + +public class CardboardBoxItem extends Item { + + static final int SLOTS = 9; + static final List ALL_BOXES = new ArrayList<>(); + + public CardboardBoxItem(Properties properties) { + super(properties); + ALL_BOXES.add(this); + } + + @Override + public ActionResult onItemRightClick(World worldIn, PlayerEntity playerIn, Hand handIn) { + if (!playerIn.isSneaking()) + return super.onItemRightClick(worldIn, playerIn, handIn); + + ItemStack box = playerIn.getHeldItem(handIn); + for (ItemStack stack : getContents(box)) + playerIn.inventory.placeItemBackInInventory(worldIn, stack); + + if (!playerIn.isCreative()) { + box.shrink(1); + } + return new ActionResult<>(ActionResultType.SUCCESS, box); + } + + public static ItemStack containing(List stacks) { + ItemStack box = new ItemStack(randomBox()); + CompoundNBT compound = new CompoundNBT(); + + NonNullList list = NonNullList.create(); + list.addAll(stacks); + ItemStackHelper.saveAllItems(compound, list); + + box.setTag(compound); + return box; + } + + public static void addAddress(ItemStack box, String address) { + box.getOrCreateTag().putString("Address", address); + } + + public static boolean matchAddress(ItemStack box, String other) { + String address = box.getTag().getString("Address"); + if (address == null || address.isEmpty()) + return false; + if (address.equals("*")) + return true; + if (address.equals(other)) + return true; + if (address.endsWith("*") && other.startsWith(address.substring(0, address.length() - 1))) + return true; + + return false; + } + + public static List getContents(ItemStack box) { + NonNullList list = NonNullList.withSize(SLOTS, ItemStack.EMPTY); + ItemStackHelper.loadAllItems(box.getOrCreateTag(), list); + return list; + } + + public static CardboardBoxItem randomBox() { + return ALL_BOXES.get(new Random().nextInt(ALL_BOXES.size())); + } + + @Override + @OnlyIn(value = Dist.CLIENT) + public void addInformation(ItemStack stack, World worldIn, List tooltip, ITooltipFlag flagIn) { + super.addInformation(stack, worldIn, tooltip, flagIn); + CompoundNBT compoundnbt = stack.getOrCreateTag(); + + if (compoundnbt.contains("Address", Constants.NBT.TAG_STRING)) { + tooltip.add(new StringTextComponent("-> " + compoundnbt.getString("Address")) + .applyTextStyle(TextFormatting.GOLD)); + } + + if (!compoundnbt.contains("Items", Constants.NBT.TAG_LIST)) + return; + + int i = 0; + int j = 0; + + for (ItemStack itemstack : getContents(stack)) { + if (itemstack.isEmpty()) + continue; + + ++j; + if (i <= 4) { + ++i; + ITextComponent itextcomponent = itemstack.getDisplayName().deepCopy(); + itextcomponent.appendText(" x").appendText(String.valueOf(itemstack.getCount())) + .applyTextStyle(TextFormatting.GRAY); + tooltip.add(itextcomponent); + } + } + + if (j - i > 0) { + tooltip.add((new TranslationTextComponent("container.shulkerBox.more", j - i)) + .applyTextStyle(TextFormatting.ITALIC)); + } + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/LogisticalDialItem.java b/src/main/java/com/simibubi/create/modules/logistics/management/LogisticalDialItem.java new file mode 100644 index 000000000..daecb8a8f --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/LogisticalDialItem.java @@ -0,0 +1,89 @@ +package com.simibubi.create.modules.logistics.management; + +import static com.simibubi.create.AllBlocks.LOGISTICAL_CONTROLLER; +import static com.simibubi.create.AllBlocks.LOGISTICAL_INDEX; + +import java.util.UUID; + +import com.simibubi.create.foundation.item.IItemWithColorHandler; +import com.simibubi.create.modules.logistics.management.base.LogisticalControllerTileEntity; + +import net.minecraft.block.BlockState; +import net.minecraft.client.renderer.color.IItemColor; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUseContext; +import net.minecraft.util.ActionResult; +import net.minecraft.util.ActionResultType; +import net.minecraft.util.Hand; +import net.minecraft.world.World; + +public class LogisticalDialItem extends Item implements IItemWithColorHandler { + + public LogisticalDialItem(Properties properties) { + super(properties); + } + + @Override + public IItemColor getColorHandler() { + return (stack, layer) -> { + if (layer == 1 && stack.getOrCreateTag().contains("NetworkIDLeast")) + return LogisticalControllerTileEntity.colorFromUUID(stack.getTag().getUniqueId("NetworkID")); + return 0xFFFFFF; + }; + } + + @Override + public void inventoryTick(ItemStack stack, World worldIn, Entity entityIn, int itemSlot, boolean isSelected) { + if (worldIn.isRemote && !stack.hasTag()) + stack.getOrCreateTag().putUniqueId("NetworkID", UUID.randomUUID()); + } + + @Override + public ActionResultType onItemUse(ItemUseContext context) { + ItemStack heldItem = context.getItem(); + boolean isRemote = context.getWorld().isRemote; + + if (!context.getPlayer().isAllowEdit()) + return super.onItemUse(context); + BlockState blockState = context.getWorld().getBlockState(context.getPos()); + if (!LOGISTICAL_CONTROLLER.typeOf(blockState) && !LOGISTICAL_INDEX.typeOf(blockState)) { + if (context.isPlacerSneaking()) { + if (!isRemote) + heldItem.getTag().putUniqueId("NetworkID", UUID.randomUUID()); + context.getPlayer().getCooldownTracker().setCooldown(heldItem.getItem(), 5); + return ActionResultType.SUCCESS; + } + return super.onItemUse(context); + } + + LogisticalControllerTileEntity tileEntity = (LogisticalControllerTileEntity) context.getWorld() + .getTileEntity(context.getPos()); + if (context.isPlacerSneaking()) { + if (!isRemote) + heldItem.getTag().putUniqueId("NetworkID", tileEntity.getNetworkId()); + context.getPlayer().getCooldownTracker().setCooldown(heldItem.getItem(), 5); + return ActionResultType.SUCCESS; + } + + tileEntity.setNetworkId(heldItem.getTag().getUniqueId("NetworkID")); + return ActionResultType.SUCCESS; + } + + @Override + public ActionResult onItemRightClick(World worldIn, PlayerEntity playerIn, Hand handIn) { + if (!playerIn.isSneaking()) + return super.onItemRightClick(worldIn, playerIn, handIn); + + ItemStack heldItem = playerIn.getHeldItem(handIn); + if (!worldIn.isRemote) { + heldItem.getTag().putUniqueId("NetworkID", UUID.randomUUID()); + playerIn.inventory.markDirty(); + } + playerIn.getCooldownTracker().setCooldown(heldItem.getItem(), 5); + return new ActionResult<>(ActionResultType.SUCCESS, heldItem); + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/LogisticalNetwork.java b/src/main/java/com/simibubi/create/modules/logistics/management/LogisticalNetwork.java new file mode 100644 index 000000000..7091c7d94 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/LogisticalNetwork.java @@ -0,0 +1,79 @@ +package com.simibubi.create.modules.logistics.management; + +import java.util.ArrayList; +import java.util.List; +import java.util.PriorityQueue; + +import com.simibubi.create.modules.logistics.management.base.LogisticalControllerTileEntity; +import com.simibubi.create.modules.logistics.management.base.LogisticalTask; +import com.simibubi.create.modules.logistics.management.base.LogisticalTask.DepositTask; +import com.simibubi.create.modules.logistics.management.base.LogisticalTask.SupplyTask; +import com.simibubi.create.modules.logistics.management.controller.TransactionsTileEntity; +import com.simibubi.create.modules.logistics.management.index.LogisticalIndexTileEntity; + +public class LogisticalNetwork { + + public List taskQueues = new ArrayList<>(); + public List indexers = new ArrayList<>(); + public PriorityQueue internalTaskQueue = new PriorityQueue<>(); + public PriorityQueue suppliers = new PriorityQueue<>(); + public PriorityQueue receivers = new PriorityQueue<>(); + public int participants = 0; + public boolean tasksUpdated; + + public void addController(LogisticalControllerTileEntity te) { + if (te instanceof TransactionsTileEntity) { + if (taskQueues.contains(te)) + return; + taskQueues.add((TransactionsTileEntity) te); + } + if (te instanceof LogisticalIndexTileEntity) { + if (indexers.contains(te)) + return; + indexers.add((LogisticalIndexTileEntity) te); + } + if (te.isSupplier()) { + if (suppliers.contains(te)) + return; + suppliers.add(te); + } + if (te.isReceiver()) { + if (receivers.contains(te)) + return; + receivers.add(te); + indexers.forEach(LogisticalIndexTileEntity::syncReceivers); + } + participants++; + } + + public void removeController(LogisticalControllerTileEntity te) { + if (te instanceof TransactionsTileEntity) + if (!taskQueues.remove((TransactionsTileEntity) te)) + return; + if (te instanceof LogisticalIndexTileEntity) + if (!indexers.remove((LogisticalIndexTileEntity) te)) + return; + if (te.isSupplier()) + if (!suppliers.remove(te)) + return; + if (te.isReceiver()) { + if (!receivers.remove(te)) + return; + indexers.forEach(LogisticalIndexTileEntity::syncReceivers); + } + participants--; + } + + public boolean isEmpty() { + return participants == 0; + } + + public void enqueueTask(LogisticalTask task) { + internalTaskQueue.add(task); + if (task instanceof SupplyTask) + suppliers.forEach(LogisticalControllerTileEntity::notifyTaskUpdate); + if (task instanceof DepositTask) + receivers.forEach(LogisticalControllerTileEntity::notifyTaskUpdate); + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/LogisticalNetworkHandler.java b/src/main/java/com/simibubi/create/modules/logistics/management/LogisticalNetworkHandler.java new file mode 100644 index 000000000..8a12becd0 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/LogisticalNetworkHandler.java @@ -0,0 +1,50 @@ +package com.simibubi.create.modules.logistics.management; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import com.simibubi.create.Create; +import com.simibubi.create.modules.logistics.management.base.LogisticalControllerTileEntity; + +import net.minecraft.world.IWorld; + +public class LogisticalNetworkHandler { + + static Map> networks = new HashMap<>(); + + public void onLoadWorld(IWorld world) { + networks.put(world, new HashMap<>()); + Create.logger.debug("Prepared Logistical Network Map for " + world.getDimension().getType().getRegistryName()); + } + + public void onUnloadWorld(IWorld world) { + networks.remove(world); + Create.logger.debug("Removed Logistical Network Map for " + world.getDimension().getType().getRegistryName()); + } + + public LogisticalNetwork handleAdded(LogisticalControllerTileEntity te) { + LogisticalNetwork networkByID = getNetworkByID(te.getWorld(), te.getNetworkId()); + networkByID.addController(te); + return networkByID; + } + + public void handleRemoved(LogisticalControllerTileEntity te) { + getNetworkByID(te.getWorld(), te.getNetworkId()).removeController(te); + removeIfEmpty(te.getWorld(), te.getNetworkId()); + } + + public LogisticalNetwork getNetworkByID(IWorld world, UUID id) { + Map worldNets = networks.get(world); + if (!worldNets.containsKey(id)) + worldNets.put(id, new LogisticalNetwork()); + return worldNets.get(id); + } + + private void removeIfEmpty(IWorld world, UUID id) { + Map worldNets = networks.get(world); + if (worldNets.get(id).isEmpty()) + worldNets.remove(id); + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/base/IncrementalInventoryUpdate.java b/src/main/java/com/simibubi/create/modules/logistics/management/base/IncrementalInventoryUpdate.java new file mode 100644 index 000000000..7f4bebaef --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/base/IncrementalInventoryUpdate.java @@ -0,0 +1,11 @@ +package com.simibubi.create.modules.logistics.management.base; + +import net.minecraft.item.ItemStack; + +public class IncrementalInventoryUpdate { + + public ItemStack item; + public int newAmount; + + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalActor.java b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalActor.java new file mode 100644 index 000000000..6a3b8b7ff --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalActor.java @@ -0,0 +1,36 @@ +package com.simibubi.create.modules.logistics.management.base; + +public abstract class LogisticalActor { + + public enum Actors { + SUPPLY(new Supply()), + STORAGE(new Storage()), + DEMAND(new Demand()), + + ; + + private LogisticalActor actor; + + public LogisticalActor get() { + return this.actor; + } + + private Actors(LogisticalActor actor) { + this.actor = actor; + } + } + + public static class Supply extends LogisticalActor { + + } + + public static class Storage extends LogisticalActor { + + } + + public static class Demand extends LogisticalActor { + + } + + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalCasingBlock.java b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalCasingBlock.java new file mode 100644 index 000000000..e5e3a9ffc --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalCasingBlock.java @@ -0,0 +1,261 @@ +package com.simibubi.create.modules.logistics.management.base; + +import static net.minecraft.util.Direction.AxisDirection.POSITIVE; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import com.simibubi.create.AllBlocks; +import com.simibubi.create.foundation.block.IWithTileEntity; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItemUseContext; +import net.minecraft.state.BooleanProperty; +import net.minecraft.state.EnumProperty; +import net.minecraft.state.IProperty; +import net.minecraft.state.StateContainer.Builder; +import net.minecraft.state.properties.BlockStateProperties; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.Hand; +import net.minecraft.util.Direction.Axis; +import net.minecraft.util.Direction.AxisDirection; +import net.minecraft.util.IStringSerializable; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.util.math.shapes.ISelectionContext; +import net.minecraft.util.math.shapes.VoxelShape; +import net.minecraft.util.math.shapes.VoxelShapes; +import net.minecraft.util.text.StringTextComponent; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.world.IBlockReader; +import net.minecraft.world.IWorld; +import net.minecraft.world.IWorldReader; +import net.minecraft.world.World; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; + +public class LogisticalCasingBlock extends Block implements IWithTileEntity { + + public static final EnumProperty AXIS = BlockStateProperties.AXIS; + public static final IProperty PART = EnumProperty.create("part", Part.class); + public static final BooleanProperty ACTIVE = BooleanProperty.create("active"); + + public static final VoxelShape SINGLE_SHAPE = VoxelShapes.or(makeCuboidShape(0, 0, 0, 16, 2, 16), + makeCuboidShape(1, 1, 1, 15, 15, 15), makeCuboidShape(0, 14, 0, 16, 16, 16)); + + public LogisticalCasingBlock() { + super(Properties.from(Blocks.DARK_OAK_PLANKS)); + setDefaultState(getDefaultState().with(PART, Part.NONE).with(AXIS, Axis.Y).with(ACTIVE, false)); + } + + @Override + public BlockState getStateForPlacement(BlockItemUseContext context) { + BlockState state = getDefaultState(); + for (Direction face : Direction.values()) { + BlockState neighbour = context.getWorld().getBlockState(context.getPos().offset(face)); + if (!AllBlocks.LOGISTICAL_CASING.typeOf(neighbour)) + continue; + if (neighbour.get(PART) != Part.NONE && face.getAxis() != neighbour.get(AXIS)) + continue; + state = state.with(PART, face.getAxisDirection() == AxisDirection.POSITIVE ? Part.START : Part.END); + state = state.with(AXIS, face.getAxis()); + } + + return state; + } + + @Override + public void onNeighborChange(BlockState state, IWorldReader world, BlockPos pos, BlockPos neighbor) { + BlockState invState = world.getBlockState(neighbor); + + if (!invState.hasTileEntity()) + return; + TileEntity invTE = world.getTileEntity(neighbor); + + LazyOptional inventory = invTE.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY); + if (inventory.isPresent() && world instanceof IWorld) { + withTileEntityDo((IWorld) world, pos, te -> te.neighbourChanged(neighbor)); + } + } + + @Override + public void onReplaced(BlockState state, World worldIn, BlockPos pos, BlockState newState, boolean isMoving) { + boolean blockChanged = state.getBlock() != newState.getBlock(); + if (state.hasTileEntity() && (blockChanged || !newState.hasTileEntity())) { + worldIn.removeTileEntity(pos); + return; + } + if (blockChanged) { + Part part = state.get(PART); + Direction facing = Direction.getFacingFromAxis(POSITIVE, state.get(AXIS)); + if (part == Part.END || part == Part.MIDDLE) + worldIn.getPendingBlockTicks().scheduleTick(pos.offset(facing.getOpposite()), state.getBlock(), 1); + if (part == Part.START || part == Part.MIDDLE) + worldIn.getPendingBlockTicks().scheduleTick(pos.offset(facing), state.getBlock(), 1); + } + } + + @Override + public boolean hasTileEntity(BlockState state) { + return state.get(ACTIVE); + } + + @Override + public TileEntity createTileEntity(BlockState state, IBlockReader world) { + return new LogisticalCasingTileEntity(); + } + + @Override + public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context) { + return state.get(PART) == Part.NONE ? SINGLE_SHAPE : VoxelShapes.fullCube(); + } + + @Override + public BlockState updatePostPlacement(BlockState state, Direction face, BlockState facingState, IWorld worldIn, + BlockPos currentPos, BlockPos facingPos) { + Part part = state.get(PART); + boolean neighbourPresent = AllBlocks.LOGISTICAL_CASING.typeOf(facingState); + boolean alongAxis = face.getAxis() == state.get(AXIS); + boolean positive = face.getAxisDirection() == AxisDirection.POSITIVE; + boolean neighbourAlongAxis = neighbourPresent + && (facingState.get(PART) == Part.NONE || facingState.get(AXIS) == face.getAxis()); + + if (part == Part.NONE && neighbourPresent && neighbourAlongAxis) { + state = state.with(PART, positive ? Part.START : Part.END); + return state.with(AXIS, face.getAxis()); + } + + if (!alongAxis) + return state; + + if (part == Part.END) { + if (positive && neighbourPresent && neighbourAlongAxis) + return state.with(PART, Part.MIDDLE); + if (!positive && !neighbourPresent) + return state.with(PART, Part.NONE).with(AXIS, Axis.Y); + } + + if (part == Part.START) { + if (!positive && neighbourPresent && neighbourAlongAxis) + return state.with(PART, Part.MIDDLE); + if (positive && !neighbourPresent) + return state.with(PART, Part.NONE).with(AXIS, Axis.Y); + } + + if (part == Part.MIDDLE) { + if (!positive && !neighbourPresent) + return state.with(PART, Part.START); + if (positive && !neighbourPresent) + return state.with(PART, Part.END); + } + + return state; + } + + @Override + public boolean onBlockActivated(BlockState state, World worldIn, BlockPos pos, PlayerEntity player, Hand handIn, + BlockRayTraceResult hit) { + if (!player.getHeldItem(handIn).isEmpty()) + return false; + if (worldIn.isRemote) + return true; + if (!state.get(ACTIVE)) + player.sendStatusMessage(new StringTextComponent("Not Active").applyTextStyle(TextFormatting.RED), false); + else { + LogisticalCasingTileEntity tileEntity = (LogisticalCasingTileEntity) worldIn.getTileEntity(pos); + player.sendStatusMessage(new StringTextComponent("Controllers: " + tileEntity.controllers.toString()) + .applyTextStyle(TextFormatting.GREEN), false); + } + + return true; + } + + @Override + public void onBlockAdded(BlockState state, World worldIn, BlockPos pos, BlockState oldState, boolean isMoving) { + boolean blockChanged = state.getBlock() != oldState.getBlock(); + if (blockChanged) + worldIn.getPendingBlockTicks().scheduleTick(pos, state.getBlock(), 1); + } + + @Override + public void tick(BlockState state, World worldIn, BlockPos pos, Random random) { + synchronizeCasingGroup(worldIn, pos); + } + + protected void synchronizeCasingGroup(World world, BlockPos start) { + List chain = LogisticalControllerBlock.collectCasings(world, start); + Set controllers = new HashSet<>(); + + // Collect all Controllers + for (BlockPos pos : chain) { + BlockState casing = world.getBlockState(pos); + if (!casing.get(ACTIVE)) + continue; + LogisticalCasingTileEntity te = (LogisticalCasingTileEntity) world.getTileEntity(pos); + if (te == null) + continue; + for (BlockPos controller : te.controllers) { + if (controller.withinDistance(te.getPos(), 1 + 1 / 512f)) + controllers.add(controller); + } + } + + // Advertise all Controllers + for (BlockPos pos : chain) { + BlockState state = world.getBlockState(pos); + boolean shouldBeActive = !controllers.isEmpty(); + if (state.get(ACTIVE) != shouldBeActive) { + if (!shouldBeActive) { + LogisticalCasingTileEntity te = (LogisticalCasingTileEntity) world.getTileEntity(pos); + te.controllers.forEach(te::detachController); + } + world.setBlockState(pos, state.with(ACTIVE, shouldBeActive)); + } + if (!shouldBeActive) + continue; + + LogisticalCasingTileEntity te = (LogisticalCasingTileEntity) world.getTileEntity(pos); + if (te == null) + continue; + + // detach missing + for (Iterator iterator = te.controllers.iterator(); iterator.hasNext();) { + BlockPos controller = iterator.next(); + if (controllers.contains(controller)) + continue; + iterator.remove(); + te.detachController(controller); + } + + // attach new + for (BlockPos controller : controllers) { + if (!te.controllers.contains(controller)) + te.addController(controller); + } + } + } + + @Override + protected void fillStateContainer(Builder builder) { + builder.add(AXIS, PART, ACTIVE); + super.fillStateContainer(builder); + } + + public enum Part implements IStringSerializable { + START, MIDDLE, END, NONE; + + @Override + public String getName() { + return name().toLowerCase(); + } + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalCasingTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalCasingTileEntity.java new file mode 100644 index 000000000..d7613adb3 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalCasingTileEntity.java @@ -0,0 +1,131 @@ +package com.simibubi.create.modules.logistics.management.base; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.base.Predicates; +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.foundation.block.SyncedTileEntity; + +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.ListNBT; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.Constants.NBT; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.IItemHandlerModifiable; +import net.minecraftforge.items.wrapper.CombinedInvWrapper; + +public class LogisticalCasingTileEntity extends SyncedTileEntity { + + Set controllers = new HashSet<>(); + + public LogisticalCasingTileEntity() { + super(AllTileEntities.LOGISTICAL_CASING.type); + } + + public boolean controllerPresent() { + if (controllers.isEmpty()) + return false; + for (BlockPos blockPos : controllers) { + if (world.isBlockPresent(blockPos)) + return true; + } + return false; + } + + @Override + public CompoundNBT write(CompoundNBT compound) { + ListNBT contollerNBT = new ListNBT(); + controllers.forEach(pos -> contollerNBT.add(NBTUtil.writeBlockPos(pos))); + compound.put("Controllers", contollerNBT); + return super.write(compound); + } + + @Override + public void read(CompoundNBT compound) { + controllers.clear(); + ListNBT controllerNBT = compound.getList("Controllers", NBT.TAG_COMPOUND); + controllerNBT.forEach(tag -> controllers.add(NBTUtil.readBlockPos((CompoundNBT) tag))); + super.read(compound); + } + + public void neighbourChanged(BlockPos neighbour) { + if (!controllerPresent()) + return; + for (LogisticalControllerTileEntity controller : getControllers()) { + if (!(controller instanceof LogisticalInventoryControllerTileEntity)) + continue; + ((LogisticalInventoryControllerTileEntity) controller).inventoryChanged(neighbour); + } + } + + public void addController(BlockPos pos) { + controllers.add(pos); + attachController(pos); + markDirty(); + } + + public void removeController(BlockPos pos) { + controllers.remove(pos); + detachController(pos); + markDirty(); + + if (controllers.isEmpty()) + world.setBlockState(getPos(), getBlockState().with(LogisticalCasingBlock.ACTIVE, false)); + } + + public void detachController(BlockPos pos) { + TileEntity tileEntity = world.getTileEntity(pos); + if (!(tileEntity instanceof LogisticalInventoryControllerTileEntity)) + return; + for (Direction facing : Direction.values()) + ((LogisticalInventoryControllerTileEntity) tileEntity).detachInventory(getPos().offset(facing)); + } + + public void attachController(BlockPos pos) { + TileEntity tileEntity = world.getTileEntity(pos); + if (!(tileEntity instanceof LogisticalControllerTileEntity)) + return; + for (Direction facing : Direction.values()) + ((LogisticalInventoryControllerTileEntity) tileEntity).inventoryChanged(getPos().offset(facing)); + } + + @Override + public void remove() { + controllers.forEach(this::detachController); + super.remove(); + } + + @Override + public LazyOptional getCapability(Capability cap, Direction side) { + if (!controllerPresent()) + return LazyOptional.empty(); + List TEs = getControllers(); + if (controllers.isEmpty()) + return LazyOptional.empty(); + List invs = TEs.stream().map(te -> te.getCasingCapability(cap, side).orElse(null)) + .filter(Predicates.notNull()).filter(inv -> inv instanceof IItemHandlerModifiable) + .collect(Collectors.toList()); + IItemHandlerModifiable[] params = new IItemHandlerModifiable[invs.size()]; + invs.toArray(params); + return LazyOptional.of(() -> new CombinedInvWrapper(params)).cast(); + } + + public List getControllers() { + List TEs = new ArrayList<>(controllers.size()); + for (BlockPos controllerPos : controllers) { + TileEntity tileEntity = world.getTileEntity(controllerPos); + if (tileEntity instanceof LogisticalControllerTileEntity) + TEs.add((LogisticalControllerTileEntity) tileEntity); + } + return TEs; + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerBlock.java b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerBlock.java new file mode 100644 index 000000000..fb2630a9c --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerBlock.java @@ -0,0 +1,263 @@ +package com.simibubi.create.modules.logistics.management.base; + +import static com.simibubi.create.modules.logistics.management.base.LogisticalCasingBlock.ACTIVE; +import static com.simibubi.create.modules.logistics.management.base.LogisticalCasingBlock.PART; +import static net.minecraft.state.properties.BlockStateProperties.AXIS; +import static net.minecraft.util.Direction.AxisDirection.POSITIVE; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllItems; +import com.simibubi.create.foundation.block.IWithTileEntity; +import com.simibubi.create.foundation.block.IWithoutBlockItem; +import com.simibubi.create.foundation.block.RenderUtilityBlock; +import com.simibubi.create.modules.logistics.management.base.LogisticalCasingBlock.Part; +import com.simibubi.create.modules.logistics.management.controller.CalculationTileEntity; +import com.simibubi.create.modules.logistics.management.controller.RequestTileEntity; +import com.simibubi.create.modules.logistics.management.controller.StorageTileEntity; +import com.simibubi.create.modules.logistics.management.controller.SupplyTileEntity; +import com.simibubi.create.modules.logistics.management.controller.TransactionsTileEntity; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.DirectionalBlock; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItemUseContext; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.state.EnumProperty; +import net.minecraft.state.IProperty; +import net.minecraft.state.StateContainer.Builder; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.Hand; +import net.minecraft.util.IStringSerializable; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.util.math.shapes.ISelectionContext; +import net.minecraft.util.math.shapes.VoxelShape; +import net.minecraft.util.math.shapes.VoxelShapes; +import net.minecraft.world.IBlockReader; +import net.minecraft.world.IWorldReader; +import net.minecraft.world.World; + +public class LogisticalControllerBlock extends DirectionalBlock + implements IWithoutBlockItem, IWithTileEntity { + + public static final IProperty TYPE = EnumProperty.create("type", Type.class); + + public static final VoxelShape UP_SHAPE = makeCuboidShape(2, -1, 2, 14, 3, 14), + DOWN_SHAPE = makeCuboidShape(2, 13, 2, 14, 17, 14), SOUTH_SHAPE = makeCuboidShape(2, 2, -1, 14, 14, 3), + NORTH_SHAPE = makeCuboidShape(2, 2, 13, 14, 14, 17), EAST_SHAPE = makeCuboidShape(-1, 2, 2, 3, 14, 14), + WEST_SHAPE = makeCuboidShape(13, 2, 2, 17, 14, 14); + + public LogisticalControllerBlock() { + super(Properties.from(Blocks.PISTON)); + } + + @Override + protected void fillStateContainer(Builder builder) { + builder.add(TYPE, FACING); + super.fillStateContainer(builder); + } + + @Override + public boolean hasTileEntity(BlockState state) { + return true; + } + + @Override + public TileEntity createTileEntity(BlockState state, IBlockReader world) { + Type type = state.get(TYPE); + + if (type == Type.SUPPLY) + return new SupplyTileEntity(); + if (type == Type.REQUEST) + return new RequestTileEntity(); + if (type == Type.STORAGE) + return new StorageTileEntity(); + if (type == Type.CALCULATION) + return new CalculationTileEntity(); + if (type == Type.TRANSACTIONS) + return new TransactionsTileEntity(); + + return null; + } + + @Override + public void onBlockPlacedBy(World worldIn, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack) { + if (!(placer instanceof PlayerEntity)) + return; + PlayerEntity player = (PlayerEntity) placer; + for (int slot = 0; slot < player.inventory.getSizeInventory(); slot++) { + ItemStack itemStack = player.inventory.getStackInSlot(slot); + if (!AllItems.LOGISTICAL_DIAL.typeOf(itemStack)) + continue; + if (!itemStack.hasTag()) + continue; + withTileEntityDo(worldIn, pos, te -> te.setNetworkId(itemStack.getTag().getUniqueId("NetworkID"))); + return; + } + } + + @Override + public BlockState getStateForPlacement(BlockItemUseContext context) { + BlockState state = getDefaultState().with(FACING, context.getFace()); + + Item item = context.getItem().getItem(); + if (item instanceof LogisticalControllerItem) + state = state.with(TYPE, ((LogisticalControllerItem) item).getType()); + + return state; + } + + @Override + public boolean isValidPosition(BlockState state, IWorldReader worldIn, BlockPos pos) { + Direction facing = state.get(FACING); + BlockPos offset = pos.offset(facing.getOpposite()); + BlockState blockState = worldIn.getBlockState(offset); + boolean isCasing = AllBlocks.LOGISTICAL_CASING.typeOf(blockState); + + return isCasing; + } + + @Override + public boolean onBlockActivated(BlockState state, World worldIn, BlockPos pos, PlayerEntity player, Hand handIn, + BlockRayTraceResult hit) { + if (player.isSneaking() || !player.isAllowEdit()) + return false; + ItemStack held = player.getHeldItem(handIn); + if (held.getItem() != Items.NAME_TAG) + return false; + if (!held.hasDisplayName()) + return false; + + withTileEntityDo(worldIn, pos, te -> { + te.setName(held.getDisplayName().getUnformattedComponentText()); + }); + + return true; + } + + @Override + public void onBlockAdded(BlockState state, World worldIn, BlockPos pos, BlockState oldState, boolean isMoving) { + BlockPos start = pos.offset(state.get(FACING).getOpposite()); + List toUpdate = collectCasings(worldIn, start); + + for (BlockPos blockPos : toUpdate) { + worldIn.setBlockState(blockPos, worldIn.getBlockState(blockPos).with(ACTIVE, true)); + LogisticalCasingTileEntity tileEntity = (LogisticalCasingTileEntity) worldIn.getTileEntity(blockPos); + tileEntity.addController(pos); + } + } + + @Override + public void onReplaced(BlockState state, World worldIn, BlockPos pos, BlockState newState, boolean isMoving) { + boolean blockChanged = state.getBlock() != newState.getBlock() || state.get(TYPE) != newState.get(TYPE); + + if (blockChanged) { + BlockPos start = pos.offset(state.get(FACING).getOpposite()); + List toUpdate = collectCasings(worldIn, start); + + for (BlockPos blockPos : toUpdate) { + if (!worldIn.getBlockState(blockPos).get(ACTIVE)) + continue; + LogisticalCasingTileEntity tileEntity = (LogisticalCasingTileEntity) worldIn.getTileEntity(blockPos); + tileEntity.removeController(pos); + } + } + + if (state.hasTileEntity() && blockChanged) { + worldIn.removeTileEntity(pos); + } + } + + public static List collectCasings(World worldIn, BlockPos start) { + BlockState casing = worldIn.getBlockState(start); + if (!AllBlocks.LOGISTICAL_CASING.typeOf(casing)) + return Collections.emptyList(); + List casings = new ArrayList<>(); + casings.add(start); + if (casing.get(PART) != Part.NONE) { + Direction casingDirection = Direction.getFacingFromAxis(POSITIVE, casing.get(AXIS)); + BlockPos search = start; + + for (int i = 0; i < 1000; i++) { + if (worldIn.getBlockState(search).get(PART) == Part.START) + break; + search = search.offset(casingDirection.getOpposite()); + if (!AllBlocks.LOGISTICAL_CASING.typeOf(worldIn.getBlockState(search))) + break; + casings.add(search); + } + search = start; + for (int i = 0; i < 1000; i++) { + if (worldIn.getBlockState(search).get(PART) == Part.END) + break; + search = search.offset(casingDirection); + if (!AllBlocks.LOGISTICAL_CASING.typeOf(worldIn.getBlockState(search))) + break; + casings.add(search); + } + } + return casings; + } + + @Override + public void neighborChanged(BlockState state, World worldIn, BlockPos pos, Block blockIn, BlockPos fromPos, + boolean isMoving) { + if (state.isValidPosition(worldIn, pos)) + return; + + TileEntity tileentity = state.hasTileEntity() ? worldIn.getTileEntity(pos) : null; + spawnDrops(state, worldIn, pos, tileentity); + worldIn.removeBlock(pos, false); + + for (Direction direction : Direction.values()) + worldIn.notifyNeighborsOfStateChange(pos.offset(direction), this); + } + + @Override + public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context) { + Direction facing = state.get(FACING); + + if (facing == Direction.UP) + return UP_SHAPE; + if (facing == Direction.DOWN) + return DOWN_SHAPE; + if (facing == Direction.EAST) + return EAST_SHAPE; + if (facing == Direction.WEST) + return WEST_SHAPE; + if (facing == Direction.NORTH) + return NORTH_SHAPE; + if (facing == Direction.SOUTH) + return SOUTH_SHAPE; + + return VoxelShapes.empty(); + } + + public static class LogisticalControllerIndicatorBlock extends RenderUtilityBlock { + @Override + protected void fillStateContainer(Builder builder) { + builder.add(TYPE, FACING); + super.fillStateContainer(builder); + } + } + + public enum Type implements IStringSerializable { + SUPPLY, REQUEST, STORAGE, CALCULATION, TRANSACTIONS; + + @Override + public String getName() { + return name().toLowerCase(); + } + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerItem.java b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerItem.java new file mode 100644 index 000000000..748e81ae0 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerItem.java @@ -0,0 +1,31 @@ +package com.simibubi.create.modules.logistics.management.base; + +import com.simibubi.create.AllBlocks; +import com.simibubi.create.modules.logistics.management.base.LogisticalControllerBlock.Type; + +import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; + +public class LogisticalControllerItem extends BlockItem { + + private Type type; + + public LogisticalControllerItem(Properties builder, Type type) { + super(AllBlocks.LOGISTICAL_CONTROLLER.get(), builder); + this.type = type; + } + + @Override + public void fillItemGroup(ItemGroup group, NonNullList items) { + if (this.isInGroup(group)) { + items.add(new ItemStack(this)); + } + } + + public Type getType() { + return type; + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerTileEntity.java new file mode 100644 index 000000000..7fcf580ca --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerTileEntity.java @@ -0,0 +1,156 @@ +package com.simibubi.create.modules.logistics.management.base; + +import java.util.UUID; + +import com.simibubi.create.Create; +import com.simibubi.create.foundation.block.SyncedTileEntity; +import com.simibubi.create.foundation.utility.ColorHelper; +import com.simibubi.create.modules.logistics.management.LogisticalNetwork; + +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.tileentity.ITickableTileEntity; +import net.minecraft.tileentity.TileEntityType; +import net.minecraft.util.Direction; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; + +public abstract class LogisticalControllerTileEntity extends SyncedTileEntity + implements Comparable, ITickableTileEntity { + + public static final int COOLDOWN = 20; + + protected Priority priority = Priority.LOW; + protected LogisticalNetwork network; + protected String name = ""; + protected UUID networkId; + protected boolean initialize; + protected boolean checkTasks; + protected int taskCooldown; + + public LogisticalControllerTileEntity(TileEntityType tileEntityTypeIn) { + super(tileEntityTypeIn); + initialize = true; + } + + @Override + public void tick() { + if (initialize) { + initialize = false; + initialize(); + return; + } + + if (taskCooldown > 0) + taskCooldown--; + } + + protected void initialize() { + if (networkId != null) + handleAdded(); + } + + @Override + public void remove() { + if (networkId != null) + handleRemoved(); + super.remove(); + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public boolean hasFastRenderer() { + return true; + } + + public void notifyTaskUpdate() { + checkTasks = true; + } + + @Override + public CompoundNBT write(CompoundNBT compound) { + if (networkId != null) + compound.putUniqueId("NetworkID", networkId); + compound.putString("Address", name); + return super.write(compound); + } + + public UUID getNetworkId() { + return networkId; + } + + @Override + public void read(CompoundNBT compound) { + if (compound.contains("NetworkIDLeast")) + networkId = compound.getUniqueId("NetworkID"); + name = compound.getString("Address"); + super.read(compound); + } + + public int getColor() { + return colorFromUUID(networkId); + } + + public static int colorFromUUID(UUID uuid) { + if (uuid == null) + return 0x333333; + int rainbowColor = ColorHelper.rainbowColor((int) uuid.getLeastSignificantBits()); + return ColorHelper.mixColors(rainbowColor, 0xFFFFFF, .5f); + } + + public LazyOptional getCasingCapability(Capability cap, Direction side) { + return LazyOptional.empty(); + } + + public void setNetworkId(UUID uniqueId) { + if (getNetwork() != null) + handleRemoved(); + networkId = uniqueId; + handleAdded(); + markDirty(); + sendData(); + } + + public void handleAdded() { + if (world.isRemote) + return; + if (getNetwork() != null) + return; + network = Create.logisticalNetworkHandler.handleAdded(this); + } + + public void handleRemoved() { + if (world.isRemote) + return; + Create.logisticalNetworkHandler.handleRemoved(this); + network = null; + } + + public boolean isSupplier() { + return false; + } + + public boolean isReceiver() { + return false; + } + + @Override + public int compareTo(LogisticalControllerTileEntity o) { + return this.priority.compareTo(o.priority); + } + + public LogisticalNetwork getNetwork() { + return network; + } + + public static enum Priority { + LOWEST, LOW, MEDIUM, HIGH, HIGHEST; + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerTileEntityRenderer.java b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerTileEntityRenderer.java new file mode 100644 index 000000000..f2b9492fa --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalControllerTileEntityRenderer.java @@ -0,0 +1,94 @@ +package com.simibubi.create.modules.logistics.management.base; + +import static com.simibubi.create.modules.logistics.management.base.LogisticalControllerBlock.TYPE; +import static net.minecraft.state.properties.BlockStateProperties.FACING; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +import org.lwjgl.opengl.GL11; + +import com.simibubi.create.AllBlocks; +import com.simibubi.create.foundation.utility.BufferManipulator; + +import net.minecraft.block.BlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BlockModelRenderer; +import net.minecraft.client.renderer.BlockRendererDispatcher; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.model.IBakedModel; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.client.model.animation.TileEntityRendererFast; +import net.minecraftforge.client.model.data.EmptyModelData; + +public class LogisticalControllerTileEntityRenderer extends TileEntityRendererFast { + + protected class LogisticalControllerIndicatorRenderer extends BufferManipulator { + + public LogisticalControllerIndicatorRenderer(ByteBuffer original) { + super(original); + } + + public ByteBuffer getTransformed(float xIn, float yIn, float zIn, int color, int packedLightCoords) { + original.rewind(); + mutable.rewind(); + + byte r = (byte) (color >> 16); + byte g = (byte) ((color >> 8) & 0xFF); + byte b = (byte) (color & 0xFF); + byte a = (byte) 255; + + for (int vertex = 0; vertex < vertexCount(original); vertex++) { + putColor(mutable, vertex, r, g, b, a); + putPos(mutable, vertex, getX(original, vertex) + xIn, getY(original, vertex) + yIn, + getZ(original, vertex) + zIn); + putLight(mutable, vertex, packedLightCoords); + } + + return mutable; + } + } + + protected static Map cachedBuffers = new HashMap<>(); + + @Override + public void renderTileEntityFast(LogisticalControllerTileEntity te, double x, double y, double z, + float partialTicks, int destroyStage, BufferBuilder buffer) { + BlockPos pos = te.getPos(); + BlockState blockState = te.getBlockState(); + + if (AllBlocks.LOGISTICAL_INDEX.typeOf(blockState)) + return; + + BlockState renderedState = AllBlocks.LOGISTICAL_CONTROLLER_INDICATOR.get().getDefaultState() + .with(FACING, blockState.get(FACING)).with(TYPE, blockState.get(TYPE)); + + if (!cachedBuffers.containsKey(renderedState)) { + BlockRendererDispatcher dispatcher = Minecraft.getInstance().getBlockRendererDispatcher(); + BlockModelRenderer blockRenderer = dispatcher.getBlockModelRenderer(); + IBakedModel originalModel = dispatcher.getModelForState(renderedState); + BufferBuilder builder = new BufferBuilder(0); + Random random = new Random(); + + builder.setTranslation(0, 1, 0); + builder.begin(GL11.GL_QUADS, DefaultVertexFormats.BLOCK); + blockRenderer.renderModelFlat(getWorld(), originalModel, renderedState, BlockPos.ZERO.down(), builder, true, + random, 42, EmptyModelData.INSTANCE); + builder.finishDrawing(); + + cachedBuffers.put(renderedState, new LogisticalControllerIndicatorRenderer(builder.getByteBuffer())); + } + + int packedLightmapCoords = blockState.getPackedLightmapCoords(getWorld(), pos); + buffer.putBulkData(cachedBuffers.get(renderedState).getTransformed((float) x, (float) y, (float) z, + te.getColor(), packedLightmapCoords)); + } + + public static void invalidateCache() { + cachedBuffers.clear(); + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalInventoryControllerTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalInventoryControllerTileEntity.java new file mode 100644 index 000000000..9a66b17c5 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalInventoryControllerTileEntity.java @@ -0,0 +1,402 @@ +package com.simibubi.create.modules.logistics.management.base; + +import static net.minecraft.state.properties.BlockStateProperties.FACING; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.tuple.Pair; + +import com.simibubi.create.foundation.type.CombinedCountedItemsList; +import com.simibubi.create.foundation.type.CountedItemsList; +import com.simibubi.create.foundation.type.CountedItemsList.ItemStackEntry; +import com.simibubi.create.foundation.utility.ItemHelper; +import com.simibubi.create.modules.logistics.item.CardboardBoxItem; +import com.simibubi.create.modules.logistics.management.base.LogisticalTask.DepositTask; +import com.simibubi.create.modules.logistics.management.base.LogisticalTask.SupplyTask; + +import net.minecraft.block.BlockState; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.Ingredient; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityType; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.ItemStackHandler; + +public abstract class LogisticalInventoryControllerTileEntity extends LogisticalControllerTileEntity { + + protected Map observedInventories = new HashMap<>(); + protected Map inventoryByHandler = new HashMap<>(); + protected CombinedCountedItemsList allItems = new CombinedCountedItemsList<>(); + protected boolean inventorySetDirty; + + protected LazyOptional shippingInventory; + protected boolean tryInsertBox; + + public LogisticalInventoryControllerTileEntity(TileEntityType tileEntityTypeIn) { + super(tileEntityTypeIn); + this.shippingInventory = LazyOptional.of(this::createInventory); + } + + @Override + public void read(CompoundNBT compound) { + super.read(compound); + if (compound.contains("ShippingInventory")) { + ShippingInventory inv = (ShippingInventory) shippingInventory.orElse(null); + inv.deserializeNBT(compound.getCompound("ShippingInventory")); + } + } + + public void inventoryChanged(BlockPos pos) { + removeInvalidatedInventories(); + TileEntity invTE = world.getTileEntity(pos); + if (invTE == null) + return; + if (invTE instanceof LogisticalCasingTileEntity) + return; + + LazyOptional inventory = invTE.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY); + if (!observedInventories.containsKey(pos)) { + addInventory(pos, inventory); + notifyIndexers(observedInventories.get(pos).countedItems); + } else { + if (inventoryByHandler.containsKey(inventory.orElse(null))) { + List localChanges = observedInventories.get(pos).refresh(); + CountedItemsList changes = new CountedItemsList(); + for (ItemStackEntry entry : localChanges) + changes.add(entry.stack, allItems.get().getItemCount(entry.stack)); + notifyIndexers(changes); + } + } + checkTasks = true; + tryInsertBox = true; + } + + public void detachInventory(BlockPos pos) { + observedInventories.remove(pos); + inventorySetDirty = true; + } + + public void addInventory(BlockPos pos, LazyOptional inventory) { + observedInventories.put(pos, new ConnectedInventory(inventory)); + inventorySetDirty = true; + } + + protected void notifyIndexers(CountedItemsList updates) { + if (network == null) + return; + network.indexers.forEach(indexer -> indexer.handleUpdatedController(getName(), updates)); + } + + @Override + public void tick() { + super.tick(); + + if (taskCooldown > 0 || world.isRemote) + return; + + if (tryInsertBox) { + tryInsertBox = false; + tryInsertBox(); + } + + if (checkTasks) { + checkTasks = false; + if (getNetwork() == null) + return; + checkTasks(); + } + + taskCooldown = COOLDOWN; + } + + private void tryInsertBox() { + if (!canReceive()) + return; + + ShippingInventory inventory = getInventory(); + ItemStack box = inventory.getStackInSlot(ShippingInventory.RECEIVING); + if (box.isEmpty()) + return; + List contents = CardboardBoxItem.getContents(box); + if (InsertAll(contents, true)) { + ItemStack copy = box.copy(); + copy.shrink(1); + inventory.setStackInSlot(ShippingInventory.RECEIVING, copy); + InsertAll(contents, false); + } + } + + public boolean InsertAll(List stacks, boolean simulate) { + removeInvalidatedInventories(); + for (ItemStack stack : stacks) { + ItemStack toInsert = stack.copy(); + + InventoryScan: for (IItemHandler inv : getObservedInventories()) { + for (int slot = 0; slot < inv.getSlots(); slot++) { + toInsert = inv.insertItem(slot, stack, simulate); + if (toInsert.isEmpty()) + break InventoryScan; + } + } + if (!toInsert.isEmpty()) + return false; + } + return true; + } + + private void removeInvalidatedInventories() { + observedInventories.keySet().stream().filter(key -> !observedInventories.get(key).itemHandler.isPresent()) + .collect(Collectors.toList()).forEach(this::detachInventory); + } + + @Override + protected void initialize() { + super.initialize(); + BlockPos start = pos.offset(getBlockState().get(FACING).getOpposite()); + List toUpdate = LogisticalControllerBlock.collectCasings(world, start); + for (BlockPos blockPos : toUpdate) { + world.updateComparatorOutputLevel(blockPos, world.getBlockState(blockPos).getBlock()); + + for (Direction face : Direction.values()) { + BlockPos neighbour = blockPos.offset(face); + BlockState invState = world.getBlockState(neighbour); + if (!invState.hasTileEntity()) + continue; + TileEntity invTE = world.getTileEntity(neighbour); + if (invTE instanceof LogisticalCasingTileEntity) + continue; + + LazyOptional inventory = invTE + .getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY); + if (!inventory.isPresent()) + continue; + addInventory(neighbour, inventory); + taskCooldown = COOLDOWN; + checkTasks = true; + } + } + } + + private void checkTasks() { + for (Iterator iterator = getNetwork().internalTaskQueue.iterator(); iterator.hasNext();) { + LogisticalTask task = iterator.next(); + + if (canSupply() && task instanceof SupplyTask) { + List> items = ((SupplyTask) task).items; + if (findItems(items, true) == null) + continue; + + List collectedStacks = findItems(items, false); + getInventory().createPackage(collectedStacks, task.targetAddress); + iterator.remove(); + checkTasks = true; + return; + } + + if (canReceive() && task instanceof DepositTask) { + + } + } + } + + public List findItems(List> items, boolean simulate) { + removeInvalidatedInventories(); + List foundItems = new ArrayList<>(); + + // Over Requested Ingredients + for (Pair pair : items) { + int amountLeft = pair.getValue(); + + // Over Attached inventories + InventoryScan: for (IItemHandler inv : getObservedInventories()) { + + // Over Slots + for (int slot = 0; slot < inv.getSlots(); slot++) { + ItemStack stackInSlot = inv.getStackInSlot(slot); + if (!pair.getKey().test(stackInSlot)) + continue; + + ItemStack extracted = inv.extractItem(slot, amountLeft, simulate); + amountLeft -= extracted.getCount(); + ItemHelper.addToList(extracted, foundItems); + + if (amountLeft == 0) + break InventoryScan; + } + } + if (amountLeft > 0) + return null; + } + return foundItems; + } + + public CountedItemsList getAllItems() { + if (inventorySetDirty) + refreshItemHandlerSet(); + return allItems.get(); + } + + public Collection getObservedInventories() { + if (inventorySetDirty) + refreshItemHandlerSet(); + return inventoryByHandler.keySet(); + } + + public void refreshItemHandlerSet() { + inventorySetDirty = false; + inventoryByHandler.clear(); + allItems.clear(); + observedInventories.forEach((pos, connectedInventory) -> { + if (connectedInventory.itemHandler.isPresent()) { + IItemHandler inv = connectedInventory.itemHandler.orElse(null); + for (IItemHandler iItemHandler : inventoryByHandler.keySet()) { + if (ItemHelper.isSameInventory(iItemHandler, inv)) + return; + } + inventoryByHandler.put(inv, connectedInventory); + allItems.add(inv, connectedInventory.countedItems); + } + }); + + } + + @Override + public CompoundNBT write(CompoundNBT compound) { + shippingInventory.ifPresent(inv -> compound.put("ShippingInventory", ((ShippingInventory) inv).serializeNBT())); + return super.write(compound); + } + + @Override + public void remove() { + shippingInventory.invalidate(); + super.remove(); + } + + public LazyOptional getCasingCapability(Capability cap, Direction side) { + if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) + return shippingInventory.cast(); + return LazyOptional.empty(); + } + + protected boolean canSupply() { + return isSupplier() && getInventory().canCreatePackage(); + } + + protected boolean canReceive() { + return isReceiver(); + } + + public ShippingInventory getInventory() { + return (ShippingInventory) shippingInventory.orElse(null); + } + + public class ConnectedInventory { + LazyOptional itemHandler; + CountedItemsList countedItems; + + public ConnectedInventory(LazyOptional inv) { + itemHandler = inv; + countedItems = makeList(inv); + } + + public CountedItemsList makeList(LazyOptional inv) { + return inv.isPresent() ? new CountedItemsList(inv.orElse(null)) : new CountedItemsList(); + } + + public List refresh() { + CountedItemsList newList = makeList(itemHandler); + List stacksToUpdate = countedItems.getStacksToUpdate(newList); + countedItems = newList; + allItems.add(itemHandler.orElse(null), countedItems); + return stacksToUpdate; + } + } + + protected abstract ShippingInventory createInventory(); + + public class ShippingInventory extends ItemStackHandler { + + static final int SHIPPING = 0; + static final int RECEIVING = 1; + boolean ships; + boolean receives; + + public ShippingInventory(boolean ships, boolean receives) { + super(2); + this.ships = ships; + this.receives = receives; + } + + @Override + public boolean isItemValid(int slot, ItemStack stack) { + if (slot == RECEIVING && receives) + return stack.getItem() instanceof CardboardBoxItem && CardboardBoxItem.matchAddress(stack, name); + return false; + } + + public boolean canCreatePackage() { + return getStackInSlot(SHIPPING).isEmpty() && ships; + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + if (slot == RECEIVING) + return ItemStack.EMPTY; + return super.extractItem(slot, amount, simulate); + } + + public void createPackage(List contents, String address) { + ItemStack box = CardboardBoxItem.containing(contents); + CardboardBoxItem.addAddress(box, address); + setStackInSlot(SHIPPING, box); + } + + @Override + protected void onContentsChanged(int slot) { + super.onContentsChanged(slot); + markDirty(); + + if (slot == RECEIVING && !getStackInSlot(slot).isEmpty()) + tryInsertBox = true; + if (slot == SHIPPING && getStackInSlot(slot).isEmpty()) + checkTasks = true; + + BlockPos start = pos.offset(getBlockState().get(FACING).getOpposite()); + List toUpdate = LogisticalControllerBlock.collectCasings(world, start); + for (BlockPos blockPos : toUpdate) { + TileEntity tileEntity = world.getTileEntity(blockPos); + if (tileEntity == null) + continue; + tileEntity.getWorld().updateComparatorOutputLevel(blockPos, tileEntity.getBlockState().getBlock()); + } + } + + @Override + public CompoundNBT serializeNBT() { + CompoundNBT tag = super.serializeNBT(); + tag.putBoolean("Ships", ships); + tag.putBoolean("Receives", receives); + return tag; + } + + @Override + public void deserializeNBT(CompoundNBT nbt) { + ships = nbt.getBoolean("Ships"); + receives = nbt.getBoolean("Receives"); + super.deserializeNBT(nbt); + } + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalTask.java b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalTask.java new file mode 100644 index 000000000..e18e3f265 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalTask.java @@ -0,0 +1,45 @@ +package com.simibubi.create.modules.logistics.management.base; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.lang3.tuple.Pair; + +import com.simibubi.create.foundation.type.CountedItemsList.ItemStackEntry; + +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.Ingredient; + +public abstract class LogisticalTask implements Comparable { + + public enum Priority { + HIGH, MEDIUM, LOW + } + + Priority priority = Priority.LOW; + public String targetAddress; + + @Override + public int compareTo(LogisticalTask o) { + return priority.compareTo(o.priority); + } + + public static class SupplyTask extends LogisticalTask { + public List> items; + + public SupplyTask(ItemStackEntry requested, String address) { + items = Arrays.asList(Pair.of(Ingredient.fromStacks(requested.stack), requested.amount)); + targetAddress = address; + } + + } + + public static class DepositTask extends LogisticalTask { + public ItemStack stack; + + public DepositTask(ItemStack stack) { + this.stack = stack.copy(); + } + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalTileEntityExtension.java b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalTileEntityExtension.java new file mode 100644 index 000000000..a36ce42fb --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/base/LogisticalTileEntityExtension.java @@ -0,0 +1,13 @@ +package com.simibubi.create.modules.logistics.management.base; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class LogisticalTileEntityExtension { + + List tags = new ArrayList<>(); + UUID networkId; + LogisticalActor actor; + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/controller/CalculationTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/management/controller/CalculationTileEntity.java new file mode 100644 index 000000000..576bf50ce --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/controller/CalculationTileEntity.java @@ -0,0 +1,17 @@ +package com.simibubi.create.modules.logistics.management.controller; + +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.modules.logistics.management.base.LogisticalInventoryControllerTileEntity; + +public class CalculationTileEntity extends LogisticalInventoryControllerTileEntity { + + public CalculationTileEntity() { + super(AllTileEntities.LOGISTICAL_CALCULATION_CONTROLLER.type); + } + + @Override + protected ShippingInventory createInventory() { + return new ShippingInventory(false, false); + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/controller/RequestTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/management/controller/RequestTileEntity.java new file mode 100644 index 000000000..6270e04d8 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/controller/RequestTileEntity.java @@ -0,0 +1,31 @@ +package com.simibubi.create.modules.logistics.management.controller; + +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.modules.logistics.management.base.LogisticalInventoryControllerTileEntity; + +public class RequestTileEntity extends LogisticalInventoryControllerTileEntity { + + public RequestTileEntity() { + super(AllTileEntities.LOGISTICAL_REQUEST_CONTROLLER.type); + } + + @Override + protected ShippingInventory createInventory() { + return new ShippingInventory(false, true); + } + + @Override + public void handleAdded() { + if (world.isRemote) + return; + if (getNetwork() != null) + return; + super.handleAdded(); + } + + @Override + public boolean isReceiver() { + return true; + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/controller/StorageTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/management/controller/StorageTileEntity.java new file mode 100644 index 000000000..8f4b445e8 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/controller/StorageTileEntity.java @@ -0,0 +1,27 @@ +package com.simibubi.create.modules.logistics.management.controller; + +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.modules.logistics.management.base.LogisticalInventoryControllerTileEntity; + +public class StorageTileEntity extends LogisticalInventoryControllerTileEntity { + + public StorageTileEntity() { + super(AllTileEntities.LOGISTICAL_STORAGE_CONTROLLER.type); + } + + @Override + protected ShippingInventory createInventory() { + return new ShippingInventory(true, true); + } + + @Override + public boolean isSupplier() { + return true; + } + + @Override + public boolean isReceiver() { + return true; + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/controller/SupplyTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/management/controller/SupplyTileEntity.java new file mode 100644 index 000000000..3b69dda1c --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/controller/SupplyTileEntity.java @@ -0,0 +1,22 @@ +package com.simibubi.create.modules.logistics.management.controller; + +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.modules.logistics.management.base.LogisticalInventoryControllerTileEntity; + +public class SupplyTileEntity extends LogisticalInventoryControllerTileEntity { + + public SupplyTileEntity() { + super(AllTileEntities.LOGISTICAL_SUPPLY_CONTROLLER.type); + } + + @Override + protected ShippingInventory createInventory() { + return new ShippingInventory(true, false); + } + + @Override + public boolean isSupplier() { + return true; + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/controller/TransactionsTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/management/controller/TransactionsTileEntity.java new file mode 100644 index 000000000..da1f21090 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/controller/TransactionsTileEntity.java @@ -0,0 +1,12 @@ +package com.simibubi.create.modules.logistics.management.controller; + +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.modules.logistics.management.base.LogisticalControllerTileEntity; + +public class TransactionsTileEntity extends LogisticalControllerTileEntity { + + public TransactionsTileEntity() { + super(AllTileEntities.LOGISTICAL_TRANSATIONS_CONTROLLER.type); + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/index/IndexContainerUpdatePacket.java b/src/main/java/com/simibubi/create/modules/logistics/management/index/IndexContainerUpdatePacket.java new file mode 100644 index 000000000..6d6a9469c --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/index/IndexContainerUpdatePacket.java @@ -0,0 +1,95 @@ +package com.simibubi.create.modules.logistics.management.index; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Supplier; + +import org.apache.commons.lang3.tuple.Pair; + +import com.simibubi.create.foundation.packet.SimplePacketBase; +import com.simibubi.create.foundation.type.CountedItemsList; +import com.simibubi.create.foundation.type.CountedItemsList.ItemStackEntry; + +import net.minecraft.client.Minecraft; +import net.minecraft.inventory.container.Container; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.fml.network.NetworkEvent.Context; + +public class IndexContainerUpdatePacket extends SimplePacketBase { + + public enum Type { + INITIAL, UPDATE + } + + Type type; + List> items; + BlockPos pos; + + public IndexContainerUpdatePacket(Type type, String address, CountedItemsList items, BlockPos pos) { + this(type, Arrays.asList(Pair.of(address, items)), pos); + } + + public IndexContainerUpdatePacket(Type type, List> items, BlockPos pos) { + this.type = type; + this.items = items; + this.pos = pos; + } + + public IndexContainerUpdatePacket(PacketBuffer buffer) { + type = Type.values()[buffer.readInt()]; + int numControllers = buffer.readInt(); + items = new ArrayList<>(numControllers); + for (int i = 0; i < numControllers; i++) { + String address = buffer.readString(4096); + CountedItemsList itemList = new CountedItemsList(); + int numEntries = buffer.readInt(); + for (int j = 0; j < numEntries; j++) + itemList.add(buffer.readItemStack(), buffer.readInt()); + items.add(Pair.of(address, itemList)); + } + pos = buffer.readBlockPos(); + } + + @Override + public void write(PacketBuffer buffer) { + buffer.writeInt(type.ordinal()); + buffer.writeInt(items.size()); + for (Pair pair : items) { + buffer.writeString(pair.getKey(), 4096); + Collection entries = pair.getValue().getFlattenedList(); + buffer.writeInt(entries.size()); + for (ItemStackEntry entry : entries) { + buffer.writeItemStack(entry.stack); + buffer.writeInt(entry.amount); + } + } + buffer.writeBlockPos(pos); + } + + @Override + public void handle(Supplier context) { + context.get().enqueueWork(() -> { + Minecraft mc = Minecraft.getInstance(); + Container openContainer = mc.player.openContainer; + if (openContainer == null) + return; + if (!(openContainer instanceof LogisticalIndexContainer)) + return; + LogisticalIndexTileEntity te = (LogisticalIndexTileEntity) mc.world.getTileEntity(pos); + if (te == null) + return; + + if (type == Type.INITIAL) + te.index(items); + if (type == Type.UPDATE) + te.update(items); + + ((LogisticalIndexContainer) openContainer).refresh(); + }); + context.get().setPacketHandled(true); + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/index/IndexOrderRequest.java b/src/main/java/com/simibubi/create/modules/logistics/management/index/IndexOrderRequest.java new file mode 100644 index 000000000..d6e3aaaec --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/index/IndexOrderRequest.java @@ -0,0 +1,72 @@ +package com.simibubi.create.modules.logistics.management.index; + +import java.util.Collection; +import java.util.UUID; +import java.util.function.Supplier; + +import com.simibubi.create.Create; +import com.simibubi.create.foundation.packet.SimplePacketBase; +import com.simibubi.create.foundation.type.CountedItemsList; +import com.simibubi.create.foundation.type.CountedItemsList.ItemStackEntry; +import com.simibubi.create.modules.logistics.management.LogisticalNetwork; +import com.simibubi.create.modules.logistics.management.base.LogisticalTask.SupplyTask; + +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.network.PacketBuffer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.fml.network.NetworkEvent.Context; + +public class IndexOrderRequest extends SimplePacketBase { + + String targetAddress; + UUID networkID; + CountedItemsList items; + BlockPos indexPos; + + public IndexOrderRequest(BlockPos indexPos, String targetAddress, CountedItemsList list, UUID networkID) { + this.targetAddress = targetAddress; + items = list; + this.networkID = networkID; + this.indexPos = indexPos; + } + + public IndexOrderRequest(PacketBuffer buffer) { + indexPos = buffer.readBlockPos(); + networkID = new UUID(buffer.readLong(), buffer.readLong()); + targetAddress = buffer.readString(4096); + items = new CountedItemsList(); + int numEntries = buffer.readInt(); + for (int j = 0; j < numEntries; j++) + items.add(buffer.readItemStack(), buffer.readInt()); + } + + @Override + public void write(PacketBuffer buffer) { + buffer.writeBlockPos(indexPos); + buffer.writeLong(networkID.getMostSignificantBits()); + buffer.writeLong(networkID.getLeastSignificantBits()); + buffer.writeString(targetAddress, 4096); + Collection entries = items.getFlattenedList(); + buffer.writeInt(entries.size()); + for (ItemStackEntry entry : entries) { + buffer.writeItemStack(entry.stack); + buffer.writeInt(entry.amount); + } + } + + @Override + public void handle(Supplier context) { + context.get().enqueueWork(() -> { + ServerPlayerEntity player = context.get().getSender(); + TileEntity tileEntity = player.getEntityWorld().getTileEntity(indexPos); + if (tileEntity != null && tileEntity instanceof LogisticalIndexTileEntity) + ((LogisticalIndexTileEntity) tileEntity).lastOrderAddress = targetAddress; + LogisticalNetwork networkByID = Create.logisticalNetworkHandler.getNetworkByID(player.getEntityWorld(), + networkID); + items.getFlattenedList().forEach(entry -> networkByID.enqueueTask(new SupplyTask(entry, targetAddress))); + }); + context.get().setPacketHandled(true); + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexBlock.java b/src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexBlock.java new file mode 100644 index 000000000..6a48ae56d --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexBlock.java @@ -0,0 +1,157 @@ +package com.simibubi.create.modules.logistics.management.index; + +import com.simibubi.create.AllItems; +import com.simibubi.create.foundation.block.IBlockWithColorHandler; +import com.simibubi.create.foundation.block.IWithTileEntity; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.HorizontalBlock; +import net.minecraft.client.renderer.color.IBlockColor; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.item.BlockItemUseContext; +import net.minecraft.item.ItemStack; +import net.minecraft.state.StateContainer.Builder; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.Direction; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.util.math.shapes.ISelectionContext; +import net.minecraft.util.math.shapes.VoxelShape; +import net.minecraft.util.math.shapes.VoxelShapes; +import net.minecraft.world.IBlockReader; +import net.minecraft.world.IWorldReader; +import net.minecraft.world.World; +import net.minecraftforge.fml.network.NetworkHooks; + +public class LogisticalIndexBlock extends HorizontalBlock + implements IBlockWithColorHandler, IWithTileEntity { + + public static final VoxelShape SOUTH_SHAPE = makeCuboidShape(3, 1, -1, 13, 15, 3), + NORTH_SHAPE = makeCuboidShape(3, 1, 13, 13, 15, 17), EAST_SHAPE = makeCuboidShape(-1, 1, 3, 3, 15, 13), + WEST_SHAPE = makeCuboidShape(13, 1, 3, 17, 15, 13); + + public LogisticalIndexBlock() { + super(Properties.from(Blocks.GRANITE)); + } + + @Override + public boolean isValidPosition(BlockState state, IWorldReader worldIn, BlockPos pos) { + Direction facing = state.get(HORIZONTAL_FACING); + BlockPos offset = pos.offset(facing.getOpposite()); + BlockState blockState = worldIn.getBlockState(offset); + return !blockState.getMaterial().isReplaceable(); + } + + @Override + protected void fillStateContainer(Builder builder) { + builder.add(HORIZONTAL_FACING); + super.fillStateContainer(builder); + } + + @Override + public BlockRenderLayer getRenderLayer() { + return BlockRenderLayer.CUTOUT_MIPPED; + } + + @Override + public BlockState getStateForPlacement(BlockItemUseContext context) { + BlockState defaultState = getDefaultState(); + if (context.getFace().getAxis().isHorizontal()) + return defaultState.with(HORIZONTAL_FACING, context.getFace()); + return defaultState; + } + + @Override + public void neighborChanged(BlockState state, World worldIn, BlockPos pos, Block blockIn, BlockPos fromPos, + boolean isMoving) { + if (state.isValidPosition(worldIn, pos)) + return; + + TileEntity tileentity = state.hasTileEntity() ? worldIn.getTileEntity(pos) : null; + spawnDrops(state, worldIn, pos, tileentity); + worldIn.removeBlock(pos, false); + + for (Direction direction : Direction.values()) + worldIn.notifyNeighborsOfStateChange(pos.offset(direction), this); + } + + @Override + public boolean hasTileEntity(BlockState state) { + return true; + } + + @Override + public TileEntity createTileEntity(BlockState state, IBlockReader world) { + return new LogisticalIndexTileEntity(); + } + + @Override + public boolean onBlockActivated(BlockState state, World worldIn, BlockPos pos, PlayerEntity player, Hand handIn, + BlockRayTraceResult hit) { + if (AllItems.LOGISTICAL_DIAL.typeOf(player.getHeldItem(handIn))) { + return false; + } + + if (worldIn.isRemote) { + return true; + } else { + withTileEntityDo(worldIn, pos, te -> { + NetworkHooks.openGui((ServerPlayerEntity) player, te, te::sendToContainer); + }); + return true; + } + } + + @Override + public void onBlockPlacedBy(World worldIn, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack) { + if (!(placer instanceof PlayerEntity)) + return; + PlayerEntity player = (PlayerEntity) placer; + for (int slot = 0; slot < player.inventory.getSizeInventory(); slot++) { + ItemStack itemStack = player.inventory.getStackInSlot(slot); + if (!AllItems.LOGISTICAL_DIAL.typeOf(itemStack)) + continue; + if (!itemStack.hasTag()) + continue; + withTileEntityDo(worldIn, pos, te -> te.setNetworkId(itemStack.getTag().getUniqueId("NetworkID"))); + return; + } + } + + @Override + public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context) { + Direction facing = state.get(HORIZONTAL_FACING); + + if (facing == Direction.EAST) + return EAST_SHAPE; + if (facing == Direction.WEST) + return WEST_SHAPE; + if (facing == Direction.NORTH) + return NORTH_SHAPE; + if (facing == Direction.SOUTH) + return SOUTH_SHAPE; + + return VoxelShapes.empty(); + } + + @Override + public IBlockColor getColorHandler() { + return (state, world, pos, layer) -> { + if (layer == 0) { + LogisticalIndexTileEntity tileEntity = getTileEntity(world, pos); + if (tileEntity == null) + return 0; + return tileEntity.getColor(); + } + + return 0; + }; + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexContainer.java b/src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexContainer.java new file mode 100644 index 000000000..e0ddcf8c3 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexContainer.java @@ -0,0 +1,63 @@ +package com.simibubi.create.modules.logistics.management.index; + +import com.simibubi.create.AllContainers; +import com.simibubi.create.foundation.type.CombinedCountedItemsList; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.inventory.container.Container; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketBuffer; + +public class LogisticalIndexContainer extends Container { + + public LogisticalIndexTileEntity te; + public CombinedCountedItemsList allItems = new CombinedCountedItemsList<>(); + + public LogisticalIndexContainer(int id, PlayerInventory inv, PacketBuffer extraData) { + super(AllContainers.LOGISTICAL_INDEX.type, id); + ClientWorld world = Minecraft.getInstance().world; + this.te = (LogisticalIndexTileEntity) world.getTileEntity(extraData.readBlockPos()); + this.te.handleUpdateTag(extraData.readCompoundTag()); + init(); + } + + public LogisticalIndexContainer(int id, PlayerInventory inv, LogisticalIndexTileEntity te) { + super(AllContainers.LOGISTICAL_INDEX.type, id); + this.te = te; + init(); + te.addPlayer((ServerPlayerEntity) inv.player); + } + + private void init() { + detectAndSendChanges(); + } + + @Override + public void onContainerClosed(PlayerEntity playerIn) { + if (!te.getWorld().isRemote) + te.removePlayer((ServerPlayerEntity) playerIn); + else + te.controllers.clear(); + super.onContainerClosed(playerIn); + } + + @Override + public ItemStack transferStackInSlot(PlayerEntity playerIn, int index) { + return ItemStack.EMPTY; + } + + @Override + public boolean canInteractWith(PlayerEntity playerIn) { + return true; + } + + public void refresh() { + allItems.clear(); + te.controllers.forEach(allItems::add); + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexScreen.java b/src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexScreen.java new file mode 100644 index 000000000..0137bb0c0 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexScreen.java @@ -0,0 +1,645 @@ +package com.simibubi.create.modules.logistics.management.index; + +import static com.simibubi.create.ScreenResources.DISABLED_SLOT_FRAME; +import static com.simibubi.create.ScreenResources.DISABLED_SLOT_INNER; +import static com.simibubi.create.ScreenResources.ICON_CONFIRM; +import static com.simibubi.create.ScreenResources.INDEX_BOTTOM; +import static com.simibubi.create.ScreenResources.INDEX_BOTTOM_TRIM; +import static com.simibubi.create.ScreenResources.INDEX_MIDDLE; +import static com.simibubi.create.ScreenResources.INDEX_SCROLLER_BOTTOM; +import static com.simibubi.create.ScreenResources.INDEX_SCROLLER_MIDDLE; +import static com.simibubi.create.ScreenResources.INDEX_SCROLLER_TOP; +import static com.simibubi.create.ScreenResources.INDEX_SEARCH; +import static com.simibubi.create.ScreenResources.INDEX_SEARCH_OVERLAY; +import static com.simibubi.create.ScreenResources.INDEX_TAB; +import static com.simibubi.create.ScreenResources.INDEX_TAB_ACTIVE; +import static com.simibubi.create.ScreenResources.INDEX_TOP; +import static com.simibubi.create.ScreenResources.INDEX_TOP_TRIM; +import static com.simibubi.create.ScreenResources.SLOT_FRAME; +import static com.simibubi.create.ScreenResources.SLOT_INNER; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +import org.lwjgl.glfw.GLFW; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.simibubi.create.AllPackets; +import com.simibubi.create.ScreenResources; +import com.simibubi.create.foundation.gui.AbstractSimiContainerScreen; +import com.simibubi.create.foundation.gui.ScreenElementRenderer; +import com.simibubi.create.foundation.gui.widgets.IconButton; +import com.simibubi.create.foundation.gui.widgets.InterpolatedChasingValue; +import com.simibubi.create.foundation.gui.widgets.ScrollInput; +import com.simibubi.create.foundation.gui.widgets.SelectionScrollInput; +import com.simibubi.create.foundation.type.CountedItemsList; +import com.simibubi.create.foundation.type.CountedItemsList.ItemStackEntry; +import com.simibubi.create.foundation.utility.ColorHelper; +import com.simibubi.create.foundation.utility.Lang; + +import net.minecraft.block.Blocks; +import net.minecraft.client.GameSettings; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.util.InputMappings; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.text.ITextComponent; + +public class LogisticalIndexScreen extends AbstractSimiContainerScreen { + + protected LogisticalIndexContainer container; + protected IconButton orderButton; + + boolean searchActive = false; + boolean searchHovered = false; + InterpolatedChasingValue searchButtonOffset; + TextFieldWidget searchTextField; + String searchKey = ""; + + TextFieldWidget receiverTextField; + ScrollInput receiverScrollInput; + List receivers = new ArrayList<>(); + + int cursorPos = 0; + boolean cursorActive = false; + InterpolatedChasingValue cursorLight; + + List displayedItems = new ArrayList<>(); + CountedItemsList order = new CountedItemsList(); + String initialTargetAddress; + + String title = Lang.translate("gui.index.title"); + String receiverScrollInputTitle = Lang.translate("gui.index.targetAddressSelect"); + String orderButtonTooltip = Lang.translate("gui.index.confirmOrder"); + String keyNumberInventories = "gui.index.numberIndexedInventories"; + ItemStack chestIcon = new ItemStack(Blocks.CHEST); + + public LogisticalIndexScreen(LogisticalIndexContainer container, PlayerInventory inv, ITextComponent title) { + super(container, inv, title); + this.container = container; + cursorLight = new InterpolatedChasingValue().withSpeed(.25f); + searchButtonOffset = new InterpolatedChasingValue().withSpeed(.5f); + receivers = container.te.availableReceivers; + initialTargetAddress = container.te.lastOrderAddress; + buildDisplayedItems(); + } + + @Override + protected void init() { + int height = INDEX_TOP.height + INDEX_TOP_TRIM.height + INDEX_MIDDLE.height + INDEX_BOTTOM_TRIM.height + + INDEX_BOTTOM.height; + int width = INDEX_MIDDLE.width; + setWindowSize(width, height); + super.init(); + widgets.clear(); + + searchTextField = new TextFieldWidget(font, guiLeft + 23, guiTop + 8, 128, 8, ""); + searchTextField.setTextColor(0xFFFFFF); + searchTextField.setDisabledTextColour(-1); + searchTextField.setEnableBackgroundDrawing(false); + searchTextField.setMaxStringLength(256); + searchTextField.func_212954_a(this::onSearchKeyChanged); + searchTextField.setFocused2(false); + + receiverTextField = new TextFieldWidget(font, guiLeft + 29, guiTop + 240, 116, 8, ""); + receiverTextField.setTextColor(0xFFFFFF); + receiverTextField.setDisabledTextColour(-1); + receiverTextField.setEnableBackgroundDrawing(false); + receiverTextField.setMaxStringLength(256); + receiverTextField.func_212954_a(this::onReceiverTextChanged); + if (initialTargetAddress != null) + receiverTextField.setText(initialTargetAddress); + receiverTextField.setFocused2(false); + + receiverScrollInput = new SelectionScrollInput(guiLeft + 24, guiTop + 235, 126, 18).forOptions(receivers) + .titled(receiverScrollInputTitle).calling(this::onReceiverScrollInputChanged); + + orderButton = new IconButton(guiLeft + 152, guiTop + 235, ICON_CONFIRM); + orderButton.active = false; + orderButton.setToolTip(orderButtonTooltip); + + widgets.add(receiverTextField); + widgets.add(receiverScrollInput); + widgets.add(orderButton); + + if (searchActive) + widgets.add(searchTextField); + } + + @Override + public void tick() { + super.tick(); + + float targetOffset = 0.75f; + if (searchTextField.isFocused()) + targetOffset = 1.5f; + else if (searchHovered || searchActive) + targetOffset = 1f; + searchButtonOffset.target(targetOffset); + searchButtonOffset.tick(); + + if (cursorActive) { + cursorLight.target(1); + cursorLight.tick(); + } else { + cursorLight.set(0); + } + + if (!searchActive && searchTextField.isFocused()) + searchTextField.changeFocus(false); + + if (container.te.update) { + buildDisplayedItems(); + container.te.update = false; + } + + } + + private void updateSubmitButton() { + orderButton.active = canSubmit(); + } + + public boolean canSubmit() { + return !order.getFlattenedList().isEmpty() + && container.te.availableReceivers.contains(receiverTextField.getText()); + } + + private void onSearchKeyChanged(String newSearch) { + if (searchKey.equals(newSearch)) + return; + searchKey = new String(newSearch); + buildDisplayedItems(); + } + + private void sendRequest() { + String address = receiverTextField.getText(); + UUID id = container.te.getNetworkId(); + AllPackets.channel.sendToServer(new IndexOrderRequest(container.te.getPos(), address, order, id)); + order = new CountedItemsList(); + updateSubmitButton(); + } + + private void onReceiverTextChanged(String newSearch) { + if (!receiverTextField.isFocused()) + return; + receiverScrollInput.setState(0); + receivers = container.te.availableReceivers.stream() + .filter(str -> str.toLowerCase().startsWith(newSearch.toLowerCase())).collect(Collectors.toList()); + ((SelectionScrollInput) receiverScrollInput).forOptions(receivers); + receiverScrollInput.active = !receivers.isEmpty(); + if (receivers.isEmpty() || newSearch.isEmpty()) + receiverTextField.setSuggestion(null); + else + receiverTextField.setSuggestion(receivers.get(0).substring(newSearch.length())); + updateSubmitButton(); + } + + private void onReceiverScrollInputChanged(int index) { + String address = receivers.get(index); + receiverTextField.func_212954_a(null); + receiverTextField.setSuggestion(null); + receiverTextField.setText(address); + receiverTextField.func_212954_a(this::onReceiverTextChanged); + updateSubmitButton(); + } + + public void buildDisplayedItems() { + if (searchKey.isEmpty()) { + displayedItems = container.allItems.get().getFlattenedList().stream().collect(Collectors.toList()); + } else { + displayedItems = container.allItems + .get().getFlattenedList().parallelStream().filter(entry -> entry.stack.getDisplayName() + .getUnformattedComponentText().toLowerCase().startsWith(searchKey.toLowerCase())) + .collect(Collectors.toList()); + } + if (cursorActive) + moveCursor(cursorPos); + } + + @Override + public boolean mouseClicked(double x, double y, int button) { + if (searchHovered && button == 0) { + setSearchActive(!searchActive); + return true; + } + + if (orderButton.isHovered() && orderButton.active) { + sendRequest(); + return true; + } + + return super.mouseClicked(x, y, button); + } + + @Override + public void mouseMoved(double xPos, double yPos) { + + super.mouseMoved(xPos, yPos); + } + + private void setSearchActive(boolean searchActive) { + this.searchActive = searchActive; + if (searchActive) { + cursorActive = false; + searchTextField.setSelectionPos(0); + searchTextField.changeFocus(true); + widgets.add(searchTextField); + } else { + widgets.remove(searchTextField); + onSearchKeyChanged(""); + } + } + + @Override + public boolean keyPressed(int code, int p_keyPressed_2_, int p_keyPressed_3_) { + InputMappings.Input mouseKey = InputMappings.getInputByCode(code, p_keyPressed_2_); + boolean receiverFocused = receiverTextField.isFocused(); + boolean searchFocused = searchTextField.isFocused(); + boolean space = code == GLFW.GLFW_KEY_SPACE; + boolean enter = code == GLFW.GLFW_KEY_ENTER; + boolean writingText = receiverFocused || searchFocused; + boolean closingScreen = this.minecraft.gameSettings.keyBindInventory.isActiveAndMatches(mouseKey); + + if (closingScreen && writingText) { + return true; + } + + if (canSubmit() && hasShiftDown() && enter) { + sendRequest(); + return true; + } + + if (enter && searchActive && searchFocused) { + searchTextField.changeFocus(false); + searchTextField.setCursorPositionEnd(); + searchTextField.setSelectionPos(searchTextField.getCursorPosition()); + + cursorActive = true; + cursorPos = 0; + + if (searchTextField.getText().isEmpty()) + setSearchActive(false); + + return true; + } + + if (enter && !writingText && cursorActive) { + ItemStackEntry entry = displayedItems.get(cursorPos); + if (!order.contains(entry.stack)) { + if (order.getFlattenedList().size() > 4) + return true; + order.add(entry); + } else + order.remove(entry.stack); + updateSubmitButton(); + return true; + } + + if (space && !writingText) { + setSearchActive(true); + return true; + } + + boolean up = code == GLFW.GLFW_KEY_UP; + boolean left = code == GLFW.GLFW_KEY_LEFT; + boolean down = code == GLFW.GLFW_KEY_DOWN; + boolean right = code == GLFW.GLFW_KEY_RIGHT; + boolean tab = code == GLFW.GLFW_KEY_TAB; + + if (!writingText && tab) { + receiverTextField.setCursorPositionEnd(); + receiverTextField.setSelectionPos(0); + receiverTextField.changeFocus(true); + } + + if (receiverFocused) { + if (enter) { + receiverTextField.changeFocus(false); + } + if (tab) { + if (receivers.isEmpty()) + return false; + receiverTextField.setText(receivers.get(0)); + return true; + } + if (up || down) { + receiverScrollInput.setState(receiverScrollInput.getState() + (up ? -1 : 1)); + receiverScrollInput.onChanged(); + return true; + } + } + + if (!writingText) { + GameSettings keys = minecraft.gameSettings; + boolean w = keys.keyBindForward.getKey().getKeyCode() == code || up; + boolean a = keys.keyBindLeft.getKey().getKeyCode() == code || left; + boolean s = keys.keyBindBack.getKey().getKeyCode() == code || down; + boolean d = keys.keyBindRight.getKey().getKeyCode() == code || right; + boolean any = w || a || s || d; + + if (any && !cursorActive) { + cursorActive = true; + return true; + } + + if (any) { + int offset = w ? -8 : a ? -1 : s ? 8 : d ? 1 : 0; + if (hasShiftDown()) { + offset *= 4; + if (a || d) { + int col = cursorPos % 8; + offset = MathHelper.clamp(offset, -col, 7 - col); + } + } + moveCursor(cursorPos + offset); + return true; + } + + } + + return super.keyPressed(code, p_keyPressed_2_, p_keyPressed_3_); + } + + protected void moveCursor(int slot) { + int clamp = MathHelper.clamp(slot, 0, slotsVisible() - 1); + if (cursorPos != clamp) + cursorLight.set(0).set(0); + cursorPos = clamp; + } + + protected int slotsVisible() { + return displayedItems.size(); + } + + @Override + protected void renderWindow(int mouseX, int mouseY, float partialTicks) { + applyNetworkColor(); + + // Search bar + searchHovered = (mouseX > guiLeft - 25 && mouseX < guiLeft && mouseY > guiTop + 6 && mouseY < guiTop + 31); + GlStateManager.pushMatrix(); + GlStateManager.translatef(searchButtonOffset.get(partialTicks) * -14, 0, 0); + INDEX_SEARCH.draw(this, guiLeft - 5, guiTop + 8); + GlStateManager.popMatrix(); + + INDEX_MIDDLE.draw(this, guiLeft, guiTop + INDEX_TOP.height + 6); + renderScrollbar(); + renderTabs(); + resetColor(); + renderSlots(); + } + + @Override + public boolean charTyped(char character, int code) { + if (!searchActive && !receiverTextField.isFocused()) + return false; + if (character == ' ' && (searchTextField.getText().isEmpty() + || searchTextField.getSelectedText().equals(searchTextField.getText()))) + return false; + return super.charTyped(character, code); + } + + @Override + protected void renderWindowForeground(int mouseX, int mouseY, float partialTicks) { + resetColor(); + INDEX_TOP_TRIM.draw(this, guiLeft, guiTop + INDEX_TOP.height); + INDEX_BOTTOM_TRIM.draw(this, guiLeft, guiTop + INDEX_TOP.height + INDEX_MIDDLE.height + 6); + applyNetworkColor(); + INDEX_TOP.draw(this, guiLeft, guiTop); + INDEX_BOTTOM.draw(this, guiLeft, guiTop + INDEX_TOP.height + INDEX_MIDDLE.height + 12); + + if (searchActive) { + INDEX_SEARCH_OVERLAY.draw(this, guiLeft - 1, guiTop + 2); + resetColor(); + + } else { + resetColor(); + int offset = (INDEX_TOP.width - font.getStringWidth(title)) / 2; + font.drawStringWithShadow(title, guiLeft + offset, guiTop + 8, + ColorHelper.mixColors(0xFFFFFF, container.te.getColor(), .25f)); + } + + orderButton.render(mouseX, mouseY, partialTicks); + if (searchActive) + searchTextField.render(mouseX, mouseY, partialTicks); + receiverTextField.render(mouseX, mouseY, partialTicks); + + renderOrderedItems(); + ScreenElementRenderer.render3DItem(() -> { + GlStateManager.translated(guiLeft + 6.5, guiTop + 235, 0); + return chestIcon; + }); + + super.renderWindowForeground(mouseX, mouseY, partialTicks); + } + + private void renderOrderedItems() { + Collection flattenedList = order.getFlattenedList(); + + int slotX = guiLeft + (getXSize() - flattenedList.size() * 18) / 2; + int slotY = guiTop + 215; + + for (ItemStackEntry entry : flattenedList) { + RenderHelper.enableGUIStandardItemLighting(); + GlStateManager.enableDepthTest(); + GlStateManager.pushMatrix(); + GlStateManager.translatef(0.0F, 0.0F, 32.0F); + this.blitOffset = 200; + this.itemRenderer.zLevel = 200.0F; + net.minecraft.client.gui.FontRenderer font = entry.stack.getItem().getFontRenderer(entry.stack); + if (font == null) + font = this.font; + this.itemRenderer.renderItemAndEffectIntoGUI(entry.stack, slotX, slotY); + this.renderItemOverlayIntoGUI(font, entry.stack, slotX, slotY, + entry.amount > 1 ? String.valueOf(entry.amount) : null, 0xFFFFFF); + this.blitOffset = 0; + this.itemRenderer.zLevel = 0.0F; + GlStateManager.popMatrix(); + slotX += 18; + } + } + + private void renderSlots() { + int slot = 0; + for (ItemStackEntry entry : displayedItems) { + resetColor(); + RenderHelper.enableGUIStandardItemLighting(); + renderSlot(slot, entry.stack, entry.amount); + slot++; + } + } + + private void renderSlot(int slot, ItemStack stack, int count) { + int slotX = getSlotX(slot); + int slotY = getSlotY(slot); + +// boolean ordered = order.contains(stack); + boolean orderedFully = order.getItemCount(stack) == count; + + if (orderedFully) { + DISABLED_SLOT_FRAME.draw(this, slotX, slotY); + DISABLED_SLOT_INNER.draw(this, slotX, slotY); + } else { + SLOT_FRAME.draw(this, slotX, slotY); + SLOT_INNER.draw(this, slotX, slotY); + } + + if (cursorActive && slot == cursorPos) { + renderCursor(); + } + + slotX++; + slotY++; + + GlStateManager.pushMatrix(); + GlStateManager.translatef(0.0F, 0.0F, 32.0F); + this.blitOffset = 200; + this.itemRenderer.zLevel = 200.0F; + net.minecraft.client.gui.FontRenderer font = stack.getItem().getFontRenderer(stack); + if (font == null) + font = this.font; + this.itemRenderer.renderItemAndEffectIntoGUI(stack, slotX, slotY); + + String text = count > 1 ? String.valueOf(count) : null; + int color = 0xFFFFFF; + if (orderedFully) { + color = ColorHelper.mixColors(container.te.getColor(), 0, 0.5f); + text = "0"; + } + + this.renderItemOverlayIntoGUI(font, stack, slotX, slotY, text, color); + this.blitOffset = 0; + this.itemRenderer.zLevel = 0.0F; + GlStateManager.popMatrix(); + } + + public int getSlotY(int slot) { + return guiTop + 28 + 18 * (slot / 8); + } + + public int getSlotX(int slot) { + return guiLeft + 15 + 18 * (slot % 8); + } + + private void renderScrollbar() { + INDEX_SCROLLER_TOP.draw(this, guiLeft + 173, guiTop + 31); + INDEX_SCROLLER_MIDDLE.draw(this, guiLeft + 173, guiTop + 37); + INDEX_SCROLLER_BOTTOM.draw(this, guiLeft + 173, guiTop + 43); + } + + private void renderTabs() { + INDEX_TAB.draw(this, guiLeft - 19, guiTop + 40); + INDEX_TAB_ACTIVE.draw(this, guiLeft - 19, guiTop + 61); + INDEX_TAB.draw(this, guiLeft - 19, guiTop + 82); + INDEX_TAB.draw(this, guiLeft - 19, guiTop + 103); + } + + public void resetColor() { + ColorHelper.glResetColor(); + } + + public void applyNetworkColor() { + ColorHelper.glColor(container.te.getColor()); + } + + private void renderCursor() { + if (!cursorActive) + return; + int x = getSlotX(cursorPos); + int y = getSlotY(cursorPos); + + float pt = minecraft.getRenderPartialTicks(); + GlStateManager.enableBlend(); + GlStateManager.color4f(1, 1, 1, cursorLight.get(pt)); + ScreenResources.SELECTED_SLOT_INNER.draw(this, x, y); + resetColor(); + } + + public void renderItemOverlayIntoGUI(FontRenderer fr, ItemStack stack, int xPosition, int yPosition, + @Nullable String text, int textColor) { + if (!stack.isEmpty()) { + if (stack.getItem().showDurabilityBar(stack)) { + GlStateManager.disableLighting(); + GlStateManager.disableDepthTest(); + GlStateManager.disableTexture(); + GlStateManager.disableAlphaTest(); + GlStateManager.disableBlend(); + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferbuilder = tessellator.getBuffer(); + double health = stack.getItem().getDurabilityForDisplay(stack); + int i = Math.round(13.0F - (float) health * 13.0F); + int j = stack.getItem().getRGBDurabilityForDisplay(stack); + this.draw(bufferbuilder, xPosition + 2, yPosition + 13, 13, 2, 0, 0, 0, 255); + this.draw(bufferbuilder, xPosition + 2, yPosition + 13, i, 1, j >> 16 & 255, j >> 8 & 255, j & 255, + 255); + GlStateManager.enableBlend(); + GlStateManager.enableAlphaTest(); + GlStateManager.enableTexture(); + GlStateManager.enableLighting(); + GlStateManager.enableDepthTest(); + } + + if (stack.getCount() != 1 || text != null) { + String s = text == null ? String.valueOf(stack.getCount()) : text; + GlStateManager.disableLighting(); + GlStateManager.disableDepthTest(); + GlStateManager.disableBlend(); + GlStateManager.pushMatrix(); + + int guiScaleFactor = (int) minecraft.mainWindow.getGuiScaleFactor(); + GlStateManager.translated((float) (xPosition + 16.5f), (float) (yPosition + 16.5f), 0); + + double scale = 1; + switch (guiScaleFactor) { + case 1: + scale = 2060 / 2048d; + break; + case 2: + scale = .5; + break; + case 3: + scale = .675; + break; + case 4: + scale = .75; + break; + default: + scale = ((float) guiScaleFactor - 1) / guiScaleFactor; + } + + GlStateManager.scaled(scale, scale, 0); + GlStateManager.translated(-fr.getStringWidth(s) - (guiScaleFactor > 1 ? 0 : -.5f), + -font.FONT_HEIGHT + (guiScaleFactor > 1 ? 1 : 1.75f), 0); + fr.drawStringWithShadow(s, 0, 0, textColor); + + GlStateManager.popMatrix(); + GlStateManager.enableBlend(); + GlStateManager.enableLighting(); + GlStateManager.enableDepthTest(); + GlStateManager.enableBlend(); + } + } + } + + private void draw(BufferBuilder renderer, int x, int y, int width, int height, int red, int green, int blue, + int alpha) { + renderer.begin(7, DefaultVertexFormats.POSITION_COLOR); + renderer.pos((double) (x + 0), (double) (y + 0), 0.0D).color(red, green, blue, alpha).endVertex(); + renderer.pos((double) (x + 0), (double) (y + height), 0.0D).color(red, green, blue, alpha).endVertex(); + renderer.pos((double) (x + width), (double) (y + height), 0.0D).color(red, green, blue, alpha).endVertex(); + renderer.pos((double) (x + width), (double) (y + 0), 0.0D).color(red, green, blue, alpha).endVertex(); + Tessellator.getInstance().draw(); + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexTileEntity.java new file mode 100644 index 000000000..0c66c77d3 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/logistics/management/index/LogisticalIndexTileEntity.java @@ -0,0 +1,214 @@ +package com.simibubi.create.modules.logistics.management.index; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.tuple.Pair; + +import com.simibubi.create.AllPackets; +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.foundation.type.CountedItemsList; +import com.simibubi.create.foundation.type.CountedItemsList.ItemStackEntry; +import com.simibubi.create.modules.logistics.management.LogisticalNetwork; +import com.simibubi.create.modules.logistics.management.base.LogisticalControllerTileEntity; +import com.simibubi.create.modules.logistics.management.base.LogisticalInventoryControllerTileEntity; +import com.simibubi.create.modules.logistics.management.index.IndexContainerUpdatePacket.Type; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.inventory.container.Container; +import net.minecraft.inventory.container.INamedContainerProvider; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.INBT; +import net.minecraft.nbt.ListNBT; +import net.minecraft.nbt.StringNBT; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.StringTextComponent; +import net.minecraftforge.common.util.Constants.NBT; +import net.minecraftforge.fml.network.PacketDistributor; + +public class LogisticalIndexTileEntity extends LogisticalControllerTileEntity implements INamedContainerProvider { + + // Server + public int nextPush; + public Set playersEntered = new HashSet<>(); + protected Set playersUsing = new HashSet<>(); + protected List> controllersToUpdate = new LinkedList<>(); + + // Both + public String lastOrderAddress = null; + protected Map controllers = new HashMap<>(); + + // Client + public boolean update = false; + public List availableReceivers = new ArrayList<>(); + + public LogisticalIndexTileEntity() { + super(AllTileEntities.LOGISTICAL_INDEX.type); + nextPush = 0; + } + + @Override + public CompoundNBT write(CompoundNBT compound) { + if (lastOrderAddress != null) + compound.putString("LastAdress", lastOrderAddress); + return super.write(compound); + } + + @Override + public void read(CompoundNBT compound) { + if (compound.contains("LastAdress")) + lastOrderAddress = compound.getString("LastAdress"); + super.read(compound); + } + + @Override + public CompoundNBT writeToClient(CompoundNBT tag) { + ListNBT receivers = new ListNBT(); + availableReceivers.forEach(s -> receivers.add(new StringNBT(s))); + tag.put("Receivers", receivers); + return super.writeToClient(tag); + } + + @Override + public void readClientUpdate(CompoundNBT tag) { + availableReceivers.clear(); + for (INBT inbt : tag.getList("Receivers", NBT.TAG_STRING)) + availableReceivers.add(((StringNBT) inbt).getString()); + update = true; + super.readClientUpdate(tag); + } + + public void syncReceivers() { + if (network == null) + return; + availableReceivers.clear(); + for (LogisticalControllerTileEntity logisticalControllerTileEntity : network.receivers) + availableReceivers.add(logisticalControllerTileEntity.getName()); + sendData(); + } + + @Override + protected void initialize() { + super.initialize(); + syncReceivers(); + } + + @Override + public void tick() { + super.tick(); + + if (nextPush == 1) + pushItems(); + + if (nextPush > 0) + nextPush--; + } + + @Override + // Prevents the inherited TESR + public double getMaxRenderDistanceSquared() { + return 0; + } + + @Override + public Container createMenu(int id, PlayerInventory inv, PlayerEntity player) { + return new LogisticalIndexContainer(id, inv, this); + } + + @Override + public ITextComponent getDisplayName() { + return new StringTextComponent(getType().getRegistryName().toString()); + } + + public void sendToContainer(PacketBuffer buffer) { + buffer.writeBlockPos(getPos()); + buffer.writeCompoundTag(getUpdateTag()); + } + + public void addPlayer(ServerPlayerEntity player) { + nextPush = 5; + playersEntered.add(player); + playersUsing.add(player); + } + + public void removePlayer(ServerPlayerEntity player) { + playersUsing.remove(player); + if (playersUsing.isEmpty()) + controllers.clear(); + } + + public void handleUpdatedController(String address, CountedItemsList updates) { + if (playersUsing.isEmpty()) + return; + controllersToUpdate.add(Pair.of(address, updates)); + if (nextPush == 0) + nextPush = 5; + } + + private void pushItems() { + LogisticalNetwork network = this.getNetwork(); + if (network == null) + return; + + // First player to open + if (!playersEntered.isEmpty() && playersUsing.size() == playersEntered.size()) { + controllers.clear(); + for (LogisticalControllerTileEntity te : network.suppliers) { + if (!(te instanceof LogisticalInventoryControllerTileEntity)) + continue; + CountedItemsList allItems = ((LogisticalInventoryControllerTileEntity) te).getAllItems(); + controllers.put(te.getName(), allItems); + } + } + + // Initial Packets + if (!playersEntered.isEmpty()) { + controllers.forEach((address, items) -> { + for (ServerPlayerEntity player : playersUsing) + AllPackets.channel.send(PacketDistributor.PLAYER.with(() -> player), + new IndexContainerUpdatePacket(Type.INITIAL, address, items, pos)); + }); + playersEntered.clear(); + } + + // pending Incremental Updates + if (!playersUsing.isEmpty() && !controllersToUpdate.isEmpty()) { + for (Pair pair : controllersToUpdate) { + CountedItemsList list = controllers.getOrDefault(pair.getKey(), new CountedItemsList()); + pair.getValue().getFlattenedList().forEach(list::add); + } + for (ServerPlayerEntity player : playersUsing) + AllPackets.channel.send(PacketDistributor.PLAYER.with(() -> player), + new IndexContainerUpdatePacket(Type.UPDATE, controllersToUpdate, pos)); + controllersToUpdate.clear(); + } + } + + public void index(List> items) { + items.forEach(pair -> { + controllers.put(pair.getKey(), pair.getValue()); + }); + update = true; + } + + public void update(List> items) { + for (Pair pair : items) { + if (!controllers.containsKey(pair.getKey())) + return; + CountedItemsList list = controllers.get(pair.getKey()); + for (ItemStackEntry entry : pair.getValue().getFlattenedList()) { + list.setItemCount(entry.stack, entry.amount); + } + } + update = true; + } + +} diff --git a/src/main/java/com/simibubi/create/modules/logistics/packet/ConfigureFlexcratePacket.java b/src/main/java/com/simibubi/create/modules/logistics/packet/ConfigureFlexcratePacket.java index a865a46b8..27b6aecc9 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/packet/ConfigureFlexcratePacket.java +++ b/src/main/java/com/simibubi/create/modules/logistics/packet/ConfigureFlexcratePacket.java @@ -1,7 +1,7 @@ package com.simibubi.create.modules.logistics.packet; import com.simibubi.create.foundation.packet.TileEntityConfigurationPacket; -import com.simibubi.create.modules.logistics.block.FlexcrateTileEntity; +import com.simibubi.create.modules.logistics.block.inventories.FlexcrateTileEntity; import net.minecraft.network.PacketBuffer; import net.minecraft.util.math.BlockPos; diff --git a/src/main/resources/assets/create/blockstates/logistical_casing.json b/src/main/resources/assets/create/blockstates/logistical_casing.json new file mode 100644 index 000000000..79f130362 --- /dev/null +++ b/src/main/resources/assets/create/blockstates/logistical_casing.json @@ -0,0 +1,20 @@ +{ + "forge_marker": 1, + "variants": { + "active": { + "true": {}, + "false": {} + }, + "part": { + "none": { "model": "create:block/logistical_casing" }, + "start": { "model": "create:block/logistical_casing_start" }, + "middle": { "model": "create:block/logistical_casing_middle" }, + "end": { "model": "create:block/logistical_casing_end" } + }, + "axis": { + "y": {}, + "x": { "x": 90, "y": 90 }, + "z": { "x": 270 } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/create/blockstates/logistical_controller.json b/src/main/resources/assets/create/blockstates/logistical_controller.json new file mode 100644 index 000000000..be2a52a7d --- /dev/null +++ b/src/main/resources/assets/create/blockstates/logistical_controller.json @@ -0,0 +1,23 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "create:block/logistical_controller" + }, + "variants": { + "type": { + "storage": { }, + "calculation": { }, + "supply": { }, + "transactions": { }, + "request": { } + }, + "facing": { + "north": { "y": 180 }, + "south": { }, + "west": { "y": 90 }, + "up": { "x": 90 }, + "down": { "x": 270 }, + "east": { "y": 270 } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/create/blockstates/logistical_controller_indicator.json b/src/main/resources/assets/create/blockstates/logistical_controller_indicator.json new file mode 100644 index 000000000..58ee7dab8 --- /dev/null +++ b/src/main/resources/assets/create/blockstates/logistical_controller_indicator.json @@ -0,0 +1,20 @@ +{ + "forge_marker": 1, + "variants": { + "type": { + "storage": { "model": "create:block/logistical_controller_icon_storage" }, + "calculation": { "model": "create:block/logistical_controller_icon_calculation" }, + "supply": { "model": "create:block/logistical_controller_icon_supply" }, + "transactions": { "model": "create:block/logistical_controller_icon_transactions" }, + "request": { "model": "create:block/logistical_controller_icon_request" } + }, + "facing": { + "north": { "y": 180 }, + "south": { }, + "west": { "y": 90 }, + "up": { "x": 90 }, + "down": { "x": 270 }, + "east": { "y": 270 } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/create/blockstates/logistical_index.json b/src/main/resources/assets/create/blockstates/logistical_index.json new file mode 100644 index 000000000..e4e136932 --- /dev/null +++ b/src/main/resources/assets/create/blockstates/logistical_index.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "create:block/logistical_index" + }, + "variants": { + "facing": { + "north": { "y": 180 }, + "south": { }, + "east": { "y": 270 }, + "west": { "y": 90 } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/create/lang/en_us.json b/src/main/resources/assets/create/lang/en_us.json index a1d85e7c8..45775892c 100644 --- a/src/main/resources/assets/create/lang/en_us.json +++ b/src/main/resources/assets/create/lang/en_us.json @@ -340,6 +340,10 @@ "create.schematicannon.status.schematicNotPlaced": "Schematic not Deployed", "create.schematicannon.status.schematicExpired": "Schematic File Expired", + "create.gui.index.title": "Logistical Index", + "create.gui.index.targetAddressSelect": "Destination Address", + "create.gui.index.confirmOrder": "Confirm Order", + "create.tooltip.holdKey": "Hold [%1$s]", "create.tooltip.holdKeyOrKey": "Hold [%1$s] or [%2$s]", "create.tooltip.keyShift": "Shift", diff --git a/src/main/resources/assets/create/models/block/logistical_casing.json b/src/main/resources/assets/create/models/block/logistical_casing.json new file mode 100644 index 000000000..5ac07276b --- /dev/null +++ b/src/main/resources/assets/create/models/block/logistical_casing.json @@ -0,0 +1,50 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "block/block", + "textures": { + "particle": "create:block/brass_casing_side", + "brass_casing_side": "create:block/brass_casing_side", + "brass_casing": "create:block/brass_casing" + }, + "elements": [ + { + "name": "Center", + "from": [ 1, 2, 1 ], + "to": [ 15, 14, 15 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 2, 1, 14, 15 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 2, 1, 14, 15 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 2, 1, 14, 15 ], "rotation": 90 }, + "west": { "texture": "#brass_casing_side", "uv": [ 2, 1, 14, 15 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 1, 1, 15, 15 ] }, + "down": { "texture": "#brass_casing", "uv": [ 1, 1, 15, 15 ] } + } + }, + { + "name": "Cube", + "from": [ 0, 0, 0 ], + "to": [ 16, 2, 16 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 14, 16, 16 ] }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 14, 16, 16 ] }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 14, 16, 16 ] }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 14, 16, 16 ] }, + "up": { "texture": "#brass_casing", "uv": [ 0, 0, 16, 16 ] }, + "down": { "texture": "#brass_casing", "uv": [ 0, 0, 16, 16 ] } + } + }, + { + "name": "Cube", + "from": [ 0, 14, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 0, 16, 2 ] }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 0, 16, 2 ] }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 0, 16, 2 ] }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 0, 16, 2 ] }, + "up": { "texture": "#brass_casing", "uv": [ 0, 0, 16, 16 ] }, + "down": { "texture": "#brass_casing", "uv": [ 0, 0, 16, 16 ] } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/block/logistical_casing_end.json b/src/main/resources/assets/create/models/block/logistical_casing_end.json new file mode 100644 index 000000000..891562165 --- /dev/null +++ b/src/main/resources/assets/create/models/block/logistical_casing_end.json @@ -0,0 +1,89 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "block/block", + "textures": { + "particle": "create:block/brass_casing_side", + "brass_casing_side": "create:block/brass_casing_side", + "brass_casing": "create:block/brass_casing" + }, + "elements": [ + { + "name": "Center", + "from": [ 1, 0, 1 ], + "to": [ 15, 14, 15 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 2, 1, 16, 15 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 2, 1, 16, 15 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 2, 1, 16, 15 ], "rotation": 90 }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 1, 14, 15 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 1, 1, 15, 15 ] }, + "down": { "texture": "#brass_casing", "uv": [ 1, 1, 15, 15 ] } + } + }, + { + "name": "Cube", + "from": [ 0, 0, 0 ], + "to": [ 2, 14, 2 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 0, 14, 2 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 2, 0, 16, 2 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 2, 0, 16, 2 ], "rotation": 270 }, + "west": { "texture": "#brass_casing_side", "uv": [ 2, 0, 16, 2 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 0, 0, 2, 2 ] }, + "down": { "texture": "#brass_casing", "uv": [ 0, 14, 2, 16 ] } + } + }, + { + "name": "Cube", + "from": [ 14, 0, 0 ], + "to": [ 16, 14, 2 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 0, 14, 2 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 270 }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 0, 14, 2 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 14, 0, 16, 2 ] }, + "down": { "texture": "#brass_casing", "uv": [ 14, 14, 16, 16 ] } + } + }, + { + "name": "Cube", + "from": [ 14, 0, 14 ], + "to": [ 16, 14, 16 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 270 }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 14, 14, 16, 16 ] }, + "down": { "texture": "#brass_casing", "uv": [ 14, 0, 16, 2 ] } + } + }, + { + "name": "Cube", + "from": [ 0, 0, 14 ], + "to": [ 2, 14, 16 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 0, 14, 2 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 0, 14, 2 ], "rotation": 270 }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 0, 14, 2, 16 ] }, + "down": { "texture": "#brass_casing", "uv": [ 0, 0, 2, 2 ] } + } + }, + { + "name": "Cube", + "from": [ 0, 14, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "north": { "texture": "#brass_casing", "uv": [ 0, 0, 16, 2 ] }, + "east": { "texture": "#brass_casing", "uv": [ 0, 0, 16, 2 ] }, + "south": { "texture": "#brass_casing", "uv": [ 0, 0, 16, 2 ] }, + "west": { "texture": "#brass_casing", "uv": [ 0, 0, 16, 2 ] }, + "up": { "texture": "#brass_casing", "uv": [ 0, 0, 16, 16 ] }, + "down": { "texture": "#brass_casing", "uv": [ 0, 0, 16, 16 ] } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/block/logistical_casing_middle.json b/src/main/resources/assets/create/models/block/logistical_casing_middle.json new file mode 100644 index 000000000..89fcafe43 --- /dev/null +++ b/src/main/resources/assets/create/models/block/logistical_casing_middle.json @@ -0,0 +1,76 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "block/block", + "textures": { + "particle": "create:block/brass_casing_side", + "brass_casing_side": "create:block/brass_casing_side", + "brass_casing": "create:block/brass_casing" + }, + "elements": [ + { + "name": "Center", + "from": [ 1, 0, 1 ], + "to": [ 15, 16, 15 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 1, 16, 15 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 1, 16, 15 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 1, 16, 15 ], "rotation": 90 }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 1, 16, 15 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 1, 1, 15, 15 ] }, + "down": { "texture": "#brass_casing", "uv": [ 1, 1, 15, 15 ] } + } + }, + { + "name": "Cube", + "from": [ 0, 0, 0 ], + "to": [ 2, 16, 2 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 0, 16, 2 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 0, 16, 2 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 0, 16, 2 ], "rotation": 270 }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 0, 16, 2 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 0, 0, 2, 2 ] }, + "down": { "texture": "#brass_casing", "uv": [ 0, 14, 2, 16 ] } + } + }, + { + "name": "Cube", + "from": [ 14, 0, 0 ], + "to": [ 16, 16, 2 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 14, 16, 16 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 0, 16, 2 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 14, 16, 16 ], "rotation": 270 }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 0, 16, 2 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 14, 0, 16, 2 ] }, + "down": { "texture": "#brass_casing", "uv": [ 14, 14, 16, 16 ] } + } + }, + { + "name": "Cube", + "from": [ 14, 0, 14 ], + "to": [ 16, 16, 16 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 14, 16, 16 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 14, 16, 16 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 14, 16, 16 ], "rotation": 270 }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 14, 16, 16 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 14, 14, 16, 16 ] }, + "down": { "texture": "#brass_casing", "uv": [ 14, 0, 16, 2 ] } + } + }, + { + "name": "Cube", + "from": [ 0, 0, 14 ], + "to": [ 2, 16, 16 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 0, 16, 2 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 14, 16, 16 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 0, 16, 2 ], "rotation": 270 }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 14, 16, 16 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 0, 14, 2, 16 ] }, + "down": { "texture": "#brass_casing", "uv": [ 0, 0, 2, 2 ] } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/block/logistical_casing_start.json b/src/main/resources/assets/create/models/block/logistical_casing_start.json new file mode 100644 index 000000000..5a9f7e9c8 --- /dev/null +++ b/src/main/resources/assets/create/models/block/logistical_casing_start.json @@ -0,0 +1,89 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "block/block", + "textures": { + "particle": "create:block/brass_casing_side", + "brass_casing_side": "create:block/brass_casing_side", + "brass_casing": "create:block/brass_casing" + }, + "elements": [ + { + "name": "Center", + "from": [ 1, 2, 1 ], + "to": [ 15, 16, 15 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 1, 14, 15 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 1, 14, 15 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 1, 14, 15 ], "rotation": 90 }, + "west": { "texture": "#brass_casing_side", "uv": [ 2, 1, 16, 15 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 1, 1, 15, 15 ] }, + "down": { "texture": "#brass_casing", "uv": [ 1, 1, 15, 15 ] } + } + }, + { + "name": "Cube", + "from": [ 0, 2, 0 ], + "to": [ 2, 16, 2 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 0, 14, 2 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 2, 0, 16, 2 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 2, 0, 16, 2 ], "rotation": 270 }, + "west": { "texture": "#brass_casing_side", "uv": [ 2, 0, 16, 2 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 0, 0, 2, 2 ] }, + "down": { "texture": "#brass_casing", "uv": [ 0, 14, 2, 16 ] } + } + }, + { + "name": "Cube", + "from": [ 14, 2, 0 ], + "to": [ 16, 16, 2 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 0, 14, 2 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 270 }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 0, 14, 2 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 14, 0, 16, 2 ] }, + "down": { "texture": "#brass_casing", "uv": [ 14, 14, 16, 16 ] } + } + }, + { + "name": "Cube", + "from": [ 14, 2, 14 ], + "to": [ 16, 16, 16 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 270 }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 14, 14, 16, 16 ] }, + "down": { "texture": "#brass_casing", "uv": [ 14, 0, 16, 2 ] } + } + }, + { + "name": "Cube", + "from": [ 0, 2, 14 ], + "to": [ 2, 16, 16 ], + "faces": { + "north": { "texture": "#brass_casing_side", "uv": [ 0, 0, 14, 2 ], "rotation": 90 }, + "east": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 90 }, + "south": { "texture": "#brass_casing_side", "uv": [ 0, 0, 14, 2 ], "rotation": 270 }, + "west": { "texture": "#brass_casing_side", "uv": [ 0, 14, 14, 16 ], "rotation": 270 }, + "up": { "texture": "#brass_casing", "uv": [ 0, 14, 2, 16 ] }, + "down": { "texture": "#brass_casing", "uv": [ 0, 0, 2, 2 ] } + } + }, + { + "name": "Cube", + "from": [ 0, 0, 0 ], + "to": [ 16, 2, 16 ], + "faces": { + "north": { "texture": "#brass_casing", "uv": [ 0, 14, 16, 16 ] }, + "east": { "texture": "#brass_casing", "uv": [ 0, 14, 16, 16 ] }, + "south": { "texture": "#brass_casing", "uv": [ 0, 14, 16, 16 ] }, + "west": { "texture": "#brass_casing", "uv": [ 0, 14, 16, 16 ] }, + "up": { "texture": "#brass_casing", "uv": [ 0, 0, 16, 16 ] }, + "down": { "texture": "#brass_casing", "uv": [ 0, 0, 16, 16 ] } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/block/logistical_controller.json b/src/main/resources/assets/create/models/block/logistical_controller.json new file mode 100644 index 000000000..10bda8ea8 --- /dev/null +++ b/src/main/resources/assets/create/models/block/logistical_controller.json @@ -0,0 +1,22 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "textures": { + "particle": "create:block/logistical_controller", + "logistical_controller": "create:block/logistical_controller" + }, + "elements": [ + { + "name": "Body", + "from": [ 2, 2, -1 ], + "to": [ 14, 14, 3 ], + "faces": { + "north": { "texture": "#logistical_controller", "uv": [ 0, 0, 12, 12 ] }, + "east": { "texture": "#logistical_controller", "uv": [ 12, 0, 16, 12 ] }, + "south": { "texture": "#logistical_controller", "uv": [ 0, 0, 12, 12 ] }, + "west": { "texture": "#logistical_controller", "uv": [ 12, 0, 16, 12 ], "rotation": 180 }, + "up": { "texture": "#logistical_controller", "uv": [ 0, 12, 12, 16 ], "rotation": 180 }, + "down": { "texture": "#logistical_controller", "uv": [ 0, 12, 12, 16 ] } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/block/logistical_controller_icon_calculation.json b/src/main/resources/assets/create/models/block/logistical_controller_icon_calculation.json new file mode 100644 index 000000000..e05641b3c --- /dev/null +++ b/src/main/resources/assets/create/models/block/logistical_controller_icon_calculation.json @@ -0,0 +1,28 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "textures": { + "logistical_controller": "create:block/logistical_controller", + "logistical_icons_1": "create:block/logistical_icons_1" + }, + "elements": [ + { + "name": "Indicator", + "from": [ 1.99, 3, 0 ], + "to": [ 14.01, 13, 2 ], + "shade": false, + "faces": { + "east": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "tintindex": 0 }, + "west": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "rotation": 180, "tintindex": 0 } + } + }, + { + "name": "Icon", + "from": [ 5, 5, 2.5 ], + "to": [ 11, 11, 3.5 ], + "shade": false, + "faces": { + "south": { "texture": "#logistical_icons_1", "uv": [ 9, 2, 14, 7 ], "tintindex": 1 } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/block/logistical_controller_icon_request.json b/src/main/resources/assets/create/models/block/logistical_controller_icon_request.json new file mode 100644 index 000000000..bd2e4022c --- /dev/null +++ b/src/main/resources/assets/create/models/block/logistical_controller_icon_request.json @@ -0,0 +1,28 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "textures": { + "logistical_controller": "create:block/logistical_controller", + "logistical_icons_1": "create:block/logistical_icons_1" + }, + "elements": [ + { + "name": "Indicator", + "from": [ 1.99, 3, 0 ], + "to": [ 14.01, 13, 2 ], + "shade": false, + "faces": { + "east": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "tintindex": 0 }, + "west": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "rotation": 180, "tintindex": 0 } + } + }, + { + "name": "Icon", + "from": [ 5, 5, 2.5 ], + "to": [ 11, 11, 3.5 ], + "shade": false, + "faces": { + "south": { "texture": "#logistical_icons_1", "uv": [ 9, 9, 14, 14 ], "tintindex": 1 } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/block/logistical_controller_icon_storage.json b/src/main/resources/assets/create/models/block/logistical_controller_icon_storage.json new file mode 100644 index 000000000..55257391f --- /dev/null +++ b/src/main/resources/assets/create/models/block/logistical_controller_icon_storage.json @@ -0,0 +1,28 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "textures": { + "logistical_controller": "create:block/logistical_controller", + "logistical_icons_1": "create:block/logistical_icons_1" + }, + "elements": [ + { + "name": "Indicator", + "from": [ 1.99, 3, 0 ], + "to": [ 14.01, 13, 2 ], + "shade": false, + "faces": { + "east": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "tintindex": 0 }, + "west": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "rotation": 180, "tintindex": 0 } + } + }, + { + "name": "Icon", + "from": [ 5, 5, 2.5 ], + "to": [ 11, 11, 3.5 ], + "shade": false, + "faces": { + "south": { "texture": "#logistical_icons_1", "uv": [ 2, 2, 7, 7 ], "tintindex": 1 } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/block/logistical_controller_icon_supply.json b/src/main/resources/assets/create/models/block/logistical_controller_icon_supply.json new file mode 100644 index 000000000..82ff249cc --- /dev/null +++ b/src/main/resources/assets/create/models/block/logistical_controller_icon_supply.json @@ -0,0 +1,28 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "textures": { + "logistical_controller": "create:block/logistical_controller", + "logistical_icons_1": "create:block/logistical_icons_1" + }, + "elements": [ + { + "name": "Indicator", + "from": [ 1.99, 3, 0 ], + "to": [ 14.01, 13, 2 ], + "shade": false, + "faces": { + "east": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "tintindex": 0 }, + "west": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "rotation": 180, "tintindex": 0 } + } + }, + { + "name": "Icon", + "from": [ 5, 5, 2.5 ], + "to": [ 11, 11, 3.5 ], + "shade": false, + "faces": { + "south": { "texture": "#logistical_icons_1", "uv": [ 2, 9, 7, 14 ], "tintindex": 1 } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/block/logistical_controller_icon_transactions.json b/src/main/resources/assets/create/models/block/logistical_controller_icon_transactions.json new file mode 100644 index 000000000..6db45a02b --- /dev/null +++ b/src/main/resources/assets/create/models/block/logistical_controller_icon_transactions.json @@ -0,0 +1,6 @@ +{ + "parent": "create:block/logistical_controller_icon_storage", + "textures": { + "logistical_icons_1": "create:block/logistical_icons_2" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/block/logistical_index.json b/src/main/resources/assets/create/models/block/logistical_index.json new file mode 100644 index 000000000..2c3632661 --- /dev/null +++ b/src/main/resources/assets/create/models/block/logistical_index.json @@ -0,0 +1,77 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "block/block", + "textures": { + "redstone_antenna": "create:block/redstone_antenna_powered", + "logistical_index_side_overlay": "create:block/logistical_index_side_overlay", + "logistical_index_side": "create:block/logistical_index_side", + "particle": "create:block/logistical_index_side" + }, + "elements": [ + { + "name": "Inner", + "from": [ 3, 1, -1 ], + "to": [ 13, 15, 3 ], + "faces": { + "north": { "texture": "#logistical_index_side", "uv": [ 0, 0, 10, 14 ] }, + "east": { "texture": "#logistical_index_side", "uv": [ 10, 0, 14, 14 ] }, + "south": { "texture": "#logistical_index_side", "uv": [ 0, 0, 10, 14 ] }, + "west": { "texture": "#logistical_index_side", "uv": [ 10, 0, 14, 14 ], "rotation": 180 }, + "up": { "texture": "#logistical_index_side", "uv": [ 12, 2, 16, 12 ], "rotation": 270 }, + "down": { "texture": "#logistical_index_side", "uv": [ 12, 2, 16, 12 ], "rotation": 90 } + } + }, + { + "name": "Overlay", + "from": [ 2.99, 1, -1 ], + "to": [ 13.01, 15, 3.01 ], + "shade": false, + "faces": { + "east": { "texture": "#logistical_index_side_overlay", "uv": [ 10, 0, 14.01, 14 ], "tintindex": 0 }, + "south": { "texture": "#logistical_index_side_overlay", "uv": [ 0, 0, 10.02, 14 ], "tintindex": 0 }, + "west": { "texture": "#logistical_index_side_overlay", "uv": [ 10, 0, 14.01, 14 ], "rotation": 180, "tintindex": 0 } + } + }, + { + "name": "Antenna stick", + "from": [ 2, 11, 1 ], + "to": [ 3, 18, 2 ], + "faces": { + "north": { "texture": "#redstone_antenna", "uv": [ 11, 3, 12, 10 ] }, + "east": { "texture": "#redstone_antenna", "uv": [ 11, 3, 12, 10 ] }, + "south": { "texture": "#redstone_antenna", "uv": [ 11, 3, 12, 10 ] }, + "west": { "texture": "#redstone_antenna", "uv": [ 11, 3, 12, 10 ] }, + "down": { "texture": "#redstone_antenna", "uv": [ 11, 9, 12, 10 ] } + } + }, + { + "name": "Antenna Top", + "from": [ 1, 18, 1 ], + "to": [ 4, 21, 2 ], + "shade": false, + "faces": { + "north": { "texture": "#redstone_antenna", "uv": [ 10, 0, 13, 3 ], "tintindex": 0 }, + "south": { "texture": "#redstone_antenna", "uv": [ 10, 0, 13, 3 ], "tintindex": 0 } + } + }, + { + "name": "Antenna Top2", + "from": [ 2, 18, 0 ], + "to": [ 3, 21, 3 ], + "shade": false, + "faces": { + "east": { "texture": "#redstone_antenna", "uv": [ 10, 0, 13, 3 ], "tintindex": 0 }, + "west": { "texture": "#redstone_antenna", "uv": [ 10, 0, 13, 3 ], "tintindex": 0 } + } + }, + { + "name": "Antenna TopFace", + "from": [ 2, 19, 1 ], + "to": [ 3, 20, 2 ], + "shade": false, + "faces": { + "up": { "texture": "#redstone_antenna", "uv": [ 11, 1, 12, 2 ], "tintindex": 0 } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/item/cardboard_box_1410.json b/src/main/resources/assets/create/models/item/cardboard_box_1410.json new file mode 100644 index 000000000..b708c67a7 --- /dev/null +++ b/src/main/resources/assets/create/models/item/cardboard_box_1410.json @@ -0,0 +1,23 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "block/block", + "textures": { + "particle": "create:item/cardboard_box_particle", + "cardboard_box_1410": "create:item/cardboard_box_1410" + }, + "elements": [ + { + "name": "Box", + "from": [ 1, 0, 1 ], + "to": [ 15, 10, 15 ], + "faces": { + "north": { "texture": "#cardboard_box_1410", "uv": [ 8, 8, 15, 13 ] }, + "east": { "texture": "#cardboard_box_1410", "uv": [ 1, 3, 8, 8 ] }, + "south": { "texture": "#cardboard_box_1410", "uv": [ 8, 8, 15, 13 ] }, + "west": { "texture": "#cardboard_box_1410", "uv": [ 1, 3, 8, 8 ] }, + "up": { "texture": "#cardboard_box_1410", "uv": [ 8, 1, 15, 8 ] }, + "down": { "texture": "#cardboard_box_1410", "uv": [ 1, 8, 8, 15 ] } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/item/cardboard_box_1416.json b/src/main/resources/assets/create/models/item/cardboard_box_1416.json new file mode 100644 index 000000000..4c3c99178 --- /dev/null +++ b/src/main/resources/assets/create/models/item/cardboard_box_1416.json @@ -0,0 +1,23 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "block/block", + "textures": { + "particle": "create:item/cardboard_box_particle", + "cardboard_box_1416": "create:item/cardboard_box_1416" + }, + "elements": [ + { + "name": "Box", + "from": [ 1, 0, 1 ], + "to": [ 15, 16, 15 ], + "faces": { + "north": { "texture": "#cardboard_box_1416", "uv": [ 8, 8, 15, 16 ] }, + "east": { "texture": "#cardboard_box_1416", "uv": [ 1, 0, 8, 8 ] }, + "south": { "texture": "#cardboard_box_1416", "uv": [ 8, 8, 15, 16 ] }, + "west": { "texture": "#cardboard_box_1416", "uv": [ 1, 0, 8, 8 ] }, + "up": { "texture": "#cardboard_box_1416", "uv": [ 8, 1, 15, 8 ] }, + "down": { "texture": "#cardboard_box_1416", "uv": [ 1, 8, 8, 15 ] } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/item/cardboard_box_1612.json b/src/main/resources/assets/create/models/item/cardboard_box_1612.json new file mode 100644 index 000000000..35225b651 --- /dev/null +++ b/src/main/resources/assets/create/models/item/cardboard_box_1612.json @@ -0,0 +1,23 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "block/block", + "textures": { + "particle": "create:item/cardboard_box_particle", + "cardboard_box_1612": "create:item/cardboard_box_1612" + }, + "elements": [ + { + "name": "Box", + "from": [ 0, 0, 0 ], + "to": [ 16, 12, 16 ], + "faces": { + "north": { "texture": "#cardboard_box_1612", "uv": [ 8, 8, 16, 14 ] }, + "east": { "texture": "#cardboard_box_1612", "uv": [ 0, 2, 8, 8 ] }, + "south": { "texture": "#cardboard_box_1612", "uv": [ 8, 8, 16, 14 ] }, + "west": { "texture": "#cardboard_box_1612", "uv": [ 0, 2, 8, 8 ] }, + "up": { "texture": "#cardboard_box_1612", "uv": [ 8, 0, 16, 8 ] }, + "down": { "texture": "#cardboard_box_1612", "uv": [ 0, 8, 8, 16 ] } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/item/cardboard_box_1616.json b/src/main/resources/assets/create/models/item/cardboard_box_1616.json new file mode 100644 index 000000000..ae58faa6f --- /dev/null +++ b/src/main/resources/assets/create/models/item/cardboard_box_1616.json @@ -0,0 +1,23 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "block/block", + "textures": { + "particle": "create:item/cardboard_box_particle", + "cardboard_box_1616": "create:item/cardboard_box_1616" + }, + "elements": [ + { + "name": "Box", + "from": [ 0, 0, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "north": { "texture": "#cardboard_box_1616", "uv": [ 8, 8, 16, 16 ] }, + "east": { "texture": "#cardboard_box_1616", "uv": [ 0, 0, 8, 8 ] }, + "south": { "texture": "#cardboard_box_1616", "uv": [ 8, 8, 16, 16 ] }, + "west": { "texture": "#cardboard_box_1616", "uv": [ 0, 0, 8, 8 ] }, + "up": { "texture": "#cardboard_box_1616", "uv": [ 8, 0, 16, 8 ] }, + "down": { "texture": "#cardboard_box_1616", "uv": [ 0, 8, 8, 16 ] } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/item/logistical_casing.json b/src/main/resources/assets/create/models/item/logistical_casing.json new file mode 100644 index 000000000..9c287f452 --- /dev/null +++ b/src/main/resources/assets/create/models/item/logistical_casing.json @@ -0,0 +1,3 @@ +{ + "parent": "create:block/logistical_casing" +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/item/logistical_controller.json b/src/main/resources/assets/create/models/item/logistical_controller.json new file mode 100644 index 000000000..c8bfaadc5 --- /dev/null +++ b/src/main/resources/assets/create/models/item/logistical_controller.json @@ -0,0 +1,26 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "block/block", + "textures": { + "particle": "create:block/logistical_controller", + "logistical_controller": "create:block/logistical_controller", + "logistical_icons_1": "create:block/logistical_icons_1" + }, + "display": { + "gui": { + "rotation": [ 30, 45, 0 ], + "translation": [ 2.6, -1, 0 ], + "scale": [ 0.625, 0.625, 0.625 ] + }, + "ground": { + "rotation": [ 0, 0, 0 ], + "translation": [ 0, 3, 2 ], + "scale": [ 0.25, 0.25, 0.25 ] + }, + "fixed": { + "rotation": [ 0, 180, 0 ], + "translation": [ 0, 0, -7 ], + "scale": [ 0.625, 0.625, 0.625 ] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/item/logistical_controller_calculation.json b/src/main/resources/assets/create/models/item/logistical_controller_calculation.json new file mode 100644 index 000000000..9bb9d4ae4 --- /dev/null +++ b/src/main/resources/assets/create/models/item/logistical_controller_calculation.json @@ -0,0 +1,38 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "create:item/logistical_controller", + "elements": [ + { + "name": "Body", + "from": [ 2, 2, -1 ], + "to": [ 14, 14, 3 ], + "faces": { + "north": { "texture": "#logistical_controller", "uv": [ 0, 0, 12, 12 ] }, + "east": { "texture": "#logistical_controller", "uv": [ 12, 0, 16, 12 ] }, + "south": { "texture": "#logistical_controller", "uv": [ 0, 0, 12, 12 ] }, + "west": { "texture": "#logistical_controller", "uv": [ 12, 0, 16, 12 ], "rotation": 180 }, + "up": { "texture": "#logistical_controller", "uv": [ 0, 12, 12, 16 ], "rotation": 180 }, + "down": { "texture": "#logistical_controller", "uv": [ 0, 12, 12, 16 ] } + } + }, + { + "name": "Indicator", + "from": [ 1.99, 3, 0 ], + "to": [ 14.01, 13, 2 ], + "shade": false, + "faces": { + "east": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "tintindex": 0 }, + "west": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "rotation": 180, "tintindex": 0 } + } + }, + { + "name": "Icon", + "from": [ 5, 5, 2.5 ], + "to": [ 11, 11, 3.5 ], + "shade": false, + "faces": { + "south": { "texture": "#logistical_icons_1", "uv": [ 9, 2, 14, 7 ], "tintindex": 1 } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/item/logistical_controller_request.json b/src/main/resources/assets/create/models/item/logistical_controller_request.json new file mode 100644 index 000000000..67bb689c8 --- /dev/null +++ b/src/main/resources/assets/create/models/item/logistical_controller_request.json @@ -0,0 +1,38 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "create:item/logistical_controller", + "elements": [ + { + "name": "Body", + "from": [ 2, 2, -1 ], + "to": [ 14, 14, 3 ], + "faces": { + "north": { "texture": "#logistical_controller", "uv": [ 0, 0, 12, 12 ] }, + "east": { "texture": "#logistical_controller", "uv": [ 12, 0, 16, 12 ] }, + "south": { "texture": "#logistical_controller", "uv": [ 0, 0, 12, 12 ] }, + "west": { "texture": "#logistical_controller", "uv": [ 12, 0, 16, 12 ], "rotation": 180 }, + "up": { "texture": "#logistical_controller", "uv": [ 0, 12, 12, 16 ], "rotation": 180 }, + "down": { "texture": "#logistical_controller", "uv": [ 0, 12, 12, 16 ] } + } + }, + { + "name": "Indicator", + "from": [ 1.99, 3, 0 ], + "to": [ 14.01, 13, 2 ], + "shade": false, + "faces": { + "east": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "tintindex": 0 }, + "west": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "rotation": 180, "tintindex": 0 } + } + }, + { + "name": "Icon", + "from": [ 5, 5, 2.5 ], + "to": [ 11, 11, 3.5 ], + "shade": false, + "faces": { + "south": { "texture": "#logistical_icons_1", "uv": [ 9, 9, 14, 14 ], "tintindex": 1 } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/item/logistical_controller_storage.json b/src/main/resources/assets/create/models/item/logistical_controller_storage.json new file mode 100644 index 000000000..f9279f0e4 --- /dev/null +++ b/src/main/resources/assets/create/models/item/logistical_controller_storage.json @@ -0,0 +1,38 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "create:item/logistical_controller", + "elements": [ + { + "name": "Body", + "from": [ 2, 2, -1 ], + "to": [ 14, 14, 3 ], + "faces": { + "north": { "texture": "#logistical_controller", "uv": [ 0, 0, 12, 12 ] }, + "east": { "texture": "#logistical_controller", "uv": [ 12, 0, 16, 12 ] }, + "south": { "texture": "#logistical_controller", "uv": [ 0, 0, 12, 12 ] }, + "west": { "texture": "#logistical_controller", "uv": [ 12, 0, 16, 12 ], "rotation": 180 }, + "up": { "texture": "#logistical_controller", "uv": [ 0, 12, 12, 16 ], "rotation": 180 }, + "down": { "texture": "#logistical_controller", "uv": [ 0, 12, 12, 16 ] } + } + }, + { + "name": "Indicator", + "from": [ 1.99, 3, 0 ], + "to": [ 14.01, 13, 2 ], + "shade": false, + "faces": { + "east": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "tintindex": 0 }, + "west": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "rotation": 180, "tintindex": 0 } + } + }, + { + "name": "Icon", + "from": [ 5, 5, 2.5 ], + "to": [ 11, 11, 3.5 ], + "shade": false, + "faces": { + "south": { "texture": "#logistical_icons_1", "uv": [ 2, 2, 7, 7 ], "tintindex": 1 } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/item/logistical_controller_supply.json b/src/main/resources/assets/create/models/item/logistical_controller_supply.json new file mode 100644 index 000000000..7b2c305cf --- /dev/null +++ b/src/main/resources/assets/create/models/item/logistical_controller_supply.json @@ -0,0 +1,38 @@ +{ + "__comment": "Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)", + "parent": "create:item/logistical_controller", + "elements": [ + { + "name": "Body", + "from": [ 2, 2, -1 ], + "to": [ 14, 14, 3 ], + "faces": { + "north": { "texture": "#logistical_controller", "uv": [ 0, 0, 12, 12 ] }, + "east": { "texture": "#logistical_controller", "uv": [ 12, 0, 16, 12 ] }, + "south": { "texture": "#logistical_controller", "uv": [ 0, 0, 12, 12 ] }, + "west": { "texture": "#logistical_controller", "uv": [ 12, 0, 16, 12 ], "rotation": 180 }, + "up": { "texture": "#logistical_controller", "uv": [ 0, 12, 12, 16 ], "rotation": 180 }, + "down": { "texture": "#logistical_controller", "uv": [ 0, 12, 12, 16 ] } + } + }, + { + "name": "Indicator", + "from": [ 1.99, 3, 0 ], + "to": [ 14.01, 13, 2 ], + "shade": false, + "faces": { + "east": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "tintindex": 0 }, + "west": { "texture": "#logistical_controller", "uv": [ 13, 1, 15, 11 ], "rotation": 180, "tintindex": 0 } + } + }, + { + "name": "Icon", + "from": [ 5, 5, 2.5 ], + "to": [ 11, 11, 3.5 ], + "shade": false, + "faces": { + "south": { "texture": "#logistical_icons_1", "uv": [ 2, 9, 7, 14 ], "tintindex": 1 } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/item/logistical_controller_transactions.json b/src/main/resources/assets/create/models/item/logistical_controller_transactions.json new file mode 100644 index 000000000..f7c9c725b --- /dev/null +++ b/src/main/resources/assets/create/models/item/logistical_controller_transactions.json @@ -0,0 +1,6 @@ +{ + "parent": "create:item/logistical_controller_storage", + "textures": { + "logistical_icons_1": "create:block/logistical_icons_2" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/item/logistical_dial.json b/src/main/resources/assets/create/models/item/logistical_dial.json new file mode 100644 index 000000000..a31bb13a8 --- /dev/null +++ b/src/main/resources/assets/create/models/item/logistical_dial.json @@ -0,0 +1,7 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "create:item/logistical_dial", + "layer1": "create:item/logistical_dial_overlay" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/item/logistical_index.json b/src/main/resources/assets/create/models/item/logistical_index.json new file mode 100644 index 000000000..593ef575a --- /dev/null +++ b/src/main/resources/assets/create/models/item/logistical_index.json @@ -0,0 +1,20 @@ +{ + "parent": "create:block/logistical_index", + "display": { + "gui": { + "rotation": [ 30, 45, 0 ], + "translation": [ 2.6, -1, 0 ], + "scale": [ 0.625, 0.625, 0.625 ] + }, + "ground": { + "rotation": [ 0, 0, 0 ], + "translation": [ 0, 3, 2 ], + "scale": [ 0.25, 0.25, 0.25 ] + }, + "fixed": { + "rotation": [ 0, 180, 0 ], + "translation": [ 0, 0, -7 ], + "scale": [ 0.625, 0.625, 0.625 ] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/create/textures/block/CONCEPT_logistical_transaction_controller.pdn b/src/main/resources/assets/create/textures/block/CONCEPT_logistical_transaction_controller.pdn new file mode 100644 index 0000000000000000000000000000000000000000..b0a5ba795b76deaf586875696b725969bd6b2fce GIT binary patch literal 4351 zcmd^BS!^5U5$0MvWu;Q>ICf&!Vi-d$Kt-fTQ6vG;@^X2K;w@5_6?wV)i(K(odJjqz z3KvFzKBNzI5Fj<$7q@YO1PzMR53SImNRuWg3=};SG!2sW!Htoi7n(Y*p}LaUf0mL- zI!>KF^vO$b_CLS*|Cw)QHsOoeSNEvKs%#}(rt`?S!m(1pV;UYc84HNZ7o=vpMAwlZ zdQ5hcQKV~#o#v!M7LRaz#bX*7vRQ|RoWl;+kbT%~9W@;vV`Nd{%g4v0f?UoStChUR z#AUtl)QU9{%=09EV(Co5pULBv7rJDUB4^>f&}G>6A503gQSenuLK=C7Bt< ztEtuHm6~fdnSsFx#$T9YR!Gt(Mse5aBN?`KItUy5sdOgg&H4(7Qg$*&}JtR4}z!Lqaq;m-hRS!!|?LCMxa;k_-jvGjb_Cm2ip_6+sR; z9}~*8S;plZE~Zyw6mOPS(zPhR7Ag3=$`n8+u&NPf8-l7p#`Y+gN`S_=5< z*_DE;8mrlyR$qzA`@K${tfl4Ih_lF2DBz}3kt#z@+1#=?XLeca1yag6XA@!Y3z^Pj zEZtwxI)2H}S2{955A#o^(DoEg%A$;g8U6K)o6HeLY=XaK-C4sBt2aRQs;e`^H zYd42=2>HZ-1IN%Rw=^WtdHcL|LEDXod}AeUATmDkw-x%T;y(c0rGT|bghUhDvLBCLszVLQjODihs&d=%nNmIiD!yn zRQT_fu@ks$FkztqOWoVnKpAy~E~8I=J?Nm2;2ZaFqL*fhd4ZQJtd6hJ3@6nMiYcNz ziwG!G>igxxfgtug((ZhPx{ABO7P}7DK89`5-LjhD81559L-QWaLjPsILNX+>OIYZXqZ#c@-KqxhHr5UL@tlf)z)&E~(1Qs1 z1mqyh7_m?wAIFUyNZW15-?Tw$faISsFz`wxFf_o_Pop;QN{{0zFBL{d3bYN)Z>jvKU$=3}5v;gZ9zydI7%OC{!SD6EMcFyf>t;3bN z|7&{KiMQ^5c52Hq@ZDRB=O@3p?o1u@Ag0aP!k^VR`YqWwfFs} zH@1G&^~Q;xp6LGI?4RHNc4GXcn=hXI*VUd^o;tYsqix-l@IS6J<{#Vh%7(Fi^5!$1 zBaJ7mzc^AlbS(bT_WG9pzOWhmrS6Tdj)%s7uoaxx{MmIv zb>Rl&^mOwC$hy)|N5hT{h`onM$aFgcD}nv*!wymU;Od-I=AoSSNhIHD_du)p^w?8HsXl|i~r{3@Ap4@=7j4KGjRDP PxprcW;NHCU4jlGB#^fe! literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/block/logistical_controller.png b/src/main/resources/assets/create/textures/block/logistical_controller.png new file mode 100644 index 0000000000000000000000000000000000000000..6dbc79bc2a2cd9660f22ddb10e8772661f30d73f GIT binary patch literal 615 zcmV-t0+{`YP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0sKisK~y+TZBxx| z(?AgZ{GTQ=F-W;^&KVv6^@_N_8J?gozzK0L7sMHfm*C6=@c=zi2@o_uQ`97_-NqYl zy!JA)wqv{MCq=WFnQ#7Wy?^sUuZ4u~+0eQ~p-OCIioIZDWE9?kB%SYAaJ>M>$6s;2 zT4EJnVv`71`w#EyA(1_D$O{dZn#T#5pVi#47;KR_JEI63+NQp1pk zRF%C8{>#BRdl%#J7*Q0Nw=r>+fm@M%2S{a=-l>T) z*C^|Xbg2z_u~@(#jM@qqiUM^j=IpnD$&<+huIse{nqG@qN@|JG=v!GDG6!MwDhNCa z4rTT0c2b%?W4zaM+f~5WS$I`kaZ>@KnE_{S+VM!qQ!JpdN4Er zY^$V?guVy50gGHzQTdfLJPY(AyDDsaKFT@)fd9lZ33OjheqaCq002ovPDHLkV1jDw B3k(1N literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/block/logistical_icons_1.png b/src/main/resources/assets/create/textures/block/logistical_icons_1.png new file mode 100644 index 0000000000000000000000000000000000000000..48bca3161ac92057c5613043df10a97cb5f79ad7 GIT binary patch literal 367 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc4J@86jv*HQOD7w09dZzGEkCN%G3u0Xv>4opVcSw%~7D{$^og6P}f9>BHp9( z%=JInT+f`fX<;;Tc7xiE?YZ6ohRnW~10tv8yttN-HD&fumk@`y@rr(JH6qIjw6?9e ze{H?-n*GN*G;eC3I5DBonsG*Q$-Aostty^BWTllRFf4o~$b2~e>;WkcgTe~DWM4fi^!H2 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/block/logistical_icons_2.png b/src/main/resources/assets/create/textures/block/logistical_icons_2.png new file mode 100644 index 0000000000000000000000000000000000000000..02f695d60b4baed22e8b0e76d853118a179152e6 GIT binary patch literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!IPdYjv*HQ$$$R;w`bN&NJZ(p;uBGft42rtdE4m?O4g9@Uk*CHFA7rYUBPl$F+iS$qW8|(>p2?m;@SjGAxl) zV`E<6z{)&bP0l+XkKddg^Z literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/block/logistical_index_side.png b/src/main/resources/assets/create/textures/block/logistical_index_side.png new file mode 100644 index 0000000000000000000000000000000000000000..8adfcb545348e6a6f0ff6439e49823a287454bf1 GIT binary patch literal 517 zcmV+g0{Z=lP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0h&ofK~y+TrISfg z!!Qs$9gsn%f`ib_z*!IZuk2X=8J zOAg>|9KvyR(+O)0mrH3!EnGsJwL7q0_@=eumOL-$u=R^B*dEu(TgPSaIhgtqs#_$B zjZ5MPWl1HLoq%~0!dyc_twY8^7}L?s00000NkvXX Hu0mjfS>oB| literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/block/logistical_index_side_overlay.png b/src/main/resources/assets/create/textures/block/logistical_index_side_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..e4cb74f29e183d66788fc0a56002df1f59ced5ca GIT binary patch literal 376 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBuf-vKbiP>*~8hAZj978Ppmj-)wwKxcz3EQKow?NMIL#f?? zw_nq5l4 znFS1N>t8=otBaJ7s$#mBTf@@x*rQP9{vT_GXC}VBOAj&{aa=awG56lU@XX%)L+yO_ zM6XFto~<`8kZJdy_xuk->%yJuPB$~0sW3LPcwoUN7c;H(?4b(+N(b0n17~3r| zDmlA#|2+q$0)v#zvJA%iSfyn)FVdQ&MBb@06=w-!~g&Q literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/block/redstone_antenna.png b/src/main/resources/assets/create/textures/block/redstone_antenna.png index 8719db70be1002d18d06a8dd9e50afcaa42e3612..31bc50188eccc89405bd146aa2ce0503ae32c31f 100644 GIT binary patch delta 255 zcmVYmYhaTMr&{GiQ zCJG(It^IX%wN<j zO4D>Wi=t?V6)*^oAreVhmME_48gWq+{Rxrp`+YP5gX`mkM0jFZ7P+oVwr%67v)?`Y zXaoj#-t{YyJ$s%TL zk}XU+7&J|TWDAq-YZI4{8){;8O?AU1gP;Dw8h>v<^aggTX_z=0WWWFb002ovPDHLk FV1n%dZsq_0 delta 219 zcmV<103`p-0;vL!R)4-pL_t(IPt}r54uc>NMhCVU!`dr&1uxO0M{?)+TobtfYGg@h z=SwxVMQGghCH{QkyfA>E10e(wLeMylVO>{zYntZmpHC#;Zgm}yo@~X&7@DSuhGE#p zVnA^3djw`#78E<@@NTVrjl_XKYmLB2DXFR|DvAQn91v{%GF05NQi{s5#G15in_L6X zbshTTg4mS;GsuyWKrqiU!WAIMO)~`q-g|^Ae4WHI_=LttZqZSACirO=QvAJu=mBpS VTQe_l1j7IT002ovPDHLkV1k7AVjutj diff --git a/src/main/resources/assets/create/textures/block/redstone_antenna_powered.png b/src/main/resources/assets/create/textures/block/redstone_antenna_powered.png index 6c3d5b6962432452601cdb646639bfb02ad47639..2e883a28d42805d70cb56f99770a651a36f5ccab 100644 GIT binary patch delta 318 zcmV-E0m1&n0*V8WReu2=NklaXP=3_O{)U?|v7tc_ptc=JF0`56X8gc_<5uT>>ULO~F4 z^)tgT;5ZJcK16GTAiBMQ>S~atDb>{*>c@M0uX2Q8NJ0qMwtr2o>vHFv9lAy&?%K^) z04J=g3Lh^|ID9Xmx4PWhXQStNBFi%JeP8gPLsO5XeZfLOx(#9Cwoq91uLOSSU=>6bJJK73X5D;3M=E`XX+=OkTvz-4}7O zE`rd(L5ppnDnjGw9aZR%3U&2^BzKqGf0qzNM0hAg4v|7bnx<4$mDo`fNnEMVb_<7- zX*m}W4sfz5&M@0G?%5GZl8`N#ak%nowt)L8!huUsAlvVt%5WUs>3}@X<)96Rr;XO) zFywjg`~bJn1oe2@MD2*c`@Rg{c^HJK{Sa4ofH;ojoviSt)>=-mZ5uYEgI^GVvMj~v xg9voB_ze*-#)#90)(`3G#QGDPwKsnO;0b`lXg3f6l6n9D002ovPDHLkV1oVOccTCR diff --git a/src/main/resources/assets/create/textures/gui/index.pdn b/src/main/resources/assets/create/textures/gui/index.pdn new file mode 100644 index 0000000000000000000000000000000000000000..d9c63a53fe2dd2e50ee89cc48711bd732009b781 GIT binary patch literal 60617 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]JqN~-)=zr3~iWIk(ll#jO2x&!d^E=c@v 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 F3jjbjkGlW> literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/gui/index.png b/src/main/resources/assets/create/textures/gui/index.png new file mode 100644 index 0000000000000000000000000000000000000000..9a8c423fbb011ec0764de03a18cf0aadba67055c GIT binary patch literal 11259 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 F{~P@hm8t*$ literal 4492 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 AXaE2J literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/item/logistical_dial.png b/src/main/resources/assets/create/textures/item/logistical_dial.png new file mode 100644 index 0000000000000000000000000000000000000000..f8074d85752d2cd73a48f782fadd3b877eca469d GIT binary patch literal 438 zcmV;n0ZIOeP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!TpgJ{U~|JI)X%d*UePb&DNu7mY z0*0g&43CaLV`qR%yJ;Z8tN!zv!(~bi( z&TI-;SzROG-OUwt;JP05?e+t$_uay8NcXEtr_BJqJ~py)*T@ilFrHT-K-S1Ae2EA# g!%`z_z~33*8)YxHCn?f)VE_OC07*qoM6N<$f|(t;Jpcdz literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/item/logistical_dial_overlay.png b/src/main/resources/assets/create/textures/item/logistical_dial_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..28c08005b02f7ca7d887b86e9e44123246ae3367 GIT binary patch literal 285 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBufiR<}hF1enaFeHtV~BdhA)