aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.gradle.kts8
-rw-r--r--gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--src/compat/jade/java/moe/nea/firmament/compat/jade/Compat.kt12
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/Compat.kt12
-rw-r--r--src/compat/wildfireGender/java/moe/nea/firmament/compat/gender/Compat.kt13
-rw-r--r--src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java36
-rw-r--r--src/main/java/moe/nea/firmament/mixins/DisableHurtCam.java18
-rw-r--r--src/main/java/moe/nea/firmament/mixins/EntityUpdateEventListener.java34
-rw-r--r--src/main/kotlin/Compat.kt11
-rw-r--r--src/main/kotlin/Firmament.kt12
-rw-r--r--src/main/kotlin/commands/rome.kt10
-rw-r--r--src/main/kotlin/events/EntityUpdateEvent.kt31
-rw-r--r--src/main/kotlin/features/FeatureManager.kt147
-rw-r--r--src/main/kotlin/features/debug/AnimatedClothingScanner.kt51
-rw-r--r--src/main/kotlin/features/fixes/Fixes.kt1
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt12
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt14
-rw-r--r--src/main/kotlin/features/world/ColeWeightCompat.kt125
-rw-r--r--src/main/kotlin/features/world/FirmWaypointManager.kt168
-rw-r--r--src/main/kotlin/features/world/FirmWaypoints.kt37
-rw-r--r--src/main/kotlin/features/world/TemporaryWaypoints.kt73
-rw-r--r--src/main/kotlin/features/world/Waypoints.kt317
-rw-r--r--src/main/kotlin/repo/RepoDownloadManager.kt193
-rw-r--r--src/main/kotlin/repo/RepoManager.kt7
-rw-r--r--src/main/kotlin/util/FirmFormatters.kt4
-rw-r--r--src/main/kotlin/util/MC.kt1
-rw-r--r--src/main/kotlin/util/compatloader/CompatLoader.kt2
-rw-r--r--src/main/kotlin/util/compatloader/CompatMeta.kt48
-rw-r--r--src/main/kotlin/util/data/MultiFileDataHolder.kt63
-rw-r--r--src/main/kotlin/util/mc/asFakeServer.kt37
-rw-r--r--src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory_0.png)bin1019 -> 1019 bytes
-rw-r--r--src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background_0.png)bin4348 -> 4348 bytes
-rw-r--r--src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background_0.png.mcmeta)0
-rw-r--r--src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls_0.png)bin4766 -> 4766 bytes
-rw-r--r--src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png.mcmeta (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls_0.png.mcmeta)0
-rw-r--r--src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row_0.png)bin649 -> 649 bytes
-rw-r--r--src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row_0.png.mcmeta)0
-rw-r--r--src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background_0.png)bin1396 -> 1396 bytes
-rw-r--r--src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background_0.png.mcmeta)0
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory_1.png)bin639 -> 639 bytes
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background_1.png)bin147 -> 147 bytes
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background_1.png.mcmeta)0
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls_1.png)bin624 -> 624 bytes
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png.mcmeta (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls_1.png.mcmeta)0
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row_1.png)bin203 -> 203 bytes
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row_1.png.mcmeta)0
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background_1.png)bin795 -> 795 bytes
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta (renamed from src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background_1.png.mcmeta)0
-rw-r--r--src/main/resources/resourcepacks/transparent_storage/pack.mcmeta10
-rw-r--r--src/test/resources/testdata/items/necron-boots.snbt68
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/Compat.kt11
-rw-r--r--symbols/src/main/kotlin/process/CompatMetaProcessor.kt63
-rw-r--r--symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt6
-rw-r--r--translations/en_us.json14
54 files changed, 1225 insertions, 446 deletions
diff --git a/build.gradle.kts b/build.gradle.kts
index 0c13dde..d3d62b5 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -32,7 +32,7 @@ plugins {
id("fabric-loom") version "1.9.2"
alias(libs.plugins.shadow)
id("moe.nea.licenseextractificator")
- id("moe.nea.mc-auto-translations") version "0.1.0"
+ id("moe.nea.mc-auto-translations") version "0.2.0"
}
version = getGitTagInfo(libs.versions.minecraft.get())
@@ -171,7 +171,8 @@ fun createIsolatedSourceSet(name: String, path: String = "compat/$name", isEnabl
}
dependencies {
runtimeOnly(ss.output)
- (ss.implementationConfigurationName)(sourceSets.main.get().output)
+ (ss.implementationConfigurationName)(project.files(tasks.compileKotlin.map { it.destinationDirectory }))
+ (ss.implementationConfigurationName)(project.files(tasks.compileJava.map { it.destinationDirectory }))
}
tasks.shadowJar {
from(ss.output)
@@ -181,8 +182,7 @@ fun createIsolatedSourceSet(name: String, path: String = "compat/$name", isEnabl
classpath.from(configurations.getByName(ss.compileClasspathConfigurationName))
}
collectTranslations {
- // TODO: this does not work, somehow
- this.classes.from(sourceSets.main.get().kotlin.classesDirectory)
+ this.classes.from(ss.kotlin.classesDirectory)
}
return ss
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 6e2ced2..4cdd0fb 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -4,6 +4,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/src/compat/jade/java/moe/nea/firmament/compat/jade/Compat.kt b/src/compat/jade/java/moe/nea/firmament/compat/jade/Compat.kt
new file mode 100644
index 0000000..d1cfef4
--- /dev/null
+++ b/src/compat/jade/java/moe/nea/firmament/compat/jade/Compat.kt
@@ -0,0 +1,12 @@
+package moe.nea.firmament.compat.jade
+
+import net.fabricmc.loader.api.FabricLoader
+import moe.nea.firmament.util.compatloader.CompatMeta
+import moe.nea.firmament.util.compatloader.ICompatMeta
+
+@CompatMeta
+object Compat : ICompatMeta {
+ override fun shouldLoad(): Boolean {
+ return FabricLoader.getInstance().isModLoaded("jade")
+ }
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/Compat.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/Compat.kt
new file mode 100644
index 0000000..9ab4d22
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/Compat.kt
@@ -0,0 +1,12 @@
+package moe.nea.firmament.compat.rei
+
+import net.fabricmc.loader.api.FabricLoader
+import moe.nea.firmament.util.compatloader.CompatMeta
+import moe.nea.firmament.util.compatloader.ICompatMeta
+
+@CompatMeta
+object Compat : ICompatMeta {
+ override fun shouldLoad(): Boolean {
+ return FabricLoader.getInstance().isModLoaded("roughlyenoughitems")
+ }
+}
diff --git a/src/compat/wildfireGender/java/moe/nea/firmament/compat/gender/Compat.kt b/src/compat/wildfireGender/java/moe/nea/firmament/compat/gender/Compat.kt
new file mode 100644
index 0000000..347dd5d
--- /dev/null
+++ b/src/compat/wildfireGender/java/moe/nea/firmament/compat/gender/Compat.kt
@@ -0,0 +1,13 @@
+package moe.nea.firmament.compat.gender
+
+import net.fabricmc.loader.api.FabricLoader
+import moe.nea.firmament.util.compatloader.CompatMeta
+import moe.nea.firmament.util.compatloader.ICompatMeta
+
+@CompatMeta
+object Compat : ICompatMeta {
+ override fun shouldLoad(): Boolean {
+ return FabricLoader.getInstance().isModLoaded("wildfire_gender")
+ }
+
+}
diff --git a/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java b/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java
index 0713068..a9db7f9 100644
--- a/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java
+++ b/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java
@@ -1,6 +1,9 @@
package moe.nea.firmament.init;
+import moe.nea.firmament.util.ErrorUtil;
+import moe.nea.firmament.util.compatloader.ICompatMeta;
+
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
@@ -94,7 +97,7 @@ public class AutoDiscoveryPlugin {
String norm = (className.substring(0, className.length() - ".class".length()))
.replace("\\", "/")
.replace("/", ".");
- if (norm.startsWith(getMixinPackage() + ".") && !norm.endsWith(".")) {
+ if (norm.startsWith(getMixinPackage() + ".") && !norm.endsWith(".") && ICompatMeta.Companion.shouldLoad(norm)) {
mixins.add(norm.substring(getMixinPackage().length() + 1));
}
}
@@ -125,24 +128,25 @@ public class AutoDiscoveryPlugin {
*/
public List<String> getMixins() {
if (mixins != null) return mixins;
- System.out.println("Trying to discover mixins");
- mixins = new ArrayList<>();
- URL classUrl = getClass().getProtectionDomain().getCodeSource().getLocation();
- System.out.println("Found classes at " + classUrl);
- tryDiscoverFromContentFile(classUrl);
- var classRoots = System.getProperty("firmament.classroots");
- if (classRoots != null && !classRoots.isBlank()) {
- System.out.println("Found firmament class roots: " + classRoots);
- for (String s : classRoots.split(File.pathSeparator)) {
- if (s.isBlank()) {
- continue;
- }
- try {
+ try {
+ System.out.println("Trying to discover mixins");
+ mixins = new ArrayList<>();
+ URL classUrl = getClass().getProtectionDomain().getCodeSource().getLocation();
+ System.out.println("Found classes at " + classUrl);
+ tryDiscoverFromContentFile(classUrl);
+ var classRoots = System.getProperty("firmament.classroots");
+ if (classRoots != null && !classRoots.isBlank()) {
+ System.out.println("Found firmament class roots: " + classRoots);
+ for (String s : classRoots.split(File.pathSeparator)) {
+ if (s.isBlank()) {
+ continue;
+ }
tryDiscoverFromContentFile(new File(s).toURI().toURL());
- } catch (MalformedURLException e) {
- throw new RuntimeException(e);
}
}
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.exit(1);
}
return mixins;
}
diff --git a/src/main/java/moe/nea/firmament/mixins/DisableHurtCam.java b/src/main/java/moe/nea/firmament/mixins/DisableHurtCam.java
new file mode 100644
index 0000000..ed7a2d4
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/DisableHurtCam.java
@@ -0,0 +1,18 @@
+package moe.nea.firmament.mixins;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.features.fixes.Fixes;
+import net.minecraft.client.render.GameRenderer;
+import org.objectweb.asm.Opcodes;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(GameRenderer.class)
+public class DisableHurtCam {
+ @ModifyExpressionValue(method = "tiltViewWhenHurt", at = @At(value = "FIELD", target = "Lnet/minecraft/entity/LivingEntity;hurtTime:I", opcode = Opcodes.GETFIELD))
+ private int replaceHurtTime(int original) {
+ if (Fixes.TConfig.INSTANCE.getNoHurtCam())
+ return 0;
+ return original;
+ }
+}
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/kotlin/Compat.kt b/src/main/kotlin/Compat.kt
new file mode 100644
index 0000000..ba3c88d
--- /dev/null
+++ b/src/main/kotlin/Compat.kt
@@ -0,0 +1,11 @@
+package moe.nea.firmament
+
+import moe.nea.firmament.util.compatloader.CompatMeta
+import moe.nea.firmament.util.compatloader.ICompatMeta
+
+@CompatMeta
+object Compat : ICompatMeta {
+ override fun shouldLoad(): Boolean {
+ return true
+ }
+}
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 8ae34f6..c3eb03d 100644
--- a/src/main/kotlin/commands/rome.kt
+++ b/src/main/kotlin/commands/rome.kt
@@ -1,6 +1,7 @@
package moe.nea.firmament.commands
import com.mojang.brigadier.CommandDispatcher
+import com.mojang.brigadier.arguments.IntegerArgumentType
import com.mojang.brigadier.arguments.StringArgumentType.string
import io.ktor.client.statement.bodyAsText
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
@@ -130,6 +131,15 @@ fun firmamentCommand() = literal("firmament") {
}
}
thenLiteral("repo") {
+ thenLiteral("checkpr") {
+ thenArgument("prnum", IntegerArgumentType.integer(1)) { prnum ->
+ thenExecute {
+ val prnum = this[prnum]
+ source.sendFeedback(tr("firmament.repo.reload.pr", "Temporarily reloading repo from PR #${prnum}."))
+ RepoManager.downloadOverridenBranch("refs/pull/$prnum/head")
+ }
+ }
+ }
thenLiteral("reload") {
thenLiteral("fetch") {
thenExecute {
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/features/FeatureManager.kt b/src/main/kotlin/features/FeatureManager.kt
index 9a3cbf8..f0c1857 100644
--- a/src/main/kotlin/features/FeatureManager.kt
+++ b/src/main/kotlin/features/FeatureManager.kt
@@ -31,89 +31,96 @@ import moe.nea.firmament.features.mining.PickaxeAbility
import moe.nea.firmament.features.mining.PristineProfitTracker
import moe.nea.firmament.features.world.FairySouls
import moe.nea.firmament.features.world.Waypoints
+import moe.nea.firmament.util.compatloader.ICompatMeta
import moe.nea.firmament.util.data.DataHolder
object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "features", ::Config) {
- @Serializable
- data class Config(
- val enabledFeatures: MutableMap<String, Boolean> = mutableMapOf()
- )
+ @Serializable
+ data class Config(
+ val enabledFeatures: MutableMap<String, Boolean> = mutableMapOf()
+ )
- private val features = mutableMapOf<String, FirmamentFeature>()
+ private val features = mutableMapOf<String, FirmamentFeature>()
- val allFeatures: Collection<FirmamentFeature> get() = features.values
+ val allFeatures: Collection<FirmamentFeature> get() = features.values
- private var hasAutoloaded = false
+ private var hasAutoloaded = false
- fun autoload() {
- synchronized(this) {
- if (hasAutoloaded) return
- loadFeature(MinorTrolling)
- loadFeature(FairySouls)
- loadFeature(AutoCompletions)
- // TODO: loadFeature(FishingWarning)
- loadFeature(SlotLocking)
- loadFeature(StorageOverlay)
- loadFeature(PristineProfitTracker)
- loadFeature(CraftingOverlay)
- loadFeature(PowerUserTools)
- loadFeature(Waypoints)
- loadFeature(ChatLinks)
- loadFeature(InventoryButtons)
- loadFeature(CompatibliltyFeatures)
- loadFeature(AnniversaryFeatures)
- loadFeature(QuickCommands)
- loadFeature(PetFeatures)
- loadFeature(SaveCursorPosition)
- loadFeature(PriceData)
- loadFeature(Fixes)
- loadFeature(DianaWaypoints)
- loadFeature(ItemRarityCosmetics)
- loadFeature(PickaxeAbility)
- loadFeature(CarnivalFeatures)
- if (Firmament.DEBUG) {
- loadFeature(DeveloperFeatures)
- loadFeature(DebugView)
- }
- allFeatures.forEach { it.config }
- FeaturesInitializedEvent.publish(FeaturesInitializedEvent(allFeatures.toList()))
- hasAutoloaded = true
- }
- }
+ fun autoload() {
+ synchronized(this) {
+ if (hasAutoloaded) return
+ loadFeature(MinorTrolling)
+ loadFeature(FairySouls)
+ loadFeature(AutoCompletions)
+ // TODO: loadFeature(FishingWarning)
+ loadFeature(SlotLocking)
+ loadFeature(StorageOverlay)
+ loadFeature(PristineProfitTracker)
+ loadFeature(CraftingOverlay)
+ loadFeature(PowerUserTools)
+ loadFeature(Waypoints)
+ loadFeature(ChatLinks)
+ loadFeature(InventoryButtons)
+ loadFeature(CompatibliltyFeatures)
+ loadFeature(AnniversaryFeatures)
+ loadFeature(QuickCommands)
+ loadFeature(PetFeatures)
+ loadFeature(SaveCursorPosition)
+ loadFeature(PriceData)
+ loadFeature(Fixes)
+ loadFeature(DianaWaypoints)
+ loadFeature(ItemRarityCosmetics)
+ loadFeature(PickaxeAbility)
+ loadFeature(CarnivalFeatures)
+ if (Firmament.DEBUG) {
+ loadFeature(DeveloperFeatures)
+ loadFeature(DebugView)
+ }
+ allFeatures.forEach { it.config }
+ FeaturesInitializedEvent.publish(FeaturesInitializedEvent(allFeatures.toList()))
+ hasAutoloaded = true
+ }
+ }
- fun subscribeEvents() {
- SubscriptionList.allLists.forEach {
- it.provideSubscriptions {
- it.owner.javaClass.classes.forEach {
- runCatching { it.getDeclaredField("INSTANCE").get(null) }
+ fun subscribeEvents() {
+ SubscriptionList.allLists.forEach { list ->
+ if (ICompatMeta.shouldLoad(list.javaClass.name))
+ runCatching {
+ list.provideSubscriptions {
+ it.owner.javaClass.classes.forEach {
+ runCatching { it.getDeclaredField("INSTANCE").get(null) }
+ }
+ subscribeSingleEvent(it)
+ }
+ }.getOrElse {
+ // TODO: allow annotating source sets to specifically opt out of loading for mods, maybe automatically
+ Firmament.logger.info("Ignoring events from $list, likely due to a missing compat mod.", it)
}
- subscribeSingleEvent(it)
- }
- }
- }
+ }
+ }
- private fun <T : FirmamentEvent> subscribeSingleEvent(it: Subscription<T>) {
- it.eventBus.subscribe(false, "${it.owner.javaClass.simpleName}:${it.methodName}", it.invoke)
- }
+ private fun <T : FirmamentEvent> subscribeSingleEvent(it: Subscription<T>) {
+ it.eventBus.subscribe(false, "${it.owner.javaClass.simpleName}:${it.methodName}", it.invoke)
+ }
- fun loadFeature(feature: FirmamentFeature) {
- synchronized(features) {
- if (feature.identifier in features) {
- Firmament.logger.error("Double registering feature ${feature.identifier}. Ignoring second instance $feature")
- return
- }
- features[feature.identifier] = feature
- feature.onLoad()
- }
- }
+ fun loadFeature(feature: FirmamentFeature) {
+ synchronized(features) {
+ if (feature.identifier in features) {
+ Firmament.logger.error("Double registering feature ${feature.identifier}. Ignoring second instance $feature")
+ return
+ }
+ features[feature.identifier] = feature
+ feature.onLoad()
+ }
+ }
- fun isEnabled(identifier: String): Boolean? =
- data.enabledFeatures[identifier]
+ fun isEnabled(identifier: String): Boolean? =
+ data.enabledFeatures[identifier]
- fun setEnabled(identifier: String, value: Boolean) {
- data.enabledFeatures[identifier] = value
- markDirty()
- }
+ fun setEnabled(identifier: String, value: Boolean) {
+ data.enabledFeatures[identifier] = value
+ markDirty()
+ }
}
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/fixes/Fixes.kt b/src/main/kotlin/features/fixes/Fixes.kt
index 7030319..3dae233 100644
--- a/src/main/kotlin/features/fixes/Fixes.kt
+++ b/src/main/kotlin/features/fixes/Fixes.kt
@@ -23,6 +23,7 @@ object Fixes : FirmamentFeature {
val autoSprintHud by position("auto-sprint-hud", 80, 10) { Point(0.0, 1.0) }
val peekChat by keyBindingWithDefaultUnbound("peek-chat")
val hidePotionEffects by toggle("hide-mob-effects") { false }
+ val noHurtCam by toggle("disable-hurt-cam") { false }
}
override val config: ManagedConfig
diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt
index 0e3a0a8..2e807de 100644
--- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt
+++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt
@@ -7,7 +7,6 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.Items
import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket
-import net.minecraft.util.StringIdentifiable
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ScreenChangeEvent
import moe.nea.firmament.events.SlotClickEvent
@@ -34,16 +33,6 @@ object StorageOverlay : FirmamentFeature {
val inverseScroll by toggle("inverse-scroll") { false }
val padding by integer("padding", 1, 20) { 5 }
val margin by integer("margin", 1, 60) { 20 }
- val texture by choice("texture") { StorageTexture.DEFAULT }
- }
-
- enum class StorageTexture(val id: Int) : StringIdentifiable {
- DEFAULT(0),
- CLEAR(1);
-
- override fun asString(): String? {
- return id.toString()
- }
}
fun adjustScrollSpeed(amount: Double): Double {
@@ -162,5 +151,4 @@ object StorageOverlay : FirmamentFeature {
}
}
}
-
}
diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt
index 292850a..63a2f54 100644
--- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt
+++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt
@@ -108,12 +108,12 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
fun getMaxScroll() = lastRenderedInnerHeight.toFloat() - getScrollPanelInner().height
- val playerInventorySprite = Identifier.of("firmament:storageoverlay/player_inventory_${StorageOverlay.TConfig.texture.asString()}")
- val upperBackgroundSprite = Identifier.of("firmament:storageoverlay/upper_background_${StorageOverlay.TConfig.texture.asString()}")
- val slotRowSprite = Identifier.of("firmament:storageoverlay/storage_row_${StorageOverlay.TConfig.texture.asString()}")
- val scrollbarBackground = Identifier.of("firmament:storageoverlay/scroll_bar_background_${StorageOverlay.TConfig.texture.asString()}")
+ val playerInventorySprite = Identifier.of("firmament:storageoverlay/player_inventory")
+ val upperBackgroundSprite = Identifier.of("firmament:storageoverlay/upper_background")
+ val slotRowSprite = Identifier.of("firmament:storageoverlay/storage_row")
+ val scrollbarBackground = Identifier.of("firmament:storageoverlay/scroll_bar_background")
val scrollbarKnob = Identifier.of("firmament:storageoverlay/scroll_bar_knob")
- val controllerBackground = Identifier.of("firmament:storageoverlay/storage_controls_${StorageOverlay.TConfig.texture.asString()}")
+ val controllerBackground = Identifier.of("firmament:storageoverlay/storage_controls")
override fun close() {
isExiting = true
@@ -357,6 +357,10 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
return super.keyReleased(keyCode, scanCode, modifiers)
}
+ override fun shouldCloseOnEsc(): Boolean {
+ return this === MC.screen // Fixes this UI closing the handled screen on Escape press.
+ }
+
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
if (typeMCComponentInPlace(
controlComponent,
diff --git a/src/main/kotlin/features/world/ColeWeightCompat.kt b/src/main/kotlin/features/world/ColeWeightCompat.kt
new file mode 100644
index 0000000..b92a91e
--- /dev/null
+++ b/src/main/kotlin/features/world/ColeWeightCompat.kt
@@ -0,0 +1,125 @@
+package moe.nea.firmament.features.world
+
+import kotlinx.serialization.Serializable
+import net.minecraft.text.Text
+import net.minecraft.util.math.BlockPos
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.commands.DefaultSource
+import moe.nea.firmament.commands.thenExecute
+import moe.nea.firmament.commands.thenLiteral
+import moe.nea.firmament.events.CommandEvent
+import moe.nea.firmament.util.ClipboardUtils
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.tr
+
+object ColeWeightCompat {
+ @Serializable
+ data class ColeWeightWaypoint(
+ val x: Int,
+ val y: Int,
+ val z: Int,
+ val r: Int = 0,
+ val g: Int = 0,
+ val b: Int = 0,
+ )
+
+ fun fromFirm(waypoints: FirmWaypoints, relativeTo: BlockPos): List<ColeWeightWaypoint> {
+ return waypoints.waypoints.map {
+ ColeWeightWaypoint(it.x - relativeTo.x, it.y - relativeTo.y, it.z - relativeTo.z)
+ }
+ }
+
+ fun intoFirm(waypoints: List<ColeWeightWaypoint>, relativeTo: BlockPos): FirmWaypoints {
+ val w = waypoints.map {
+ FirmWaypoints.Waypoint(it.x + relativeTo.x, it.y + relativeTo.y, it.z + relativeTo.z)
+ }
+ return FirmWaypoints(
+ "Imported Waypoints",
+ "imported",
+ null,
+ w.toMutableList(),
+ false
+ )
+ }
+
+ fun copyAndInform(
+ source: DefaultSource,
+ origin: BlockPos,
+ positiveFeedback: (Int) -> Text,
+ ) {
+ val waypoints = Waypoints.useNonEmptyWaypoints()
+ ?.let { fromFirm(it, origin) }
+ if (waypoints == null) {
+ source.sendError(Waypoints.textNothingToExport())
+ return
+ }
+ val data =
+ Firmament.tightJson.encodeToString<List<ColeWeightWaypoint>>(waypoints)
+ ClipboardUtils.setTextContent(data)
+ source.sendFeedback(positiveFeedback(waypoints.size))
+ }
+
+ fun importAndInform(
+ source: DefaultSource,
+ pos: BlockPos?,
+ positiveFeedback: (Int) -> Text
+ ) {
+ val text = ClipboardUtils.getTextContents()
+ val wr = tryParse(text).map { intoFirm(it, pos ?: BlockPos.ORIGIN) }
+ val waypoints = wr.getOrElse {
+ source.sendError(
+ tr("firmament.command.waypoint.import.cw.error",
+ "Could not import ColeWeight waypoints."))
+ Firmament.logger.error(it)
+ return
+ }
+ waypoints.lastRelativeImport = pos
+ Waypoints.waypoints = waypoints
+ source.sendFeedback(positiveFeedback(waypoints.size))
+ }
+
+ @Subscribe
+ fun onEvent(event: CommandEvent.SubCommand) {
+ event.subcommand(Waypoints.WAYPOINTS_SUBCOMMAND) {
+ thenLiteral("exportcw") {
+ thenExecute {
+ copyAndInform(source, BlockPos.ORIGIN) {
+ tr("firmament.command.waypoint.export.cw",
+ "Copied $it waypoints to clipboard in ColeWeight format.")
+ }
+ }
+ }
+ thenLiteral("exportrelativecw") {
+ thenExecute {
+ copyAndInform(source, MC.player?.blockPos ?: BlockPos.ORIGIN) {
+ tr("firmament.command.waypoint.export.cw.relative",
+ "Copied $it relative waypoints to clipboard in ColeWeight format. Make sure to stand in the same position when importing.")
+ }
+ }
+ }
+ thenLiteral("importcw") {
+ thenExecute {
+ importAndInform(source, null) {
+ Text.stringifiedTranslatable("firmament.command.waypoint.import.cw",
+ it)
+ }
+ }
+ }
+ thenLiteral("importrelativecw") {
+ thenExecute {
+ importAndInform(source, MC.player!!.blockPos) {
+ tr("firmament.command.waypoint.import.cw.relative",
+ "Imported $it relative waypoints from clipboard. Make sure you stand in the same position as when you exported these waypoints for them to line up correctly.")
+ }
+ }
+ }
+ }
+ }
+
+ fun tryParse(string: String): Result<List<ColeWeightWaypoint>> {
+ return runCatching {
+ Firmament.tightJson.decodeFromString<List<ColeWeightWaypoint>>(string)
+ }
+ }
+}
diff --git a/src/main/kotlin/features/world/FirmWaypointManager.kt b/src/main/kotlin/features/world/FirmWaypointManager.kt
new file mode 100644
index 0000000..d18483c
--- /dev/null
+++ b/src/main/kotlin/features/world/FirmWaypointManager.kt
@@ -0,0 +1,168 @@
+package moe.nea.firmament.features.world
+
+import com.mojang.brigadier.arguments.StringArgumentType
+import kotlinx.serialization.serializer
+import net.minecraft.text.Text
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.commands.DefaultSource
+import moe.nea.firmament.commands.RestArgumentType
+import moe.nea.firmament.commands.get
+import moe.nea.firmament.commands.suggestsList
+import moe.nea.firmament.commands.thenArgument
+import moe.nea.firmament.commands.thenExecute
+import moe.nea.firmament.commands.thenLiteral
+import moe.nea.firmament.events.CommandEvent
+import moe.nea.firmament.util.ClipboardUtils
+import moe.nea.firmament.util.FirmFormatters
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.TemplateUtil
+import moe.nea.firmament.util.data.MultiFileDataHolder
+import moe.nea.firmament.util.tr
+
+object FirmWaypointManager {
+ object DataHolder : MultiFileDataHolder<FirmWaypoints>(serializer(), "waypoints")
+
+ val SHARE_PREFIX = "FIRM_WAYPOINTS/"
+ val ENCODED_SHARE_PREFIX = TemplateUtil.getPrefixComparisonSafeBase64Encoding(SHARE_PREFIX)
+
+ fun createExportableCopy(
+ waypoints: FirmWaypoints,
+ ): FirmWaypoints {
+ val copy = waypoints.copy(waypoints = waypoints.waypoints.toMutableList())
+ if (waypoints.isRelativeTo != null) {
+ val origin = waypoints.lastRelativeImport
+ if (origin != null) {
+ copy.waypoints.replaceAll {
+ it.copy(
+ x = it.x - origin.x,
+ y = it.y - origin.y,
+ z = it.z - origin.z,
+ )
+ }
+ } else {
+ TODO("Add warning!")
+ }
+ }
+ return copy
+ }
+
+ fun loadWaypoints(waypoints: FirmWaypoints, sendFeedback: (Text) -> Unit) {
+ val copy = waypoints.deepCopy()
+ if (copy.isRelativeTo != null) {
+ val origin = MC.player!!.blockPos
+ copy.waypoints.replaceAll {
+ it.copy(
+ x = it.x + origin.x,
+ y = it.y + origin.y,
+ z = it.z + origin.z,
+ )
+ }
+ copy.lastRelativeImport = origin.toImmutable()
+ sendFeedback(tr("firmament.command.waypoint.import.ordered.success",
+ "Imported ${copy.size} relative waypoints. Make sure you stand in the correct spot while loading the waypoints: ${copy.isRelativeTo}."))
+ } else {
+ sendFeedback(tr("firmament.command.waypoint.import.success",
+ "Imported ${copy.size} waypoints."))
+ }
+ Waypoints.waypoints = copy
+ }
+
+ fun setOrigin(source: DefaultSource, text: String?) {
+ val waypoints = Waypoints.useEditableWaypoints()
+ waypoints.isRelativeTo = text ?: waypoints.isRelativeTo ?: ""
+ val pos = MC.player!!.blockPos
+ waypoints.lastRelativeImport = pos
+ source.sendFeedback(tr("firmament.command.waypoint.originset",
+ "Set the origin of waypoints to ${FirmFormatters.formatPosition(pos)}. Run /firm waypoints export to save the waypoints relative to this position."))
+ }
+
+ @Subscribe
+ fun onCommands(event: CommandEvent.SubCommand) {
+ event.subcommand(Waypoints.WAYPOINTS_SUBCOMMAND) {
+ thenLiteral("setorigin") {
+ thenExecute {
+ setOrigin(source, null)
+ }
+ thenArgument("hint", RestArgumentType) { text ->
+ thenExecute {
+ setOrigin(source, this[text])
+ }
+ }
+ }
+ thenLiteral("clearorigin") {
+ thenExecute {
+ val waypoints = Waypoints.useEditableWaypoints()
+ waypoints.lastRelativeImport = null
+ waypoints.isRelativeTo = null
+ source.sendFeedback(tr("firmament.command.waypoint.originunset",
+ "Unset the origin of the waypoints. Run /firm waypoints export to save the waypoints with absolute coordinates."))
+ }
+ }
+ thenLiteral("save") {
+ thenArgument("name", StringArgumentType.string()) { name ->
+ suggestsList { DataHolder.list().keys }
+ thenExecute {
+ val waypoints = Waypoints.useNonEmptyWaypoints()
+ if (waypoints == null) {
+ source.sendError(Waypoints.textNothingToExport())
+ return@thenExecute
+ }
+ waypoints.id = get(name)
+ val exportableWaypoints = createExportableCopy(waypoints)
+ DataHolder.insert(get(name), exportableWaypoints)
+ DataHolder.save()
+ source.sendFeedback(tr("firmament.command.waypoint.saved",
+ "Saved waypoints locally as ${get(name)}. Use /firm waypoints load to load them again."))
+ }
+ }
+ }
+ thenLiteral("load") {
+ thenArgument("name", StringArgumentType.string()) { name ->
+ suggestsList { DataHolder.list().keys }
+ thenExecute {
+ val name = get(name)
+ val waypoints = DataHolder.list()[name]
+ if (waypoints == null) {
+ source.sendError(
+ tr("firmament.command.waypoint.nosaved",
+ "No saved waypoint for ${name}. Use tab completion to see available names."))
+ return@thenExecute
+ }
+ loadWaypoints(waypoints, source::sendFeedback)
+ }
+ }
+ }
+ thenLiteral("export") {
+ thenExecute {
+ val waypoints = Waypoints.useNonEmptyWaypoints()
+ if (waypoints == null) {
+ source.sendError(Waypoints.textNothingToExport())
+ return@thenExecute
+ }
+ val exportableWaypoints = createExportableCopy(waypoints)
+ val data = TemplateUtil.encodeTemplate(SHARE_PREFIX, exportableWaypoints)
+ ClipboardUtils.setTextContent(data)
+ source.sendFeedback(tr("firmament.command.waypoint.export",
+ "Copied ${exportableWaypoints.size} waypoints to clipboard in Firmament format."))
+ }
+ }
+ thenLiteral("import") {
+ thenExecute {
+ val text = ClipboardUtils.getTextContents()
+ if (text.startsWith("[")) {
+ source.sendError(tr("firmament.command.waypoint.import.lookslikecw",
+ "The waypoints in your clipboard look like they might be ColeWeight waypoints. If so, use /firm waypoints importcw or /firm waypoints importrelativecw."))
+ return@thenExecute
+ }
+ val waypoints = TemplateUtil.maybeDecodeTemplate<FirmWaypoints>(SHARE_PREFIX, text)
+ if (waypoints == null) {
+ source.sendError(tr("firmament.command.waypoint.import.error",
+ "Could not import Firmament waypoints from your clipboard. Make sure they are Firmament compatible waypoints."))
+ return@thenExecute
+ }
+ loadWaypoints(waypoints, source::sendFeedback)
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/features/world/FirmWaypoints.kt b/src/main/kotlin/features/world/FirmWaypoints.kt
new file mode 100644
index 0000000..d0cd55a
--- /dev/null
+++ b/src/main/kotlin/features/world/FirmWaypoints.kt
@@ -0,0 +1,37 @@
+package moe.nea.firmament.features.world
+
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Transient
+import net.minecraft.util.math.BlockPos
+
+@Serializable
+data class FirmWaypoints(
+ var label: String,
+ var id: String,
+ /**
+ * A hint to indicate where to stand while loading the waypoints.
+ */
+ var isRelativeTo: String?,
+ var waypoints: MutableList<Waypoint>,
+ var isOrdered: Boolean,
+ // TODO: val resetOnSwap: Boolean,
+) {
+
+ fun deepCopy() = copy(waypoints = waypoints.toMutableList())
+ @Transient
+ var lastRelativeImport: BlockPos? = null
+
+ val size get() = waypoints.size
+ @Serializable
+ data class Waypoint(
+ val x: Int,
+ val y: Int,
+ val z: Int,
+ ) {
+ val blockPos get() = BlockPos(x, y, z)
+
+ companion object {
+ fun from(blockPos: BlockPos) = Waypoint(blockPos.x, blockPos.y, blockPos.z)
+ }
+ }
+}
diff --git a/src/main/kotlin/features/world/TemporaryWaypoints.kt b/src/main/kotlin/features/world/TemporaryWaypoints.kt
new file mode 100644
index 0000000..b36c49d
--- /dev/null
+++ b/src/main/kotlin/features/world/TemporaryWaypoints.kt
@@ -0,0 +1,73 @@
+package moe.nea.firmament.features.world
+
+import kotlin.compareTo
+import kotlin.text.clear
+import kotlin.time.Duration.Companion.seconds
+import net.minecraft.text.Text
+import net.minecraft.util.math.BlockPos
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.ProcessChatEvent
+import moe.nea.firmament.events.WorldReadyEvent
+import moe.nea.firmament.events.WorldRenderLastEvent
+import moe.nea.firmament.features.world.Waypoints.TConfig
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.TimeMark
+import moe.nea.firmament.util.render.RenderInWorldContext
+
+object TemporaryWaypoints {
+ data class TemporaryWaypoint(
+ val pos: BlockPos,
+ val postedAt: TimeMark,
+ )
+ val temporaryPlayerWaypointList = mutableMapOf<String, TemporaryWaypoint>()
+ val temporaryPlayerWaypointMatcher = "(?i)x: (-?[0-9]+),? y: (-?[0-9]+),? z: (-?[0-9]+)".toPattern()
+ @Subscribe
+ fun onProcessChat(it: ProcessChatEvent) {
+ val matcher = temporaryPlayerWaypointMatcher.matcher(it.unformattedString)
+ if (it.nameHeuristic != null && TConfig.tempWaypointDuration > 0.seconds && matcher.find()) {
+ temporaryPlayerWaypointList[it.nameHeuristic] = TemporaryWaypoint(BlockPos(
+ matcher.group(1).toInt(),
+ matcher.group(2).toInt(),
+ matcher.group(3).toInt(),
+ ), TimeMark.now())
+ }
+ }
+ @Subscribe
+ fun onRenderTemporaryWaypoints(event: WorldRenderLastEvent) {
+ temporaryPlayerWaypointList.entries.removeIf { it.value.postedAt.passedTime() > TConfig.tempWaypointDuration }
+ if (temporaryPlayerWaypointList.isEmpty()) return
+ RenderInWorldContext.renderInWorld(event) {
+ temporaryPlayerWaypointList.forEach { (_, waypoint) ->
+ block(waypoint.pos, 0xFFFFFF00.toInt())
+ }
+ temporaryPlayerWaypointList.forEach { (player, waypoint) ->
+ val skin =
+ MC.networkHandler?.listedPlayerListEntries?.find { it.profile.name == player }?.skinTextures?.texture
+ withFacingThePlayer(waypoint.pos.toCenterPos()) {
+ waypoint(waypoint.pos, Text.stringifiedTranslatable("firmament.waypoint.temporary", player))
+ if (skin != null) {
+ matrixStack.translate(0F, -20F, 0F)
+ // Head front
+ texture(
+ skin, 16, 16,
+ 1 / 8f, 1 / 8f,
+ 2 / 8f, 2 / 8f,
+ )
+ // Head overlay
+ texture(
+ skin, 16, 16,
+ 5 / 8f, 1 / 8f,
+ 6 / 8f, 2 / 8f,
+ )
+ }
+ }
+ }
+ }
+ }
+
+ @Subscribe
+ fun onWorldReady(event: WorldReadyEvent) {
+ temporaryPlayerWaypointList.clear()
+ }
+
+}
diff --git a/src/main/kotlin/features/world/Waypoints.kt b/src/main/kotlin/features/world/Waypoints.kt
index 3ebfe70..b5c2b66 100644
--- a/src/main/kotlin/features/world/Waypoints.kt
+++ b/src/main/kotlin/features/world/Waypoints.kt
@@ -2,36 +2,24 @@ package moe.nea.firmament.features.world
import com.mojang.brigadier.arguments.IntegerArgumentType
import me.shedaniel.math.Color
-import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.encodeToString
-import kotlin.collections.component1
-import kotlin.collections.component2
-import kotlin.collections.set
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds
import net.minecraft.command.argument.BlockPosArgumentType
-import net.minecraft.server.command.CommandOutput
-import net.minecraft.server.command.ServerCommandSource
import net.minecraft.text.Text
-import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
-import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.commands.get
import moe.nea.firmament.commands.thenArgument
import moe.nea.firmament.commands.thenExecute
import moe.nea.firmament.commands.thenLiteral
import moe.nea.firmament.events.CommandEvent
-import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.events.WorldReadyEvent
import moe.nea.firmament.events.WorldRenderLastEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
-import moe.nea.firmament.util.ClipboardUtils
import moe.nea.firmament.util.MC
-import moe.nea.firmament.util.TimeMark
+import moe.nea.firmament.util.mc.asFakeServer
import moe.nea.firmament.util.render.RenderInWorldContext
import moe.nea.firmament.util.tr
@@ -43,99 +31,85 @@ object Waypoints : FirmamentFeature {
val tempWaypointDuration by duration("temp-waypoint-duration", 0.seconds, 1.hours) { 30.seconds }
val showIndex by toggle("show-index") { true }
val skipToNearest by toggle("skip-to-nearest") { false }
+ val resetWaypointOrderOnWorldSwap by toggle("reset-order-on-swap") { true }
// TODO: look ahead size
}
- data class TemporaryWaypoint(
- val pos: BlockPos,
- val postedAt: TimeMark,
- )
-
override val config get() = TConfig
-
- val temporaryPlayerWaypointList = mutableMapOf<String, TemporaryWaypoint>()
- val temporaryPlayerWaypointMatcher = "(?i)x: (-?[0-9]+),? y: (-?[0-9]+),? z: (-?[0-9]+)".toPattern()
-
- val waypoints = mutableListOf<BlockPos>()
- var ordered = false
+ var waypoints: FirmWaypoints? = null
var orderedIndex = 0
- @Serializable
- data class ColeWeightWaypoint(
- val x: Int,
- val y: Int,
- val z: Int,
- val r: Int = 0,
- val g: Int = 0,
- val b: Int = 0,
- )
-
@Subscribe
fun onRenderOrderedWaypoints(event: WorldRenderLastEvent) {
- if (waypoints.isEmpty()) return
+ val w = useNonEmptyWaypoints() ?: return
RenderInWorldContext.renderInWorld(event) {
- if (!ordered) {
- waypoints.withIndex().forEach {
- block(it.value, 0x800050A0.toInt())
- if (TConfig.showIndex)
- withFacingThePlayer(it.value.toCenterPos()) {
- text(Text.literal(it.index.toString()))
- }
+ if (!w.isOrdered) {
+ w.waypoints.withIndex().forEach {
+ block(it.value.blockPos, 0x800050A0.toInt())
+ if (TConfig.showIndex) withFacingThePlayer(it.value.blockPos.toCenterPos()) {
+ text(Text.literal(it.index.toString()))
+ }
}
} else {
- orderedIndex %= waypoints.size
+ orderedIndex %= w.waypoints.size
val firstColor = Color.ofRGBA(0, 200, 40, 180)
color(firstColor)
- tracer(waypoints[orderedIndex].toCenterPos(), lineWidth = 3f)
- waypoints.withIndex().toList()
- .wrappingWindow(orderedIndex, 3)
- .zip(
- listOf(
- firstColor,
- Color.ofRGBA(180, 200, 40, 150),
- Color.ofRGBA(180, 80, 20, 140),
- )
- )
- .reversed()
- .forEach { (waypoint, col) ->
- val (index, pos) = waypoint
- block(pos, col.color)
- if (TConfig.showIndex)
- withFacingThePlayer(pos.toCenterPos()) {
- text(Text.literal(index.toString()))
- }
+ tracer(w.waypoints[orderedIndex].blockPos.toCenterPos(), lineWidth = 3f)
+ w.waypoints.withIndex().toList().wrappingWindow(orderedIndex, 3).zip(listOf(
+ firstColor,
+ Color.ofRGBA(180, 200, 40, 150),
+ Color.ofRGBA(180, 80, 20, 140),
+ )).reversed().forEach { (waypoint, col) ->
+ val (index, pos) = waypoint
+ block(pos.blockPos, col.color)
+ if (TConfig.showIndex) withFacingThePlayer(pos.blockPos.toCenterPos()) {
+ text(Text.literal(index.toString()))
}
+ }
}
}
}
@Subscribe
fun onTick(event: TickEvent) {
- if (waypoints.isEmpty() || !ordered) return
- orderedIndex %= waypoints.size
+ val w = useNonEmptyWaypoints() ?: return
+ if (!w.isOrdered) return
+ orderedIndex %= w.waypoints.size
val p = MC.player?.pos ?: return
if (TConfig.skipToNearest) {
orderedIndex =
- (waypoints.withIndex().minBy { it.value.getSquaredDistance(p) }.index + 1) % waypoints.size
+ (w.waypoints.withIndex().minBy { it.value.blockPos.getSquaredDistance(p) }.index + 1) % w.waypoints.size
+
} else {
- if (waypoints[orderedIndex].isWithinDistance(p, 3.0)) {
- orderedIndex = (orderedIndex + 1) % waypoints.size
+ if (w.waypoints[orderedIndex].blockPos.isWithinDistance(p, 3.0)) {
+ orderedIndex = (orderedIndex + 1) % w.waypoints.size
}
}
}
+
+ fun useEditableWaypoints(): FirmWaypoints {
+ var w = waypoints
+ if (w == null) {
+ w = FirmWaypoints("Unlabeled", "unknown", null, mutableListOf(), false)
+ waypoints = w
+ }
+ return w
+ }
+
+ fun useNonEmptyWaypoints(): FirmWaypoints? {
+ val w = waypoints
+ if (w == null) return null
+ if (w.waypoints.isEmpty()) return null
+ return w
+ }
+
+ val WAYPOINTS_SUBCOMMAND = "waypoints"
+
@Subscribe
- fun onProcessChat(it: ProcessChatEvent) {
- val matcher = temporaryPlayerWaypointMatcher.matcher(it.unformattedString)
- if (it.nameHeuristic != null && TConfig.tempWaypointDuration > 0.seconds && matcher.find()) {
- temporaryPlayerWaypointList[it.nameHeuristic] = TemporaryWaypoint(
- BlockPos(
- matcher.group(1).toInt(),
- matcher.group(2).toInt(),
- matcher.group(3).toInt(),
- ),
- TimeMark.now()
- )
+ fun onWorldSwap(event: WorldReadyEvent) {
+ if (TConfig.resetWaypointOrderOnWorldSwap) {
+ orderedIndex = 0
}
}
@@ -144,41 +118,77 @@ object Waypoints : FirmamentFeature {
event.subcommand("waypoint") {
thenArgument("pos", BlockPosArgumentType.blockPos()) { pos ->
thenExecute {
+ source
val position = pos.get(this).toAbsoluteBlockPos(source.asFakeServer())
- waypoints.add(position)
- source.sendFeedback(
- Text.stringifiedTranslatable(
- "firmament.command.waypoint.added",
- position.x,
- position.y,
- position.z
- )
- )
+ val w = useEditableWaypoints()
+ w.waypoints.add(FirmWaypoints.Waypoint.from(position))
+ source.sendFeedback(Text.stringifiedTranslatable("firmament.command.waypoint.added",
+ position.x,
+ position.y,
+ position.z))
}
}
}
- event.subcommand("waypoints") {
+ event.subcommand(WAYPOINTS_SUBCOMMAND) {
+ thenLiteral("reset") {
+ thenExecute {
+ orderedIndex = 0
+ source.sendFeedback(tr(
+ "firmament.command.waypoint.reset",
+ "Reset your ordered waypoint index back to 0. If you want to delete all waypoints use /firm waypoints clear instead."))
+ }
+ }
+ thenLiteral("changeindex") {
+ thenArgument("from", IntegerArgumentType.integer(0)) { fromIndex ->
+ thenArgument("to", IntegerArgumentType.integer(0)) { toIndex ->
+ thenExecute {
+ val w = useEditableWaypoints()
+ val toIndex = toIndex.get(this)
+ val fromIndex = fromIndex.get(this)
+ if (fromIndex !in w.waypoints.indices) {
+ source.sendError(textInvalidIndex(fromIndex))
+ return@thenExecute
+ }
+ if (toIndex !in w.waypoints.indices) {
+ source.sendError(textInvalidIndex(toIndex))
+ return@thenExecute
+ }
+ val waypoint = w.waypoints.removeAt(fromIndex)
+ w.waypoints.add(
+ if (toIndex > fromIndex) toIndex - 1
+ else toIndex,
+ waypoint)
+ source.sendFeedback(
+ tr("firmament.command.waypoint.indexchange",
+ "Moved waypoint from index $fromIndex to $toIndex. Note that this only matters for ordered waypoints.")
+ )
+ }
+ }
+ }
+ }
thenLiteral("clear") {
thenExecute {
- waypoints.clear()
+ waypoints = null
source.sendFeedback(Text.translatable("firmament.command.waypoint.clear"))
}
}
thenLiteral("toggleordered") {
thenExecute {
- ordered = !ordered
- if (ordered) {
+ val w = useEditableWaypoints()
+ w.isOrdered = !w.isOrdered
+ if (w.isOrdered) {
val p = MC.player?.pos ?: Vec3d.ZERO
- orderedIndex =
- waypoints.withIndex().minByOrNull { it.value.getSquaredDistance(p) }?.index ?: 0
+ orderedIndex = // TODO: this should be extracted to a utility method
+ w.waypoints.withIndex().minByOrNull { it.value.blockPos.getSquaredDistance(p) }?.index ?: 0
}
- source.sendFeedback(Text.translatable("firmament.command.waypoint.ordered.toggle.$ordered"))
+ source.sendFeedback(Text.translatable("firmament.command.waypoint.ordered.toggle.${w.isOrdered}"))
}
}
thenLiteral("skip") {
thenExecute {
- if (ordered && waypoints.isNotEmpty()) {
- orderedIndex = (orderedIndex + 1) % waypoints.size
+ val w = useNonEmptyWaypoints()
+ if (w != null && w.isOrdered) {
+ orderedIndex = (orderedIndex + 1) % w.size
source.sendFeedback(Text.translatable("firmament.command.waypoint.skip"))
} else {
source.sendError(Text.translatable("firmament.command.waypoint.skip.error"))
@@ -189,90 +199,27 @@ object Waypoints : FirmamentFeature {
thenArgument("index", IntegerArgumentType.integer(0)) { indexArg ->
thenExecute {
val index = get(indexArg)
- if (index in waypoints.indices) {
- waypoints.removeAt(index)
- source.sendFeedback(Text.stringifiedTranslatable(
- "firmament.command.waypoint.remove",
- index))
+ val w = useNonEmptyWaypoints()
+ if (w != null && index in w.waypoints.indices) {
+ w.waypoints.removeAt(index)
+ source.sendFeedback(Text.stringifiedTranslatable("firmament.command.waypoint.remove",
+ index))
} else {
source.sendError(Text.stringifiedTranslatable("firmament.command.waypoint.remove.error"))
}
}
}
}
- thenLiteral("export") {
- thenExecute {
- val data = Firmament.tightJson.encodeToString<List<ColeWeightWaypoint>>(waypoints.map {
- ColeWeightWaypoint(it.x,
- it.y,
- it.z)
- })
- ClipboardUtils.setTextContent(data)
- source.sendFeedback(tr("firmament.command.waypoint.export", "Copied ${waypoints.size} waypoints to clipboard"))
- }
- }
- thenLiteral("import") {
- thenExecute {
- val contents = ClipboardUtils.getTextContents()
- val data = try {
- Firmament.tightJson.decodeFromString<List<ColeWeightWaypoint>>(contents)
- } catch (ex: Exception) {
- Firmament.logger.error("Could not load waypoints from clipboard", ex)
- source.sendError(Text.translatable("firmament.command.waypoint.import.error"))
- return@thenExecute
- }
- waypoints.clear()
- data.mapTo(waypoints) { BlockPos(it.x, it.y, it.z) }
- source.sendFeedback(
- Text.stringifiedTranslatable(
- "firmament.command.waypoint.import",
- data.size
- )
- )
- }
- }
}
}
- @Subscribe
- fun onRenderTemporaryWaypoints(event: WorldRenderLastEvent) {
- temporaryPlayerWaypointList.entries.removeIf { it.value.postedAt.passedTime() > TConfig.tempWaypointDuration }
- if (temporaryPlayerWaypointList.isEmpty()) return
- RenderInWorldContext.renderInWorld(event) {
- temporaryPlayerWaypointList.forEach { (player, waypoint) ->
- block(waypoint.pos, 0xFFFFFF00.toInt())
- }
- temporaryPlayerWaypointList.forEach { (player, waypoint) ->
- val skin =
- MC.networkHandler?.listedPlayerListEntries?.find { it.profile.name == player }
- ?.skinTextures
- ?.texture
- withFacingThePlayer(waypoint.pos.toCenterPos()) {
- waypoint(waypoint.pos, Text.stringifiedTranslatable("firmament.waypoint.temporary", player))
- if (skin != null) {
- matrixStack.translate(0F, -20F, 0F)
- // Head front
- texture(
- skin, 16, 16,
- 1 / 8f, 1 / 8f,
- 2 / 8f, 2 / 8f,
- )
- // Head overlay
- texture(
- skin, 16, 16,
- 5 / 8f, 1 / 8f,
- 6 / 8f, 2 / 8f,
- )
- }
- }
- }
- }
- }
+ fun textInvalidIndex(index: Int) =
+ tr("firmament.command.waypoint.invalid-index",
+ "Invalid index $index provided.")
- @Subscribe
- fun onWorldReady(event: WorldReadyEvent) {
- temporaryPlayerWaypointList.clear()
- }
+ fun textNothingToExport(): Text =
+ tr("firmament.command.waypoint.export.nowaypoints",
+ "No waypoints to export found. Add some with /firm waypoint ~ ~ ~.")
}
fun <E> List<E>.wrappingWindow(startIndex: Int, windowSize: Int): List<E> {
@@ -285,35 +232,3 @@ fun <E> List<E>.wrappingWindow(startIndex: Int, windowSize: Int): List<E> {
}
return result
}
-
-
-fun FabricClientCommandSource.asFakeServer(): ServerCommandSource {
- val source = this
- return ServerCommandSource(
- object : CommandOutput {
- override fun sendMessage(message: Text?) {
- source.player.sendMessage(message, false)
- }
-
- override fun shouldReceiveFeedback(): Boolean {
- return true
- }
-
- override fun shouldTrackOutput(): Boolean {
- return true
- }
-
- override fun shouldBroadcastConsoleToOps(): Boolean {
- return true
- }
- },
- source.position,
- source.rotation,
- null,
- 0,
- "FakeServerCommandSource",
- Text.literal("FakeServerCommandSource"),
- null,
- source.player
- )
-}
diff --git a/src/main/kotlin/repo/RepoDownloadManager.kt b/src/main/kotlin/repo/RepoDownloadManager.kt
index 3efd83b..888248d 100644
--- a/src/main/kotlin/repo/RepoDownloadManager.kt
+++ b/src/main/kotlin/repo/RepoDownloadManager.kt
@@ -1,5 +1,3 @@
-
-
package moe.nea.firmament.repo
import io.ktor.client.call.body
@@ -28,101 +26,102 @@ import moe.nea.firmament.util.iterate
object RepoDownloadManager {
- val repoSavedLocation = Firmament.DATA_DIR.resolve("repo-extracted")
- val repoMetadataLocation = Firmament.DATA_DIR.resolve("loaded-repo-sha.txt")
-
- private fun loadSavedVersionHash(): String? =
- if (repoSavedLocation.exists()) {
- if (repoMetadataLocation.exists()) {
- try {
- repoMetadataLocation.readText().trim()
- } catch (e: IOException) {
- null
- }
- } else {
- null
- }
- } else null
-
- private fun saveVersionHash(versionHash: String) {
- latestSavedVersionHash = versionHash
- repoMetadataLocation.writeText(versionHash)
- }
-
- var latestSavedVersionHash: String? = loadSavedVersionHash()
- private set
-
- @Serializable
- private class GithubCommitsResponse(val sha: String)
-
- private suspend fun requestLatestGithubSha(): String? {
- if (RepoManager.Config.branch == "prerelease") {
- RepoManager.Config.branch = "master"
- }
- val response =
- Firmament.httpClient.get("https://api.github.com/repos/${RepoManager.Config.username}/${RepoManager.Config.reponame}/commits/${RepoManager.Config.branch}")
- if (response.status.value != 200) {
- return null
- }
- return response.body<GithubCommitsResponse>().sha
- }
-
- private suspend fun downloadGithubArchive(url: String): Path = withContext(IO) {
- val response = Firmament.httpClient.get(url)
- val targetFile = Files.createTempFile("firmament-repo", ".zip")
- val outputChannel = Files.newByteChannel(targetFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE)
- response.bodyAsChannel().copyTo(outputChannel)
- targetFile
- }
-
- /**
- * Downloads the latest repository from github, setting [latestSavedVersionHash].
- * @return true, if an update was performed, false, otherwise (no update needed, or wasn't able to complete update)
- */
- suspend fun downloadUpdate(force: Boolean): Boolean = withContext(CoroutineName("Repo Update Check")) {
- val latestSha = requestLatestGithubSha()
- if (latestSha == null) {
- logger.warn("Could not request github API to retrieve latest REPO sha.")
- return@withContext false
- }
- val currentSha = loadSavedVersionHash()
- if (latestSha != currentSha || force) {
- val requestUrl =
- "https://github.com/${RepoManager.Config.username}/${RepoManager.Config.reponame}/archive/$latestSha.zip"
- logger.info("Planning to upgrade repository from $currentSha to $latestSha from $requestUrl")
- val zipFile = downloadGithubArchive(requestUrl)
- logger.info("Download repository zip file to $zipFile. Deleting old repository")
- withContext(IO) { repoSavedLocation.toFile().deleteRecursively() }
- logger.info("Extracting new repository")
- withContext(IO) { extractNewRepository(zipFile) }
- logger.info("Repository loaded on disk.")
- saveVersionHash(latestSha)
- return@withContext true
- } else {
- logger.debug("Repository on latest sha $currentSha. Not performing update")
- return@withContext false
- }
- }
-
- private fun extractNewRepository(zipFile: Path) {
- repoSavedLocation.createDirectories()
- ZipInputStream(zipFile.inputStream()).use { cis ->
- while (true) {
- val entry = cis.nextEntry ?: break
- if (entry.isDirectory) continue
- val extractedLocation =
- repoSavedLocation.resolve(
- entry.name.substringAfter('/', missingDelimiterValue = "")
- )
- if (repoSavedLocation !in extractedLocation.iterate { it.parent }) {
- logger.error("Firmament detected an invalid zip file. This is a potential security risk, please report this in the Firmament discord.")
- throw RuntimeException("Firmament detected an invalid zip file. This is a potential security risk, please report this in the Firmament discord.")
- }
- extractedLocation.parent.createDirectories()
- extractedLocation.outputStream().use { cis.copyTo(it) }
- }
- }
- }
+ val repoSavedLocation = Firmament.DATA_DIR.resolve("repo-extracted")
+ val repoMetadataLocation = Firmament.DATA_DIR.resolve("loaded-repo-sha.txt")
+
+ private fun loadSavedVersionHash(): String? =
+ if (repoSavedLocation.exists()) {
+ if (repoMetadataLocation.exists()) {
+ try {
+ repoMetadataLocation.readText().trim()
+ } catch (e: IOException) {
+ null
+ }
+ } else {
+ null
+ }
+ } else null
+
+ private fun saveVersionHash(versionHash: String) {
+ latestSavedVersionHash = versionHash
+ repoMetadataLocation.writeText(versionHash)
+ }
+
+ var latestSavedVersionHash: String? = loadSavedVersionHash()
+ private set
+
+ @Serializable
+ private class GithubCommitsResponse(val sha: String)
+
+ private suspend fun requestLatestGithubSha(branchOverride: String?): String? {
+ if (RepoManager.Config.branch == "prerelease") {
+ RepoManager.Config.branch = "master"
+ }
+ val response =
+ Firmament.httpClient.get("https://api.github.com/repos/${RepoManager.Config.username}/${RepoManager.Config.reponame}/commits/${branchOverride ?: RepoManager.Config.branch}")
+ if (response.status.value != 200) {
+ return null
+ }
+ return response.body<GithubCommitsResponse>().sha
+ }
+
+ private suspend fun downloadGithubArchive(url: String): Path = withContext(IO) {
+ val response = Firmament.httpClient.get(url)
+ val targetFile = Files.createTempFile("firmament-repo", ".zip")
+ val outputChannel = Files.newByteChannel(targetFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE)
+ response.bodyAsChannel().copyTo(outputChannel)
+ targetFile
+ }
+
+ /**
+ * Downloads the latest repository from github, setting [latestSavedVersionHash].
+ * @return true, if an update was performed, false, otherwise (no update needed, or wasn't able to complete update)
+ */
+ suspend fun downloadUpdate(force: Boolean, branch: String? = null): Boolean =
+ withContext(CoroutineName("Repo Update Check")) {
+ val latestSha = requestLatestGithubSha(branch)
+ if (latestSha == null) {
+ logger.warn("Could not request github API to retrieve latest REPO sha.")
+ return@withContext false
+ }
+ val currentSha = loadSavedVersionHash()
+ if (latestSha != currentSha || force) {
+ val requestUrl =
+ "https://github.com/${RepoManager.Config.username}/${RepoManager.Config.reponame}/archive/$latestSha.zip"
+ logger.info("Planning to upgrade repository from $currentSha to $latestSha from $requestUrl")
+ val zipFile = downloadGithubArchive(requestUrl)
+ logger.info("Download repository zip file to $zipFile. Deleting old repository")
+ withContext(IO) { repoSavedLocation.toFile().deleteRecursively() }
+ logger.info("Extracting new repository")
+ withContext(IO) { extractNewRepository(zipFile) }
+ logger.info("Repository loaded on disk.")
+ saveVersionHash(latestSha)
+ return@withContext true
+ } else {
+ logger.debug("Repository on latest sha $currentSha. Not performing update")
+ return@withContext false
+ }
+ }
+
+ private fun extractNewRepository(zipFile: Path) {
+ repoSavedLocation.createDirectories()
+ ZipInputStream(zipFile.inputStream()).use { cis ->
+ while (true) {
+ val entry = cis.nextEntry ?: break
+ if (entry.isDirectory) continue
+ val extractedLocation =
+ repoSavedLocation.resolve(
+ entry.name.substringAfter('/', missingDelimiterValue = "")
+ )
+ if (repoSavedLocation !in extractedLocation.iterate { it.parent }) {
+ logger.error("Firmament detected an invalid zip file. This is a potential security risk, please report this in the Firmament discord.")
+ throw RuntimeException("Firmament detected an invalid zip file. This is a potential security risk, please report this in the Firmament discord.")
+ }
+ extractedLocation.parent.createDirectories()
+ extractedLocation.outputStream().use { cis.copyTo(it) }
+ }
+ }
+ }
}
diff --git a/src/main/kotlin/repo/RepoManager.kt b/src/main/kotlin/repo/RepoManager.kt
index e50a131..cc36fba 100644
--- a/src/main/kotlin/repo/RepoManager.kt
+++ b/src/main/kotlin/repo/RepoManager.kt
@@ -102,6 +102,13 @@ object RepoManager {
fun getNEUItem(skyblockId: SkyblockId): NEUItem? = neuRepo.items.getItemBySkyblockId(skyblockId.neuItem)
+ fun downloadOverridenBranch(branch: String) {
+ Firmament.coroutineScope.launch {
+ RepoDownloadManager.downloadUpdate(true, branch)
+ reload()
+ }
+ }
+
fun launchAsyncUpdate(force: Boolean = false) {
Firmament.coroutineScope.launch {
RepoDownloadManager.downloadUpdate(force)
diff --git a/src/main/kotlin/util/FirmFormatters.kt b/src/main/kotlin/util/FirmFormatters.kt
index acb7102..a660f51 100644
--- a/src/main/kotlin/util/FirmFormatters.kt
+++ b/src/main/kotlin/util/FirmFormatters.kt
@@ -13,6 +13,7 @@ import kotlin.math.roundToInt
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import net.minecraft.text.Text
+import net.minecraft.util.math.BlockPos
object FirmFormatters {
@@ -131,4 +132,7 @@ object FirmFormatters {
return if (boolean == trueIsGood) text.lime() else text.red()
}
+ fun formatPosition(position: BlockPos): Text {
+ return Text.literal("x: ${position.x}, y: ${position.y}, z: ${position.z}")
+ }
}
diff --git a/src/main/kotlin/util/MC.kt b/src/main/kotlin/util/MC.kt
index a0d2fc0..c1a5e65 100644
--- a/src/main/kotlin/util/MC.kt
+++ b/src/main/kotlin/util/MC.kt
@@ -113,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/compatloader/CompatLoader.kt b/src/main/kotlin/util/compatloader/CompatLoader.kt
index 6b60e87..d1073af 100644
--- a/src/main/kotlin/util/compatloader/CompatLoader.kt
+++ b/src/main/kotlin/util/compatloader/CompatLoader.kt
@@ -6,7 +6,7 @@ import kotlin.reflect.KClass
import kotlin.streams.asSequence
import moe.nea.firmament.Firmament
-abstract class CompatLoader<T : Any>(val kClass: Class<T>) {
+open class CompatLoader<T : Any>(val kClass: Class<T>) {
constructor(kClass: KClass<T>) : this(kClass.java)
val loader: ServiceLoader<T> = ServiceLoader.load(kClass)
diff --git a/src/main/kotlin/util/compatloader/CompatMeta.kt b/src/main/kotlin/util/compatloader/CompatMeta.kt
new file mode 100644
index 0000000..cf63645
--- /dev/null
+++ b/src/main/kotlin/util/compatloader/CompatMeta.kt
@@ -0,0 +1,48 @@
+package moe.nea.firmament.util.compatloader
+
+import java.util.ServiceLoader
+import moe.nea.firmament.events.subscription.SubscriptionList
+import moe.nea.firmament.init.AutoDiscoveryPlugin
+import moe.nea.firmament.util.ErrorUtil
+
+/**
+ * Declares the compat meta interface for the current source set.
+ * This is used by [CompatLoader], [SubscriptionList], and [AutoDiscoveryPlugin]. Annotate a [ICompatMeta] object with
+ * this.
+ */
+annotation class CompatMeta
+
+interface ICompatMetaGen {
+ fun owns(className: String): Boolean
+ val meta: ICompatMeta
+}
+
+interface ICompatMeta {
+ fun shouldLoad(): Boolean
+
+ companion object {
+ val allMetas = ServiceLoader
+ .load(ICompatMetaGen::class.java)
+ .toList()
+
+ fun shouldLoad(className: String): Boolean {
+ // TODO: replace this with a more performant package lookup
+ val meta = if (ErrorUtil.aggressiveErrors) {
+ val fittingMetas = allMetas.filter { it.owns(className) }
+ require(fittingMetas.size == 1) { "Orphaned or duplicate owned class $className (${fittingMetas.map { it.meta }}). Consider adding a @CompatMeta object." }
+ fittingMetas.single()
+ } else {
+ allMetas.firstOrNull { it.owns(className) }
+ }
+ return meta?.meta?.shouldLoad() ?: true
+ }
+ }
+}
+
+object CompatHelper {
+ fun isOwnedByPackage(className: String, vararg packages: String): Boolean {
+ // TODO: create package lookup structure once
+ val packageName = className.substringBeforeLast('.')
+ return packageName in packages
+ }
+}
diff --git a/src/main/kotlin/util/data/MultiFileDataHolder.kt b/src/main/kotlin/util/data/MultiFileDataHolder.kt
new file mode 100644
index 0000000..94c6f05
--- /dev/null
+++ b/src/main/kotlin/util/data/MultiFileDataHolder.kt
@@ -0,0 +1,63 @@
+package moe.nea.firmament.util.data
+
+import kotlinx.serialization.KSerializer
+import kotlin.io.path.createDirectories
+import kotlin.io.path.deleteExisting
+import kotlin.io.path.exists
+import kotlin.io.path.extension
+import kotlin.io.path.listDirectoryEntries
+import kotlin.io.path.nameWithoutExtension
+import kotlin.io.path.readText
+import kotlin.io.path.writeText
+import moe.nea.firmament.Firmament
+
+abstract class MultiFileDataHolder<T>(
+ val dataSerializer: KSerializer<T>,
+ val configName: String
+) { // TODO: abstract this + ProfileSpecificDataHolder
+ val configDirectory = Firmament.CONFIG_DIR.resolve(configName)
+ private var allData = readValues()
+ protected fun readValues(): MutableMap<String, T> {
+ if (!configDirectory.exists()) {
+ configDirectory.createDirectories()
+ }
+ val profileFiles = configDirectory.listDirectoryEntries()
+ return profileFiles
+ .filter { it.extension == "json" }
+ .mapNotNull {
+ try {
+ it.nameWithoutExtension to Firmament.json.decodeFromString(dataSerializer, it.readText())
+ } catch (e: Exception) { /* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/
+ IDataHolder.badLoads.add(configName)
+ Firmament.logger.error(
+ "Exception during loading of multi file data holder $it ($configName). This will reset that profiles config.",
+ e
+ )
+ null
+ }
+ }.toMap().toMutableMap()
+ }
+
+ fun save() {
+ if (!configDirectory.exists()) {
+ configDirectory.createDirectories()
+ }
+ val c = allData
+ configDirectory.listDirectoryEntries().forEach {
+ if (it.nameWithoutExtension !in c.mapKeys { it.toString() }) {
+ it.deleteExisting()
+ }
+ }
+ c.forEach { (name, value) ->
+ val f = configDirectory.resolve("$name.json")
+ f.writeText(Firmament.json.encodeToString(dataSerializer, value))
+ }
+ }
+
+ fun list(): Map<String, T> = allData
+ val validPathRegex = "[a-zA-Z0-9_][a-zA-Z0-9\\-_.]*".toPattern()
+ fun insert(name: String, value: T) {
+ require(validPathRegex.matcher(name).matches()) { "Not a valid name: $name" }
+ allData[name] = value
+ }
+}
diff --git a/src/main/kotlin/util/mc/asFakeServer.kt b/src/main/kotlin/util/mc/asFakeServer.kt
new file mode 100644
index 0000000..d3811bd
--- /dev/null
+++ b/src/main/kotlin/util/mc/asFakeServer.kt
@@ -0,0 +1,37 @@
+package moe.nea.firmament.util.mc
+
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
+import net.minecraft.server.command.CommandOutput
+import net.minecraft.server.command.ServerCommandSource
+import net.minecraft.text.Text
+
+fun FabricClientCommandSource.asFakeServer(): ServerCommandSource {
+ val source = this
+ return ServerCommandSource(
+ object : CommandOutput {
+ override fun sendMessage(message: Text?) {
+ source.player.sendMessage(message, false)
+ }
+
+ override fun shouldReceiveFeedback(): Boolean {
+ return true
+ }
+
+ override fun shouldTrackOutput(): Boolean {
+ return true
+ }
+
+ override fun shouldBroadcastConsoleToOps(): Boolean {
+ return true
+ }
+ },
+ source.position,
+ source.rotation,
+ null,
+ 0,
+ "FakeServerCommandSource",
+ Text.literal("FakeServerCommandSource"),
+ null,
+ source.player
+ )
+}
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory_0.png b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png
index 8dccb7f..8dccb7f 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory_0.png
+++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png
Binary files differ
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background_0.png b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png
index 10a3d83..10a3d83 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background_0.png
+++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png
Binary files differ
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background_0.png.mcmeta b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta
index 94b9a1d..94b9a1d 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background_0.png.mcmeta
+++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls_0.png b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png
index 97dd0ea..97dd0ea 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls_0.png
+++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png
Binary files differ
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls_0.png.mcmeta b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png.mcmeta
index 5964a6f..5964a6f 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls_0.png.mcmeta
+++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png.mcmeta
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row_0.png b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png
index 5ffc990..5ffc990 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row_0.png
+++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png
Binary files differ
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row_0.png.mcmeta b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta
index cd2857e..cd2857e 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row_0.png.mcmeta
+++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background_0.png b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png
index 8362bb6..8362bb6 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background_0.png
+++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png
Binary files differ
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background_0.png.mcmeta b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta
index a29299d..a29299d 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background_0.png.mcmeta
+++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory_1.png b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png
index 1831ef3..1831ef3 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory_1.png
+++ 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/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background_1.png b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png
index 5b774b2..5b774b2 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background_1.png
+++ 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/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background_1.png.mcmeta b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta
index 94b9a1d..94b9a1d 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background_1.png.mcmeta
+++ b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls_1.png b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png
index d4852d8..d4852d8 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls_1.png
+++ 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/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls_1.png.mcmeta b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png.mcmeta
index 5964a6f..5964a6f 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls_1.png.mcmeta
+++ b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_controls.png.mcmeta
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row_1.png b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png
index 61e9ee5..61e9ee5 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row_1.png
+++ 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/assets/firmament/textures/gui/sprites/storageoverlay/storage_row_1.png.mcmeta b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta
index cd2857e..cd2857e 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row_1.png.mcmeta
+++ b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta
diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background_1.png b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png
index 653a99e..653a99e 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background_1.png
+++ 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/assets/firmament/textures/gui/sprites/storageoverlay/upper_background_1.png.mcmeta b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta
index a29299d..a29299d 100644
--- a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background_1.png.mcmeta
+++ b/src/main/resources/resourcepacks/transparent_storage/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta
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"
+ }
+}
diff --git a/src/test/resources/testdata/items/necron-boots.snbt b/src/test/resources/testdata/items/necron-boots.snbt
new file mode 100644
index 0000000..35f8cf0
--- /dev/null
+++ b/src/test/resources/testdata/items/necron-boots.snbt
@@ -0,0 +1,68 @@
+{
+ components: {
+ "minecraft:attribute_modifiers": {
+ modifiers: [
+ ],
+ show_in_tooltip: 0b
+ },
+ "minecraft:custom_data": {
+ enchantments: {
+ depth_strider: 3,
+ feather_falling: 10,
+ growth: 5,
+ protection: 5
+ },
+ id: "POWER_WITHER_BOOTS",
+ modifier: "ancient",
+ rarity_upgrades: 1,
+ timestamp: 1704550620000L,
+ upgrade_level: 5,
+ uuid: "8b6c7485-cb59-44d3-ac8f-9e52a611cc64"
+ },
+ "minecraft:custom_name": '{"extra":[{"color":"light_purple","text":"Ancient Necron\'s Boots "},{"color":"gold","text":"✪✪✪✪✪"}],"italic":false,"text":""}',
+ "minecraft:dyed_color": {
+ rgb: 15167036,
+ show_in_tooltip: 0b
+ },
+ "minecraft:enchantments": {
+ levels: {
+ "minecraft:depth_strider": 3
+ }
+ },
+ "minecraft:hide_additional_tooltip": {
+ },
+ "minecraft:lore": [
+ '{"extra":[{"color":"gray","text":"Gear Score: "},{"color":"light_purple","text":"713 "},{"color":"dark_gray","text":"(2753)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Strength: "},{"color":"red","text":"+79 "},{"color":"blue","text":"(+35) "},{"color":"dark_gray","text":"(+333.75)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Crit Chance: "},{"color":"red","text":"+15% "},{"color":"blue","text":"(+15%) "},{"color":"dark_gray","text":"(+23.1%)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Crit Damage: "},{"color":"red","text":"+71% "},{"color":"blue","text":"(+38%) "},{"color":"dark_gray","text":"(+302.6%)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Health: "},{"color":"green","text":"+241.5 "},{"color":"blue","text":"(+7) "},{"color":"dark_gray","text":"(+1,010.15)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Defense: "},{"color":"green","text":"+120.5 "},{"color":"blue","text":"(+7) "},{"color":"dark_gray","text":"(+498.4)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Intelligence: "},{"color":"green","text":"+36 "},{"color":"blue","text":"(+25) "},{"color":"dark_gray","text":"(+155.75)"}],"italic":false,"text":""}',
+ '{"extra":[" ",{"color":"dark_gray","text":"["},{"color":"dark_gray","text":"❁"},{"color":"dark_gray","text":"] "},{"color":"dark_gray","text":"["},{"color":"dark_gray","text":"⚔"},{"color":"dark_gray","text":"]"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"Depth Strider III"},{"color":"blue","text":", "},{"color":"blue","text":"Feather Falling X"},{"color":"blue","text":", "},{"color":"blue","text":"Growth V"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"Protection V"}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Reduces the damage you take from"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"withers by "},{"color":"red","text":"10%"},{"color":"gray","text":"."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"gold","text":"Full Set Bonus: Witherborn "},{"color":"gray","text":"(3/4)"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Spawns a wither minion every "},{"color":"yellow","text":"30"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"yellow","text":""},{"color":"gray","text":"seconds up to a maximum "},{"color":"green","text":"1 "},{"color":"gray","text":"wither."}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Your withers will travel to and"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"explode on nearby enemies."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"color":"blue","text":"Ancient Bonus"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":"Grants "},{"color":"green","text":"+1 "},{"color":"blue","text":"☠ Crit Damage "},{"color":"gray","text":"per"}],"italic":false,"text":""}',
+ '{"extra":[{"color":"gray","text":""},{"color":"red","text":"Catacombs "},{"color":"gray","text":"level."}],"italic":false,"text":""}',
+ '{"italic":false,"text":""}',
+ '{"extra":[{"bold":true,"color":"light_purple","obfuscated":true,"text":"a"},"",{"bold":false,"extra":[" "],"italic":false,"obfuscated":false,"strikethrough":false,"text":"","underlined":false},{"bold":true,"color":"light_purple","text":"MYTHIC DUNGEON BOOTS "},{"bold":true,"color":"light_purple","obfuscated":true,"text":"a"}],"italic":false,"text":""}'
+ ],
+ "minecraft:unbreakable": {
+ show_in_tooltip: 0b
+ }
+ },
+ count: 1,
+ id: "minecraft:leather_boots"
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/Compat.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/Compat.kt
new file mode 100644
index 0000000..d95712b
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/Compat.kt
@@ -0,0 +1,11 @@
+package moe.nea.firmament.features.texturepack
+
+import moe.nea.firmament.util.compatloader.CompatMeta
+import moe.nea.firmament.util.compatloader.ICompatMeta
+
+@CompatMeta
+object Compat : ICompatMeta {
+ override fun shouldLoad(): Boolean {
+ return true
+ }
+}
diff --git a/symbols/src/main/kotlin/process/CompatMetaProcessor.kt b/symbols/src/main/kotlin/process/CompatMetaProcessor.kt
new file mode 100644
index 0000000..0753e4c
--- /dev/null
+++ b/symbols/src/main/kotlin/process/CompatMetaProcessor.kt
@@ -0,0 +1,63 @@
+package moe.nea.firmament.annotations.process
+
+import com.google.auto.service.AutoService
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.Dependencies
+import com.google.devtools.ksp.processing.KSPLogger
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSName
+
+class CompatMetaProcessor(val logger: KSPLogger, val codeGenerator: CodeGenerator, val sourceSetName: String) :
+ SymbolProcessor {
+ override fun process(resolver: Resolver): List<KSAnnotated> {
+ val files = resolver.getAllFiles().toList()
+ val packages = files.mapTo(mutableSetOf()) { it.packageName.asString() }
+ packages.add("moe.nea.firmament.annotations.generated.$sourceSetName")
+ val compatMeta = resolver.getSymbolsWithAnnotation("moe.nea.firmament.util.compatloader.CompatMeta")
+ .singleOrNull() as KSClassDeclaration? ?: return listOf()
+ val dependencies = Dependencies(aggregating = true, *files.toTypedArray())
+ val generatedFileName = "GeneratedCompat${sourceSetName.replaceFirstChar { it.uppercaseChar() }}"
+ val compatFile =
+ codeGenerator.createNewFile(dependencies, "moe.nea.firmament.annotations.generated.$sourceSetName", generatedFileName)
+ .bufferedWriter()
+ compatFile.appendLine("// This file is @generated by SubscribeAnnotationProcessor")
+ compatFile.appendLine("// Do not edit")
+ compatFile.appendLine("package moe.nea.firmament.annotations.generated.$sourceSetName")
+ compatFile.appendLine("class $generatedFileName : moe.nea.firmament.util.compatloader.ICompatMetaGen {")
+ compatFile.appendLine("""
+ override fun owns(className: String): Boolean {
+ return moe.nea.firmament.util.compatloader.CompatHelper.isOwnedByPackage(className, ${
+ packages.joinToString { "\"" + it + "\"" }
+ })
+ }
+
+ override val meta: moe.nea.firmament.util.compatloader.ICompatMeta
+ get() = ${compatMeta.qualifiedName!!.asString()}
+""")
+ compatFile.appendLine("}")
+ compatFile.close()
+ val metaInf = codeGenerator.createNewFileByPath(
+ dependencies,
+ "META-INF/services/moe.nea.firmament.util.compatloader.ICompatMetaGen", extensionName = "")
+ .bufferedWriter()
+ metaInf.append("moe.nea.firmament.annotations.generated.$sourceSetName.")
+ metaInf.appendLine(generatedFileName)
+ metaInf.close()
+ return listOf()
+ }
+
+
+ @AutoService(SymbolProcessorProvider::class)
+ class Provider : SymbolProcessorProvider {
+ override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
+ return CompatMetaProcessor(environment.logger,
+ environment.codeGenerator,
+ environment.options["firmament.sourceset"] ?: "main")
+ }
+ }
+}
diff --git a/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt b/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt
index d7aaf28..fe1518f 100644
--- a/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt
+++ b/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt
@@ -32,7 +32,7 @@ class SubscribeAnnotationProcessor(
val generatedFileName = "AllSubscriptions${sourceSetName.replaceFirstChar { it.uppercaseChar() }}"
val subscriptionsFile =
codeGenerator
- .createNewFile(dependencies, "moe.nea.firmament.annotations.generated", generatedFileName)
+ .createNewFile(dependencies, "moe.nea.firmament.annotations.generated.$sourceSetName", generatedFileName)
.bufferedWriter()
subscriptionsFile.apply {
appendLine("// This file is @generated by SubscribeAnnotationProcessor")
@@ -40,7 +40,7 @@ class SubscribeAnnotationProcessor(
for (file in subscriptionSet) {
appendLine("// Dependency: ${file.filePath}")
}
- appendLine("package moe.nea.firmament.annotations.generated")
+ appendLine("package moe.nea.firmament.annotations.generated.$sourceSetName")
appendLine()
appendLine("import moe.nea.firmament.events.subscription.*")
appendLine()
@@ -65,7 +65,7 @@ class SubscribeAnnotationProcessor(
dependencies,
"META-INF/services/moe.nea.firmament.events.subscription.SubscriptionList", extensionName = "")
.bufferedWriter()
- metaInf.append("moe.nea.firmament.annotations.generated.")
+ metaInf.append("moe.nea.firmament.annotations.generated.$sourceSetName.")
metaInf.appendLine(generatedFileName)
metaInf.close()
}
diff --git a/translations/en_us.json b/translations/en_us.json
index 14ed3f6..8033eaf 100644
--- a/translations/en_us.json
+++ b/translations/en_us.json
@@ -7,7 +7,8 @@
"firmament.command.waypoint.added": "Added waypoint %s %s %s.",
"firmament.command.waypoint.clear": "Cleared waypoints.",
"firmament.command.waypoint.import": "Imported %s waypoints from clipboard.",
- "firmament.command.waypoint.import.error": "Could not import waypoints from clipboard. Make sure they are on ColeWeight format:\n[{\"x\": 69, \"y\":420, \"z\": 36}]",
+ "firmament.command.waypoint.ordered.toggle.false": "Disabled ordered waypoints",
+ "firmament.command.waypoint.ordered.toggle.true": "Enabled ordered waypoints",
"firmament.command.waypoint.remove": "Removed waypoint %s. Other waypoints may have different indexes now.",
"firmament.command.waypoint.remove.error": "Could not find waypoint with that index to delete.",
"firmament.command.waypoint.skip": "Skipped 1 waypoint",
@@ -117,6 +118,8 @@
"firmament.config.fixes.auto-sprint-keybinding": "Auto Sprint KeyBinding",
"firmament.config.fixes.auto-sprint-keybinding.description": "Toggle auto sprint via this keybinding.",
"firmament.config.fixes.auto-sprint.description": "This is different from vanilla sprint in the way that it only marks the keybinding pressed for the first tick of walking.",
+ "firmament.config.fixes.disable-hurt-cam": "No Hurt Cam",
+ "firmament.config.fixes.disable-hurt-cam.description": "Disable the damage screen shake animation.",
"firmament.config.fixes.hide-mob-effects": "Hide Potion Effects",
"firmament.config.fixes.hide-mob-effects.description": "Hide Potion effects on the right side of your player inventory.",
"firmament.config.fixes.peek-chat": "Peek Chat",
@@ -266,11 +269,9 @@
"firmament.config.storage-overlay.rows.description": "Max columns used by the storage overlay and overview.",
"firmament.config.storage-overlay.scroll-speed": "Scroll Speed",
"firmament.config.storage-overlay.scroll-speed.description": "Scroll speed inside of the storage overlay and overview.",
- "firmament.config.storage-overlay.texture": "Storage Overlay Texture",
- "firmament.config.storage-overlay.texture.description": "Set which texture to use for storage overlay",
- "firmament.config.storage-overlay.texture.choice.default": "Default",
- "firmament.config.storage-overlay.texture.choice.clear": "Clear",
"firmament.config.waypoints": "Waypoints",
+ "firmament.config.waypoints.reset-order-on-swap": "Reset Ordered Waypoints On Hop",
+ "firmament.config.waypoints.reset-order-on-swap.description": "Resets Ordered Waypoint progress after swapping to another world.",
"firmament.config.waypoints.show-index": "Show ordered waypoint indexes",
"firmament.config.waypoints.show-index.description": "Show the number of an ordered waypoint in the world.",
"firmament.config.waypoints.skip-to-nearest": "Allow skipping waypoints",
@@ -296,7 +297,6 @@
"firmament.inventory-buttons.import-failed": "One of your buttons could only be imported partially",
"firmament.inventory-buttons.load-preset": "Load Preset",
"firmament.inventory-buttons.save-preset": "Save Preset",
- "firmament.jade.breaking_power": "Required Breaking Power: %s",
"firmament.key.category": "Firmament",
"firmament.keybinding.external": "%s",
"firmament.mixins.start": "Applied firmament mixins:",
@@ -349,8 +349,6 @@
"firmament.recipe.mobs.name": "§8[§7Lv %d§8] §c%s",
"firmament.recipe.mobs.name.nolevel": "§c%s",
"firmament.recipe.novanilla": "Hypixel cannot super craft vanilla recipes",
- "firmament.recipecategory.reforge": "Reforge",
- "firmament.recipecategory.reforge.basic": "This is a basic reforge, available at the Blacksmith.",
"firmament.reiwarning": "Firmament needs RoughlyEnoughItems to display its item list!",
"firmament.reiwarning.disable": "Click here to disable this warning",
"firmament.reiwarning.disabled": "Disabled the RoughlyEnoughItems warning. Keep in mind that you will not have an item list without REI.",