aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/moe/nea/firmament/mixins/AppendRepoAsResourcePack.java24
-rw-r--r--src/main/java/moe/nea/firmament/mixins/EntityUpdateEventListener.java34
-rw-r--r--src/main/java/moe/nea/firmament/mixins/PropertySignatureIgnorePatch.java36
-rw-r--r--src/main/java/moe/nea/firmament/mixins/accessor/AccessorWorldRenderer.java17
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/ChangeColorOfLivingEntities.java62
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/EntityRenderStateTint.java55
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/InjectIntoRenderState.java30
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/ReplaceOverlayTexture.java24
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableEquipmentRenderer.java34
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableHeadFeatureRenderer.java25
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableItemRenderer.java25
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableSkullBlockEntityRenderer.java25
-rw-r--r--src/main/kotlin/Firmament.kt12
-rw-r--r--src/main/kotlin/commands/rome.kt8
-rw-r--r--src/main/kotlin/events/EntityRenderTintEvent.kt66
-rw-r--r--src/main/kotlin/events/EntityUpdateEvent.kt31
-rw-r--r--src/main/kotlin/events/IsSlotProtectedEvent.kt11
-rw-r--r--src/main/kotlin/features/debug/AnimatedClothingScanner.kt51
-rw-r--r--src/main/kotlin/features/debug/PowerUserTools.kt15
-rw-r--r--src/main/kotlin/features/events/anniversity/CenturyRaffleFeatures.kt63
-rw-r--r--src/main/kotlin/features/inventory/SlotLocking.kt123
-rw-r--r--src/main/kotlin/features/inventory/TimerInLore.kt3
-rw-r--r--src/main/kotlin/features/mining/MiningBlockInfoUi.kt54
-rw-r--r--src/main/kotlin/gui/CheckboxComponent.kt5
-rw-r--r--src/main/kotlin/repo/ItemCache.kt42
-rw-r--r--src/main/kotlin/repo/MiningRepoData.kt131
-rw-r--r--src/main/kotlin/repo/RepoManager.kt18
-rw-r--r--src/main/kotlin/repo/RepoModResourcePack.kt5
-rw-r--r--src/main/kotlin/repo/SBItemStack.kt8
-rw-r--r--src/main/kotlin/util/MC.kt5
-rw-r--r--src/main/kotlin/util/SBData.kt4
-rw-r--r--src/main/kotlin/util/SkyBlockIsland.kt63
-rw-r--r--src/main/kotlin/util/SkyblockId.kt6
-rw-r--r--src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt24
-rw-r--r--src/main/kotlin/util/regex.kt7
-rw-r--r--src/main/kotlin/util/render/TintedOverlayTexture.kt44
-rw-r--r--src/main/kotlin/util/skyblock/SkyBlockItems.kt5
-rw-r--r--src/main/kotlin/util/textutil.kt2
-rw-r--r--src/main/resources/assets/firmament/gui/mining_block_info/index.xml38
-rw-r--r--src/main/resources/fabric.mod.json5
-rw-r--r--src/main/resources/firmament.accesswidener6
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.pngbin0 -> 639 bytes
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.pngbin0 -> 147 bytes
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta10
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.pngbin0 -> 624 bytes
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png.mcmeta10
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.pngbin0 -> 203 bytes
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta9
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.pngbin0 -> 795 bytes
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta10
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/pack.mcmeta10
51 files changed, 1103 insertions, 192 deletions
diff --git a/src/main/java/moe/nea/firmament/mixins/AppendRepoAsResourcePack.java b/src/main/java/moe/nea/firmament/mixins/AppendRepoAsResourcePack.java
index 22ce991..d8e35d7 100644
--- a/src/main/java/moe/nea/firmament/mixins/AppendRepoAsResourcePack.java
+++ b/src/main/java/moe/nea/firmament/mixins/AppendRepoAsResourcePack.java
@@ -1,28 +1,34 @@
package moe.nea.firmament.mixins;
+import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.repo.RepoModResourcePack;
import net.fabricmc.fabric.api.resource.ModResourcePack;
+import net.fabricmc.fabric.impl.resource.loader.ModResourcePackSorter;
import net.fabricmc.fabric.impl.resource.loader.ModResourcePackUtil;
+import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.resource.ResourceType;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
@Mixin(ModResourcePackUtil.class)
public class AppendRepoAsResourcePack {
- @Inject(method = "appendModResourcePacks", at = @At("TAIL"))
- private static void onAppendModResourcePack(
- List<ModResourcePack> packs,
- ResourceType type,
- @Nullable String subPath,
- CallbackInfo ci
- ) {
- RepoModResourcePack.Companion.append(packs);
- }
+ @Inject(
+ method = "getModResourcePacks",
+ at = @At(value = "INVOKE", target = "Lnet/fabricmc/fabric/impl/resource/loader/ModResourcePackSorter;getPacks()Ljava/util/List;"),
+ require = 0
+ )
+ private static void onAppendModResourcePack(
+ FabricLoader fabricLoader, ResourceType type, @Nullable String subPath, CallbackInfoReturnable<List<ModResourcePack>> cir,
+ @Local ModResourcePackSorter sorter
+ ) {
+ RepoModResourcePack.Companion.append(sorter);
+ }
}
diff --git a/src/main/java/moe/nea/firmament/mixins/EntityUpdateEventListener.java b/src/main/java/moe/nea/firmament/mixins/EntityUpdateEventListener.java
index c2d6e46..d956da9 100644
--- a/src/main/java/moe/nea/firmament/mixins/EntityUpdateEventListener.java
+++ b/src/main/java/moe/nea/firmament/mixins/EntityUpdateEventListener.java
@@ -12,6 +12,7 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.s2c.play.EntityAttributesS2CPacket;
+import net.minecraft.network.packet.s2c.play.EntityEquipmentUpdateS2CPacket;
import net.minecraft.network.packet.s2c.play.EntityTrackerUpdateS2CPacket;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -22,21 +23,26 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientPlayNetworkHandler.class)
public abstract class EntityUpdateEventListener extends ClientCommonNetworkHandler {
- @Shadow
- private ClientWorld world;
+ @Shadow
+ private ClientWorld world;
- protected EntityUpdateEventListener(MinecraftClient client, ClientConnection connection, ClientConnectionState connectionState) {
- super(client, connection, connectionState);
- }
+ protected EntityUpdateEventListener(MinecraftClient client, ClientConnection connection, ClientConnectionState connectionState) {
+ super(client, connection, connectionState);
+ }
- @Inject(method = "onEntityAttributes", at = @At("TAIL"))
- private void onAttributeUpdate(EntityAttributesS2CPacket packet, CallbackInfo ci) {
- EntityUpdateEvent.Companion.publish(new EntityUpdateEvent.AttributeUpdate(
- (LivingEntity) world.getEntityById(packet.getEntityId()), packet.getEntries()));
- }
+ @Inject(method = "onEntityEquipmentUpdate", at = @At(value = "INVOKE", target = "Ljava/util/List;forEach(Ljava/util/function/Consumer;)V", shift = At.Shift.AFTER))
+ private void onEquipmentUpdate(EntityEquipmentUpdateS2CPacket packet, CallbackInfo ci, @Local LivingEntity entity) {
+ EntityUpdateEvent.Companion.publish(new EntityUpdateEvent.EquipmentUpdate(entity, packet.getEquipmentList()));
+ }
- @Inject(method = "onEntityTrackerUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/data/DataTracker;writeUpdatedEntries(Ljava/util/List;)V", shift = At.Shift.AFTER))
- private void onEntityTracker(EntityTrackerUpdateS2CPacket packet, CallbackInfo ci, @Local Entity entity) {
- EntityUpdateEvent.Companion.publish(new EntityUpdateEvent.TrackedDataUpdate(entity, packet.trackedValues()));
- }
+ @Inject(method = "onEntityAttributes", at = @At("TAIL"))
+ private void onAttributeUpdate(EntityAttributesS2CPacket packet, CallbackInfo ci) {
+ EntityUpdateEvent.Companion.publish(new EntityUpdateEvent.AttributeUpdate(
+ (LivingEntity) world.getEntityById(packet.getEntityId()), packet.getEntries()));
+ }
+
+ @Inject(method = "onEntityTrackerUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/data/DataTracker;writeUpdatedEntries(Ljava/util/List;)V", shift = At.Shift.AFTER))
+ private void onEntityTracker(EntityTrackerUpdateS2CPacket packet, CallbackInfo ci, @Local Entity entity) {
+ EntityUpdateEvent.Companion.publish(new EntityUpdateEvent.TrackedDataUpdate(entity, packet.trackedValues()));
+ }
}
diff --git a/src/main/java/moe/nea/firmament/mixins/PropertySignatureIgnorePatch.java b/src/main/java/moe/nea/firmament/mixins/PropertySignatureIgnorePatch.java
deleted file mode 100644
index e7331c5..0000000
--- a/src/main/java/moe/nea/firmament/mixins/PropertySignatureIgnorePatch.java
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-package moe.nea.firmament.mixins;
-
-import com.mojang.authlib.properties.Property;
-import moe.nea.firmament.features.fixes.Fixes;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-
-import java.security.PublicKey;
-
-@Mixin(value = Property.class, remap = false)
-public class PropertySignatureIgnorePatch {
- @Inject(method = "isSignatureValid", cancellable = true, at = @At("HEAD"), remap = false)
- public void onValidateSignature(PublicKey publicKey, CallbackInfoReturnable<Boolean> cir) {
- if (Fixes.TConfig.INSTANCE.getFixUnsignedPlayerSkins()) {
- cir.setReturnValue(true);
- }
- }
-
- @Inject(method = "signature", cancellable = true, at = @At("HEAD"), remap = false)
- public void returnEmptySignatureInsteadOfNull(CallbackInfoReturnable<String> cir) {
- if (Fixes.TConfig.INSTANCE.getFixUnsignedPlayerSkins()) {
- cir.setReturnValue("");
- }
- }
-
- @Inject(method = "hasSignature", cancellable = true, at = @At("HEAD"), remap = false)
- public void onHasSignature(CallbackInfoReturnable<Boolean> cir) {
- if (Fixes.TConfig.INSTANCE.getFixUnsignedPlayerSkins()) {
- cir.setReturnValue(true);
- }
- }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/accessor/AccessorWorldRenderer.java b/src/main/java/moe/nea/firmament/mixins/accessor/AccessorWorldRenderer.java
new file mode 100644
index 0000000..8b25562
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/accessor/AccessorWorldRenderer.java
@@ -0,0 +1,17 @@
+package moe.nea.firmament.mixins.accessor;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import net.minecraft.client.render.WorldRenderer;
+import net.minecraft.entity.player.BlockBreakingInfo;
+import org.jetbrains.annotations.NotNull;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+import java.util.SortedSet;
+
+@Mixin(WorldRenderer.class)
+public interface AccessorWorldRenderer {
+ @Accessor("blockBreakingProgressions")
+ @NotNull
+ Long2ObjectMap<SortedSet<BlockBreakingInfo>> getBlockBreakingProgressions_firmament();
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/ChangeColorOfLivingEntities.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/ChangeColorOfLivingEntities.java
new file mode 100644
index 0000000..2b96e5c
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/ChangeColorOfLivingEntities.java
@@ -0,0 +1,62 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyReturnValue;
+import com.llamalad7.mixinextras.sugar.Local;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.entity.LivingEntityRenderer;
+import net.minecraft.client.render.entity.model.EntityModel;
+import net.minecraft.client.render.entity.state.LivingEntityRenderState;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.LivingEntity;
+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.ModifyArg;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+/**
+ * Applies various rendering modifications from {@link EntityRenderTintEvent}
+ */
+@Mixin(LivingEntityRenderer.class)
+public class ChangeColorOfLivingEntities<T extends LivingEntity, S extends LivingEntityRenderState, M extends EntityModel<? super S>> {
+ @ModifyReturnValue(method = "getMixColor", at = @At("RETURN"))
+ private int changeColor(int original, @Local(argsOnly = true) S state) {
+ var tintState = EntityRenderTintEvent.HasTintRenderState.cast(state);
+ if (tintState.getHasTintOverride_firmament())
+ return tintState.getTint_firmament();
+ return original;
+ }
+
+ @ModifyArg(
+ method = "getOverlay",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/OverlayTexture;getU(F)I"),
+ allow = 1
+ )
+ private static float modifyLightOverlay(float originalWhiteOffset, @Local(argsOnly = true) LivingEntityRenderState state) {
+ var tintState = EntityRenderTintEvent.HasTintRenderState.cast(state);
+ if (tintState.getHasTintOverride_firmament() || tintState.getOverlayTexture_firmament() != null) {
+ return 1F; // TODO: add interpolation percentage to render state extension
+ }
+ return originalWhiteOffset;
+ }
+
+ @Inject(method = "render(Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;pop()V"))
+ private void afterRender(S livingEntityRenderState, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) {
+ var tintState = EntityRenderTintEvent.HasTintRenderState.cast(livingEntityRenderState);
+ var overlayTexture = tintState.getOverlayTexture_firmament();
+ if (overlayTexture != null && vertexConsumerProvider instanceof VertexConsumerProvider.Immediate imm) {
+ imm.drawCurrentLayer();
+ }
+ EntityRenderTintEvent.overlayOverride = null;
+ }
+
+ @Inject(method = "render(Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;push()V"))
+ private void beforeRender(S livingEntityRenderState, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) {
+ var tintState = EntityRenderTintEvent.HasTintRenderState.cast(livingEntityRenderState);
+ var overlayTexture = tintState.getOverlayTexture_firmament();
+ if (overlayTexture != null) {
+ EntityRenderTintEvent.overlayOverride = overlayTexture;
+ }
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/EntityRenderStateTint.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/EntityRenderStateTint.java
new file mode 100644
index 0000000..1019027
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/EntityRenderStateTint.java
@@ -0,0 +1,55 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import moe.nea.firmament.util.render.TintedOverlayTexture;
+import net.minecraft.client.render.entity.state.EntityRenderState;
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+
+@Mixin(EntityRenderState.class)
+public class EntityRenderStateTint implements EntityRenderTintEvent.HasTintRenderState {
+ @Unique
+ int tint = -1;
+ @Unique
+ TintedOverlayTexture overlayTexture;
+ @Unique
+ boolean hasTintOverride = false;
+
+ @Override
+ public int getTint_firmament() {
+ return tint;
+ }
+
+ @Override
+ public void setTint_firmament(int i) {
+ tint = i;
+ hasTintOverride = true;
+ }
+
+ @Override
+ public boolean getHasTintOverride_firmament() {
+ return hasTintOverride;
+ }
+
+ @Override
+ public void setHasTintOverride_firmament(boolean b) {
+ hasTintOverride = b;
+ }
+
+ @Override
+ public void reset_firmament() {
+ hasTintOverride = false;
+ overlayTexture = null;
+ }
+
+ @Override
+ public @Nullable TintedOverlayTexture getOverlayTexture_firmament() {
+ return overlayTexture;
+ }
+
+ @Override
+ public void setOverlayTexture_firmament(@Nullable TintedOverlayTexture tintedOverlayTexture) {
+ this.overlayTexture = tintedOverlayTexture;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/InjectIntoRenderState.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/InjectIntoRenderState.java
new file mode 100644
index 0000000..7938340
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/InjectIntoRenderState.java
@@ -0,0 +1,30 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.entity.EntityRenderer;
+import net.minecraft.client.render.entity.state.EntityRenderState;
+import net.minecraft.entity.Entity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+/**
+ * Dispatches {@link EntityRenderTintEvent} to collect additional render state used by {@link ChangeColorOfLivingEntities}
+ */
+@Mixin(EntityRenderer.class)
+public class InjectIntoRenderState<T extends Entity, S extends EntityRenderState> {
+
+ @Inject(
+ method = "updateRenderState",
+ at = @At("RETURN"))
+ private void onUpdateRenderState(T entity, S state, float tickDelta, CallbackInfo ci) {
+ var renderState = EntityRenderTintEvent.HasTintRenderState.cast(state);
+ renderState.reset_firmament();
+ var tintEvent = new EntityRenderTintEvent(
+ entity,
+ renderState
+ );
+ EntityRenderTintEvent.Companion.publish(tintEvent);
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/ReplaceOverlayTexture.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/ReplaceOverlayTexture.java
new file mode 100644
index 0000000..61e5c65
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/ReplaceOverlayTexture.java
@@ -0,0 +1,24 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Replaces the overlay texture used by rendering with the override specified in {@link EntityRenderTintEvent#overlayOverride}
+ */
+@Mixin(RenderLayer.Overlay.class)
+public class ReplaceOverlayTexture {
+ @ModifyExpressionValue(
+ method = {"method_23555", "method_23556"},
+ expect = 2,
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/GameRenderer;getOverlayTexture()Lnet/minecraft/client/render/OverlayTexture;"))
+ private static OverlayTexture replaceOverlayTexture(OverlayTexture original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return EntityRenderTintEvent.overlayOverride;
+ return original;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableEquipmentRenderer.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableEquipmentRenderer.java
new file mode 100644
index 0000000..d9c174c
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableEquipmentRenderer.java
@@ -0,0 +1,34 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.client.render.entity.equipment.EquipmentRenderer;
+import net.minecraft.util.Identifier;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Patch to make {@link EquipmentRenderer} use a {@link RenderLayer} that allows uses Minecraft's overlay texture, if a {@link EntityRenderTintEvent#overlayOverride} is specified.
+ */
+@Mixin(EquipmentRenderer.class)
+public class UseOverlayableEquipmentRenderer {
+ @WrapOperation(method = "render(Lnet/minecraft/client/render/entity/equipment/EquipmentModel$LayerType;Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/RenderLayer;getArmorCutoutNoCull(Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/RenderLayer;"))
+ private RenderLayer replace(Identifier texture, Operation<RenderLayer> original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return RenderLayer.getEntityTranslucent(texture);
+ return original.call(texture);
+ }
+
+ @ModifyExpressionValue(method = "render(Lnet/minecraft/client/render/entity/equipment/EquipmentModel$LayerType;Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V",
+ at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/OverlayTexture;DEFAULT_UV:I"))
+ private int replaceUvIndex(int original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return OverlayTexture.packUv(15, 10); // TODO: store this info in a global alongside overlayOverride
+ return original;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableHeadFeatureRenderer.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableHeadFeatureRenderer.java
new file mode 100644
index 0000000..07bc5cf
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableHeadFeatureRenderer.java
@@ -0,0 +1,25 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.client.render.entity.feature.HeadFeatureRenderer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Patch to make {@link HeadFeatureRenderer} use a {@link RenderLayer} that allows uses Minecraft's overlay texture, if a {@link EntityRenderTintEvent#overlayOverride} is specified.
+ * @see UseOverlayableItemRenderer
+ */
+@Mixin(HeadFeatureRenderer.class)
+public class UseOverlayableHeadFeatureRenderer {
+
+ @ModifyExpressionValue(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V",
+ at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/OverlayTexture;DEFAULT_UV:I"))
+ private int replaceUvIndex(int original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return OverlayTexture.packUv(15, 10); // TODO: store this info in a global alongside overlayOverride
+ return original;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableItemRenderer.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableItemRenderer.java
new file mode 100644
index 0000000..620ab2c
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableItemRenderer.java
@@ -0,0 +1,25 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.client.render.RenderPhase;
+import net.minecraft.client.render.item.ItemRenderState;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Patch to make {@link ItemRenderState} use a {@link RenderLayer} that allows uses Minecraft's overlay texture.
+ *
+ * @see UseOverlayableHeadFeatureRenderer
+ */
+@Mixin(ItemRenderState.LayerRenderState.class)
+public class UseOverlayableItemRenderer {
+ @ModifyExpressionValue(method = "render", at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/item/ItemRenderState$LayerRenderState;renderLayer:Lnet/minecraft/client/render/RenderLayer;"))
+ private RenderLayer replace(RenderLayer original) {
+ if (EntityRenderTintEvent.overlayOverride != null && original instanceof RenderLayer.MultiPhase multiPhase && multiPhase.phases.texture instanceof RenderPhase.Texture texture && texture.getId().isPresent())
+ return RenderLayer.getEntityTranslucent(texture.getId().get());
+ return original;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableSkullBlockEntityRenderer.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableSkullBlockEntityRenderer.java
new file mode 100644
index 0000000..9905af1
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableSkullBlockEntityRenderer.java
@@ -0,0 +1,25 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.client.render.block.entity.SkullBlockEntityRenderer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Patch to make {@link SkullBlockEntityRenderer} use a {@link RenderLayer} that allows uses Minecraft's overlay texture, if a {@link EntityRenderTintEvent#overlayOverride} is specified.
+ */
+
+@Mixin(SkullBlockEntityRenderer.class)
+public class UseOverlayableSkullBlockEntityRenderer {
+ @ModifyExpressionValue(method = "renderSkull",
+ at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/OverlayTexture;DEFAULT_UV:I"))
+ private static int replaceUvIndex(int original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return OverlayTexture.packUv(15, 10); // TODO: store this info in a global alongside overlayOverride
+ return original;
+ }
+
+}
diff --git a/src/main/kotlin/Firmament.kt b/src/main/kotlin/Firmament.kt
index 01905c7..0191036 100644
--- a/src/main/kotlin/Firmament.kt
+++ b/src/main/kotlin/Firmament.kt
@@ -19,6 +19,8 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents
+import net.fabricmc.fabric.api.resource.ResourceManagerHelper
+import net.fabricmc.fabric.api.resource.ResourcePackActivationType
import net.fabricmc.loader.api.FabricLoader
import net.fabricmc.loader.api.Version
import net.fabricmc.loader.api.metadata.ModMetadata
@@ -49,6 +51,7 @@ import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SBData
import moe.nea.firmament.util.data.IDataHolder
+import moe.nea.firmament.util.tr
object Firmament {
val modContainer by lazy { FabricLoader.getInstance().getModContainer(MOD_ID).get() }
@@ -117,9 +120,8 @@ object Firmament {
@JvmStatic
fun onClientInitialize() {
FeatureManager.subscribeEvents()
- var tick = 0
ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { instance ->
- TickEvent.publish(TickEvent(tick++))
+ TickEvent.publish(TickEvent(MC.currentTick++))
})
IDataHolder.registerEvents()
RepoManager.initialize()
@@ -145,6 +147,12 @@ object Firmament {
})
})
ClientInitEvent.publish(ClientInitEvent())
+ ResourceManagerHelper.registerBuiltinResourcePack(
+ identifier("transparent_storage"),
+ modContainer,
+ tr("firmament.resourcepack.transparentstorage", "Transparent Firmament Storage Overlay"),
+ ResourcePackActivationType.NORMAL
+ )
}
diff --git a/src/main/kotlin/commands/rome.kt b/src/main/kotlin/commands/rome.kt
index 13acb3c..8ae34f6 100644
--- a/src/main/kotlin/commands/rome.kt
+++ b/src/main/kotlin/commands/rome.kt
@@ -15,6 +15,7 @@ import moe.nea.firmament.features.debug.PowerUserTools
import moe.nea.firmament.features.inventory.buttons.InventoryButtons
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverviewScreen
+import moe.nea.firmament.features.mining.MiningBlockInfoUi
import moe.nea.firmament.gui.config.AllConfigsGui
import moe.nea.firmament.gui.config.BooleanHandler
import moe.nea.firmament.gui.config.ManagedConfig
@@ -217,6 +218,11 @@ fun firmamentCommand() = literal("firmament") {
}
}
}
+ thenLiteral("blocks") {
+ thenExecute {
+ ScreenUtil.setScreenLater(MiningBlockInfoUi.makeScreen())
+ }
+ }
thenLiteral("dumpchat") {
thenExecute {
MC.inGameHud.chatHud.messages.forEach {
@@ -246,6 +252,7 @@ fun firmamentCommand() = literal("firmament") {
source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.gametype", locrawInfo.gametype))
source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.mode", locrawInfo.mode))
source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.map", locrawInfo.map))
+ source.sendFeedback(tr("firmament.sbinfo.custommining", "Custom Mining: ${formatBool(locrawInfo.skyblockLocation?.hasCustomMining ?: false)}"))
}
}
}
@@ -254,6 +261,7 @@ fun firmamentCommand() = literal("firmament") {
val player = MC.player ?: return@thenExecute
player.world.getOtherEntities(player, player.boundingBox.expand(12.0))
.forEach(PowerUserTools::showEntity)
+ PowerUserTools.showEntity(player)
}
}
thenLiteral("callUrsa") {
diff --git a/src/main/kotlin/events/EntityRenderTintEvent.kt b/src/main/kotlin/events/EntityRenderTintEvent.kt
new file mode 100644
index 0000000..29b888b
--- /dev/null
+++ b/src/main/kotlin/events/EntityRenderTintEvent.kt
@@ -0,0 +1,66 @@
+package moe.nea.firmament.events
+
+import net.minecraft.client.render.GameRenderer
+import net.minecraft.client.render.OverlayTexture
+import net.minecraft.client.render.entity.state.EntityRenderState
+import net.minecraft.entity.Entity
+import net.minecraft.entity.LivingEntity
+import moe.nea.firmament.util.render.TintedOverlayTexture
+
+/**
+ * Change the tint color of a [LivingEntity]
+ */
+class EntityRenderTintEvent(
+ val entity: Entity,
+ val renderState: HasTintRenderState
+) : FirmamentEvent.Cancellable() {
+ init {
+ if (entity !is LivingEntity) {
+ cancel()
+ }
+ }
+
+ companion object : FirmamentEventBus<EntityRenderTintEvent>() {
+ /**
+ * Static variable containing an override for [GameRenderer.getOverlayTexture]. Should be only set briefly.
+ *
+ * This variable only affects render layers that naturally make use of the overlay texture, have proper overlay UVs set (`overlay u != 0`), and have a shader that makes use of the overlay (does not have the `NO_OVERLAY` flag set in its json definition).
+ *
+ * Currently supported layers: [net.minecraft.client.render.entity.equipment.EquipmentRenderer], [net.minecraft.client.render.entity.model.PlayerEntityModel], as well as some others naturally.
+ *
+ * @see moe.nea.firmament.mixins.render.entitytints.ReplaceOverlayTexture
+ * @see TintedOverlayTexture
+ */
+ @JvmField
+ var overlayOverride: OverlayTexture? = null
+ }
+
+ @Suppress("PropertyName", "FunctionName")
+ interface HasTintRenderState {
+ /**
+ * Multiplicative tint applied before the overlay.
+ */
+ var tint_firmament: Int
+
+ /**
+ * Must be set for [tint_firmament] to have any effect.
+ */
+ var hasTintOverride_firmament: Boolean
+
+ // TODO: allow for more specific selection of which layers get tinted
+ /**
+ * Specify a [TintedOverlayTexture] to be used. This does not apply to render layers not using the overlay texture.
+ * @see overlayOverride
+ */
+ var overlayTexture_firmament: TintedOverlayTexture?
+ fun reset_firmament()
+
+ companion object {
+ @JvmStatic
+ fun cast(state: EntityRenderState): HasTintRenderState {
+ return state as HasTintRenderState
+ }
+ }
+ }
+
+}
diff --git a/src/main/kotlin/events/EntityUpdateEvent.kt b/src/main/kotlin/events/EntityUpdateEvent.kt
index d091984..27a90f9 100644
--- a/src/main/kotlin/events/EntityUpdateEvent.kt
+++ b/src/main/kotlin/events/EntityUpdateEvent.kt
@@ -1,9 +1,11 @@
-
package moe.nea.firmament.events
+import com.mojang.datafixers.util.Pair
import net.minecraft.entity.Entity
+import net.minecraft.entity.EquipmentSlot
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.data.DataTracker
+import net.minecraft.item.ItemStack
import net.minecraft.network.packet.s2c.play.EntityAttributesS2CPacket
/**
@@ -13,19 +15,24 @@ import net.minecraft.network.packet.s2c.play.EntityAttributesS2CPacket
* *after* the values have been applied to the entity.
*/
sealed class EntityUpdateEvent : FirmamentEvent() {
- companion object : FirmamentEventBus<EntityUpdateEvent>()
+ companion object : FirmamentEventBus<EntityUpdateEvent>()
+
+ abstract val entity: Entity
- abstract val entity: Entity
+ data class AttributeUpdate(
+ override val entity: LivingEntity,
+ val attributes: List<EntityAttributesS2CPacket.Entry>,
+ ) : EntityUpdateEvent()
- data class AttributeUpdate(
- override val entity: LivingEntity,
- val attributes: List<EntityAttributesS2CPacket.Entry>,
- ) : EntityUpdateEvent()
+ data class TrackedDataUpdate(
+ override val entity: Entity,
+ val trackedValues: List<DataTracker.SerializedEntry<*>>,
+ ) : EntityUpdateEvent()
- data class TrackedDataUpdate(
- override val entity: Entity,
- val trackedValues: List<DataTracker.SerializedEntry<*>>,
- ) : EntityUpdateEvent()
+ data class EquipmentUpdate(
+ override val entity: Entity,
+ val newEquipment: List<Pair<EquipmentSlot, ItemStack>>,
+ ) : EntityUpdateEvent()
-// TODO: onEntityPassengersSet, onEntityAttach?, onEntityEquipmentUpdate, onEntityStatusEffect
+// TODO: onEntityPassengersSet, onEntityAttach?, onEntityStatusEffect
}
diff --git a/src/main/kotlin/events/IsSlotProtectedEvent.kt b/src/main/kotlin/events/IsSlotProtectedEvent.kt
index eac2d9b..8fe0a96 100644
--- a/src/main/kotlin/events/IsSlotProtectedEvent.kt
+++ b/src/main/kotlin/events/IsSlotProtectedEvent.kt
@@ -6,6 +6,10 @@ import net.minecraft.screen.slot.SlotActionType
import net.minecraft.text.Text
import moe.nea.firmament.util.CommonSoundEffects
import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.grey
+import moe.nea.firmament.util.hover
+import moe.nea.firmament.util.red
+import moe.nea.firmament.util.tr
data class IsSlotProtectedEvent(
val slot: Slot?,
@@ -35,6 +39,7 @@ data class IsSlotProtectedEvent(
INVENTORY_MOVE
;
}
+
companion object : FirmamentEventBus<IsSlotProtectedEvent>() {
@JvmStatic
@JvmOverloads
@@ -47,7 +52,11 @@ data class IsSlotProtectedEvent(
val event = IsSlotProtectedEvent(slot, action, false, itemStackOverride, origin)
publish(event)
if (event.isProtected && !event.silent) {
- MC.sendChat(Text.translatable("firmament.protectitem").append(event.itemStack.name))
+ MC.sendChat(tr("firmament.protectitem", "Firmament protected your item: ${event.itemStack.name}.\n")
+ .red()
+ .append(tr("firmament.protectitem.hoverhint", "Hover for more info.").grey())
+ .hover(tr("firmament.protectitem.hint",
+ "To unlock this item use the Lock Slot or Lock Item keybind from Firmament while hovering over this item.")))
CommonSoundEffects.playFailure()
}
return event.isProtected
diff --git a/src/main/kotlin/features/debug/AnimatedClothingScanner.kt b/src/main/kotlin/features/debug/AnimatedClothingScanner.kt
new file mode 100644
index 0000000..11b47a9
--- /dev/null
+++ b/src/main/kotlin/features/debug/AnimatedClothingScanner.kt
@@ -0,0 +1,51 @@
+package moe.nea.firmament.features.debug
+
+import net.minecraft.component.DataComponentTypes
+import net.minecraft.entity.Entity
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.commands.thenExecute
+import moe.nea.firmament.commands.thenLiteral
+import moe.nea.firmament.events.CommandEvent
+import moe.nea.firmament.events.EntityUpdateEvent
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.skyBlockId
+import moe.nea.firmament.util.tr
+
+object AnimatedClothingScanner {
+
+ var observedEntity: Entity? = null
+
+ @OptIn(ExperimentalStdlibApi::class)
+ @Subscribe
+ fun onUpdate(event: EntityUpdateEvent) {
+ if (event.entity != observedEntity) return
+ if (event is EntityUpdateEvent.EquipmentUpdate) {
+ event.newEquipment.forEach {
+ val id = it.second.skyBlockId?.neuItem
+ val colour = it.second.get(DataComponentTypes.DYED_COLOR)
+ ?.rgb?.toHexString(HexFormat.UpperCase)
+ ?.let { " #$it" } ?: ""
+ MC.sendChat(tr("firmament.fitstealer.update",
+ "[FIT CHECK][${MC.currentTick}] ${it.first.asString()} => ${id}${colour}"))
+ }
+ }
+ }
+
+ @Subscribe
+ fun onSubCommand(event: CommandEvent.SubCommand) {
+ event.subcommand("dev") {
+ thenLiteral("stealthisfit") {
+ thenExecute {
+ observedEntity =
+ if (observedEntity == null) MC.instance.targetedEntity else null
+
+ MC.sendChat(
+ observedEntity?.let {
+ tr("firmament.fitstealer.targeted", "Observing the equipment of ${it.name}.")
+ } ?: tr("firmament.fitstealer.targetlost", "No longer logging equipment."),
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/features/debug/PowerUserTools.kt b/src/main/kotlin/features/debug/PowerUserTools.kt
index ac7a6fb..8be5d5d 100644
--- a/src/main/kotlin/features/debug/PowerUserTools.kt
+++ b/src/main/kotlin/features/debug/PowerUserTools.kt
@@ -1,22 +1,31 @@
package moe.nea.firmament.features.debug
+import com.mojang.serialization.Codec
+import com.mojang.serialization.DynamicOps
import com.mojang.serialization.JsonOps
+import com.mojang.serialization.codecs.RecordCodecBuilder
import kotlin.jvm.optionals.getOrNull
import net.minecraft.block.SkullBlock
import net.minecraft.block.entity.SkullBlockEntity
import net.minecraft.component.DataComponentTypes
import net.minecraft.component.type.ProfileComponent
import net.minecraft.entity.Entity
+import net.minecraft.entity.EntityType
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
+import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtOps
+import net.minecraft.nbt.NbtString
+import net.minecraft.predicate.NbtPredicate
import net.minecraft.text.Text
import net.minecraft.text.TextCodecs
import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.hit.EntityHitResult
import net.minecraft.util.hit.HitResult
+import net.minecraft.util.math.Position
+import net.minecraft.util.math.Vec3d
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.CustomItemModelEvent
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
@@ -31,6 +40,7 @@ import moe.nea.firmament.util.ClipboardUtils
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.focusedItemStack
import moe.nea.firmament.util.mc.IntrospectableItemModelManager
+import moe.nea.firmament.util.mc.SNbtFormatter
import moe.nea.firmament.util.mc.SNbtFormatter.Companion.toPrettyString
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt
@@ -88,6 +98,11 @@ object PowerUserTools : FirmamentFeature {
}
fun showEntity(target: Entity) {
+ val nbt = NbtPredicate.entityToNbt(target)
+ nbt.remove("Inventory")
+ nbt.put("StyledName", TextCodecs.CODEC.encodeStart(NbtOps.INSTANCE, target.styledDisplayName).orThrow)
+ println(SNbtFormatter.prettify(nbt))
+ ClipboardUtils.setTextContent(SNbtFormatter.prettify(nbt))
MC.sendChat(Text.translatable("firmament.poweruser.entity.type", target.type))
MC.sendChat(Text.translatable("firmament.poweruser.entity.name", target.name))
MC.sendChat(Text.stringifiedTranslatable("firmament.poweruser.entity.position", target.pos))
diff --git a/src/main/kotlin/features/events/anniversity/CenturyRaffleFeatures.kt b/src/main/kotlin/features/events/anniversity/CenturyRaffleFeatures.kt
new file mode 100644
index 0000000..9935051
--- /dev/null
+++ b/src/main/kotlin/features/events/anniversity/CenturyRaffleFeatures.kt
@@ -0,0 +1,63 @@
+package moe.nea.firmament.features.events.anniversity
+
+import java.util.Optional
+import me.shedaniel.math.Color
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.text.Style
+import net.minecraft.util.Formatting
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.EntityRenderTintEvent
+import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.SkyblockId
+import moe.nea.firmament.util.render.TintedOverlayTexture
+import moe.nea.firmament.util.skyBlockId
+import moe.nea.firmament.util.skyblock.SkyBlockItems
+
+object CenturyRaffleFeatures {
+ object TConfig : ManagedConfig("centuryraffle", Category.EVENTS) {
+ val highlightPlayersForSlice by toggle("highlight-cake-players") { true }
+// val highlightAllPlayers by toggle("highlight-all-cake-players") { true }
+ }
+
+ val cakeIcon = "⛃"
+
+ val cakeColors = listOf(
+ CakeTeam(SkyBlockItems.SLICE_OF_BLUEBERRY_CAKE, Formatting.BLUE),
+ CakeTeam(SkyBlockItems.SLICE_OF_CHEESECAKE, Formatting.YELLOW),
+ CakeTeam(SkyBlockItems.SLICE_OF_GREEN_VELVET_CAKE, Formatting.GREEN),
+ CakeTeam(SkyBlockItems.SLICE_OF_RED_VELVET_CAKE, Formatting.RED),
+ CakeTeam(SkyBlockItems.SLICE_OF_STRAWBERRY_SHORTCAKE, Formatting.LIGHT_PURPLE),
+ )
+
+ data class CakeTeam(
+ val id: SkyblockId,
+ val formatting: Formatting,
+ ) {
+ val searchedTextRgb = formatting.colorValue!!
+ val brightenedRgb = Color.ofOpaque(searchedTextRgb)//.brighter(2.0)
+ val tintOverlay by lazy {
+ TintedOverlayTexture().setColor(brightenedRgb)
+ }
+ }
+
+ val sliceToColor = cakeColors.associateBy { it.id }
+
+ @Subscribe
+ fun onEntityRender(event: EntityRenderTintEvent) {
+ if (!TConfig.highlightPlayersForSlice) return
+ val requestedCakeTeam = sliceToColor[MC.stackInHand?.skyBlockId] ?: return
+ // TODO: cache the requested color
+ val player = event.entity as? PlayerEntity ?: return
+ val cakeColor: Style = player.styledDisplayName.visit(
+ { style, text ->
+ if (text == cakeIcon) Optional.of(style)
+ else Optional.empty()
+ }, Style.EMPTY).getOrNull() ?: return
+ if (cakeColor.color?.rgb == requestedCakeTeam.searchedTextRgb) {
+ event.renderState.overlayTexture_firmament = requestedCakeTeam.tintOverlay
+ }
+ }
+
+}
diff --git a/src/main/kotlin/features/inventory/SlotLocking.kt b/src/main/kotlin/features/inventory/SlotLocking.kt
index 7a3a152..0083c40 100644
--- a/src/main/kotlin/features/inventory/SlotLocking.kt
+++ b/src/main/kotlin/features/inventory/SlotLocking.kt
@@ -4,8 +4,17 @@ package moe.nea.firmament.features.inventory
import java.util.UUID
import org.lwjgl.glfw.GLFW
+import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
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.JsonObject
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.int
import kotlinx.serialization.serializer
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.entity.player.PlayerInventory
@@ -51,9 +60,66 @@ object SlotLocking : FirmamentFeature {
val lockedSlots: MutableSet<Int> = mutableSetOf(),
val lockedSlotsRift: MutableSet<Int> = mutableSetOf(),
val lockedUUIDs: MutableSet<UUID> = mutableSetOf(),
- val boundSlots: MutableMap<Int, Int> = mutableMapOf()
+ val boundSlots: BoundSlots = BoundSlots()
)
+ @Serializable
+ data class BoundSlot(
+ val hotbar: Int,
+ val inventory: Int,
+ )
+
+ @Serializable(with = BoundSlots.Serializer::class)
+ data class BoundSlots(
+ val pairs: MutableSet<BoundSlot> = mutableSetOf()
+ ) {
+ fun findMatchingSlots(index: Int): List<BoundSlot> {
+ return pairs.filter { it.hotbar == index || it.inventory == index }
+ }
+
+ fun removeDuplicateForInventory(index: Int) {
+ pairs.removeIf { it.inventory == index }
+ }
+
+ fun removeAllInvolving(index: Int): Boolean {
+ return pairs.removeIf { it.inventory == index || it.hotbar == index }
+ }
+
+ fun insert(hotbar: Int, inventory: Int) {
+ if (!TConfig.allowMultiBinding) {
+ removeAllInvolving(hotbar)
+ removeAllInvolving(inventory)
+ }
+ pairs.add(BoundSlot(hotbar, inventory))
+ }
+
+ object Serializer : KSerializer<BoundSlots> {
+ override val descriptor: SerialDescriptor
+ get() = serializer<JsonElement>().descriptor
+
+ override fun serialize(
+ encoder: Encoder,
+ value: BoundSlots
+ ) {
+ serializer<MutableSet<BoundSlot>>()
+ .serialize(encoder, value.pairs)
+ }
+
+ override fun deserialize(decoder: Decoder): BoundSlots {
+ decoder as JsonDecoder
+ val json = decoder.decodeJsonElement()
+ if (json is JsonObject) {
+ return BoundSlots(json.entries.map {
+ BoundSlot(it.key.toInt(), (it.value as JsonPrimitive).int)
+ }.toMutableSet())
+ }
+ return BoundSlots(decoder.json.decodeFromJsonElement(serializer<MutableSet<BoundSlot>>(), json))
+
+ }
+ }
+ }
+
+
object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
val lockSlot by keyBinding("lock") { GLFW.GLFW_KEY_L }
val lockUUID by keyBindingWithOutDefaultModifiers("lock-uuid") {
@@ -62,6 +128,7 @@ object SlotLocking : FirmamentFeature {
val slotBind by keyBinding("bind") { GLFW.GLFW_KEY_L }
val slotBindRequireShift by toggle("require-quick-move") { true }
val slotRenderLines by choice("bind-render") { SlotRenderLinesMode.ONLY_BOXES }
+ val allowMultiBinding by toggle("multi-bind") { true } // TODO: filter based on this option
val allowDroppingInDungeons by toggle("drop-in-dungeons") { true }
}
@@ -177,19 +244,19 @@ object SlotLocking : FirmamentFeature {
@Subscribe
fun onQuickMoveBoundSlot(it: IsSlotProtectedEvent) {
- val boundSlots = DConfig.data?.boundSlots ?: mapOf()
+ val boundSlots = DConfig.data?.boundSlots ?: BoundSlots()
val isValidAction =
it.actionType == SlotActionType.QUICK_MOVE || (it.actionType == SlotActionType.PICKUP && !TConfig.slotBindRequireShift)
if (!isValidAction) return
val handler = MC.handledScreen?.screenHandler ?: return
val slot = it.slot
if (slot != null && it.slot.inventory is PlayerInventory) {
- val boundSlot = boundSlots.entries.find {
- it.value == slot.index || it.key == slot.index
- } ?: return
+ val matchingSlots = boundSlots.findMatchingSlots(slot.index)
+ if (matchingSlots.isEmpty()) return
it.protectSilent()
- val inventorySlot = MC.handledScreen?.getSlotByIndex(boundSlot.value, true)
- inventorySlot?.swapWithHotBar(handler, boundSlot.key)
+ val boundSlot = matchingSlots.singleOrNull() ?: return
+ val inventorySlot = MC.handledScreen?.getSlotByIndex(boundSlot.inventory, true)
+ inventorySlot?.swapWithHotBar(handler, boundSlot.hotbar)
}
}
@@ -228,10 +295,8 @@ object SlotLocking : FirmamentFeature {
val boundSlots = DConfig.data?.boundSlots ?: return
lockedSlots?.remove(hotBarSlot.index)
lockedSlots?.remove(invSlot.index)
- boundSlots.entries.removeIf {
- it.value == invSlot.index
- }
- boundSlots[hotBarSlot.index] = invSlot.index
+ boundSlots.removeDuplicateForInventory(invSlot.index)
+ boundSlots.insert(hotBarSlot.index, invSlot.index)
DConfig.markDirty()
CommonSoundEffects.playSuccess()
return
@@ -245,9 +310,7 @@ object SlotLocking : FirmamentFeature {
storedLockingSlot = null
val boundSlots = DConfig.data?.boundSlots ?: return
if (slot != null)
- boundSlots.entries.removeIf {
- it.value == slot.index || it.key == slot.index
- }
+ boundSlots.removeAllInvolving(slot.index)
}
}
@@ -258,9 +321,10 @@ object SlotLocking : FirmamentFeature {
val accScreen = event.screen as AccessorHandledScreen
val sx = accScreen.x_Firmament
val sy = accScreen.y_Firmament
- for (it in boundSlots.entries) {
- val hotbarSlot = findByIndex(it.key) ?: continue
- val inventorySlot = findByIndex(it.value) ?: continue
+ val highlitSlots = mutableSetOf<Slot>()
+ for (it in boundSlots.pairs) {
+ val hotbarSlot = findByIndex(it.hotbar) ?: continue
+ val inventorySlot = findByIndex(it.inventory) ?: continue
val (hotX, hotY) = hotbarSlot.lineCenter()
val (invX, invY) = inventorySlot.lineCenter()
@@ -268,22 +332,27 @@ object SlotLocking : FirmamentFeature {
|| accScreen.focusedSlot_Firmament === inventorySlot
if (!anyHovered && TConfig.slotRenderLines == SlotRenderLinesMode.NOTHING)
continue
- val color = if (anyHovered)
- me.shedaniel.math.Color.ofOpaque(0x00FF00)
- else
- me.shedaniel.math.Color.ofTransparent(0xc0a0f000.toInt())
+ if (anyHovered) {
+ highlitSlots.add(hotbarSlot)
+ highlitSlots.add(inventorySlot)
+ }
+ fun color(highlit: Boolean) =
+ if (highlit)
+ me.shedaniel.math.Color.ofOpaque(0x00FF00)
+ else
+ me.shedaniel.math.Color.ofTransparent(0xc0a0f000.toInt())
if (TConfig.slotRenderLines == SlotRenderLinesMode.EVERYTHING || anyHovered)
event.context.drawLine(
invX + sx, invY + sy,
hotX + sx, hotY + sy,
- color
+ color(anyHovered)
)
event.context.drawBorder(hotbarSlot.x + sx,
hotbarSlot.y + sy,
- 16, 16, color.color)
+ 16, 16, color(hotbarSlot in highlitSlots).color)
event.context.drawBorder(inventorySlot.x + sx,
inventorySlot.y + sy,
- 16, 16, color.color)
+ 16, 16, color(inventorySlot in highlitSlots).color)
}
}
@@ -339,11 +408,9 @@ object SlotLocking : FirmamentFeature {
fun toggleSlotLock(slot: Slot) {
val lockedSlots = lockedSlots ?: return
- val boundSlots = DConfig.data?.boundSlots ?: mutableMapOf()
+ val boundSlots = DConfig.data?.boundSlots ?: BoundSlots()
if (slot.inventory is PlayerInventory) {
- if (boundSlots.entries.removeIf {
- it.value == slot.index || it.key == slot.index
- }) {
+ if (boundSlots.removeAllInvolving(slot.index)) {
// intentionally do nothing
} else if (slot.index in lockedSlots) {
lockedSlots.remove(slot.index)
diff --git a/src/main/kotlin/features/inventory/TimerInLore.kt b/src/main/kotlin/features/inventory/TimerInLore.kt
index f1b77c6..309ea61 100644
--- a/src/main/kotlin/features/inventory/TimerInLore.kt
+++ b/src/main/kotlin/features/inventory/TimerInLore.kt
@@ -80,7 +80,8 @@ object TimerInLore {
COMMUNITYPROJECTS("Contribute again", "Come back at"),
CHOCOLATEFACTORY("Next Charge", "Available at"),
STONKSAUCTION("Auction ends in", "Ends at"),
- LIZSTONKREDEMPTION("Resets in:", "Resets at");
+ LIZSTONKREDEMPTION("Resets in:", "Resets at"),
+ EVENTENDING("Event ends in:", "Ends at");
}
val regex =
diff --git a/src/main/kotlin/features/mining/MiningBlockInfoUi.kt b/src/main/kotlin/features/mining/MiningBlockInfoUi.kt
new file mode 100644
index 0000000..e8ea4f4
--- /dev/null
+++ b/src/main/kotlin/features/mining/MiningBlockInfoUi.kt
@@ -0,0 +1,54 @@
+package moe.nea.firmament.features.mining
+
+import io.github.notenoughupdates.moulconfig.observer.ObservableList
+import io.github.notenoughupdates.moulconfig.observer.Property
+import io.github.notenoughupdates.moulconfig.platform.ModernItemStack
+import io.github.notenoughupdates.moulconfig.xml.Bind
+import net.minecraft.client.gui.screen.Screen
+import net.minecraft.item.ItemStack
+import moe.nea.firmament.repo.MiningRepoData
+import moe.nea.firmament.repo.RepoManager
+import moe.nea.firmament.util.MoulConfigUtils
+import moe.nea.firmament.util.SkyBlockIsland
+
+object MiningBlockInfoUi {
+ class MiningInfo(miningData: MiningRepoData) {
+ @field:Bind("search")
+ @JvmField
+ var search = ""
+
+ @get:Bind("ores")
+ val blocks = miningData.customMiningBlocks.mapTo(ObservableList(mutableListOf())) { OreInfo(it, this) }
+ }
+
+ class OreInfo(block: MiningRepoData.CustomMiningBlock, info: MiningInfo) {
+ @get:Bind("oreName")
+ val oreName = block.name ?: "No Name"
+
+ @get:Bind("blocks")
+ val res = ObservableList(block.blocks189.map { BlockInfo(it, info) })
+ }
+
+ class BlockInfo(val block: MiningRepoData.Block189, val info: MiningInfo) {
+ @get:Bind("item")
+ val item = ModernItemStack.of(block.block?.let { ItemStack(it) } ?: ItemStack.EMPTY)
+
+ @get:Bind("isSelected")
+ val isSelected get() = info.search.let { block.isActiveIn(SkyBlockIsland.forMode(it)) }
+
+ @get:Bind("itemName")
+ val itemName get() = item.getDisplayName()
+
+ @get:Bind("restrictions")
+ val res = ObservableList(
+ if (block.onlyIn != null)
+ block.onlyIn.map { " §r- §a${it.userFriendlyName}" }
+ else
+ listOf("Everywhere")
+ )
+ }
+
+ fun makeScreen(): Screen {
+ return MoulConfigUtils.loadScreen("mining_block_info/index", MiningInfo(RepoManager.miningData), null)
+ }
+}
diff --git a/src/main/kotlin/gui/CheckboxComponent.kt b/src/main/kotlin/gui/CheckboxComponent.kt
index 761c086..fc48661 100644
--- a/src/main/kotlin/gui/CheckboxComponent.kt
+++ b/src/main/kotlin/gui/CheckboxComponent.kt
@@ -28,8 +28,8 @@ class CheckboxComponent<T>(
val ctx = (context.renderContext as ModernRenderContext).drawContext
ctx.drawGuiTexture(
RenderLayer::getGuiTextured,
- if (isEnabled()) Firmament.identifier("firmament:widget/checkbox_checked")
- else Firmament.identifier("firmament:widget/checkbox_unchecked"),
+ if (isEnabled()) Firmament.identifier("widget/checkbox_checked")
+ else Firmament.identifier("widget/checkbox_unchecked"),
0, 0,
16, 16
)
@@ -43,6 +43,7 @@ class CheckboxComponent<T>(
isClicking = false
if (context.isHovered)
state.set(value)
+ blur()
return true
}
if (mouseEvent.mouseState && mouseEvent.mouseButton == 0 && context.isHovered) {
diff --git a/src/main/kotlin/repo/ItemCache.kt b/src/main/kotlin/repo/ItemCache.kt
index 9fa0083..0967ad1 100644
--- a/src/main/kotlin/repo/ItemCache.kt
+++ b/src/main/kotlin/repo/ItemCache.kt
@@ -61,11 +61,12 @@ object ItemCache : IReloadable {
putShort("Damage", damage.toShort())
}
- private fun NbtCompound.transformFrom10809ToModern(): NbtCompound? =
+ private fun NbtCompound.transformFrom10809ToModern() = convert189ToModern(this@transformFrom10809ToModern)
+ fun convert189ToModern(nbtComponent: NbtCompound): NbtCompound? =
try {
df.update(
TypeReferences.ITEM_STACK,
- Dynamic(NbtOps.INSTANCE, this),
+ Dynamic(NbtOps.INSTANCE, nbtComponent),
-1,
SharedConstants.getGameVersion().saveVersion.id
).value as NbtCompound
@@ -184,31 +185,6 @@ object ItemCache : IReloadable {
var job: Job? = null
- object ReloadProgressHud : MoulConfigHud(
- "repo_reload", HudMeta(HudPosition(0.0, 0.0, 1F), Text.literal("Repo Reload"), 180, 18)) {
-
-
- var isEnabled = false
- override fun shouldRender(): Boolean {
- return isEnabled
- }
-
- @get:Bind("current")
- var current: Double = 0.0
-
- @get:Bind("label")
- var label: String = ""
-
- @get:Bind("max")
- var max: Double = 0.0
-
- fun reportProgress(label: String, current: Int, max: Int) {
- this.label = label
- this.current = current.toDouble()
- this.max = max.toDouble()
- }
- }
-
override fun reload(repository: NEURepository) {
val j = job
if (j != null && j.isActive) {
@@ -218,20 +194,10 @@ object ItemCache : IReloadable {
isFlawless = true
if (TestUtil.isInTest) return
job = Firmament.coroutineScope.launch {
- val items = repository.items?.items
- if (items == null) {
- ReloadProgressHud.isEnabled = false
- return@launch
- }
- val recacheItems = I18n.translate("firmament.repo.cache")
- ReloadProgressHud.reportProgress(recacheItems, 0, items.size)
- ReloadProgressHud.isEnabled = true
- var i = 0
+ val items = repository.items?.items ?: return@launch
items.values.forEach {
it.asItemStack() // Rebuild cache
- ReloadProgressHud.reportProgress(recacheItems, i++, items.size)
}
- ReloadProgressHud.isEnabled = false
}
}
diff --git a/src/main/kotlin/repo/MiningRepoData.kt b/src/main/kotlin/repo/MiningRepoData.kt
new file mode 100644
index 0000000..e40292d
--- /dev/null
+++ b/src/main/kotlin/repo/MiningRepoData.kt
@@ -0,0 +1,131 @@
+package moe.nea.firmament.repo
+
+import io.github.moulberry.repo.IReloadable
+import io.github.moulberry.repo.NEURepository
+import io.github.moulberry.repo.data.NEUItem
+import java.util.Collections
+import java.util.NavigableMap
+import java.util.TreeMap
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Transient
+import kotlinx.serialization.serializer
+import kotlin.jvm.optionals.getOrNull
+import kotlin.streams.asSequence
+import net.minecraft.block.Block
+import net.minecraft.item.BlockItem
+import net.minecraft.item.ItemStack
+import net.minecraft.nbt.NbtCompound
+import net.minecraft.text.Text
+import moe.nea.firmament.repo.ReforgeStore.kJson
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.SBData
+import moe.nea.firmament.util.SkyBlockIsland
+import moe.nea.firmament.util.SkyblockId
+import moe.nea.firmament.util.mc.FirmamentDataComponentTypes
+import moe.nea.firmament.util.mc.displayNameAccordingToNbt
+import moe.nea.firmament.util.skyblockId
+
+class MiningRepoData : IReloadable {
+ var customMiningAreas: Map<SkyBlockIsland, CustomMiningArea> = mapOf()
+ private set
+ var customMiningBlocks: List<CustomMiningBlock> = listOf()
+ private set
+ var toolsByBreakingPower: NavigableMap<BreakingPowerKey, SBItemStack> = Collections.emptyNavigableMap()
+ private set
+
+
+ data class BreakingPowerKey(
+ val breakingPower: Int,
+ val itemId: SkyblockId? = null
+ ) {
+ companion object {
+ val COMPARATOR: Comparator<BreakingPowerKey> =
+ Comparator
+ .comparingInt<BreakingPowerKey> { it.breakingPower }
+ .thenComparing(Comparator.comparing(
+ { it.itemId },
+ nullsFirst(Comparator.comparing<SkyblockId, Boolean> { "PICK" in it.neuItem || "BING" in it.neuItem }.thenComparing(Comparator.naturalOrder<SkyblockId>()))))
+ }
+ }
+
+ override fun reload(repo: NEURepository) {
+ customMiningAreas = repo.file("mining/custom_mining_areas.json")
+ ?.kJson(serializer()) ?: mapOf()
+ customMiningBlocks = repo.tree("mining/blocks")
+ .asSequence()
+ .filter { it.path.endsWith(".json") }
+ .map { it.kJson(serializer<CustomMiningBlock>()) }
+ .toList()
+ toolsByBreakingPower = Collections.unmodifiableNavigableMap(
+ repo.items.items
+ .values
+ .asSequence()
+ .map { SBItemStack(it.skyblockId) }
+ .filter { it.breakingPower > 0 }
+ .associateTo(TreeMap<BreakingPowerKey, SBItemStack>(BreakingPowerKey.COMPARATOR)) {
+ BreakingPowerKey(it.breakingPower, it.skyblockId) to it
+ })
+ }
+
+ fun getToolsThatCanBreak(breakingPower: Int): Collection<SBItemStack> {
+ return toolsByBreakingPower.tailMap(BreakingPowerKey(breakingPower, null), true).values
+ }
+
+ @Serializable
+ data class CustomMiningBlock(
+ val breakingPower: Int = 0,
+ val blockStrength: Int = 0,
+ val name: String? = null,
+ val baseDrop: SkyblockId? = null,
+ val blocks189: List<Block189> = emptyList()
+ ) {
+ @Transient
+ val dropItem = baseDrop?.let(::SBItemStack)
+ private val labeledStack by lazy {
+ dropItem?.asCopiedItemStack()?.also(::markItemStack)
+ }
+
+ private fun markItemStack(itemStack: ItemStack) {
+ itemStack.set(FirmamentDataComponentTypes.CUSTOM_MINING_BLOCK_DATA, this)
+ if (name != null)
+ itemStack.displayNameAccordingToNbt = Text.literal(name)
+ }
+
+ fun getDisplayItem(block: Block): ItemStack {
+ return labeledStack ?: ItemStack(block).also(::markItemStack)
+ }
+ }
+
+ @Serializable
+ data class Block189(
+ val itemId: String,
+ val damage: Short = 0,
+ val onlyIn: List<SkyBlockIsland>? = null,
+ ) {
+ @Transient
+ val block = convertToModernBlock()
+
+ val isCurrentlyActive: Boolean
+ get() = isActiveIn(SBData.skyblockLocation ?: SkyBlockIsland.NIL)
+
+ fun isActiveIn(location: SkyBlockIsland) = onlyIn == null || location in onlyIn
+
+ private fun convertToModernBlock(): Block? {
+ // TODO: this should be in a shared util, really
+ val newCompound = ItemCache.convert189ToModern(NbtCompound().apply {
+ putString("id", itemId)
+ putShort("Damage", damage)
+ }) ?: return null
+ val itemStack = ItemStack.fromNbt(MC.defaultRegistries, newCompound).getOrNull() ?: return null
+ val blockItem = itemStack.item as? BlockItem ?: return null
+ return blockItem.block
+ }
+ }
+
+ @Serializable
+ data class CustomMiningArea(
+ val isSpecialMining: Boolean = true
+ )
+
+
+}
diff --git a/src/main/kotlin/repo/RepoManager.kt b/src/main/kotlin/repo/RepoManager.kt
index 6d9ba14..e50a131 100644
--- a/src/main/kotlin/repo/RepoManager.kt
+++ b/src/main/kotlin/repo/RepoManager.kt
@@ -54,6 +54,7 @@ object RepoManager {
val essenceRecipeProvider = EssenceRecipeProvider()
val recipeCache = BetterRepoRecipeCache(essenceRecipeProvider, ReforgeStore)
+ val miningData = MiningRepoData()
fun makeNEURepository(path: Path): NEURepository {
return NEURepository.of(path).apply {
@@ -63,6 +64,8 @@ object RepoManager {
registerReloadListener(ItemNameLookup)
registerReloadListener(ReforgeStore)
registerReloadListener(essenceRecipeProvider)
+ registerReloadListener(recipeCache)
+ registerReloadListener(miningData)
ReloadRegistrationEvent.publish(ReloadRegistrationEvent(this))
registerReloadListener {
if (TestUtil.isInTest) return@registerReloadListener
@@ -73,7 +76,6 @@ object RepoManager {
}
}
}
- registerReloadListener(recipeCache)
}
}
@@ -102,14 +104,7 @@ object RepoManager {
fun launchAsyncUpdate(force: Boolean = false) {
Firmament.coroutineScope.launch {
- ItemCache.ReloadProgressHud.reportProgress("Downloading", 0, -1) // TODO: replace with a proper bouncy bar
- ItemCache.ReloadProgressHud.isEnabled = true
- try {
- RepoDownloadManager.downloadUpdate(force)
- ItemCache.ReloadProgressHud.reportProgress("Download complete", 1, 1)
- } finally {
- ItemCache.ReloadProgressHud.isEnabled = false
- }
+ RepoDownloadManager.downloadUpdate(force)
reload()
}
}
@@ -127,10 +122,6 @@ object RepoManager {
return
}
try {
- ItemCache.ReloadProgressHud.reportProgress("Reloading from Disk",
- 0,
- -1) // TODO: replace with a proper bouncy bar
- ItemCache.ReloadProgressHud.isEnabled = true
logger.info("Repo reload started.")
neuRepo.reload()
logger.info("Repo reload completed.")
@@ -140,7 +131,6 @@ object RepoManager {
tr("firmament.repo.reloadfail",
"Failed to reload repository. This will result in some mod features not working.")
)
- ItemCache.ReloadProgressHud.isEnabled = false
}
}
diff --git a/src/main/kotlin/repo/RepoModResourcePack.kt b/src/main/kotlin/repo/RepoModResourcePack.kt
index 617efec..2fdf710 100644
--- a/src/main/kotlin/repo/RepoModResourcePack.kt
+++ b/src/main/kotlin/repo/RepoModResourcePack.kt
@@ -5,6 +5,7 @@ import java.nio.file.Files
import java.nio.file.Path
import java.util.*
import net.fabricmc.fabric.api.resource.ModResourcePack
+import net.fabricmc.fabric.impl.resource.loader.ModResourcePackSorter
import net.fabricmc.loader.api.FabricLoader
import net.fabricmc.loader.api.metadata.ModMetadata
import kotlin.io.path.exists
@@ -28,9 +29,9 @@ import moe.nea.firmament.Firmament
class RepoModResourcePack(val basePath: Path) : ModResourcePack {
companion object {
- fun append(packs: MutableList<in ModResourcePack>) {
+ fun append(packs: ModResourcePackSorter) {
Firmament.logger.info("Registering mod resource pack")
- packs.add(RepoModResourcePack(RepoDownloadManager.repoSavedLocation))
+ packs.addPack(RepoModResourcePack(RepoDownloadManager.repoSavedLocation))
}
fun createResourceDirectly(identifier: Identifier): Optional<Resource> {
diff --git a/src/main/kotlin/repo/SBItemStack.kt b/src/main/kotlin/repo/SBItemStack.kt
index da34707..3690866 100644
--- a/src/main/kotlin/repo/SBItemStack.kt
+++ b/src/main/kotlin/repo/SBItemStack.kt
@@ -34,6 +34,7 @@ import moe.nea.firmament.util.modifyExtraAttributes
import moe.nea.firmament.util.petData
import moe.nea.firmament.util.prepend
import moe.nea.firmament.util.reconstitute
+import moe.nea.firmament.util.removeColorCodes
import moe.nea.firmament.util.skyBlockId
import moe.nea.firmament.util.skyblock.ItemType
import moe.nea.firmament.util.skyblock.Rarity
@@ -81,6 +82,7 @@ data class SBItemStack constructor(
}
val EMPTY = SBItemStack(SkyblockId.NULL, 0)
+ private val BREAKING_POWER_REGEX = "Breaking Power (?<power>[0-9]+)".toPattern()
operator fun invoke(itemStack: ItemStack): SBItemStack {
val skyblockId = itemStack.skyBlockId ?: SkyblockId.NULL
return SBItemStack(
@@ -349,6 +351,12 @@ data class SBItemStack constructor(
private var itemStack_: ItemStack? = null
+ val breakingPower: Int
+ get() =
+ BREAKING_POWER_REGEX.useMatch(neuItem?.lore?.firstOrNull()?.removeColorCodes()) {
+ group("power").toInt()
+ } ?: 0
+
private val itemStack: ItemStack
get() {
val itemStack = itemStack_ ?: run {
diff --git a/src/main/kotlin/util/MC.kt b/src/main/kotlin/util/MC.kt
index 215d2a8..c1a5e65 100644
--- a/src/main/kotlin/util/MC.kt
+++ b/src/main/kotlin/util/MC.kt
@@ -7,11 +7,13 @@ import net.minecraft.client.gui.hud.InGameHud
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.client.network.ClientPlayerEntity
+import net.minecraft.client.render.GameRenderer
import net.minecraft.client.render.WorldRenderer
import net.minecraft.client.render.item.ItemRenderer
import net.minecraft.client.world.ClientWorld
import net.minecraft.entity.Entity
import net.minecraft.item.Item
+import net.minecraft.item.ItemStack
import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket
import net.minecraft.registry.BuiltinRegistries
import net.minecraft.registry.RegistryKeys
@@ -85,6 +87,7 @@ object MC {
inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl)
inline val itemRenderer: ItemRenderer get() = instance.itemRenderer
inline val worldRenderer: WorldRenderer get() = instance.worldRenderer
+ inline val gameRenderer: GameRenderer get() = instance.gameRenderer
inline val networkHandler get() = player?.networkHandler
inline val instance get() = MinecraftClient.getInstance()
inline val keyboard get() = instance.keyboard
@@ -96,6 +99,7 @@ object MC {
inline val soundManager get() = instance.soundManager
inline val player: ClientPlayerEntity? get() = TestUtil.unlessTesting { instance.player }
inline val camera: Entity? get() = instance.cameraEntity
+ inline val stackInHand: ItemStack get() = player?.inventory?.mainHandStack ?: ItemStack.EMPTY
inline val guiAtlasManager get() = instance.guiAtlasManager
inline val world: ClientWorld? get() = TestUtil.unlessTesting { instance.world }
inline val playerName: String? get() = player?.name?.unformattedString
@@ -109,6 +113,7 @@ object MC {
val defaultRegistries: RegistryWrapper.WrapperLookup by lazy { BuiltinRegistries.createWrapperLookup() }
inline val currentOrDefaultRegistries get() = currentRegistries ?: defaultRegistries
val defaultItems: RegistryWrapper.Impl<Item> by lazy { defaultRegistries.getOrThrow(RegistryKeys.ITEM) }
+ var currentTick = 0
var lastWorld: World? = null
get() {
field = world ?: field
diff --git a/src/main/kotlin/util/SBData.kt b/src/main/kotlin/util/SBData.kt
index b2f9449..1a4734c 100644
--- a/src/main/kotlin/util/SBData.kt
+++ b/src/main/kotlin/util/SBData.kt
@@ -31,6 +31,10 @@ object SBData {
val hypixelTimeZone = ZoneId.of("US/Eastern")
private var hasReceivedProfile = false
var locraw: Locraw? = null
+
+ /**
+ * The current server location the player is in. This will be null outside of SkyBlock.
+ */
val skyblockLocation: SkyBlockIsland? get() = locraw?.skyblockLocation
val hasValidLocraw get() = locraw?.server !in listOf("limbo", null)
val isOnSkyblock get() = locraw?.gametype == "SKYBLOCK"
diff --git a/src/main/kotlin/util/SkyBlockIsland.kt b/src/main/kotlin/util/SkyBlockIsland.kt
index a86543c..e7f955a 100644
--- a/src/main/kotlin/util/SkyBlockIsland.kt
+++ b/src/main/kotlin/util/SkyBlockIsland.kt
@@ -1,4 +1,3 @@
-
package moe.nea.firmament.util
import kotlinx.serialization.KSerializer
@@ -13,33 +12,41 @@ import moe.nea.firmament.repo.RepoManager
@Serializable(with = SkyBlockIsland.Serializer::class)
class SkyBlockIsland
private constructor(
- val locrawMode: String,
+ val locrawMode: String,
) {
- object Serializer : KSerializer<SkyBlockIsland> {
- override val descriptor: SerialDescriptor
- get() = PrimitiveSerialDescriptor("SkyBlockIsland", PrimitiveKind.STRING)
-
- override fun deserialize(decoder: Decoder): SkyBlockIsland {
- return forMode(decoder.decodeString())
- }
-
- override fun serialize(encoder: Encoder, value: SkyBlockIsland) {
- encoder.encodeString(value.locrawMode)
- }
- }
- companion object {
- private val allIslands = mutableMapOf<String, SkyBlockIsland>()
- fun forMode(mode: String): SkyBlockIsland = allIslands.computeIfAbsent(mode, ::SkyBlockIsland)
- val HUB = forMode("hub")
- val PRIVATE_ISLAND = forMode("dynamic")
- val RIFT = forMode("rift")
- val MINESHAFT = forMode("mineshaft")
- val GARDEN = forMode("garden")
- val DUNGEON = forMode("dungeon")
- }
-
- val userFriendlyName
- get() = RepoManager.neuRepo.constants.islands.areaNames
- .getOrDefault(locrawMode, locrawMode)
+ object Serializer : KSerializer<SkyBlockIsland> {
+ override val descriptor: SerialDescriptor
+ get() = PrimitiveSerialDescriptor("SkyBlockIsland", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): SkyBlockIsland {
+ return forMode(decoder.decodeString())
+ }
+
+ override fun serialize(encoder: Encoder, value: SkyBlockIsland) {
+ encoder.encodeString(value.locrawMode)
+ }
+ }
+
+ companion object {
+ private val allIslands = mutableMapOf<String, SkyBlockIsland>()
+ fun forMode(mode: String): SkyBlockIsland = allIslands.computeIfAbsent(mode, ::SkyBlockIsland)
+ val HUB = forMode("hub")
+ val DWARVEN_MINES = forMode("dwarven_mines")
+ val CRYSTAL_HOLLOWS = forMode("crystal_hollows")
+ val CRIMSON_ISLE = forMode("crimson_isle")
+ val PRIVATE_ISLAND = forMode("dynamic")
+ val RIFT = forMode("rift")
+ val MINESHAFT = forMode("mineshaft")
+ val GARDEN = forMode("garden")
+ val DUNGEON = forMode("dungeon")
+ val NIL = forMode("_")
+ }
+
+ val hasCustomMining
+ get() = RepoManager.miningData.customMiningAreas[this]?.isSpecialMining ?: false
+
+ val userFriendlyName
+ get() = RepoManager.neuRepo.constants.islands.areaNames
+ .getOrDefault(locrawMode, locrawMode)
}
diff --git a/src/main/kotlin/util/SkyblockId.kt b/src/main/kotlin/util/SkyblockId.kt
index a99afda..a31255c 100644
--- a/src/main/kotlin/util/SkyblockId.kt
+++ b/src/main/kotlin/util/SkyblockId.kt
@@ -34,7 +34,7 @@ import moe.nea.firmament.util.json.DashlessUUIDSerializer
*/
@JvmInline
@Serializable
-value class SkyblockId(val neuItem: String) {
+value class SkyblockId(val neuItem: String) : Comparable<SkyblockId> {
val identifier
get() = Identifier.of("skyblockitem",
neuItem.lowercase().replace(";", "__")
@@ -48,6 +48,10 @@ value class SkyblockId(val neuItem: String) {
return neuItem
}
+ override fun compareTo(other: SkyblockId): Int {
+ return neuItem.compareTo(other.neuItem)
+ }
+
/**
* A bazaar stock item id, as returned by the HyPixel bazaar api endpoint.
* These are not equivalent to the in-game ids, or the NEU repo ids, and in fact, do not refer to items, but instead
diff --git a/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt b/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt
index 012f52e..0866665 100644
--- a/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt
+++ b/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt
@@ -1,12 +1,15 @@
package moe.nea.firmament.util.mc
import com.mojang.serialization.Codec
+import io.netty.buffer.ByteBuf
import net.minecraft.component.ComponentType
+import net.minecraft.network.codec.PacketCodec
import net.minecraft.registry.Registries
import net.minecraft.registry.Registry
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ClientInitEvent
+import moe.nea.firmament.repo.MiningRepoData
object FirmamentDataComponentTypes {
@@ -26,11 +29,32 @@ object FirmamentDataComponentTypes {
)
}
+ fun <T> errorCodec(message: String): PacketCodec<in ByteBuf, T> =
+ object : PacketCodec<ByteBuf, T> {
+ override fun decode(buf: ByteBuf?): T? {
+ error(message)
+ }
+
+ override fun encode(buf: ByteBuf?, value: T?) {
+ error(message)
+ }
+ }
+
+ fun <T, B : ComponentType.Builder<T>> B.neverEncode(message: String = "This element should never be encoded or decoded"): B {
+ packetCodec(errorCodec(message))
+ codec(null)
+ return this
+ }
+
val IS_BROKEN = register<Boolean>(
"is_broken"
) {
it.codec(Codec.BOOL.fieldOf("is_broken").codec())
}
+ val CUSTOM_MINING_BLOCK_DATA = register<MiningRepoData.CustomMiningBlock>("custom_mining_block") {
+ it.neverEncode()
+ }
+
}
diff --git a/src/main/kotlin/util/regex.kt b/src/main/kotlin/util/regex.kt
index a44435c..f239810 100644
--- a/src/main/kotlin/util/regex.kt
+++ b/src/main/kotlin/util/regex.kt
@@ -16,12 +16,13 @@ import kotlin.time.Duration.Companion.seconds
inline fun <T> String.ifMatches(regex: Regex, block: (MatchResult) -> T): T? =
regex.matchEntire(this)?.let(block)
-inline fun <T> Pattern.useMatch(string: String, block: Matcher.() -> T): T? {
+inline fun <T> Pattern.useMatch(string: String?, block: Matcher.() -> T): T? {
contract {
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}
- return matcher(string)
- .takeIf(Matcher::matches)
+ return string
+ ?.let(this::matcher)
+ ?.takeIf(Matcher::matches)
?.let(block)
}
diff --git a/src/main/kotlin/util/render/TintedOverlayTexture.kt b/src/main/kotlin/util/render/TintedOverlayTexture.kt
new file mode 100644
index 0000000..a02eccc
--- /dev/null
+++ b/src/main/kotlin/util/render/TintedOverlayTexture.kt
@@ -0,0 +1,44 @@
+package moe.nea.firmament.util.render
+
+import com.mojang.blaze3d.platform.GlConst
+import com.mojang.blaze3d.systems.RenderSystem
+import me.shedaniel.math.Color
+import net.minecraft.client.render.OverlayTexture
+import net.minecraft.util.math.ColorHelper
+import moe.nea.firmament.util.ErrorUtil
+
+class TintedOverlayTexture : OverlayTexture() {
+ companion object {
+ val size = 16
+ }
+
+ private var lastColor: Color? = null
+ fun setColor(color: Color): TintedOverlayTexture {
+ val image = ErrorUtil.notNullOr(texture.image, "Disposed TintedOverlayTexture written to") { return this }
+ if (color == lastColor) return this
+ lastColor = color
+
+ for (i in 0..<size) {
+ for (j in 0..<size) {
+ if (i < 8) {
+ image.setColorArgb(j, i, 0xB2FF0000.toInt())
+ } else {
+ val k = ((1F - j / 15F * 0.75F) * 255F).toInt()
+ image.setColorArgb(j, i, ColorHelper.withAlpha(k, color.color))
+ }
+ }
+ }
+
+ RenderSystem.activeTexture(GlConst.GL_TEXTURE1)
+ texture.bindTexture()
+ texture.setFilter(false, false)
+ texture.setClamp(true)
+ image.upload(0,
+ 0, 0,
+ 0, 0,
+ image.width, image.height,
+ false)
+ RenderSystem.activeTexture(GlConst.GL_TEXTURE0)
+ return this
+ }
+}
diff --git a/src/main/kotlin/util/skyblock/SkyBlockItems.kt b/src/main/kotlin/util/skyblock/SkyBlockItems.kt
index cfd8429..ca2b17b 100644
--- a/src/main/kotlin/util/skyblock/SkyBlockItems.kt
+++ b/src/main/kotlin/util/skyblock/SkyBlockItems.kt
@@ -8,4 +8,9 @@ object SkyBlockItems {
val DIAMOND = SkyblockId("DIAMOND")
val ANCESTRAL_SPADE = SkyblockId("ANCESTRAL_SPADE")
val REFORGE_ANVIL = SkyblockId("REFORGE_ANVIL")
+ val SLICE_OF_BLUEBERRY_CAKE = SkyblockId("SLICE_OF_BLUEBERRY_CAKE")
+ val SLICE_OF_CHEESECAKE = SkyblockId("SLICE_OF_CHEESECAKE")
+ val SLICE_OF_GREEN_VELVET_CAKE = SkyblockId("SLICE_OF_GREEN_VELVET_CAKE")
+ val SLICE_OF_RED_VELVET_CAKE = SkyblockId("SLICE_OF_RED_VELVET_CAKE")
+ val SLICE_OF_STRAWBERRY_SHORTCAKE = SkyblockId("SLICE_OF_STRAWBERRY_SHORTCAKE")
}
diff --git a/src/main/kotlin/util/textutil.kt b/src/main/kotlin/util/textutil.kt
index c295ae0..806f61e 100644
--- a/src/main/kotlin/util/textutil.kt
+++ b/src/main/kotlin/util/textutil.kt
@@ -2,6 +2,7 @@ package moe.nea.firmament.util
import java.util.Optional
import net.minecraft.text.ClickEvent
+import net.minecraft.text.HoverEvent
import net.minecraft.text.MutableText
import net.minecraft.text.OrderedText
import net.minecraft.text.PlainTextContent
@@ -126,6 +127,7 @@ fun MutableText.darkGrey() = withColor(Formatting.DARK_GRAY)
fun MutableText.red() = withColor(Formatting.RED)
fun MutableText.white() = withColor(Formatting.WHITE)
fun MutableText.bold(): MutableText = styled { it.withBold(true) }
+fun MutableText.hover(text: Text): MutableText = styled {it.withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, text))}
fun MutableText.clickCommand(command: String): MutableText {
diff --git a/src/main/resources/assets/firmament/gui/mining_block_info/index.xml b/src/main/resources/assets/firmament/gui/mining_block_info/index.xml
new file mode 100644
index 0000000..6404995
--- /dev/null
+++ b/src/main/resources/assets/firmament/gui/mining_block_info/index.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<Root xmlns="http://notenoughupdates.org/moulconfig"
+ xmlns:firm="http://firmament.nea.moe/moulconfig"
+>
+ <Gui>
+ <Column>
+ <Row>
+ <Text text="Search: "/>
+ <TextField value="@search"/>
+ </Row>
+ <ScrollPanel width="200" height="150">
+ <Array data="@ores">
+ <Column>
+ <Text text="@oreName"/>
+ <Array data="@blocks">
+ <Row>
+ <When condition="@isSelected">
+ <Center>
+ <Text text="§a+" textAlign="CENTER" width="10"/>
+ </Center>
+ <Spacer width="10" height="0"/>
+ </When>
+ <firm:Hover lines="@restrictions">
+ <Row>
+ <ItemStack value="@item"/>
+ <Align horizontal="LEFT" vertical="CENTER">
+ <Text text="@itemName"/>
+ </Align>
+ </Row>
+ </firm:Hover>
+ </Row>
+ </Array>
+ </Column>
+ </Array>
+ </ScrollPanel>
+ </Column>
+ </Gui>
+</Root>
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 3b988b1..02c11ee 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -40,6 +40,9 @@
"modmenu": [
"moe.nea.firmament.compat.modmenu.FirmamentModMenuPlugin"
],
+ "jade": [
+ "moe.nea.firmament.compat.jade.FirmamentJadePlugin"
+ ],
"jarvis": [
"moe.nea.firmament.jarvis.JarvisIntegration"
]
@@ -48,7 +51,7 @@
"firmament.mixins.json"
],
"depends": {
- "fabric": "*",
+ "fabric": ">=${fabric_api_version}",
"fabric-language-kotlin": ">=${fabric_kotlin_version}",
"minecraft": ">=${minecraft_version}"
},
diff --git a/src/main/resources/firmament.accesswidener b/src/main/resources/firmament.accesswidener
index 7418529..fd79cb5 100644
--- a/src/main/resources/firmament.accesswidener
+++ b/src/main/resources/firmament.accesswidener
@@ -20,3 +20,9 @@ mutable field net/minecraft/screen/slot/Slot y I
accessible field net/minecraft/entity/player/PlayerEntity PLAYER_MODEL_PARTS Lnet/minecraft/entity/data/TrackedData;
accessible field net/minecraft/client/render/WorldRenderer chunks Lnet/minecraft/client/render/BuiltChunkStorage;
+accessible field net/minecraft/client/render/OverlayTexture texture Lnet/minecraft/client/texture/NativeImageBackedTexture;
+
+accessible method net/minecraft/client/render/RenderPhase$Texture getId ()Ljava/util/Optional;
+accessible field net/minecraft/client/render/RenderLayer$MultiPhase phases Lnet/minecraft/client/render/RenderLayer$MultiPhaseParameters;
+accessible field net/minecraft/client/render/RenderLayer$MultiPhaseParameters texture Lnet/minecraft/client/render/RenderPhase$TextureBase;
+accessible field net/minecraft/client/network/ClientPlayerInteractionManager currentBreakingPos Lnet/minecraft/util/math/BlockPos;
diff --git a/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png
new file mode 100644
index 0000000..1831ef3
--- /dev/null
+++ b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png
Binary files differ
diff --git a/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png
new file mode 100644
index 0000000..5b774b2
--- /dev/null
+++ b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png
Binary files differ
diff --git a/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta
new file mode 100644
index 0000000..94b9a1d
--- /dev/null
+++ b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta
@@ -0,0 +1,10 @@
+{
+ "gui": {
+ "scaling": {
+ "type": "nine_slice",
+ "width": 17,
+ "height": 18,
+ "border": 2
+ }
+ }
+}
diff --git a/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png
new file mode 100644
index 0000000..d4852d8
--- /dev/null
+++ b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png
Binary files differ
diff --git a/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png.mcmeta b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png.mcmeta
new file mode 100644
index 0000000..5964a6f
--- /dev/null
+++ b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png.mcmeta
@@ -0,0 +1,10 @@
+{
+ "gui": {
+ "scaling": {
+ "type": "nine_slice",
+ "width": 91,
+ "height": 184,
+ "border": 7
+ }
+ }
+}
diff --git a/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png
new file mode 100644
index 0000000..61e9ee5
--- /dev/null
+++ b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png
Binary files differ
diff --git a/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta
new file mode 100644
index 0000000..cd2857e
--- /dev/null
+++ b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta
@@ -0,0 +1,9 @@
+{
+ "gui": {
+ "scaling": {
+ "type": "tile",
+ "width": 162,
+ "height": 18
+ }
+ }
+}
diff --git a/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png
new file mode 100644
index 0000000..653a99e
--- /dev/null
+++ b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png
Binary files differ
diff --git a/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta
new file mode 100644
index 0000000..a29299d
--- /dev/null
+++ b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta
@@ -0,0 +1,10 @@
+{
+ "gui": {
+ "scaling": {
+ "type": "nine_slice",
+ "width": 176,
+ "height": 222,
+ "border": 10
+ }
+ }
+}
diff --git a/src/main/resources/resourcepacks/transparent_storage/pack.mcmeta b/src/main/resources/resourcepacks/transparent_storage/pack.mcmeta
new file mode 100644
index 0000000..c37df06
--- /dev/null
+++ b/src/main/resources/resourcepacks/transparent_storage/pack.mcmeta
@@ -0,0 +1,10 @@
+{
+ "pack": {
+ "pack_format": 15,
+ "supported_formats": {
+ "min_inclusive": 15,
+ "max_inclusive": 2147483647
+ },
+ "description": "Adds a more transparent storage overlay for /firm storage"
+ }
+}