aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-08-09 00:49:36 +0200
committerLinnea Gräf <nea@nea.moe>2024-08-09 02:20:41 +0200
commit1606188d9ad65c66e9d873497ea3271dbdadaf77 (patch)
treea33ea87c76bd672fe3902455742ba42ae8c91ee1 /src/main
parent2a023d0a8d4e9af1dff21ea1bc997dfa0c686b14 (diff)
downloadFirmament-1606188d9ad65c66e9d873497ea3271dbdadaf77.tar.gz
Firmament-1606188d9ad65c66e9d873497ea3271dbdadaf77.tar.bz2
Firmament-1606188d9ad65c66e9d873497ea3271dbdadaf77.zip
Add custom block textures
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/moe/nea/firmament/mixins/CustomModelBakerPatch.java15
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/PatchBlockModelInSodiumChunkGenerator.java29
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java27
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java30
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java38
-rw-r--r--src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java21
-rw-r--r--src/main/kotlin/moe/nea/firmament/events/BakeExtraModelsEvent.kt11
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt277
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt2
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt5
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/IdentifierSerializer.kt4
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/MC.kt13
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/json/BlockPosSerializer.kt25
-rw-r--r--src/main/resources/assets/firmament/lang/en_us.json1
14 files changed, 485 insertions, 13 deletions
diff --git a/src/main/java/moe/nea/firmament/mixins/CustomModelBakerPatch.java b/src/main/java/moe/nea/firmament/mixins/CustomModelBakerPatch.java
index 97f81b0..c1e359d 100644
--- a/src/main/java/moe/nea/firmament/mixins/CustomModelBakerPatch.java
+++ b/src/main/java/moe/nea/firmament/mixins/CustomModelBakerPatch.java
@@ -1,5 +1,3 @@
-
-
package moe.nea.firmament.mixins;
import moe.nea.firmament.events.BakeExtraModelsEvent;
@@ -10,6 +8,7 @@ import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -29,9 +28,19 @@ public abstract class CustomModelBakerPatch {
@Shadow
abstract UnbakedModel getOrLoadModel(Identifier id);
+ @Shadow
+ protected abstract void add(ModelIdentifier id, UnbakedModel model);
+
+ @Unique
+ private void loadNonItemModel(ModelIdentifier identifier) {
+ UnbakedModel unbakedModel = this.getOrLoadModel(identifier.id());
+ this.add(identifier, unbakedModel);
+ }
+
+
@Inject(method = "bake", at = @At("HEAD"))
public void onBake(ModelLoader.SpriteGetter spliteGetter, CallbackInfo ci) {
- BakeExtraModelsEvent.Companion.publish(new BakeExtraModelsEvent(this::loadItemModel));
+ BakeExtraModelsEvent.Companion.publish(new BakeExtraModelsEvent(this::loadItemModel, this::loadNonItemModel));
modelsToBake.values().forEach(model -> model.setParents(this::getOrLoadModel));
// modelsToBake.keySet().stream()
// .filter(it -> !it.id().getNamespace().equals("minecraft"))
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchBlockModelInSodiumChunkGenerator.java b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchBlockModelInSodiumChunkGenerator.java
new file mode 100644
index 0000000..90f20bc
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchBlockModelInSodiumChunkGenerator.java
@@ -0,0 +1,29 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import com.llamalad7.mixinextras.sugar.Local;
+import me.jellysquid.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderMeshingTask;
+import moe.nea.firmament.features.texturepack.CustomBlockTextures;
+import net.minecraft.block.BlockState;
+import net.minecraft.client.render.block.BlockModels;
+import net.minecraft.client.render.model.BakedModel;
+import net.minecraft.util.math.BlockPos;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(ChunkBuilderMeshingTask.class)
+public class PatchBlockModelInSodiumChunkGenerator {
+ @WrapOperation(
+ method = "execute(Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildContext;Lme/jellysquid/mods/sodium/client/util/task/CancellationToken;)Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildOutput;",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockModels;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BakedModel;"))
+ private BakedModel replaceBlockModel(BlockModels instance, BlockState state, Operation<BakedModel> original,
+ @Local(name = "blockPos") BlockPos.Mutable pos) {
+ var replacement = CustomBlockTextures.getReplacementModel(state, pos);
+ if (replacement != null) return replacement;
+ CustomBlockTextures.enterFallbackCall();
+ var fallback = original.call(instance, state);
+ CustomBlockTextures.exitFallbackCall();
+ return fallback;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java
new file mode 100644
index 0000000..9401889
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java
@@ -0,0 +1,27 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import com.llamalad7.mixinextras.sugar.Local;
+import moe.nea.firmament.features.texturepack.CustomBlockTextures;
+import net.minecraft.block.BlockState;
+import net.minecraft.client.render.WorldRenderer;
+import net.minecraft.sound.BlockSoundGroup;
+import net.minecraft.sound.SoundEvent;
+import net.minecraft.util.math.BlockPos;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(WorldRenderer.class)
+public class ReplaceBlockBreakSoundPatch {
+// Sadly hypixel does not send a world event here and instead plays the sound on the server directly
+// @WrapOperation(method = "processWorldEvent", at = @At(value = "INVOKE", target = "Lnet/minecraft/sound/BlockSoundGroup;getBreakSound()Lnet/minecraft/sound/SoundEvent;"))
+// private SoundEvent replaceBreakSoundEvent(BlockSoundGroup instance, Operation<SoundEvent> original,
+// @Local(argsOnly = true) BlockPos pos, @Local BlockState blockState) {
+// var replacement = CustomBlockTextures.getReplacement(blockState, pos);
+// if (replacement != null && replacement.getSound() != null) {
+// return SoundEvent.of(replacement.getSound());
+// }
+// return original.call(instance);
+// }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java
new file mode 100644
index 0000000..f9a1d0d
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java
@@ -0,0 +1,30 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import com.llamalad7.mixinextras.sugar.Local;
+import moe.nea.firmament.features.texturepack.CustomBlockTextures;
+import net.minecraft.block.BlockState;
+import net.minecraft.client.network.ClientPlayerInteractionManager;
+import net.minecraft.client.sound.PositionedSoundInstance;
+import net.minecraft.sound.SoundCategory;
+import net.minecraft.sound.SoundEvent;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.random.Random;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(ClientPlayerInteractionManager.class)
+public class ReplaceBlockHitSoundPatch {
+ @WrapOperation(method = "updateBlockBreakingProgress", at = @At(value = "NEW", target = "(Lnet/minecraft/sound/SoundEvent;Lnet/minecraft/sound/SoundCategory;FFLnet/minecraft/util/math/random/Random;Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/client/sound/PositionedSoundInstance;"))
+ private PositionedSoundInstance replaceSound(
+ SoundEvent sound, SoundCategory category, float volume, float pitch,
+ Random random, BlockPos pos, Operation<PositionedSoundInstance> original,
+ @Local BlockState blockState) {
+ var replacement = CustomBlockTextures.getReplacement(blockState, pos);
+ if (replacement != null && replacement.getSound() != null) {
+ sound = SoundEvent.of(replacement.getSound());
+ }
+ return original.call(sound, category, volume, pitch, random, pos);
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java
new file mode 100644
index 0000000..711b2af
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java
@@ -0,0 +1,38 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import com.llamalad7.mixinextras.sugar.Local;
+import moe.nea.firmament.features.texturepack.CustomBlockTextures;
+import net.minecraft.block.BlockState;
+import net.minecraft.client.render.block.BlockModels;
+import net.minecraft.client.render.block.BlockRenderManager;
+import net.minecraft.client.render.model.BakedModel;
+import net.minecraft.util.math.BlockPos;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(BlockRenderManager.class)
+public class ReplaceBlockRenderManagerBlockModel {
+ @WrapOperation(method = "renderBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockRenderManager;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BakedModel;"))
+ private BakedModel replaceModelInRenderBlock(
+ BlockRenderManager instance, BlockState state, Operation<BakedModel> original, @Local(argsOnly = true) BlockPos pos) {
+ var replacement = CustomBlockTextures.getReplacementModel(state, pos);
+ if (replacement != null) return replacement;
+ CustomBlockTextures.enterFallbackCall();
+ var fallback = original.call(instance, state);
+ CustomBlockTextures.exitFallbackCall();
+ return fallback;
+ }
+
+ @WrapOperation(method = "renderDamage", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockModels;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BakedModel;"))
+ private BakedModel replaceModelInRenderDamage(
+ BlockModels instance, BlockState state, Operation<BakedModel> original, @Local(argsOnly = true) BlockPos pos) {
+ var replacement = CustomBlockTextures.getReplacementModel(state, pos);
+ if (replacement != null) return replacement;
+ CustomBlockTextures.enterFallbackCall();
+ var fallback = original.call(instance, state);
+ CustomBlockTextures.exitFallbackCall();
+ return fallback;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java
new file mode 100644
index 0000000..53ab74a
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java
@@ -0,0 +1,21 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import moe.nea.firmament.features.texturepack.CustomBlockTextures;
+import net.minecraft.block.BlockState;
+import net.minecraft.client.render.block.BlockModels;
+import net.minecraft.client.render.model.BakedModel;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+@Mixin(BlockModels.class)
+public class ReplaceFallbackBlockModel {
+ // TODO: add check to BlockDustParticle
+ @Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
+ private void getModel(BlockState state, CallbackInfoReturnable<BakedModel> cir) {
+ var replacement = CustomBlockTextures.getReplacementModel(state, null);
+ if (replacement != null)
+ cir.setReturnValue(replacement);
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/events/BakeExtraModelsEvent.kt b/src/main/kotlin/moe/nea/firmament/events/BakeExtraModelsEvent.kt
index db073e3..f75bedc 100644
--- a/src/main/kotlin/moe/nea/firmament/events/BakeExtraModelsEvent.kt
+++ b/src/main/kotlin/moe/nea/firmament/events/BakeExtraModelsEvent.kt
@@ -5,11 +5,16 @@ import java.util.function.Consumer
import net.minecraft.client.util.ModelIdentifier
class BakeExtraModelsEvent(
- private val addModel: Consumer<ModelIdentifier>,
+ private val addItemModel: Consumer<ModelIdentifier>,
+ private val addAnyModel: Consumer<ModelIdentifier>,
) : FirmamentEvent() {
- fun addModel(modelIdentifier: ModelIdentifier) {
- this.addModel.accept(modelIdentifier)
+ fun addNonItemModel(modelIdentifier: ModelIdentifier) {
+ this.addAnyModel.accept(modelIdentifier)
+ }
+
+ fun addItemModel(modelIdentifier: ModelIdentifier) {
+ this.addItemModel.accept(modelIdentifier)
}
companion object : FirmamentEventBus<BakeExtraModelsEvent>()
diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt
new file mode 100644
index 0000000..2289be2
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt
@@ -0,0 +1,277 @@
+@file:UseSerializers(BlockPosSerializer::class, IdentifierSerializer::class)
+
+package moe.nea.firmament.features.texturepack
+
+import java.util.concurrent.CompletableFuture
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Transient
+import kotlinx.serialization.UseSerializers
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.json.JsonDecoder
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.serializer
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.block.Block
+import net.minecraft.block.BlockState
+import net.minecraft.client.render.model.BakedModel
+import net.minecraft.client.util.ModelIdentifier
+import net.minecraft.registry.RegistryKey
+import net.minecraft.registry.RegistryKeys
+import net.minecraft.resource.ResourceManager
+import net.minecraft.resource.SinglePreparationResourceReloader
+import net.minecraft.util.Identifier
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.profiler.Profiler
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.BakeExtraModelsEvent
+import moe.nea.firmament.events.EarlyResourceReloadEvent
+import moe.nea.firmament.events.FinalizeResourceManagerEvent
+import moe.nea.firmament.events.SkyblockServerUpdateEvent
+import moe.nea.firmament.features.texturepack.CustomGlobalTextures.logger
+import moe.nea.firmament.util.IdentifierSerializer
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.SBData
+import moe.nea.firmament.util.SkyBlockIsland
+import moe.nea.firmament.util.json.BlockPosSerializer
+import moe.nea.firmament.util.json.SingletonSerializableList
+
+
+object CustomBlockTextures {
+ @Serializable
+ data class CustomBlockOverride(
+ val modes: @Serializable(SingletonSerializableList::class) List<String>,
+ val area: List<Area>? = null,
+ val replacements: Map<Identifier, Replacement>,
+ )
+
+ @Serializable(with = Replacement.Serializer::class)
+ data class Replacement(
+ val block: Identifier,
+ val sound: Identifier?,
+ ) {
+
+ @Transient
+ val blockModelIdentifier get() = ModelIdentifier(block.withPrefixedPath("block/"), "firmament")
+
+ @Transient
+ val bakedModel: BakedModel by lazy(LazyThreadSafetyMode.NONE) {
+ MC.instance.bakedModelManager.getModel(blockModelIdentifier)
+ }
+
+ @OptIn(ExperimentalSerializationApi::class)
+ @kotlinx.serialization.Serializer(Replacement::class)
+ object DefaultSerializer : KSerializer<Replacement>
+
+ object Serializer : KSerializer<Replacement> {
+ val delegate = serializer<JsonElement>()
+ override val descriptor: SerialDescriptor
+ get() = delegate.descriptor
+
+ override fun deserialize(decoder: Decoder): Replacement {
+ val jsonElement = decoder.decodeSerializableValue(delegate)
+ if (jsonElement is JsonPrimitive) {
+ require(jsonElement.isString)
+ return Replacement(Identifier.tryParse(jsonElement.content)!!, null)
+ }
+ return (decoder as JsonDecoder).json.decodeFromJsonElement(DefaultSerializer, jsonElement)
+ }
+
+ override fun serialize(encoder: Encoder, value: Replacement) {
+ encoder.encodeSerializableValue(DefaultSerializer, value)
+ }
+ }
+ }
+
+ @Serializable
+ data class Area(
+ val min: BlockPos,
+ val max: BlockPos,
+ ) {
+ @Transient
+ val realMin = BlockPos(
+ minOf(min.x, max.x),
+ minOf(min.y, max.y),
+ minOf(min.z, max.z),
+ )
+
+ @Transient
+ val realMax = BlockPos(
+ maxOf(min.x, max.x),
+ maxOf(min.y, max.y),
+ maxOf(min.z, max.z),
+ )
+
+ fun roughJoin(other: Area): Area {
+ return Area(
+ BlockPos(
+ minOf(realMin.x, other.realMin.x),
+ minOf(realMin.y, other.realMin.y),
+ minOf(realMin.z, other.realMin.z),
+ ),
+ BlockPos(
+ maxOf(realMax.x, other.realMax.x),
+ maxOf(realMax.y, other.realMax.y),
+ maxOf(realMax.z, other.realMax.z),
+ )
+ )
+ }
+
+ fun contains(blockPos: BlockPos): Boolean {
+ return (blockPos.x in realMin.x..realMax.x) &&
+ (blockPos.y in realMin.y..realMax.y) &&
+ (blockPos.z in realMin.z..realMax.z)
+ }
+ }
+
+ data class LocationReplacements(
+ val lookup: Map<Block, List<BlockReplacement>>
+ )
+
+ data class BlockReplacement(
+ val checks: List<Area>?,
+ val replacement: Replacement,
+ ) {
+ val roughCheck by lazy(LazyThreadSafetyMode.NONE) {
+ if (checks == null || checks.size < 3) return@lazy null
+ checks.reduce { acc, next -> acc.roughJoin(next) }
+ }
+ }
+
+ data class BakedReplacements(val data: Map<SkyBlockIsland, LocationReplacements>)
+
+ var allLocationReplacements: BakedReplacements = BakedReplacements(mapOf())
+ var currentIslandReplacements: LocationReplacements? = null
+
+ fun refreshReplacements() {
+ val location = SBData.skyblockLocation
+ val replacements =
+ if (CustomSkyBlockTextures.TConfig.enableBlockOverrides) location?.let(allLocationReplacements.data::get)
+ else null
+ val lastReplacements = currentIslandReplacements
+ currentIslandReplacements = replacements
+ if (lastReplacements != replacements) {
+ MC.nextTick {
+ MC.worldRenderer.reload()
+ }
+ }
+ }
+
+ fun matchesPosition(replacement: BlockReplacement, blockPos: BlockPos?): Boolean {
+ if (blockPos == null) return true
+ val rc = replacement.roughCheck
+ if (rc != null && !rc.contains(blockPos)) return false
+ val areas = replacement.checks
+ if (areas != null && !areas.any { it.contains(blockPos) }) return false
+ return true
+ }
+
+ @JvmStatic
+ fun getReplacementModel(block: BlockState, blockPos: BlockPos?): BakedModel? {
+ return getReplacement(block, blockPos)?.bakedModel
+ }
+
+ @JvmStatic
+ fun getReplacement(block: BlockState, blockPos: BlockPos?): Replacement? {
+ if (isInFallback() && blockPos == null) return null
+ val replacements = currentIslandReplacements?.lookup?.get(block.block) ?: return null
+ for (replacement in replacements) {
+ if (replacement.checks == null || matchesPosition(replacement, blockPos))
+ return replacement.replacement
+ }
+ return null
+ }
+
+
+ @Subscribe
+ fun onLocation(event: SkyblockServerUpdateEvent) {
+ refreshReplacements()
+ }
+
+ @Volatile
+ var preparationFuture: CompletableFuture<BakedReplacements> = CompletableFuture.completedFuture(BakedReplacements(
+ mapOf()))
+
+ val insideFallbackCall = ThreadLocal.withInitial { 0 }
+
+ @JvmStatic
+ fun enterFallbackCall() {
+ insideFallbackCall.set(insideFallbackCall.get() + 1)
+ }
+
+ fun isInFallback() = insideFallbackCall.get() > 0
+
+ @JvmStatic
+ fun exitFallbackCall() {
+ insideFallbackCall.set(insideFallbackCall.get() - 1)
+ }
+
+ @Subscribe
+ fun onEarlyReload(event: EarlyResourceReloadEvent) {
+ preparationFuture = CompletableFuture
+ .supplyAsync(
+ { prepare(event.resourceManager) }, event.preparationExecutor)
+ }
+
+ @Subscribe
+ fun bakeExtraModels(event: BakeExtraModelsEvent) {
+ preparationFuture.join().data.values
+ .flatMap { it.lookup.values }
+ .flatten()
+ .mapTo(mutableSetOf()) { it.replacement.blockModelIdentifier }
+ .forEach { event.addNonItemModel(it) }
+ }
+
+ private fun prepare(manager: ResourceManager): BakedReplacements {
+ val resources = manager.findResources("overrides/blocks") {
+ it.namespace == "firmskyblock" && it.path.endsWith(".json")
+ }
+ val map = mutableMapOf<SkyBlockIsland, MutableMap<Block, MutableList<BlockReplacement>>>()
+ for ((file, resource) in resources) {
+ val json =
+ Firmament.tryDecodeJsonFromStream<CustomBlockOverride>(resource.inputStream)
+ .getOrElse { ex ->
+ logger.error("Failed to load block texture override at $file", ex)
+ continue
+ }
+ for (mode in json.modes) {
+ val island = SkyBlockIsland.forMode(mode)
+ val islandMpa = map.getOrPut(island, ::mutableMapOf)
+ for ((blockId, replacement) in json.replacements) {
+ val block = MC.defaultRegistries.getWrapperOrThrow(RegistryKeys.BLOCK)
+ .getOptional(RegistryKey.of(RegistryKeys.BLOCK, blockId))
+ .getOrNull()
+ if (block == null) {
+ logger.error("Failed to load block texture override at ${file}: unknown block '$blockId'")
+ continue
+ }
+ val replacements = islandMpa.getOrPut(block.value(), ::mutableListOf)
+ replacements.add(BlockReplacement(json.area, replacement))
+ }
+ }
+ }
+
+ return BakedReplacements(map.mapValues { LocationReplacements(it.value) })
+ }
+
+
+ @Subscribe
+ fun onStart(event: FinalizeResourceManagerEvent) {
+ event.resourceManager.registerReloader(object :
+ SinglePreparationResourceReloader<BakedReplacements>() {
+ override fun prepare(manager: ResourceManager, profiler: Profiler): BakedReplacements {
+ return preparationFuture.join()
+ }
+
+ override fun apply(prepared: BakedReplacements, manager: ResourceManager, profiler: Profiler?) {
+ allLocationReplacements = prepared
+ refreshReplacements()
+ }
+ })
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt
index ed5981a..d64c844 100644
--- a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt
+++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt
@@ -78,7 +78,7 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
fun onBakeModels(event: BakeExtraModelsEvent) {
for (guiClassOverride in preparationFuture.join().classes) {
for (override in guiClassOverride.overrides) {
- event.addModel(ModelIdentifier(override.model, "inventory"))
+ event.addItemModel(ModelIdentifier(override.model, "inventory"))
}
}
}
diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt
index 634d5c0..dec6046 100644
--- a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt
+++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt
@@ -1,5 +1,3 @@
-
-
package moe.nea.firmament.features.texturepack
import com.mojang.authlib.minecraft.MinecraftProfileTexture
@@ -31,6 +29,7 @@ object CustomSkyBlockTextures : FirmamentFeature {
val cacheDuration by integer("cache-duration", 0, 20) { 1 }
val enableModelOverrides by toggle("model-overrides") { true }
val enableArmorOverrides by toggle("armor-overrides") { true }
+ val enableBlockOverrides by toggle("block-overrides") { true }
}
override val config: ManagedConfig
@@ -63,7 +62,7 @@ object CustomSkyBlockTextures : FirmamentFeature {
"models/item/".length,
identifier.path.length - ".json".length),
))
- event.addModel(modelId)
+ event.addItemModel(modelId)
}
}
diff --git a/src/main/kotlin/moe/nea/firmament/util/IdentifierSerializer.kt b/src/main/kotlin/moe/nea/firmament/util/IdentifierSerializer.kt
index feea9e9..65c5b1c 100644
--- a/src/main/kotlin/moe/nea/firmament/util/IdentifierSerializer.kt
+++ b/src/main/kotlin/moe/nea/firmament/util/IdentifierSerializer.kt
@@ -3,6 +3,8 @@ package moe.nea.firmament.util
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
@@ -11,7 +13,7 @@ import net.minecraft.util.Identifier
object IdentifierSerializer : KSerializer<Identifier> {
val delegateSerializer = String.serializer()
override val descriptor: SerialDescriptor
- get() = SerialDescriptor("Identifier", delegateSerializer.descriptor)
+ get() = PrimitiveSerialDescriptor("Identifier", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): Identifier {
return Identifier.of(decoder.decodeSerializableValue(delegateSerializer))
diff --git a/src/main/kotlin/moe/nea/firmament/util/MC.kt b/src/main/kotlin/moe/nea/firmament/util/MC.kt
index 8935766..b0d3056 100644
--- a/src/main/kotlin/moe/nea/firmament/util/MC.kt
+++ b/src/main/kotlin/moe/nea/firmament/util/MC.kt
@@ -1,11 +1,10 @@
-
-
package moe.nea.firmament.util
import io.github.moulberry.repo.data.Coordinate
import java.util.concurrent.ConcurrentLinkedQueue
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.ingame.HandledScreen
+import net.minecraft.client.render.WorldRenderer
import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket
import net.minecraft.registry.BuiltinRegistries
import net.minecraft.registry.RegistryKeys
@@ -24,6 +23,9 @@ object MC {
while (true) {
inGameHud.chatHud.addMessage(messageQueue.poll() ?: break)
}
+ while (true) {
+ (nextTickTodos.poll() ?: break).invoke()
+ }
}
}
@@ -58,7 +60,14 @@ object MC {
instance.send(block)
}
+ private val nextTickTodos = ConcurrentLinkedQueue<() -> Unit>()
+ fun nextTick(function: () -> Unit) {
+ nextTickTodos.add(function)
+ }
+
+
inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl)
+ inline val worldRenderer: WorldRenderer get() = instance.worldRenderer
inline val networkHandler get() = player?.networkHandler
inline val instance get() = MinecraftClient.getInstance()
inline val keyboard get() = instance.keyboard
diff --git a/src/main/kotlin/moe/nea/firmament/util/json/BlockPosSerializer.kt b/src/main/kotlin/moe/nea/firmament/util/json/BlockPosSerializer.kt
new file mode 100644
index 0000000..144b0a0
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/util/json/BlockPosSerializer.kt
@@ -0,0 +1,25 @@
+package moe.nea.firmament.util.json
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.serializer
+import net.minecraft.util.math.BlockPos
+
+object BlockPosSerializer : KSerializer<BlockPos> {
+ val delegate = serializer<List<Int>>()
+
+ override val descriptor: SerialDescriptor
+ get() = SerialDescriptor("BlockPos", delegate.descriptor)
+
+ override fun deserialize(decoder: Decoder): BlockPos {
+ val list = decoder.decodeSerializableValue(delegate)
+ require(list.size == 3)
+ return BlockPos(list[0], list[1], list[2])
+ }
+
+ override fun serialize(encoder: Encoder, value: BlockPos) {
+ encoder.encodeSerializableValue(delegate, listOf(value.x, value.y, value.z))
+ }
+}
diff --git a/src/main/resources/assets/firmament/lang/en_us.json b/src/main/resources/assets/firmament/lang/en_us.json
index 05b10d5..dd514fe 100644
--- a/src/main/resources/assets/firmament/lang/en_us.json
+++ b/src/main/resources/assets/firmament/lang/en_us.json
@@ -164,6 +164,7 @@
"firmament.config.custom-skyblock-textures.cache-duration": "Model Cache Duration",
"firmament.config.custom-skyblock-textures.model-overrides": "Enable model overrides/predicates",
"firmament.config.custom-skyblock-textures.armor-overrides": "Enable Armor re-texturing",
+ "firmament.config.custom-skyblock-textures.block-overrides": "Enable Block re-modelling",
"firmament.config.custom-skyblock-textures.enabled": "Enable Custom Item Textures",
"firmament.config.custom-skyblock-textures.skulls-enabled": "Enable Custom Placed Skull Textures",
"firmament.config.fixes": "Fixes",