Add custom block textures
This commit is contained in:
@@ -139,6 +139,7 @@ dependencies {
|
||||
modRuntimeOnly(libs.fabric.api.deprecated)
|
||||
modApi(libs.architectury)
|
||||
modCompileOnly(libs.jarvis.api)
|
||||
modCompileOnly(libs.sodium)
|
||||
include(libs.jarvis.fabric)
|
||||
|
||||
modCompileOnly(libs.femalegender)
|
||||
|
||||
@@ -465,3 +465,54 @@ to avoid collisions with other texture packs that might use the same id for a sc
|
||||
Currently, the only supported filter is `title`, which accepts a [string matcher](#string-matcher). You can also use
|
||||
`firmament:always` as an always on filter (this is the recommended way).
|
||||
|
||||
## Block Model Replacements
|
||||
|
||||
If you want to replace block textures in the world you can do so using block overrides. Those are stored in
|
||||
`assets/firmskyblock/overrides/blocks/<id>.json`. The id does not matter, all overrides are loaded. This file specifies
|
||||
which block models are replaced under which conditions:
|
||||
|
||||
```json
|
||||
{
|
||||
"modes": [
|
||||
"mining_3"
|
||||
],
|
||||
"area": [
|
||||
{
|
||||
"min": [
|
||||
-31,
|
||||
200,
|
||||
-117
|
||||
],
|
||||
"max": [
|
||||
12,
|
||||
223,
|
||||
-95
|
||||
]
|
||||
}
|
||||
],
|
||||
"replacements": {
|
||||
"minecraft:blue_wool": "firmskyblock:mithril_deep",
|
||||
"minecraft:light_blue_wool": {
|
||||
"block": "firmskyblock:mithril_deep",
|
||||
"sound": "minecraft:block.wet_sponge.hit"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `modes` | yes | A list of `/locraw` mode names. |
|
||||
| `area` | no | A list of areas. Blocks outside of the coordinate range will be ignored. If the block is in *any* range it will be considered inside |
|
||||
| `area.min` | yes | The lowest coordinate in the area. Is included in the area. |
|
||||
| `area.max` | yes | The highest coordinate in the area. Is included in the area. |
|
||||
| `replacements` | yes | A map of block id to replacement mappings |
|
||||
| `replacements` (string) | yes | You can directly specify a string. Equivalent to just setting `replacements.block`. |
|
||||
| `replacements.block` | yes | You can specify a block model to be used instead of the regular one. The model will be loaded from `assets/<namespace>/models/block/<path>.json` like regular block models. |
|
||||
| `replacements.sound` | no | You can also specify a sound override. This is only used for the "hit" sound effect that repeats while the block is mined. The "break" sound effect played after a block was finished mining is sadly sent by hypixel directly and cannot be replaced reliably. |
|
||||
|
||||
> A quick note about optimization: Not specifying an area (by just omitting the `area` field) is quicker than having an
|
||||
> area encompass the entire map.
|
||||
>
|
||||
> If you need to use multiple `area`s for unrelated sections of the world it might be a performance improvement to move
|
||||
> unrelated models to different files to reduce the amount of area checks being done for each block.
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
// }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user