From 2078899ed98c05ce5b9dd1d4587dc030703f7eaa Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Wed, 10 Nov 2021 21:07:47 +0100 Subject: [PATCH] Bigger Boxes - Added the Item Vault --- src/generated/resources/.cache/cache | 31 +- .../assets/create/blockstates/item_vault.json | 18 + .../resources/assets/create/lang/en_ud.json | 1 + .../resources/assets/create/lang/en_us.json | 1 + .../assets/create/lang/unfinished/de_de.json | 3 +- .../assets/create/lang/unfinished/es_es.json | 3 +- .../assets/create/lang/unfinished/fr_fr.json | 3 +- .../assets/create/lang/unfinished/it_it.json | 3 +- .../assets/create/lang/unfinished/ja_jp.json | 3 +- .../assets/create/lang/unfinished/ko_kr.json | 3 +- .../assets/create/lang/unfinished/nl_nl.json | 3 +- .../assets/create/lang/unfinished/pl_pl.json | 3 +- .../assets/create/lang/unfinished/pt_br.json | 3 +- .../assets/create/lang/unfinished/ru_ru.json | 3 +- .../assets/create/lang/unfinished/zh_cn.json | 3 +- .../assets/create/lang/unfinished/zh_tw.json | 3 +- .../assets/create/models/item/item_vault.json | 3 + .../create/loot_tables/blocks/item_vault.json | 21 ++ .../java/com/simibubi/create/AllBlocks.java | 17 + .../com/simibubi/create/AllSpriteShifts.java | 11 + .../com/simibubi/create/AllTileEntities.java | 6 + .../BlockMovementChecks.java | 4 + .../structureMovement/Contraption.java | 4 +- .../logistics/block/vault/VaultBlock.java | 177 ++++++++++ .../block/vault/VaultCTBehaviour.java | 68 ++++ .../block/vault/VaultConnectivityHandler.java | 329 ++++++++++++++++++ .../logistics/block/vault/VaultItem.java | 128 +++++++ .../block/vault/VaultTileEntity.java | 245 +++++++++++++ .../create/foundation/config/CLogistics.java | 2 + .../create/foundation/utility/Couple.java | 4 + .../create/models/block/item_vault.json | 32 ++ .../create/textures/block/vault_bottom.png | Bin 0 -> 241 bytes .../textures/block/vault_bottom_connected.png | Bin 0 -> 1322 bytes .../block/vault_bottom_large_connected.png | Bin 0 -> 1921 bytes .../create/textures/block/vault_front.png | Bin 0 -> 425 bytes .../textures/block/vault_front_connected.png | Bin 0 -> 2006 bytes .../block/vault_front_large_connected.png | Bin 0 -> 2546 bytes .../create/textures/block/vault_side.png | Bin 0 -> 289 bytes .../textures/block/vault_side_connected.png | Bin 0 -> 2070 bytes .../block/vault_side_large_connected.png | Bin 0 -> 1970 bytes .../create/textures/block/vault_top.png | Bin 0 -> 337 bytes .../textures/block/vault_top_connected.png | Bin 0 -> 1636 bytes .../block/vault_top_large_connected.png | Bin 0 -> 1816 bytes 43 files changed, 1111 insertions(+), 27 deletions(-) create mode 100644 src/generated/resources/assets/create/blockstates/item_vault.json create mode 100644 src/generated/resources/assets/create/models/item/item_vault.json create mode 100644 src/generated/resources/data/create/loot_tables/blocks/item_vault.json create mode 100644 src/main/java/com/simibubi/create/content/logistics/block/vault/VaultBlock.java create mode 100644 src/main/java/com/simibubi/create/content/logistics/block/vault/VaultCTBehaviour.java create mode 100644 src/main/java/com/simibubi/create/content/logistics/block/vault/VaultConnectivityHandler.java create mode 100644 src/main/java/com/simibubi/create/content/logistics/block/vault/VaultItem.java create mode 100644 src/main/java/com/simibubi/create/content/logistics/block/vault/VaultTileEntity.java create mode 100644 src/main/resources/assets/create/models/block/item_vault.json create mode 100644 src/main/resources/assets/create/textures/block/vault_bottom.png create mode 100644 src/main/resources/assets/create/textures/block/vault_bottom_connected.png create mode 100644 src/main/resources/assets/create/textures/block/vault_bottom_large_connected.png create mode 100644 src/main/resources/assets/create/textures/block/vault_front.png create mode 100644 src/main/resources/assets/create/textures/block/vault_front_connected.png create mode 100644 src/main/resources/assets/create/textures/block/vault_front_large_connected.png create mode 100644 src/main/resources/assets/create/textures/block/vault_side.png create mode 100644 src/main/resources/assets/create/textures/block/vault_side_connected.png create mode 100644 src/main/resources/assets/create/textures/block/vault_side_large_connected.png create mode 100644 src/main/resources/assets/create/textures/block/vault_top.png create mode 100644 src/main/resources/assets/create/textures/block/vault_top_connected.png create mode 100644 src/main/resources/assets/create/textures/block/vault_top_large_connected.png diff --git a/src/generated/resources/.cache/cache b/src/generated/resources/.cache/cache index 00d81fccb..3398d1c07 100644 --- a/src/generated/resources/.cache/cache +++ b/src/generated/resources/.cache/cache @@ -200,6 +200,7 @@ be3bef7e091d8b50bfc1c6b7275946d1f636aefd assets/create/blockstates/horizontal_fr 18d9fdaa1352a7e2ec91135e46dae5c02ccd8f8f assets/create/blockstates/horizontal_framed_glass_pane.json 30ec347dfc827a9ae52cf3da964b828005acede1 assets/create/blockstates/hose_pulley.json 6651c84ea621777d572a3d7aa13b75d9f061191b assets/create/blockstates/item_drain.json +10ef455fd61ed1ca831d27bf2b533d05dec9c67d assets/create/blockstates/item_vault.json 5d851c90d23de5087ce546d4bbe509e112b84c49 assets/create/blockstates/jungle_window.json b15bea757ef981e0ca60f740ca234ee2014eb7b7 assets/create/blockstates/jungle_window_pane.json f651091db216b009b3379b2f48d56d03481c8675 assets/create/blockstates/large_cogwheel.json @@ -442,20 +443,20 @@ bf2b0310500213ff853c748c236eb5d01f61658e assets/create/blockstates/yellow_toolbo 6801fa1f466f172700e573e5b8ee8ee5f9ca4583 assets/create/blockstates/yellow_valve_handle.json 7f39521b211441f5c3e06d60c5978cebe16cacfb assets/create/blockstates/zinc_block.json b7181bcd8182b2f17088e5aa881f374c9c65470c assets/create/blockstates/zinc_ore.json -7c31858e6239b72e4da8830fa4cf1748ef2a7ae1 assets/create/lang/en_ud.json -124dd97c100eda3e8078c5e524db74ba91bf206c assets/create/lang/en_us.json -738b1c5391dedb308e2b782d48cf867c8a8bf041 assets/create/lang/unfinished/de_de.json -a987345dd3375ad01fe4900f93941570d374dba9 assets/create/lang/unfinished/es_es.json -9c26f0e9e28c76ef1f5e5e8bf6d0de8eb4d956d6 assets/create/lang/unfinished/fr_fr.json -211c4c8cbd62546a08616b2482319c63d87083a5 assets/create/lang/unfinished/it_it.json -9590169c9c1708af58db1adba3e38e7c58952cfd assets/create/lang/unfinished/ja_jp.json -97eb7f70b3dffea14fc4df5a0147395c3663f321 assets/create/lang/unfinished/ko_kr.json -a9a7bacadde4d6daa4f4ae85da2821c33252442b assets/create/lang/unfinished/nl_nl.json -e8a42aacad3b0b606cf79a30d209dabe6e13a6b3 assets/create/lang/unfinished/pl_pl.json -1a0fa057db601d3aa35c971a9e3d113d3cf61cca assets/create/lang/unfinished/pt_br.json -4d0e6890f838528bed5c7ec58703b4369b10a0b4 assets/create/lang/unfinished/ru_ru.json -6aeaa68280d6fb85aa0e3eabc27d4723a2af4b8b assets/create/lang/unfinished/zh_cn.json -a4b3fceb0d539b6f9cc27d6d81ea1f862f83f3c0 assets/create/lang/unfinished/zh_tw.json +1467113c879a243b2f077b196839491306733e98 assets/create/lang/en_ud.json +b2c605fc34b13c7239521f9269498e81f9206df8 assets/create/lang/en_us.json +a6a58d7de53eb8604d4931ebae0ac2db21cdc294 assets/create/lang/unfinished/de_de.json +1e17cccd3640755df4791103703321b20939270a assets/create/lang/unfinished/es_es.json +b3080971e3ee4fa68a939aa950a2efb526c2514b assets/create/lang/unfinished/fr_fr.json +d049c3352e50b7e712a7e8f960bacbf05ccd67c2 assets/create/lang/unfinished/it_it.json +f5b457214c5e016819cfc40963a8416b87f0f6c0 assets/create/lang/unfinished/ja_jp.json +dc58414c920b5a133aabc0199c31c6d1825cf49b assets/create/lang/unfinished/ko_kr.json +020b76171c6c05ed68aa850e7178a5d3b66dc726 assets/create/lang/unfinished/nl_nl.json +eb77d2c1b2e596811a201d8ad99c05fd1a2f87ec assets/create/lang/unfinished/pl_pl.json +0b5a48da526045a8ca23aabb7ee188bdf799355a assets/create/lang/unfinished/pt_br.json +5e853989000583f44b25721d8f3fe6d456ef3127 assets/create/lang/unfinished/ru_ru.json +baf3023293ad9565d6898ec18e54109e0fc9c7e1 assets/create/lang/unfinished/zh_cn.json +b5d55f65cf5ac2b262622b0bf15cfd2b03100dcf assets/create/lang/unfinished/zh_tw.json 487a511a01b2a4531fb672f917922312db78f958 assets/create/models/block/acacia_window.json b48060cba1a382f373a05bf0039054053eccf076 assets/create/models/block/acacia_window_pane_noside.json 3066db1bf03cffa1a9c7fbacf47ae586632f4eb3 assets/create/models/block/acacia_window_pane_noside_alt.json @@ -1498,6 +1499,7 @@ ff92f6a9dfb73a6ee1eaaed3279c89390ff04a80 assets/create/models/item/hose_pulley.j 153a185852af79654f0fb216c4b1b8e69c85ee8a assets/create/models/item/incomplete_precision_mechanism.json 9d605ce0da83a73b535bce45c107e604364e2b20 assets/create/models/item/iron_sheet.json 52e435014cb03e93411666c4799ebff206e55fc9 assets/create/models/item/item_drain.json +76ab46c9b64faf5e94391579eec5115360dff73d assets/create/models/item/item_vault.json 83fa8699318e51f838b483b40b3e897c34ed53d1 assets/create/models/item/jungle_window.json 766323f6026c3505a75db2dee2996d342370d9c2 assets/create/models/item/jungle_window_pane.json bcaaf60d9a853cce90169dabcb36d29a3ce19e18 assets/create/models/item/large_cogwheel.json @@ -2674,6 +2676,7 @@ b80ea3ecf06dc3b394eb5006ed22a1cc1c276be7 data/create/loot_tables/blocks/haunted_ 6a95342ca2e88c1e829a649d33c9b4aa8ea9b590 data/create/loot_tables/blocks/horizontal_framed_glass_pane.json a40f78789e92c72d248cf5c99c04682e4fd29d28 data/create/loot_tables/blocks/hose_pulley.json d913437bf5ea95e80abd389fffa031343e14c49f data/create/loot_tables/blocks/item_drain.json +34d7db36bfc0a6da4a8f1803a24de0d9765ce003 data/create/loot_tables/blocks/item_vault.json 88a1de3ac08023cbc920c338db9f97a4ffecbd5d data/create/loot_tables/blocks/jungle_window.json bf2d5854965b850fe72fac1992924bf4a0bba456 data/create/loot_tables/blocks/jungle_window_pane.json 7935444e5cef99d58c2dc26d72f234732b6b5632 data/create/loot_tables/blocks/large_cogwheel.json diff --git a/src/generated/resources/assets/create/blockstates/item_vault.json b/src/generated/resources/assets/create/blockstates/item_vault.json new file mode 100644 index 000000000..5f3e11412 --- /dev/null +++ b/src/generated/resources/assets/create/blockstates/item_vault.json @@ -0,0 +1,18 @@ +{ + "variants": { + "axis=x,large=false": { + "model": "create:block/item_vault", + "y": 90 + }, + "axis=z,large=false": { + "model": "create:block/item_vault" + }, + "axis=x,large=true": { + "model": "create:block/item_vault", + "y": 90 + }, + "axis=z,large=true": { + "model": "create:block/item_vault" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/create/lang/en_ud.json b/src/generated/resources/assets/create/lang/en_ud.json index ead8a1e87..e26431f26 100644 --- a/src/generated/resources/assets/create/lang/en_ud.json +++ b/src/generated/resources/assets/create/lang/en_ud.json @@ -201,6 +201,7 @@ "block.create.horizontal_framed_glass_pane": "\u01DDu\u0250\u0500 ss\u0250\u05DF\u2141 p\u01DD\u026F\u0250\u0279\u2132 \u05DF\u0250\u0287uoz\u0131\u0279oH", "block.create.hose_pulley": "\u028E\u01DD\u05DF\u05DFn\u0500 \u01DDsoH", "block.create.item_drain": "u\u0131\u0250\u0279\u15E1 \u026F\u01DD\u0287I", + "block.create.item_vault": "\u0287\u05DFn\u0250\u039B \u026F\u01DD\u0287I", "block.create.jungle_window": "\u028Dopu\u0131M \u01DD\u05DFbun\u017F", "block.create.jungle_window_pane": "\u01DDu\u0250\u0500 \u028Dopu\u0131M \u01DD\u05DFbun\u017F", "block.create.large_cogwheel": "\u05DF\u01DD\u01DD\u0265\u028Dbo\u0186 \u01DDb\u0279\u0250\uA780", diff --git a/src/generated/resources/assets/create/lang/en_us.json b/src/generated/resources/assets/create/lang/en_us.json index 1a66e8d6b..e5455748d 100644 --- a/src/generated/resources/assets/create/lang/en_us.json +++ b/src/generated/resources/assets/create/lang/en_us.json @@ -204,6 +204,7 @@ "block.create.horizontal_framed_glass_pane": "Horizontal Framed Glass Pane", "block.create.hose_pulley": "Hose Pulley", "block.create.item_drain": "Item Drain", + "block.create.item_vault": "Item Vault", "block.create.jungle_window": "Jungle Window", "block.create.jungle_window_pane": "Jungle Window Pane", "block.create.large_cogwheel": "Large Cogwheel", diff --git a/src/generated/resources/assets/create/lang/unfinished/de_de.json b/src/generated/resources/assets/create/lang/unfinished/de_de.json index 4673277a0..5e5e4edd4 100644 --- a/src/generated/resources/assets/create/lang/unfinished/de_de.json +++ b/src/generated/resources/assets/create/lang/unfinished/de_de.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 1166", + "_": "Missing Localizations: 1167", "_": "->------------------------] Game Elements [------------------------<-", @@ -205,6 +205,7 @@ "block.create.horizontal_framed_glass_pane": "Horizontal Gerahmte Glasscheibe", "block.create.hose_pulley": "Umlenkrolle", "block.create.item_drain": "Abfluss", + "block.create.item_vault": "UNLOCALIZED: Item Vault", "block.create.jungle_window": "Tropenholzfenster", "block.create.jungle_window_pane": "Tropenholzfensterscheibe", "block.create.large_cogwheel": "Großes Zahnrad", diff --git a/src/generated/resources/assets/create/lang/unfinished/es_es.json b/src/generated/resources/assets/create/lang/unfinished/es_es.json index d1fc7718d..7f89c2de6 100644 --- a/src/generated/resources/assets/create/lang/unfinished/es_es.json +++ b/src/generated/resources/assets/create/lang/unfinished/es_es.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 44", + "_": "Missing Localizations: 45", "_": "->------------------------] Game Elements [------------------------<-", @@ -205,6 +205,7 @@ "block.create.horizontal_framed_glass_pane": "Panel de cristal con marco horizontal", "block.create.hose_pulley": "Polea de manguera", "block.create.item_drain": "Drenador de objetos", + "block.create.item_vault": "UNLOCALIZED: Item Vault", "block.create.jungle_window": "Ventana de jungla", "block.create.jungle_window_pane": "Panel de ventana de jungla", "block.create.large_cogwheel": "Engranaje grande", diff --git a/src/generated/resources/assets/create/lang/unfinished/fr_fr.json b/src/generated/resources/assets/create/lang/unfinished/fr_fr.json index a79389d46..d2d5c2ed6 100644 --- a/src/generated/resources/assets/create/lang/unfinished/fr_fr.json +++ b/src/generated/resources/assets/create/lang/unfinished/fr_fr.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 1417", + "_": "Missing Localizations: 1418", "_": "->------------------------] Game Elements [------------------------<-", @@ -205,6 +205,7 @@ "block.create.horizontal_framed_glass_pane": "Vitre encadrée horizontale", "block.create.hose_pulley": "UNLOCALIZED: Hose Pulley", "block.create.item_drain": "UNLOCALIZED: Item Drain", + "block.create.item_vault": "UNLOCALIZED: Item Vault", "block.create.jungle_window": "UNLOCALIZED: Jungle Window", "block.create.jungle_window_pane": "UNLOCALIZED: Jungle Window Pane", "block.create.large_cogwheel": "Grande roue dentée", diff --git a/src/generated/resources/assets/create/lang/unfinished/it_it.json b/src/generated/resources/assets/create/lang/unfinished/it_it.json index 999855572..2127a4e95 100644 --- a/src/generated/resources/assets/create/lang/unfinished/it_it.json +++ b/src/generated/resources/assets/create/lang/unfinished/it_it.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 946", + "_": "Missing Localizations: 947", "_": "->------------------------] Game Elements [------------------------<-", @@ -205,6 +205,7 @@ "block.create.horizontal_framed_glass_pane": "Pannello di finestra di vetro orizzontale", "block.create.hose_pulley": "Carrucola per tubi", "block.create.item_drain": "Drenante di oggetti", + "block.create.item_vault": "UNLOCALIZED: Item Vault", "block.create.jungle_window": "Finestra della giungla", "block.create.jungle_window_pane": "Pannello di finestra della giungla", "block.create.large_cogwheel": "Ruota dentata grande", diff --git a/src/generated/resources/assets/create/lang/unfinished/ja_jp.json b/src/generated/resources/assets/create/lang/unfinished/ja_jp.json index e47915a8e..6d1fb5ddb 100644 --- a/src/generated/resources/assets/create/lang/unfinished/ja_jp.json +++ b/src/generated/resources/assets/create/lang/unfinished/ja_jp.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 53", + "_": "Missing Localizations: 54", "_": "->------------------------] Game Elements [------------------------<-", @@ -205,6 +205,7 @@ "block.create.horizontal_framed_glass_pane": "横型ガラス窓板", "block.create.hose_pulley": "ホースプーリー", "block.create.item_drain": "アイテム排液口", + "block.create.item_vault": "UNLOCALIZED: Item Vault", "block.create.jungle_window": "ジャングルの窓", "block.create.jungle_window_pane": "ジャングルの窓板", "block.create.large_cogwheel": "大きな歯車", diff --git a/src/generated/resources/assets/create/lang/unfinished/ko_kr.json b/src/generated/resources/assets/create/lang/unfinished/ko_kr.json index e3ac9e39b..cbd27c2f1 100644 --- a/src/generated/resources/assets/create/lang/unfinished/ko_kr.json +++ b/src/generated/resources/assets/create/lang/unfinished/ko_kr.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 68", + "_": "Missing Localizations: 69", "_": "->------------------------] Game Elements [------------------------<-", @@ -205,6 +205,7 @@ "block.create.horizontal_framed_glass_pane": "수평 유리판", "block.create.hose_pulley": "호스 도르래", "block.create.item_drain": "아이템 배수구", + "block.create.item_vault": "UNLOCALIZED: Item Vault", "block.create.jungle_window": "정글나무 유리창", "block.create.jungle_window_pane": "정글나무 유리판", "block.create.large_cogwheel": "큰 톱니바퀴", diff --git a/src/generated/resources/assets/create/lang/unfinished/nl_nl.json b/src/generated/resources/assets/create/lang/unfinished/nl_nl.json index bfd5727ea..a2a097461 100644 --- a/src/generated/resources/assets/create/lang/unfinished/nl_nl.json +++ b/src/generated/resources/assets/create/lang/unfinished/nl_nl.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 1795", + "_": "Missing Localizations: 1796", "_": "->------------------------] Game Elements [------------------------<-", @@ -205,6 +205,7 @@ "block.create.horizontal_framed_glass_pane": "UNLOCALIZED: Horizontal Framed Glass Pane", "block.create.hose_pulley": "UNLOCALIZED: Hose Pulley", "block.create.item_drain": "UNLOCALIZED: Item Drain", + "block.create.item_vault": "UNLOCALIZED: Item Vault", "block.create.jungle_window": "UNLOCALIZED: Jungle Window", "block.create.jungle_window_pane": "UNLOCALIZED: Jungle Window Pane", "block.create.large_cogwheel": "Groot Tandwiel", diff --git a/src/generated/resources/assets/create/lang/unfinished/pl_pl.json b/src/generated/resources/assets/create/lang/unfinished/pl_pl.json index b667bc658..6300f5dbe 100644 --- a/src/generated/resources/assets/create/lang/unfinished/pl_pl.json +++ b/src/generated/resources/assets/create/lang/unfinished/pl_pl.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 44", + "_": "Missing Localizations: 45", "_": "->------------------------] Game Elements [------------------------<-", @@ -205,6 +205,7 @@ "block.create.horizontal_framed_glass_pane": "Pozioma oprawiona szyba", "block.create.hose_pulley": "Krążek z wężem", "block.create.item_drain": "Odpływ", + "block.create.item_vault": "UNLOCALIZED: Item Vault", "block.create.jungle_window": "Dżunglowe okno", "block.create.jungle_window_pane": "Dżunglowa szyba okienna", "block.create.large_cogwheel": "Duże koło zębate", diff --git a/src/generated/resources/assets/create/lang/unfinished/pt_br.json b/src/generated/resources/assets/create/lang/unfinished/pt_br.json index d7e3b719f..f3b61d2ba 100644 --- a/src/generated/resources/assets/create/lang/unfinished/pt_br.json +++ b/src/generated/resources/assets/create/lang/unfinished/pt_br.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 1644", + "_": "Missing Localizations: 1645", "_": "->------------------------] Game Elements [------------------------<-", @@ -205,6 +205,7 @@ "block.create.horizontal_framed_glass_pane": "UNLOCALIZED: Horizontal Framed Glass Pane", "block.create.hose_pulley": "Polia de Mangueira", "block.create.item_drain": "Dreno de Item", + "block.create.item_vault": "UNLOCALIZED: Item Vault", "block.create.jungle_window": "UNLOCALIZED: Jungle Window", "block.create.jungle_window_pane": "UNLOCALIZED: Jungle Window Pane", "block.create.large_cogwheel": "Roda Dentada Grande", diff --git a/src/generated/resources/assets/create/lang/unfinished/ru_ru.json b/src/generated/resources/assets/create/lang/unfinished/ru_ru.json index b1a7f9c25..40a193ef1 100644 --- a/src/generated/resources/assets/create/lang/unfinished/ru_ru.json +++ b/src/generated/resources/assets/create/lang/unfinished/ru_ru.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 49", + "_": "Missing Localizations: 50", "_": "->------------------------] Game Elements [------------------------<-", @@ -205,6 +205,7 @@ "block.create.horizontal_framed_glass_pane": "Горизонтальная обрамлённая стеклянная панель", "block.create.hose_pulley": "Шкив со шлангом", "block.create.item_drain": "Предметный осушитель", + "block.create.item_vault": "UNLOCALIZED: Item Vault", "block.create.jungle_window": "Окно из тропического дерева", "block.create.jungle_window_pane": "Панель окна из тропического дерева", "block.create.large_cogwheel": "Большая шестерня", diff --git a/src/generated/resources/assets/create/lang/unfinished/zh_cn.json b/src/generated/resources/assets/create/lang/unfinished/zh_cn.json index ac4106e51..083efbc42 100644 --- a/src/generated/resources/assets/create/lang/unfinished/zh_cn.json +++ b/src/generated/resources/assets/create/lang/unfinished/zh_cn.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 48", + "_": "Missing Localizations: 49", "_": "->------------------------] Game Elements [------------------------<-", @@ -205,6 +205,7 @@ "block.create.horizontal_framed_glass_pane": "竖直边框玻璃板", "block.create.hose_pulley": "软管滑轮", "block.create.item_drain": "分液池", + "block.create.item_vault": "UNLOCALIZED: Item Vault", "block.create.jungle_window": "丛林木窗户", "block.create.jungle_window_pane": "丛林木窗户板", "block.create.large_cogwheel": "大齿轮", diff --git a/src/generated/resources/assets/create/lang/unfinished/zh_tw.json b/src/generated/resources/assets/create/lang/unfinished/zh_tw.json index 181454afc..b9a82e080 100644 --- a/src/generated/resources/assets/create/lang/unfinished/zh_tw.json +++ b/src/generated/resources/assets/create/lang/unfinished/zh_tw.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 63", + "_": "Missing Localizations: 64", "_": "->------------------------] Game Elements [------------------------<-", @@ -205,6 +205,7 @@ "block.create.horizontal_framed_glass_pane": "豎直邊框玻璃片", "block.create.hose_pulley": "軟管滑輪", "block.create.item_drain": "分液池", + "block.create.item_vault": "UNLOCALIZED: Item Vault", "block.create.jungle_window": "叢林木窗戶", "block.create.jungle_window_pane": "叢林木窗戶片", "block.create.large_cogwheel": "大齒輪", diff --git a/src/generated/resources/assets/create/models/item/item_vault.json b/src/generated/resources/assets/create/models/item/item_vault.json new file mode 100644 index 000000000..820969273 --- /dev/null +++ b/src/generated/resources/assets/create/models/item/item_vault.json @@ -0,0 +1,3 @@ +{ + "parent": "create:block/item_vault" +} \ No newline at end of file diff --git a/src/generated/resources/data/create/loot_tables/blocks/item_vault.json b/src/generated/resources/data/create/loot_tables/blocks/item_vault.json new file mode 100644 index 000000000..75cb6ac2c --- /dev/null +++ b/src/generated/resources/data/create/loot_tables/blocks/item_vault.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "rolls": 1.0, + "bonus_rolls": 0.0, + "entries": [ + { + "type": "minecraft:item", + "name": "create:item_vault" + } + ], + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "functions": [] + } + ] +} \ No newline at end of file diff --git a/src/main/java/com/simibubi/create/AllBlocks.java b/src/main/java/com/simibubi/create/AllBlocks.java index 56dad46de..4ff8218a8 100644 --- a/src/main/java/com/simibubi/create/AllBlocks.java +++ b/src/main/java/com/simibubi/create/AllBlocks.java @@ -160,6 +160,9 @@ import com.simibubi.create.content.logistics.block.redstone.RedstoneContactBlock import com.simibubi.create.content.logistics.block.redstone.RedstoneLinkBlock; import com.simibubi.create.content.logistics.block.redstone.RedstoneLinkGenerator; import com.simibubi.create.content.logistics.block.redstone.StockpileSwitchBlock; +import com.simibubi.create.content.logistics.block.vault.VaultBlock; +import com.simibubi.create.content.logistics.block.vault.VaultCTBehaviour; +import com.simibubi.create.content.logistics.block.vault.VaultItem; import com.simibubi.create.content.logistics.item.LecternControllerBlock; import com.simibubi.create.content.schematics.block.SchematicTableBlock; import com.simibubi.create.content.schematics.block.SchematicannonBlock; @@ -181,6 +184,7 @@ import com.tterrag.registrate.providers.RegistrateRecipeProvider; import com.tterrag.registrate.util.entry.BlockEntry; import net.minecraft.client.renderer.RenderType; +import net.minecraft.core.Direction.Axis; import net.minecraft.core.Direction.AxisDirection; import net.minecraft.data.recipes.ShapedRecipeBuilder; import net.minecraft.resources.ResourceLocation; @@ -1241,6 +1245,19 @@ public class AllBlocks { .transform(customItemModel()) .register(); + public static final BlockEntry ITEM_VAULT = REGISTRATE.block("item_vault", VaultBlock::new) + .initialProperties(SharedProperties::softMetal) + .properties(p -> p.sound(SoundType.NETHERITE_BLOCK)) + .blockstate((c, p) -> p.getVariantBuilder(c.get()) + .forAllStates(s -> ConfiguredModel.builder() + .modelFile(AssetLookup.standardModel(c, p)) + .rotationY(s.getValue(VaultBlock.HORIZONTAL_AXIS) == Axis.X ? 90 : 0) + .build())) + .onRegister(connectedTextures(new VaultCTBehaviour())) + .item(VaultItem::new) + .build() + .register(); + public static final BlockEntry ANDESITE_FUNNEL = REGISTRATE.block("andesite_funnel", AndesiteFunnelBlock::new) .initialProperties(SharedProperties::stone) diff --git a/src/main/java/com/simibubi/create/AllSpriteShifts.java b/src/main/java/com/simibubi/create/AllSpriteShifts.java index b4c393dba..598651ed5 100644 --- a/src/main/java/com/simibubi/create/AllSpriteShifts.java +++ b/src/main/java/com/simibubi/create/AllSpriteShifts.java @@ -13,9 +13,11 @@ import com.simibubi.create.content.palettes.PaletteBlockPattern; import com.simibubi.create.content.palettes.PaletteBlockPattern.CTs; import com.simibubi.create.content.palettes.PaletteStoneVariants; import com.simibubi.create.foundation.block.connected.CTSpriteShiftEntry; +import com.simibubi.create.foundation.block.connected.CTSpriteShifter; import com.simibubi.create.foundation.block.connected.CTSpriteShifter.CTType; import com.simibubi.create.foundation.block.render.SpriteShiftEntry; import com.simibubi.create.foundation.block.render.SpriteShifter; +import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Lang; import net.minecraft.world.item.DyeColor; @@ -58,6 +60,9 @@ public class AllSpriteShifts { FLUID_TANK = getCT(CTType.CROSS, "fluid_tank"), CREATIVE_FLUID_TANK = getCT(CTType.CROSS, "creative_fluid_tank"); + public static final Couple VAULT_TOP = vault("top"), VAULT_FRONT = vault("front"), + VAULT_SIDE = vault("side"), VAULT_BOTTOM = vault("bottom"); + public static final SpriteShiftEntry BELT = SpriteShifter.get("block/belt", "block/belt_scroll"), BELT_OFFSET = SpriteShifter.get("block/belt_offset", "block/belt_scroll"), BELT_DIAGONAL = SpriteShifter.get("block/belt_diagonal", "block/belt_diagonal_scroll"), @@ -108,6 +113,12 @@ public class AllSpriteShifts { } } + static Couple vault(String name) { + final String prefixed = "vault_" + name; + return Couple + .createWithContext(b -> getCT(CTSpriteShifter.CTType.CROSS, prefixed, b ? prefixed : prefixed + "_large")); + } + // static CTSpriteShiftEntry omni(String name) { diff --git a/src/main/java/com/simibubi/create/AllTileEntities.java b/src/main/java/com/simibubi/create/AllTileEntities.java index d56938d0f..bed82065c 100644 --- a/src/main/java/com/simibubi/create/AllTileEntities.java +++ b/src/main/java/com/simibubi/create/AllTileEntities.java @@ -163,6 +163,7 @@ import com.simibubi.create.content.logistics.block.redstone.NixieTubeRenderer; import com.simibubi.create.content.logistics.block.redstone.NixieTubeTileEntity; import com.simibubi.create.content.logistics.block.redstone.RedstoneLinkTileEntity; import com.simibubi.create.content.logistics.block.redstone.StockpileSwitchTileEntity; +import com.simibubi.create.content.logistics.block.vault.VaultTileEntity; import com.simibubi.create.content.logistics.item.LecternControllerRenderer; import com.simibubi.create.content.logistics.item.LecternControllerTileEntity; import com.simibubi.create.content.schematics.block.SchematicTableTileEntity; @@ -406,6 +407,11 @@ public class AllTileEntities { .validBlocks(AllBlocks.MECHANICAL_ARM) .renderer(() -> ArmRenderer::new) .register(); + + public static final TileEntityEntry ITEM_VAULT = Create.registrate() + .tileEntity("item_vault", VaultTileEntity::new) + .validBlocks(AllBlocks.ITEM_VAULT) + .register(); public static final TileEntityEntry MECHANICAL_PISTON = Create.registrate() .tileEntity("mechanical_piston", MechanicalPistonTileEntity::new) diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/BlockMovementChecks.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/BlockMovementChecks.java index 5a97f4e4c..d970a152d 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/BlockMovementChecks.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/BlockMovementChecks.java @@ -29,6 +29,8 @@ import com.simibubi.create.content.contraptions.components.structureMovement.pul import com.simibubi.create.content.contraptions.fluids.tank.FluidTankBlock; import com.simibubi.create.content.contraptions.fluids.tank.FluidTankConnectivityHandler; import com.simibubi.create.content.logistics.block.redstone.RedstoneLinkBlock; +import com.simibubi.create.content.logistics.block.vault.VaultBlock; +import com.simibubi.create.content.logistics.block.vault.VaultConnectivityHandler; import com.simibubi.create.foundation.config.ContraptionMovementSetting; import net.minecraft.core.BlockPos; @@ -331,6 +333,8 @@ public class BlockMovementChecks { .getAxis(); if (state.getBlock() instanceof FluidTankBlock) return FluidTankConnectivityHandler.isConnected(world, pos, pos.relative(direction)); + if (state.getBlock() instanceof VaultBlock) + return VaultConnectivityHandler.isConnected(world, pos, pos.relative(direction)); if (AllBlocks.STICKER.has(state) && state.getValue(StickerBlock.EXTENDED)) { return direction == state.getValue(StickerBlock.FACING) && !isNotSupportive(world.getBlockState(pos.relative(direction)), direction.getOpposite()); diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java index 39042c14c..4e9d9eb9d 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java @@ -59,6 +59,7 @@ import com.simibubi.create.content.contraptions.relays.belt.BeltBlock; import com.simibubi.create.content.logistics.block.inventories.AdjustableCrateBlock; import com.simibubi.create.content.logistics.block.inventories.CreativeCrateTileEntity; import com.simibubi.create.content.logistics.block.redstone.RedstoneContactBlock; +import com.simibubi.create.content.logistics.block.vault.VaultTileEntity; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.fluid.CombinedTankWrapper; import com.simibubi.create.foundation.tileEntity.behaviour.filtering.FilteringBehaviour; @@ -656,7 +657,8 @@ public abstract class Contraption { nbt.remove("y"); nbt.remove("z"); - if (tileentity instanceof FluidTankTileEntity && nbt.contains("Controller")) + if ((tileentity instanceof FluidTankTileEntity || tileentity instanceof VaultTileEntity) + && nbt.contains("Controller")) nbt.put("Controller", NbtUtils.writeBlockPos(toLocalPos(NbtUtils.readBlockPos(nbt.getCompound("Controller"))))); diff --git a/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultBlock.java b/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultBlock.java new file mode 100644 index 000000000..6c262ee3d --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultBlock.java @@ -0,0 +1,177 @@ +package com.simibubi.create.content.logistics.block.vault; + +import javax.annotation.Nullable; + +import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.content.contraptions.wrench.IWrenchable; +import com.simibubi.create.foundation.block.ITE; +import com.simibubi.create.foundation.item.ItemHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Direction.AxisDirection; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition.Builder; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraftforge.common.util.ForgeSoundType; +import net.minecraftforge.items.CapabilityItemHandler; + +public class VaultBlock extends Block implements IWrenchable, ITE { + + public static final Property HORIZONTAL_AXIS = BlockStateProperties.HORIZONTAL_AXIS; + public static final BooleanProperty LARGE = BooleanProperty.create("large"); + + public VaultBlock(Properties p_i48440_1_) { + super(p_i48440_1_); + registerDefaultState(defaultBlockState().setValue(LARGE, false)); + } + + @Override + protected void createBlockStateDefinition(Builder pBuilder) { + pBuilder.add(HORIZONTAL_AXIS, LARGE); + super.createBlockStateDefinition(pBuilder); + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext pContext) { + if (pContext.getPlayer() == null || !pContext.getPlayer() + .isSteppingCarefully()) { + BlockState placedOn = pContext.getLevel() + .getBlockState(pContext.getClickedPos() + .relative(pContext.getClickedFace() + .getOpposite())); + Axis preferredAxis = getVaultBlockAxis(placedOn); + if (preferredAxis != null) + return this.defaultBlockState() + .setValue(HORIZONTAL_AXIS, preferredAxis); + } + return this.defaultBlockState() + .setValue(HORIZONTAL_AXIS, pContext.getHorizontalDirection() + .getAxis()); + } + + @Override + public void onPlace(BlockState pState, Level pLevel, BlockPos pPos, BlockState pOldState, boolean pIsMoving) { + if (pOldState.getBlock() == pState.getBlock()) + return; + if (pIsMoving) + return; + withTileEntityDo(pLevel, pPos, VaultTileEntity::updateConnectivity); + } + + @Override + public InteractionResult onWrenched(BlockState state, UseOnContext context) { + if (context.getClickedFace() + .getAxis() + .isVertical()) { + BlockEntity te = context.getLevel() + .getBlockEntity(context.getClickedPos()); + if (te instanceof VaultTileEntity) { + VaultTileEntity vault = (VaultTileEntity) te; + VaultConnectivityHandler.splitVault(vault); + vault.removeController(true); + } + state = state.setValue(LARGE, false); + } + InteractionResult onWrenched = IWrenchable.super.onWrenched(state, context); + return onWrenched; + } + + @Override + public void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean pIsMoving) { + if (state.hasBlockEntity() && (state.getBlock() != newState.getBlock() || !newState.hasBlockEntity())) { + BlockEntity te = world.getBlockEntity(pos); + if (!(te instanceof VaultTileEntity)) + return; + VaultTileEntity tankTE = (VaultTileEntity) te; + ItemHelper.dropContents(world, pos, tankTE.inventory); + world.removeBlockEntity(pos); + VaultConnectivityHandler.splitVault(tankTE); + } + } + + public static boolean isVault(BlockState state) { + return AllBlocks.ITEM_VAULT.has(state); + } + + @Nullable + public static Axis getVaultBlockAxis(BlockState state) { + if (!isVault(state)) + return null; + return state.getValue(HORIZONTAL_AXIS); + } + + public static boolean isLarge(BlockState state) { + if (!isVault(state)) + return false; + return state.getValue(LARGE); + } + + @Override + public BlockState rotate(BlockState state, Rotation rot) { + Axis axis = state.getValue(HORIZONTAL_AXIS); + return state.setValue(HORIZONTAL_AXIS, rot.rotate(Direction.fromAxisAndDirection(axis, AxisDirection.POSITIVE)) + .getAxis()); + } + + @Override + public BlockState mirror(BlockState state, Mirror mirrorIn) { + return state; + } + + // Vaults are less noisy when placed in batch + public static final SoundType SILENCED_METAL = + new ForgeSoundType(0.1F, 1.5F, () -> SoundEvents.NETHERITE_BLOCK_BREAK, () -> SoundEvents.NETHERITE_BLOCK_STEP, + () -> SoundEvents.NETHERITE_BLOCK_PLACE, () -> SoundEvents.NETHERITE_BLOCK_HIT, + () -> SoundEvents.NETHERITE_BLOCK_FALL); + + @Override + public SoundType getSoundType(BlockState state, LevelReader world, BlockPos pos, Entity entity) { + SoundType soundType = super.getSoundType(state, world, pos, entity); + if (entity != null && entity.getPersistentData() + .contains("SilenceVaultSound")) + return SILENCED_METAL; + return soundType; + } + + @Override + public boolean hasAnalogOutputSignal(BlockState p_149740_1_) { + return true; + } + + @Override + public int getAnalogOutputSignal(BlockState pState, Level pLevel, BlockPos pPos) { + return getTileEntityOptional(pLevel, pPos) + .map(vte -> vte.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY)) + .map(lo -> lo.map(ItemHelper::calcRedstoneFromInventory) + .orElse(0)) + .orElse(0); + } + + @Override + public BlockEntityType getTileEntityType() { + return AllTileEntities.ITEM_VAULT.get(); + } + + @Override + public Class getTileEntityClass() { + return VaultTileEntity.class; + } +} diff --git a/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultCTBehaviour.java b/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultCTBehaviour.java new file mode 100644 index 000000000..61bcd4941 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultCTBehaviour.java @@ -0,0 +1,68 @@ +package com.simibubi.create.content.logistics.block.vault; + +import com.simibubi.create.AllSpriteShifts; +import com.simibubi.create.foundation.block.connected.CTSpriteShiftEntry; +import com.simibubi.create.foundation.block.connected.ConnectedTextureBehaviour; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Direction.AxisDirection; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.block.state.BlockState; + +public class VaultCTBehaviour extends ConnectedTextureBehaviour { + + @Override + public CTSpriteShiftEntry get(BlockState state, Direction direction) { + Axis vaultBlockAxis = VaultBlock.getVaultBlockAxis(state); + boolean small = !VaultBlock.isLarge(state); + if (vaultBlockAxis == null) + return null; + + if (direction.getAxis() == vaultBlockAxis) + return AllSpriteShifts.VAULT_FRONT.get(small); + if (direction == Direction.UP) + return AllSpriteShifts.VAULT_TOP.get(small); + if (direction == Direction.DOWN) + return AllSpriteShifts.VAULT_BOTTOM.get(small); + + return AllSpriteShifts.VAULT_SIDE.get(small); + } + + @Override + protected Direction getUpDirection(BlockAndTintGetter reader, BlockPos pos, BlockState state, Direction face) { + Axis vaultBlockAxis = VaultBlock.getVaultBlockAxis(state); + boolean alongX = vaultBlockAxis == Axis.X; + if (face.getAxis() + .isVertical() && alongX) + return super.getUpDirection(reader, pos, state, face).getClockWise(); + if (face.getAxis() == vaultBlockAxis || face.getAxis() + .isVertical()) + return super.getUpDirection(reader, pos, state, face); + return Direction.fromAxisAndDirection(vaultBlockAxis, alongX ? AxisDirection.POSITIVE : AxisDirection.NEGATIVE); + } + + @Override + protected Direction getRightDirection(BlockAndTintGetter reader, BlockPos pos, BlockState state, Direction face) { + Axis vaultBlockAxis = VaultBlock.getVaultBlockAxis(state); + if (face.getAxis() + .isVertical() && vaultBlockAxis == Axis.X) + return super.getRightDirection(reader, pos, state, face).getClockWise(); + if (face.getAxis() == vaultBlockAxis || face.getAxis() + .isVertical()) + return super.getRightDirection(reader, pos, state, face); + return Direction.fromAxisAndDirection(Axis.Y, face.getAxisDirection()); + } + + public boolean buildContextForOccludedDirections() { + return super.buildContextForOccludedDirections(); + } + + @Override + public boolean connectsTo(BlockState state, BlockState other, BlockAndTintGetter reader, BlockPos pos, + BlockPos otherPos, Direction face) { + return state == other && VaultConnectivityHandler.isConnected(reader, pos, otherPos); + } + +} diff --git a/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultConnectivityHandler.java b/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultConnectivityHandler.java new file mode 100644 index 000000000..9a673537d --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultConnectivityHandler.java @@ -0,0 +1,329 @@ +package com.simibubi.create.content.logistics.block.vault; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.Set; + +import javax.annotation.Nullable; + +import org.apache.commons.lang3.tuple.Pair; + +import com.simibubi.create.foundation.utility.Iterate; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Direction.AxisDirection; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +public class VaultConnectivityHandler { + + public static void formVaults(VaultTileEntity te) { + VaultSearchCache cache = new VaultSearchCache(); + List frontier = new ArrayList<>(); + frontier.add(te); + formVaults(te.getType(), te.getLevel(), cache, frontier); + } + + private static void formVaults(BlockEntityType type, BlockGetter world, VaultSearchCache cache, + List frontier) { + PriorityQueue> creationQueue = makeCreationQueue(); + Set visited = new HashSet<>(); + + int minY = Integer.MAX_VALUE; + for (VaultTileEntity fluidTankTileEntity : frontier) { + BlockPos pos = fluidTankTileEntity.getBlockPos(); + minY = Math.min(pos.getY(), minY); + } + + minY -= 3; + + while (!frontier.isEmpty()) { + VaultTileEntity tank = frontier.remove(0); + BlockPos tankPos = tank.getBlockPos(); + if (visited.contains(tankPos)) + continue; + + visited.add(tankPos); + + int amount = tryToFormNewVault(tank, cache, true); + if (amount > 1) + creationQueue.add(Pair.of(amount, tank)); + + for (Axis axis : Iterate.axes) { + Direction d = Direction.fromAxisAndDirection(axis, AxisDirection.NEGATIVE); + BlockPos next = tankPos.relative(d); + + if (next.getY() <= minY) + continue; + if (visited.contains(next)) + continue; + VaultTileEntity nextTank = vaultAt(type, world, next); + if (nextTank == null) + continue; + if (nextTank.isRemoved()) + continue; + frontier.add(nextTank); + } + } + + visited.clear(); + + while (!creationQueue.isEmpty()) { + Pair next = creationQueue.poll(); + VaultTileEntity toCreate = next.getValue(); + if (visited.contains(toCreate.getBlockPos())) + continue; + visited.add(toCreate.getBlockPos()); + tryToFormNewVault(toCreate, cache, false); + } + + } + + public static void splitVault(VaultTileEntity te) { + splitVaultAndInvalidate(te, null, false); + } + + private static int tryToFormNewVault(VaultTileEntity te, VaultSearchCache cache, boolean simulate) { + int bestWidth = 1; + int bestAmount = -1; + + if (!te.isController()) + return 0; + + for (int w = 1; w <= 3; w++) { + int amount = tryToFormNewVaultOfRadius(te, w, cache, true); + if (amount < bestAmount) + continue; + bestWidth = w; + bestAmount = amount; + } + + if (!simulate) { + if (te.radius == bestWidth && te.radius * te.radius * te.length == bestAmount) + return bestAmount; + + splitVaultAndInvalidate(te, cache, false); + tryToFormNewVaultOfRadius(te, bestWidth, cache, simulate); + te.updateConnectivity = false; + te.radius = bestWidth; + te.length = bestAmount / bestWidth / bestWidth; + + BlockState state = te.getBlockState(); + if (VaultBlock.isVault(state)) + te.getLevel() + .setBlock(te.getBlockPos(), state.setValue(VaultBlock.LARGE, te.radius > 2), 22); + + te.itemCapability.invalidate(); + te.setChanged(); + } + + return bestAmount; + } + + private static int tryToFormNewVaultOfRadius(VaultTileEntity te, int width, VaultSearchCache cache, + boolean simulate) { + int amount = 0; + int height = 0; + BlockEntityType type = te.getType(); + Level world = te.getLevel(); + BlockPos origin = te.getBlockPos(); + boolean alongZ = VaultBlock.getVaultBlockAxis(te.getBlockState()) == Axis.Z; + + Search: + + for (int yOffset = 0; yOffset < VaultTileEntity.getMaxLength(width); yOffset++) { + for (int xOffset = 0; xOffset < width; xOffset++) { + for (int zOffset = 0; zOffset < width; zOffset++) { + + BlockPos pos = + alongZ ? origin.offset(xOffset, zOffset, yOffset) : origin.offset(yOffset, xOffset, zOffset); + Optional tank = cache.getOrCache(type, world, pos); + if (!tank.isPresent()) + break Search; + + VaultTileEntity controller = tank.get(); + int otherWidth = controller.radius; + if (otherWidth > width) + break Search; + if (otherWidth == width && controller.length == VaultTileEntity.getMaxLength(width)) + break Search; + if ((VaultBlock.getVaultBlockAxis(controller.getBlockState()) == Axis.Z) != alongZ) + break Search; + + BlockPos controllerPos = controller.getBlockPos(); + if (!controllerPos.equals(origin)) { + if (alongZ && controllerPos.getX() < origin.getX()) + break Search; + if (controllerPos.getY() < origin.getY()) + break Search; + if (!alongZ && controllerPos.getZ() < origin.getZ()) + break Search; + if (alongZ && controllerPos.getX() + otherWidth > origin.getX() + width) + break Search; + if (controllerPos.getY() + otherWidth > origin.getY() + width) + break Search; + if (!alongZ && controllerPos.getZ() + otherWidth > origin.getZ() + width) + break Search; + } + + } + } + + amount += width * width; + height++; + } + + if (simulate) + return amount; + + for (int yOffset = 0; yOffset < height; yOffset++) { + for (int xOffset = 0; xOffset < width; xOffset++) { + for (int zOffset = 0; zOffset < width; zOffset++) { + BlockPos pos = + alongZ ? origin.offset(xOffset, zOffset, yOffset) : origin.offset(yOffset, xOffset, zOffset); + VaultTileEntity tank = vaultAt(type, world, pos); + if (tank == te) + continue; + + splitVaultAndInvalidate(tank, cache, false); + tank.setController(origin); + tank.updateConnectivity = false; + cache.put(pos, te); + + BlockState state = world.getBlockState(pos); + if (!VaultBlock.isVault(state)) + continue; + state = state.setValue(VaultBlock.LARGE, width > 2); + world.setBlock(pos, state, 22); + } + } + } + + return amount; + } + + private static void splitVaultAndInvalidate(VaultTileEntity te, @Nullable VaultSearchCache cache, + boolean tryReconnect) { + // tryReconnect helps whenever only few tanks have been removed + + te = te.getControllerTE(); + if (te == null) + return; + + int height = te.length; + int width = te.radius; + BlockState state = te.getBlockState(); + boolean alongZ = VaultBlock.getVaultBlockAxis(state) == Axis.Z; + if (width == 1 && height == 1) + return; + + Level world = te.getLevel(); + BlockPos origin = te.getBlockPos(); + List frontier = new ArrayList<>(); + + for (int yOffset = 0; yOffset < height; yOffset++) { + for (int xOffset = 0; xOffset < width; xOffset++) { + for (int zOffset = 0; zOffset < width; zOffset++) { + + BlockPos pos = + alongZ ? origin.offset(xOffset, zOffset, yOffset) : origin.offset(yOffset, xOffset, zOffset); + VaultTileEntity tankAt = vaultAt(te.getType(), world, pos); + if (tankAt == null) + continue; + if (!tankAt.getController() + .equals(origin)) + continue; + + tankAt.removeController(true); + + if (tryReconnect) { + frontier.add(tankAt); + tankAt.updateConnectivity = false; + } + if (cache != null) + cache.put(pos, tankAt); + } + } + } + + te.itemCapability.invalidate(); + if (tryReconnect) + formVaults(te.getType(), world, cache == null ? new VaultSearchCache() : cache, frontier); + } + + private static PriorityQueue> makeCreationQueue() { + return new PriorityQueue<>(new Comparator>() { + @Override + public int compare(Pair o1, Pair o2) { + return o2.getKey() - o1.getKey(); + } + }); + } + + @Nullable + public static VaultTileEntity vaultAt(BlockEntityType type, BlockGetter world, BlockPos pos) { + BlockEntity te = world.getBlockEntity(pos); + if (te instanceof VaultTileEntity && te.getType() == type) + return (VaultTileEntity) te; + return null; + } + + private static class VaultSearchCache { + Map> controllerMap; + + public VaultSearchCache() { + controllerMap = new HashMap<>(); + } + + void put(BlockPos pos, VaultTileEntity target) { + controllerMap.put(pos, Optional.of(target)); + } + + void putEmpty(BlockPos pos) { + controllerMap.put(pos, Optional.empty()); + } + + boolean hasVisited(BlockPos pos) { + return controllerMap.containsKey(pos); + } + + Optional getOrCache(BlockEntityType type, BlockGetter world, BlockPos pos) { + if (hasVisited(pos)) + return controllerMap.get(pos); + VaultTileEntity tankAt = vaultAt(type, world, pos); + if (tankAt == null) { + putEmpty(pos); + return Optional.empty(); + } + VaultTileEntity controller = tankAt.getControllerTE(); + if (controller == null) { + putEmpty(pos); + return Optional.empty(); + } + put(pos, controller); + return Optional.of(controller); + } + + } + + public static boolean isConnected(BlockGetter world, BlockPos tankPos, BlockPos otherTankPos) { + BlockEntity te1 = world.getBlockEntity(tankPos); + BlockEntity te2 = world.getBlockEntity(otherTankPos); + if (!(te1 instanceof VaultTileEntity) || !(te2 instanceof VaultTileEntity)) + return false; + return ((VaultTileEntity) te1).getController() + .equals(((VaultTileEntity) te2).getController()); + } + +} diff --git a/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultItem.java b/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultItem.java new file mode 100644 index 000000000..ae602deca --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultItem.java @@ -0,0 +1,128 @@ +package com.simibubi.create.content.logistics.block.vault; + +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.foundation.utility.VecHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Direction.AxisDirection; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +public class VaultItem extends BlockItem { + + public VaultItem(Block p_i48527_1_, Properties p_i48527_2_) { + super(p_i48527_1_, p_i48527_2_); + } + + @Override + public InteractionResult place(BlockPlaceContext ctx) { + InteractionResult initialResult = super.place(ctx); + if (!initialResult.consumesAction()) + return initialResult; + tryMultiPlace(ctx); + return initialResult; + } + + @Override + protected boolean updateCustomBlockEntityTag(BlockPos p_195943_1_, Level p_195943_2_, Player p_195943_3_, + ItemStack p_195943_4_, BlockState p_195943_5_) { + MinecraftServer minecraftserver = p_195943_2_.getServer(); + if (minecraftserver == null) + return false; + CompoundTag nbt = p_195943_4_.getTagElement("BlockEntityTag"); + if (nbt != null) { + nbt.remove("Length"); + nbt.remove("Size"); + nbt.remove("Controller"); + nbt.remove("LastKnownPos"); + } + return super.updateCustomBlockEntityTag(p_195943_1_, p_195943_2_, p_195943_3_, p_195943_4_, p_195943_5_); + } + + private void tryMultiPlace(BlockPlaceContext ctx) { + Player player = ctx.getPlayer(); + if (player == null) + return; + if (player.isSteppingCarefully()) + return; + Direction face = ctx.getClickedFace(); + ItemStack stack = ctx.getItemInHand(); + Level world = ctx.getLevel(); + BlockPos pos = ctx.getClickedPos(); + BlockPos placedOnPos = pos.relative(face.getOpposite()); + BlockState placedOnState = world.getBlockState(placedOnPos); + + if (!VaultBlock.isVault(placedOnState)) + return; + VaultTileEntity tankAt = VaultConnectivityHandler.vaultAt(AllTileEntities.ITEM_VAULT.get(), world, placedOnPos); + if (tankAt == null) + return; + VaultTileEntity controllerTE = tankAt.getControllerTE(); + if (controllerTE == null) + return; + + int width = controllerTE.radius; + if (width == 1) + return; + + int tanksToPlace = 0; + Axis vaultBlockAxis = VaultBlock.getVaultBlockAxis(placedOnState); + if (vaultBlockAxis == null) + return; + if (face.getAxis() != vaultBlockAxis) + return; + + Direction vaultFacing = Direction.fromAxisAndDirection(vaultBlockAxis, AxisDirection.POSITIVE); + BlockPos startPos = face == vaultFacing.getOpposite() ? controllerTE.getBlockPos() + .relative(vaultFacing.getOpposite()) + : controllerTE.getBlockPos() + .relative(vaultFacing, controllerTE.length); + + if (VecHelper.getCoordinate(startPos, vaultBlockAxis) != VecHelper.getCoordinate(pos, vaultBlockAxis)) + return; + + for (int xOffset = 0; xOffset < width; xOffset++) { + for (int zOffset = 0; zOffset < width; zOffset++) { + BlockPos offsetPos = vaultBlockAxis == Axis.X ? startPos.offset(0, xOffset, zOffset) + : startPos.offset(xOffset, zOffset, 0); + BlockState blockState = world.getBlockState(offsetPos); + if (VaultBlock.isVault(blockState)) + continue; + if (!blockState.getMaterial() + .isReplaceable()) + return; + tanksToPlace++; + } + } + + if (!player.isCreative() && stack.getCount() < tanksToPlace) + return; + + for (int xOffset = 0; xOffset < width; xOffset++) { + for (int zOffset = 0; zOffset < width; zOffset++) { + BlockPos offsetPos = vaultBlockAxis == Axis.X ? startPos.offset(0, xOffset, zOffset) + : startPos.offset(xOffset, zOffset, 0); + BlockState blockState = world.getBlockState(offsetPos); + if (VaultBlock.isVault(blockState)) + continue; + BlockPlaceContext context = BlockPlaceContext.at(ctx, offsetPos, face); + player.getPersistentData() + .putBoolean("SilenceVaultSound", true); + super.place(context); + player.getPersistentData() + .remove("SilenceVaultSound"); + } + } + } + +} diff --git a/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultTileEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultTileEntity.java new file mode 100644 index 000000000..59fb23e0d --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/block/vault/VaultTileEntity.java @@ -0,0 +1,245 @@ +package com.simibubi.create.content.logistics.block.vault; + +import java.util.List; + +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.foundation.config.AllConfigs; +import com.simibubi.create.foundation.tileEntity.SmartTileEntity; +import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.IItemHandlerModifiable; +import net.minecraftforge.items.ItemStackHandler; +import net.minecraftforge.items.wrapper.CombinedInvWrapper; + +public class VaultTileEntity extends SmartTileEntity { + + protected LazyOptional itemCapability; + + protected ItemStackHandler inventory; + protected BlockPos controller; + protected BlockPos lastKnownPos; + protected boolean updateConnectivity; + protected int radius; + protected int length; + protected Axis axis; + + public VaultTileEntity(BlockEntityType tileEntityTypeIn, BlockPos pos, BlockState state) { + super(tileEntityTypeIn, pos, state); + + inventory = new ItemStackHandler(AllConfigs.SERVER.logistics.vaultCapacity.get()) { + @Override + protected void onContentsChanged(int slot) { + super.onContentsChanged(slot); + updateComparators(); + } + }; + + itemCapability = LazyOptional.empty(); + radius = 1; + length = 1; + } + + @Override + public void addBehaviours(List behaviours) {} + + protected void updateConnectivity() { + updateConnectivity = false; + if (level.isClientSide()) + return; + if (!isController()) + return; + VaultConnectivityHandler.formVaults(this); + } + + protected void updateComparators() { + VaultTileEntity controllerTE = getControllerTE(); + if (controllerTE == null) + return; + + BlockPos pos = controllerTE.getBlockPos(); + for (int y = 0; y < controllerTE.radius; y++) { + for (int z = 0; z < (controllerTE.axis == Axis.X ? controllerTE.radius : controllerTE.length); z++) { + for (int x = 0; x < (controllerTE.axis == Axis.Z ? controllerTE.radius : controllerTE.length); x++) { + level.updateNeighbourForOutputSignal(pos.offset(x, y, z), getBlockState().getBlock()); + } + } + } + } + + @Override + public void tick() { + super.tick(); + + if (lastKnownPos == null) + lastKnownPos = getBlockPos(); + else if (!lastKnownPos.equals(worldPosition) && worldPosition != null) { + onPositionChanged(); + return; + } + + if (updateConnectivity) + updateConnectivity(); + } + + public boolean isController() { + return controller == null || worldPosition.getX() == controller.getX() + && worldPosition.getY() == controller.getY() && worldPosition.getZ() == controller.getZ(); + } + + private void onPositionChanged() { + removeController(true); + lastKnownPos = worldPosition; + } + + public VaultTileEntity getControllerTE() { + if (isController()) + return this; + BlockEntity tileEntity = level.getBlockEntity(controller); + if (tileEntity instanceof VaultTileEntity) + return (VaultTileEntity) tileEntity; + return null; + } + + public void removeController(boolean keepContents) { + if (level.isClientSide()) + return; + updateConnectivity = true; + controller = null; + radius = 1; + length = 1; + + BlockState state = getBlockState(); + if (VaultBlock.isVault(state)) { + state = state.setValue(VaultBlock.LARGE, false); + getLevel().setBlock(worldPosition, state, 22); + } + + itemCapability.invalidate(); + setChanged(); + sendData(); + } + + public void setController(BlockPos controller) { + if (level.isClientSide()) + return; + if (controller.equals(this.controller)) + return; + this.controller = controller; + itemCapability.invalidate(); + setChanged(); + sendData(); + } + + public BlockPos getController() { + return isController() ? worldPosition : controller; + } + + @Override + protected void fromTag(CompoundTag compound, boolean clientPacket) { + super.fromTag(compound, clientPacket); + + BlockPos controllerBefore = controller; + int prevSize = radius; + int prevLength = length; + + updateConnectivity = compound.contains("Uninitialized"); + controller = null; + lastKnownPos = null; + + if (compound.contains("LastKnownPos")) + lastKnownPos = NbtUtils.readBlockPos(compound.getCompound("LastKnownPos")); + if (compound.contains("Controller")) + controller = NbtUtils.readBlockPos(compound.getCompound("Controller")); + + if (isController()) { + radius = compound.getInt("Size"); + length = compound.getInt("Length"); + } + + if (!clientPacket) { + inventory.deserializeNBT(compound.getCompound("Inventory")); + return; + } + + boolean changeOfController = + controllerBefore == null ? controller != null : !controllerBefore.equals(controller); + if (hasLevel() && (changeOfController || prevSize != radius || prevLength != length)) + level.setBlocksDirty(getBlockPos(), Blocks.AIR.defaultBlockState(), getBlockState()); + } + + @Override + protected void write(CompoundTag compound, boolean clientPacket) { + if (updateConnectivity) + compound.putBoolean("Uninitialized", true); + if (lastKnownPos != null) + compound.put("LastKnownPos", NbtUtils.writeBlockPos(lastKnownPos)); + if (!isController()) + compound.put("Controller", NbtUtils.writeBlockPos(controller)); + if (isController()) { + compound.putInt("Size", radius); + compound.putInt("Length", length); + } + + super.write(compound, clientPacket); + + if (!clientPacket) + compound.put("Inventory", inventory.serializeNBT()); + } + + @Override + public LazyOptional getCapability(Capability cap, Direction side) { + if (isItemHandlerCap(cap)) { + initCapability(); + return itemCapability.cast(); + } + return super.getCapability(cap, side); + } + + private void initCapability() { + if (itemCapability.isPresent()) + return; + if (!isController()) { + VaultTileEntity controllerTE = getControllerTE(); + if (controllerTE == null) + return; + controllerTE.initCapability(); + itemCapability = controllerTE.itemCapability; + return; + } + + boolean alongZ = VaultBlock.getVaultBlockAxis(getBlockState()) == Axis.Z; + IItemHandlerModifiable[] invs = new IItemHandlerModifiable[length * radius * radius]; + for (int yOffset = 0; yOffset < length; yOffset++) { + for (int xOffset = 0; xOffset < radius; xOffset++) { + for (int zOffset = 0; zOffset < radius; zOffset++) { + BlockPos vaultPos = alongZ ? worldPosition.offset(xOffset, zOffset, yOffset) + : worldPosition.offset(yOffset, xOffset, zOffset); + VaultTileEntity vaultAt = + VaultConnectivityHandler.vaultAt(AllTileEntities.ITEM_VAULT.get(), level, vaultPos); + invs[yOffset * radius * radius + xOffset * radius + zOffset] = + vaultAt != null ? vaultAt.inventory : new ItemStackHandler(); + } + } + } + + CombinedInvWrapper combinedInvWrapper = new CombinedInvWrapper(invs); + itemCapability = LazyOptional.of(() -> combinedInvWrapper); + } + + public static int getMaxLength(int radius) { + return radius * 3; + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/config/CLogistics.java b/src/main/java/com/simibubi/create/foundation/config/CLogistics.java index 8f299b039..17ef5f196 100644 --- a/src/main/java/com/simibubi/create/foundation/config/CLogistics.java +++ b/src/main/java/com/simibubi/create/foundation/config/CLogistics.java @@ -7,6 +7,7 @@ public class CLogistics extends ConfigBase { public final ConfigInt psiTimeout = i(20, 1, "psiTimeout", Comments.psiTimeout); public final ConfigInt mechanicalArmRange = i(5, 1, "mechanicalArmRange", Comments.mechanicalArmRange); public final ConfigInt linkRange = i(128, 1, "linkRange", Comments.linkRange); + public final ConfigInt vaultCapacity = i(27, 1, "vaultCapacity", Comments.vaultCapacity); @Override public String getName() { @@ -22,6 +23,7 @@ public class CLogistics extends ConfigBase { static String psiTimeout = "The amount of ticks a portable storage interface waits for transfers until letting contraptions move along."; static String mechanicalArmRange = "Maximum distance in blocks a Mechanical Arm can reach across."; + static String vaultCapacity = "The total amount of stacks a vault can hold per block in size."; } } diff --git a/src/main/java/com/simibubi/create/foundation/utility/Couple.java b/src/main/java/com/simibubi/create/foundation/utility/Couple.java index 775b74d99..dc27e3b13 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/Couple.java +++ b/src/main/java/com/simibubi/create/foundation/utility/Couple.java @@ -30,6 +30,10 @@ public class Couple extends Pair implements Iterable { return new Couple<>(factory.get(), factory.get()); } + public static Couple createWithContext(Function factory) { + return new Couple<>(factory.apply(true), factory.apply(false)); + } + public T get(boolean first) { return first ? getFirst() : getSecond(); } diff --git a/src/main/resources/assets/create/models/block/item_vault.json b/src/main/resources/assets/create/models/block/item_vault.json new file mode 100644 index 000000000..a89cecfa0 --- /dev/null +++ b/src/main/resources/assets/create/models/block/item_vault.json @@ -0,0 +1,32 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "0": "create:block/vault_bottom", + "1": "create:block/vault_front", + "2": "create:block/vault_side", + "3": "create:block/vault_top", + "particle": "create:block/vault_bottom" + }, + "elements": [ + { + "from": [0, 0, 0], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 0, 16, 16], "texture": "#1"}, + "east": {"uv": [0, 0, 16, 16], "rotation": 90, "texture": "#2"}, + "south": {"uv": [0, 0, 16, 16], "texture": "#1"}, + "west": {"uv": [0, 16, 16, 0], "rotation": 90, "texture": "#2"}, + "up": {"uv": [0, 0, 16, 16], "texture": "#3"}, + "down": {"uv": [0, 0, 16, 16], "texture": "#0"} + } + } + ], + "display": { + "gui": { + "rotation": [ 30, 135, 0 ], + "translation": [ 0, 0, 0], + "scale":[ 0.625, 0.625, 0.625 ] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/create/textures/block/vault_bottom.png b/src/main/resources/assets/create/textures/block/vault_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..da7743376594c6d39044592b797e594ca211a2f4 GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;jxQ zi(`m|VDgsoO@GfbusAvG;dy_rmhG~FogPCl^CHIv&go7`Mdl)aB`Pa|CPT&|rXX6tG zh9!J#0Xz(xO*zL{H=InAXz(`H`xwZ)V#0(K@*N9A+PM=ZI~Ou)G&f9o?Z3=lBCX*_ lf~VxPi4I*7>^A0140q>QeGa&NTM_7722WQ%mvv4FO#m;~Rfhlo literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/block/vault_bottom_connected.png b/src/main/resources/assets/create/textures/block/vault_bottom_connected.png new file mode 100644 index 0000000000000000000000000000000000000000..11d7b06afee1ad0264576cef9006b5e0d21e8610 GIT binary patch literal 1322 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 T!AZLK00000NkvXXu0mjfqs+&j literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/block/vault_front_connected.png b/src/main/resources/assets/create/textures/block/vault_front_connected.png new file mode 100644 index 0000000000000000000000000000000000000000..a14c0ef875413e1b1ea91d6c049e09954f6c37e2 GIT binary patch literal 2006 zcmV;{2Pyc8P)|u@T8dCgL?v28QTcJG zDo&`!-uh?t#-WEES_xGx60~Zeqcys_I(pE>oqr>9f8JG0W$k&;ZdM;7z*8XwH{%fq<`f-9FR(4wy-!DIXY=DigPfa0-%;2{HHJ0TO14H|j0Jd$Y z(HmMR3AM+}1AI2yS2fJtsSa5!y%5GMoSB-H?vLD(p{!No1lBxAXix1^44-oJD}b+O{)HGOWx3W?L(3we$uJAOM}|vbyg{@1?jZ z-ccF=WK?ad3H4?l^>>5@xYM6VVWj{}a^~a_C4yui=JVYx!I07SldH%Qp>*{FtaS$xyTyM8u7YVlY8?;~|$uVO_K|-V6 zAltQioP&ep@Id|%)Y8&$0O}MQxj|ri(hI$Bg*+p?5;V%K#eRFRM9BDitfkbT9 zE5V`;)7HS+H0sa=2NAf-2pd8Z5Cj>_gA4!*$}w=rZ%)iKgaJEJXLm|Lg9JQQ8yf1Q zMy1dM*giS)R_1_rsi;=sDWhLX5&Z*urrT=z;b_k z>9UM{JS;2a+1|G=tFs>*=!v79zVXYHujCfTCO|3JqpL6L8{KzjvS+_0?F!89WRS+tlNSc}bPQ)A(Uo1owA4UXb(i)xRZo z=$_2|R}^@mi_aWb-5HGCujhbG|r%YWAWE@z#hl8HzY#WoApOxdkbrTd23X*1djH-d!Gpl9u%p9yqTEVZH*6eQ0~#Fw zXCC$KaW%Qtvd{!rU|W40Gml+?vv2DId@XHobuvCV z_3PN)8c&BN;HwKKbuH4734R)2?RqD87)YGx_Ow;jey}a_PpYlUeowr38=!-miV zV2>9(n}mhRZz+B)-b46;Iti#ef1(F?y96k&)3hORXadGQIILUX)$4bpcfWT&EeG5I zT>0&`4wy0Azz^h*zEh5t$G1v zn9<%o=hIu@Y{TWk*z+J*q;b7vS5;|?OlSh^AhfEAb8pZxV&(G>jiF& z^~$#+4^4pm*!rt=;eHKyBjDK{;Q0p){!f7}GV8!|gvV$-NR~AlhbDkl9s{7K=HeqY z_v;`joP8QaJM(ma&^O2c_^88%{|7Yl0g!;>?)v(=Bz9H0glueVL@r)9tueq`yMQzd z9fIEUls}I1{>y^97(qH1NCe|x`F8T5HNbu>kx0nO%5z_YLlRW`+NBA>c>#fTf@xTG zUbkcRgXuM z{>ETOOf9f7o331(&PZqWfGM(f2}Zn)WF!Otz(W4Hz%e2TxBi-xTQVtwcPITjf$pr^ zzi9_>rpIhnwz?DU@85ag-2qH8yCKHa84ozT`~(S^ z?67AK<~`4`L&nbasRpbSO1@Ksj-qgkA|U`808bBUUn>Mjsa1zElEA#Z8^9SO3efBu zacBZy<*}D@Kc{=V9ncsEw$NP(HyqpxA|TH+fb+gU3nO}kCcusZRv&8?nm{6K`BrIq zdv2DUAHf;7;3?axzedVK6TlN3GyqEh@!2!SeNT?|AvhQsLHP7EhCCzztn>A!{bo^u z_B-_wp$R}|B*AvfI{?PEOw;8%giO}`9p9D%Y!tq}?K4it48#n?48#n?48#n?48#n? o48#n?48#n?48#n?43L5U0l_)dj$)0S%K!iX07*qoM6N<$f@itFR{#J2 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/block/vault_front_large_connected.png b/src/main/resources/assets/create/textures/block/vault_front_large_connected.png new file mode 100644 index 0000000000000000000000000000000000000000..bd9f872bdee88f8ad758762c4e477153bbebc832 GIT binary patch literal 2546 zcmVH+cNfW!rH1e~c*TXavOD&8o}cIIneo^g=enhfKo@~70$l{U2y_wXBG5&ki$E8FE&^Qyx(IX; z_`e}=U8=%QKl|kJ`E!4)In2{fJ?!59@C&zibupKF>alS*SQ-lL%X2e<#_gFaOKzxt zn}e#%&d;gpW@hHy(9nSUtG>te^!4X_D)q%Hp}qKL)je`#)P3~H_pY~ZAnZLvtpQSpkA^0*#-ac?l97@goVF*HB=J_VlvLf>V?p>E};Ho0Hmk}gleU& zsf%yrFz;(inSV=pnJsdfu$}>I-iPnY3aT@ina8Vc$I+bS&PjCVF3S!hLVb{2jt*W&E zRv+bM1#*GxDL;;hS;+v%+%-6q)qVNJV-AEzc9q?^OVzylhTg`i!TfS^D(`b{%FWMR z%G>2Vz4=N?8LVJ#ZmMbZc>oKb00dTK0f8?oqOJlk$pB!|0zvfauTHv8Ym;u@NZI{z zE>k{MT>Co{R|IEb1EpPVw>N~_{1*V8I{QNausT=MyaUZ#i2R{vm9i)=z~uLUrWj%e zN(SKQk-LK_YOB-WOptBrw~NgtPd{#<7!W5g05a5H4~a{8&uLfvwHA_=%hOr1q#Z%; zf$~he;|NSY`xwHUV4@|Ace$Ri&|OL0WJoC)sN#qG5}VwwI)d)E6=WA zS3iyc$YBNA!=(`XBBkKRC05+VC+h<6lL2_{*pb2@s7ax$^>-;xfFz^aKH&FkpT`Rb z<@0(T00>qbW+)Z}_`g@baHW;}!75&~+F^j`D;a>W$D4gV8b35=n={bBLwU+wd+YA> zg$xAR0AXVSJS8%+3MmSoUFfspKjt7HPqEQn$Ly@9@eM!uKH&A&UR}nNKKytxADP@h zcK~AA3BhB9hDUD7&-9X0e4s7Mfl{Q1Oq#ZG1wf$aJ?tW%AFqTspg<}Bl{8)7^J}^b zgtwY6<@&Ic7*V&r$0Yf1mr%ZanC?6sdM zUY_p2aagONL8UbOb-~~+!56K%;sS>qt)#}aR4WQ^Y=5IiuF>H5MesL56s^Ac0E4@P z?+9o%jGC0?Y0u^iyX2Cm8@mc~*WS z1?8nUb*;XFqQGG+J88fuN_Q0?c}~j85@_>W5pZ&cj@$<{e6;y!3@G>BI~oEalp^J+ z&(8jypXzkR4>Eq`{#v=zlrr#D1O(K^0AwXEJ|M~skk_;9G-1otpq-?$29hL9`_s24 z9r;3Ba0?>&%#|#kx2q^wj>IFovfJb4U7+rt&F&A7wdPMdb^&-rCD{dv28Bm{fYMw& zAH4IfaIk45t8K1Tf8*lxd^j#Q&vtaUYHJUG!+nC#fN-Y@=(Q(7zoZ5aZw=c2mnP2>>MhE zJb=6Vk>S0$uWKThbG(Jp$7&J0I5V*FSd#7pE;=qz7Xk>%7I&A&!Z=b|Am}}c3ks0h zSriL80)R}|T7lxgnUfd1byPH;Rg|kUB{)n{6CNy$dLU-}F46A}PTU$SK&!?evHPc z=k|Eqj^eEpu}W*zi)3YSiZPPGBIFiQUKE*R0Q4d#Q6Ha`C0H=5Ts!sLSACS1-&-K! zIAVjV+(Fd7S0E$VdT4yPw&lil z-W_DIfKtyt&h_ukpVi(BL%y$0xq;dO_?CSK#M%^1jscrdo|zJ$__1q;{~b@oM)$4W zMB}#Pp!)p}Q?_zeW+U}v0Cl`EQ9tVFAJyaU8?MPnE7G)^)AQhq%J<4FVy z@NS^8(Ur6cQCfNc~&-_*))u8~Rww>@= z^*DWAOICf!0AzQ18m@iX>Lj-j<%#y3>Ge|@Z;#Im@Hk@#9NXWK`+yT?PVDIGr*3^Z8X*C}vU0i(N?X9U7 zv-a-cns`v@wBr()XFD0XH%&P4S0lUkJYQ&I;3=+SC+c>%h@VZ|Z~A?!v*a-qx8$sV zEayw9l`8jUOshzdOx9FQx>x8`x|j!_)i?%+De5tYBrF1&ecEI`FH^2AJ+`04S zOO@nM_X7*__N(rVZWu+^`%waZ>vFRvA8g?0IH`9Q)!2>B*+0T$z*d?3|g~ z^7vX=Zu>AyA09AXEb|9I=*gs)*=k@8i)TyW3EsLfTf6=?W0_3R(An8(DU~jgPbMdC z`+Yt=3~b)G(I00AQDZkmkCtP9IqlOYMj`G~_&^A=A(X>fr99IV#^6WzZ`za-Y>(|* z_sf|Jf5`LOHyBla-W-$7>xO0bp2y5q`||5ECISe;V%ewP+{U~I#rAeJe+#r*xJy`% zK^}{o{5LJ5W7lMAd`?dNc+Fg6*UN_W^r_zskCtOUs(l^4lm(3{uVv6T5nvCtyKm*h z<&b$E+6`lG9@^`i!*;`^o3{n;_(OUNPE3{jaaX6+79_9sKN0|?Qs4-b=siTG zpjWO;%V<5)^7$cuQRPZWMn*0fRxCs(!0v1%W);vLczt^d!2d$d+m7JS{zn3U<%~dR z1e?t_kJ$3sfyGFIt_Xq`^p_qX=P>VZEEE7FCx)h0rWXK3E(baZVc5YQ<~f4SY5N>Q z1m*kt{5sAA>dFC#LfsH~@Ewg0F%$qUUMsQtOA*4f5oVuH5y-&X2o6#vk+u0A(QXS+Cuk~e5))b6I*z9}kRR4Qnm$6< zmBnqwFnRLoE$knKIk{4l;p-}AQGDt|UgvdL;S7()5JECxALFj~0hK%5 zn3D30_ieFK|DHcxs>tDkFPSezQ`fIZPr64g6|069AM;i3E9tvO4x6#%us=Uvm8(yqF+d}l{Jg8m(o!9f7^j-2RZ9g;#>cw}m zQ1adn@}W5Z`#8=bqR8v56UP=o*EEX#1^}4BsiaefYh$Qq6|~5XETRClJNPQt~vM z1F(<6S|$~a*ZnA;>LiKjBtoF#%0mxz*j0o!NL}{h%BSKcvDaSQZTyQ<$FYqF0v8Kv zmr+EZ3xqiG4O>7PU+F&2Afv>!5C70;KRl}$)`fqZ`08w3 zcu;z}`xdS!qNKg>2Ky~^`0oQS&%LCnEyulzjL&0cfPP~HXot{e!&BfBpgzoLyPT&C zDfwxiwmtd4KKVfzl^oOdO$1O0owTW~z*7|bVSpa(V^ z?Z--jbezgNrBUTw3xTbu^5E6L;~ow2OPT{bGwnYa3E*U()RyMBcj$^1h0xIo#83cS zsM!wbd-qn+9L@cKHMQRZ)xXrh!72pG^98F8=IttgF}t-|u+0|=z>&8OnO|wnpFUT6 zjcwkYN?E#W9sWIxb_&Aet!cq^S#Fw;#1lqnl8UgBcvMFKl5oDT*0+xieCX|O{8_xW zU$cyv8v+yx0FXu}&bPF%Kxd z4*(y5!$W|5>ZJ})`o$^|3P58WZO*7rxR@cVx11eGjf;onpmCu#aA7id3l6zZ0H{Da z7MS&VaYI<^gFTlm8w@RD_g%cMG0L?BfQu(%7}ZYze3T`1I}q&kFx^W%*YX?yr`fm= z-ginnk$3UB#yasKOx{EQ69bg8PihRpOqw)FBehkq-RZ^;a3fOMg&=MO z5wsu{LEVU(Zr%A02)Zu1>BhAPDu@e3tcosL6%3+9Yq2KHN7Kx_WZL_?Z+>r1?%X?b z-@HjxdR7RkTKpBBD0%Zis2$T^hBTz=5j6fNIG6H1;$_V`5 zA~5mh8)t99>9y-C#`l#Qt${IE-MF6et!$WfW3{DP+o{ZUD^c}YRiRzKv87&o_N2OS z>1TCid0AB}vAUjARXa&k(rITHXJ38Z^!FQobvssZwI4sqED7k?&M*J=i&~nw+bMaV zF|FQv|5M{^qt;OE?L-^kWDTAyg0EEK#F;pL?EYKZZM}2aI@y97yX7LegU5~@RcmXT z+0G*aK*$2fICuC>&rKV@#3`aE>jV<~UFLPvci$ zEOtwhJ(g>(zRhosT{3dc=C{X^*VVWA?XgRSbEXCq*+D2OB9G(4qhh!A5wOQ}q6oeT z03zm60zjO16tO|%lOH6*wKJ!mG~JL}z*PhxTm;{n0l3CqfB6~p?Tz(D|o%t!M%6Se^{sp);h&9$axLIq4gW-+hC=8-@}b-Tm8;3g=O_-D%&Y* zkD;w-5j?cGlo{1dQUh7kWi6mhV2)M#9zfq204#*wmK#{)ywLGkG@emyW3_unyl+DxGjC%Xf#bv zJ@JTnNd2{ZSv8uoYN|1(*7a`!JMEh)il+?E?yYO4y%YFJe8^x){OlGmDzkg2t~qA3f2vE9_-ZiaMsJzY)zj}spnefY3}Sts$MnVEyeLF}BJ!Z4l`>2HC!o_-X? zZ5_?7Gg}+0WJ%>a{qJSyF@|NFy-pr{W246P;qxf=wD)?j-$fPdZV1WLmd^O(W$+Mv zDbL^m*C%h=MCUQ@!^Z~tNa9PL2&o=w1O=rspFEtFL7*&JS%mP8#}R~6?QZtK9;3H= zbZ8%s62?18`opRZU*e(fJpwkP9770T&1QNd$hC3x7Y`pWs*2nKA+}8Gqwb=Cx4^RLrutQwhX%%vz~PVPMUix$BRU4w!$DE$@Rz%s)y7#IND^UHr;H9~8x zNF|QnwSx^XTGCXM280$txr4F@+Q)2jbHyAV$v>_3?!Fvn0gwfR2J#-qJ!%7W{ZV%+ zgm?_1FU{O8!<;YICScehcs@NO@l-Oq_fBE%SQ-wd3x`w>}7c z5WoG+K>w4!5q$jFMNVfe!0gn;u7GG9cL_#H8WkT!4V4|_a@L3cug(A&n7n;?0}Ya$ zc=(}5_hbN=`GLZ|RDfrvoL^WxY{vEugk}JDMq(7jr!G4am~~iZKjMdubRe$wQkW?# zg$N+mIG48tzQl&S3!j0Kn6fW%c?9>&d9LTaBVb33N@xJb<81jVqt4LuC4Z4N`xc-Q zJ9RnlYE2sH7v87@c~@Wu9RXs22Y(B-^ayeuZD;_2-4_6L1o`IK(5*Scg~%+r*aix7Gckp zQ_+Xi9))_~!X*!##!W(U6>=T}9$So^N5CZv#V#Y-tu0kFg_!{~-qpfTIw049&w9f^ za6P1$L%uHqLIisty&q2GU2dl^_~`(50CTdBabAjRz0Y5XZ|fnpJrDWp9biY$W_FGJ ziqor+yW^6@oKN1zZ_kIy`vML5s7Mh@4e}I0>&a{zk3tD@6{%r?qb{FgTp!j#J}O$o zSnvoo>ermiz&zI|aiu)xLfY&IgfQQ_*U|KoA_Q@haC?O~F2hU!p~j{{K&uKIlWVq(>Vg zDswR543}Eaf!yA1c4xDj-Oh6LcFM98<8*|~8a$6OeC={Cr>`@7u0KG?Mwz=3+hT`2 zFVOXGZ#X)O`TI%nHl66|WzRf$c__ax?5^0f;vGP zL9Hqs_IuY}0)vTe^5|vT(b@xM7XBh>B~UPBNEX_8j}oZXrgKcM3D^`tO_MGud+1ve z@Gtxqp>ItP3vSjPD=kVuM|Qs1eU%Uo|^a>^h_g3I&0Xpp_63s>GoIBvf(a z0v9ela^!D8aN~dk7mi51_0S6+5=aPvDiM%qk&3D$b(=5OaU2)Q{NCo-lgZ3@))U7G z+9z#hc4pqZk9l9S@x)tizwx+Uuh7{_jq2qhRcm$H+Fa*$asE8rUHh8=GKO}&)uD~% zCateG>7P!KCMU~OD3&QHKFMnfr82cQ*J$a|)AaN2%e3>b)|P7A&>2EcShc+-?a6p{rW!4Qvls<>RJ z3)=VzrSDXLzNzlV`gmyHhaZ*&Dn=GMcOVN5;=@PZssQ+}`2C>GZSz_1kwzOUd+NEj z9|TwSw_H)Fv&nt22%>bZAB2lMA7ep11t6%2$+?7=;D)t2m<(VJOv`?K0aC2K0O>n| zAVAg&eR07;SDYga#FP8<*=G!T&qzmV} z+z)j>FqH*=;`2{G=G!3d{rdeM8|mKw@Q&{Q0DKDo;CsOA;u80}J898Un641|yW4B@ z=IhV$_kfLtVoK%ut}1tT+dEXL&QWu#q59kO(T7(Ve_ZsfS%5i;{4+H(PdC0@=3OBC zaYHEm5_J(#N<2aHj(FJnD0S<1k?n#MZ_t(5V0rLjiMKCTtTf#D#2lG8TgG zRRDws_>>TP0>Z4V4WVztld%x|pgI7WjDeGkpO`@;YaWip4^gbo0%P%$*dP{*?-M|; zLwxI8b0PRaF5n_C%5kYfF1<3(c{?kuQ2Yt`eEIMEX_c`@&(O}lfE|y%0(hb3d^F?fIqSrV9|$jNG>K=Ab}JY z7(B2<6jFGkMv>OJVTK8>0wj-0Ntt0^UYcG<_4sjkhw5B8G7Gp}FoeES0nk2j@Wq#2 zjL^2Hk0000SP)#YZ zOLp~i_gB?bU0u~Zvm$k=El^vawm@xx+5)u&Y75jBs4Y-ipteA5f!YGK1^#aqnEK%T z_xAhiJ)`$xyJIMyds_qP%rym+Wo={KRC~P*Ll>4VvP*_rJ!#B!WHi{7t^P0a%GGD( z>+kPK_wKednllnLW@US0Eg6r>>#sg9ly?&Hqs(lS2UmHe+cZJpC|2-H)JlfT#)i25 z`E41-eNet?IcoXQjW4^hx3^;z8FtlqLJ7AF_qNUBV0R!3OKwlL4kJVj^0qWPLX45n z2JaZ1fcyw9+c=n&>%#X(R!JDgPR>%eaAUe9Fv9%2)gG&lVlV|)8+h5|z( z$0h$vB0a9qg(=+a^Xr(rUrr<5!~pJLt{@c2GXnOsyDdWS{Xc#*cBGg2M#VE_CDhr1 z561l=@{IxL!oKjGII2VFq#!m9@AK21ktxzFaNZ2zvlakt7HSEy(7ZIw_z?MO7QpI5 z`FGyBCg1&VOK#l!Ib#H_gr*SM^1KKAE*_ZzrD_nIY4n^RzGLQX7&`wbk92d>rhNqQ z%ocDkruysZiv0BZs^6S>_FWi;^9uzh)N$eS=dL6!%peTPGA;o=g3Wrv09R*u8apTh=I;6wkGCrY>mTkVNTDLkG4sk#H~5#ubXoSV*w~jn&Q(e z#y;RP24DvTBae&pk~GMEQmkmG3v1^H%7>iM&`z8(T}qE~+fXlqHwIu3F~Ha=TnRj5 zZ|0S8d7KlO;h5Rz9;Gd@C~z$y*mlPcUzD|EGlIzg;)2zgwS znb(HBi%6~=#`19NbMevT7nXza@XIeAULJS1AnN1JAy^1kd2I_7#b=Jfw2(`29S$i) z@ctHpJf1@?UwR^QZqViuf}gtj(v^KM?R4f1@1^;6B7^S&sDyuC$MeUZeq}C3ToIF6 z4(|QSkGJHxr!SaHF}L_w!WR{;+P$?~rv0Skt7(uk)sCXE~}@qbnMb8Wjgf3AF6QL=@pLC{sc z3is25&y4|9lrM|fh~F?_3$h@^@w`EAwC9ubf&>F^_4P9g`4pwO1~9L`;y7UUdr_9< z#yzmSU;v$teZ%BtA#&2QP+ORVIIapEYXB&Z9Q@2v7v;l`J~6k$Dgq{9LW5vg590Q` zKeBfm)c`ya<6XdFdqvfOX{_u7z|3{wszGq!k+w;r=2RGaDHZd*)eqiOrnzYGssX?Z ze|dSFjE=&EsSIUIqVBz*(e$p*@jXY&V&thB;J=c`IZ2K39HI4b{#qZ~h*u3zWCged zG#B>q+6vHxwLI?QIcb+tzT3un8N7N9U<@F`vh+0JBQ1zQVFnk}twJ^r)4F_NQWg(> z;DL{)A%!knl?U-r^&loD4}UhaS>h=2c<4E#2wvR+v?>oEd@f5v@H~g$+l2P2ebQWt z;?3^^TxO3xd`|E^0MspP19i*II|D&L$}4a;1}%=I3tqaA9)rQ-xaH@1K%P=sUo>OC zKu|e<43+oW59_&#{HfQjy=~qIRhwlYWep&Lz7JcSx`Kt2G z?*kyrW#9|v#7WVQ`nclra&f?ncDE9!ra;npP$8Za&4(pl-2!>c{mE(1rwJe7jcW3C z=Rg~O1vMqv7K#tu0(jXPGXKm`7<TIr`U$*>{HZtIe0Seh z(A{g`?z%L`YHgfJ8GNV*Xm6Z3k0R7;0Su&K<3Qv3LlvM4yZki+y8W0V7XVujcl#;@ zJR*Pplw}m^!Z=8U6wH5;!Tf&6qXC11Ab%f_Dq(-}B0`$>Xl?WWca*={Zl7Nd4Z_9) ze1V$9AXsZh9-o9Kj+DHp%p^R=xp=n0148iXIe^SbDS(viMJaKlMR6QQ94Xt2QsPKm zocVo#qzALKPisqilUkLVS^|zM{Aj98j^nWMDB2iofpLANBmS3%H@^?yK+v*zSY+9S zm&R*BP*YTEgJ3O)Yh$%G;0tX53_Qx>PmU|*%N*Apg8vs_nK7Rz-uD;)0000