diff --git a/build.gradle b/build.gradle
index bc8c7282..20f4a1a3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,5 @@
plugins {
- id 'fabric-loom' version '1.2-SNAPSHOT'
+ id 'fabric-loom' version '1.5-SNAPSHOT'
id 'maven-publish'
}
@@ -23,6 +23,17 @@ repositories {
name = "Ladysnake Mods"
url = 'https://maven.ladysnake.org/releases'
}
+ exclusiveContent {
+ forRepository {
+ maven {
+ name = "Modrinth"
+ url = "https://api.modrinth.com/maven"
+ }
+ }
+ filter {
+ includeGroup "maven.modrinth"
+ }
+ }
}
dependencies {
@@ -41,6 +52,10 @@ dependencies {
include "dev.onyxstudios.cardinal-components-api:cardinal-components-base:${project.cca_version}"
include "dev.onyxstudios.cardinal-components-api:cardinal-components-entity:${project.cca_version}"
+ modCompileOnly("maven.modrinth:overflowing-bars:${project.overflowing_bars_version}") { transitive = false }
+ modCompileOnly("maven.modrinth:colorful-hearts:${project.colorful_hearts_version}") { transitive = false }
+
+ modCompileOnly("maven.modrinth:fabric-seasons:${project.fabric_seasons_version}") { transitive = false }
// Uncomment the following line to enable the deprecated Fabric API modules.
// These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time.
@@ -86,6 +101,9 @@ subprojects {
}
loom {
+
+ accessWidenerPath = file("src/main/resources/thermoo.accesswidener")
+
interfaceInjection {
enableDependencyInterfaceInjection = true
}
diff --git a/docs/requires-thermoo.png b/docs/requires-thermoo.png
new file mode 100644
index 00000000..10294d79
Binary files /dev/null and b/docs/requires-thermoo.png differ
diff --git a/gradle.properties b/gradle.properties
index 22ed9603..94519332 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,18 +5,25 @@ org.gradle.parallel=true
# Fabric Properties
minecraft_version=1.20.1
-yarn_mappings=1.20.1+build.5
-loader_version=0.14.21
+yarn_mappings=1.20.1+build.10
+loader_version=0.15.7
# Mod Properties
-mod_version=2.1.2
+mod_version=2.2
maven_group=com.github.thedeathlycow
archives_base_name=thermoo
#Fabric api
-fabric_version=0.84.0+1.20.1
+fabric_version=0.91.0+1.20.1
# Cardinal components
cca_version=5.2.2
+
+# Health bar mods
+overflowing_bars_version=v8.0.0-1.20.1-Fabric
+colorful_hearts_version=4.0.4
+
+# Seasons mods
+fabric_seasons_version=2.3+1.20
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index c898b7af..fe4d52ed 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
\ No newline at end of file
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/ThermooCodecs.java b/src/main/java/com/github/thedeathlycow/thermoo/api/ThermooCodecs.java
new file mode 100644
index 00000000..4f161b8f
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/ThermooCodecs.java
@@ -0,0 +1,71 @@
+package com.github.thedeathlycow.thermoo.api;
+
+import com.mojang.datafixers.util.Either;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.entity.attribute.EntityAttributeModifier;
+import net.minecraft.util.Uuids;
+import net.minecraft.util.math.MathHelper;
+import net.minecraft.util.math.random.Random;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Helpful codecs used by Thermoo.
+ *
+ * Exposed in API for the convenience of API users.
+ */
+@ApiStatus.Experimental
+public class ThermooCodecs {
+
+ public static final Codec EQUIPMENT_SLOT_CODEC = createEnumCodec(EquipmentSlot.class);
+
+ public static final Codec ENTITY_ATTRIBUTE_OPERATION_CODEC = createEnumCodec(EntityAttributeModifier.Operation.class);
+
+ public static final Codec ATTRIBUTE_MODIFIER_CODEC = RecordCodecBuilder.create(
+ instance -> instance.group(
+ Uuids.CODEC
+ .fieldOf("uuid")
+ .orElseGet(() -> MathHelper.randomUuid(Random.createLocal()))
+ .forGetter(EntityAttributeModifier::getId),
+ Codec.STRING
+ .fieldOf("name")
+ .forGetter(EntityAttributeModifier::getName),
+ Codec.DOUBLE
+ .fieldOf("value")
+ .forGetter(EntityAttributeModifier::getValue),
+ ENTITY_ATTRIBUTE_OPERATION_CODEC
+ .fieldOf("operation")
+ .forGetter(EntityAttributeModifier::getOperation)
+ ).apply(instance, EntityAttributeModifier::new)
+ );
+
+ /**
+ * Creates a codec for an Enum. Either uses the enum ordinal or the name, but prefers the ordinal for more efficient
+ * storage.
+ *
+ * @param clazz The class of the enum.
+ * @param The enum type
+ * @return Returns a codec for the enum class
+ */
+ public static > Codec createEnumCodec(Class clazz) {
+ return Codec.either(
+ Codec.INT.xmap(
+ ordinal -> clazz.getEnumConstants()[ordinal],
+ Enum::ordinal
+ ),
+ Codec.STRING.xmap(
+ name -> Enum.valueOf(clazz, name),
+ Enum::name
+ )
+ ).xmap(
+ either -> either.left().orElseGet(() -> either.right().orElseThrow()),
+ Either::left
+ );
+ }
+
+ private ThermooCodecs() {
+
+ }
+
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/ThermooRegistryKeys.java b/src/main/java/com/github/thedeathlycow/thermoo/api/ThermooRegistryKeys.java
index dad30d92..471faa52 100644
--- a/src/main/java/com/github/thedeathlycow/thermoo/api/ThermooRegistryKeys.java
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/ThermooRegistryKeys.java
@@ -1,5 +1,6 @@
package com.github.thedeathlycow.thermoo.api;
+import com.github.thedeathlycow.thermoo.api.attribute.ItemAttributeModifier;
import com.github.thedeathlycow.thermoo.api.temperature.effects.TemperatureEffect;
import com.github.thedeathlycow.thermoo.impl.Thermoo;
import net.minecraft.registry.Registry;
@@ -10,6 +11,8 @@ public final class ThermooRegistryKeys {
public static final RegistryKey>> TEMPERATURE_EFFECT = createRegistryKey("temperature_effects");
+ public static final RegistryKey> ITEM_ATTRIBUTE_MODIFIER = createRegistryKey("item_attribute_modifier");
+
private static RegistryKey> createRegistryKey(String registryId) {
return RegistryKey.ofRegistry(Thermoo.id(registryId));
}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/attribute/ItemAttributeModifier.java b/src/main/java/com/github/thedeathlycow/thermoo/api/attribute/ItemAttributeModifier.java
new file mode 100644
index 00000000..60f7c167
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/attribute/ItemAttributeModifier.java
@@ -0,0 +1,150 @@
+package com.github.thedeathlycow.thermoo.api.attribute;
+
+import com.github.thedeathlycow.thermoo.api.ThermooCodecs;
+import com.google.common.collect.Multimap;
+import com.mojang.datafixers.util.Either;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.entity.attribute.EntityAttribute;
+import net.minecraft.entity.attribute.EntityAttributeModifier;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.registry.Registries;
+import net.minecraft.registry.RegistryKeys;
+import net.minecraft.registry.tag.TagKey;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * Applies changes to the default attributes of an item. Item attribute modifiers are provided in datapack JSON files at
+ * {@code data//thermoo/item_attribute_modifier/}. They are synchronized to clients so that
+ * they will properly show up in the item tooltip.
+ *
+ * Example JSON file:
+ *
+ * {
+ * "attribute": "thermoo:generic.heat_resistance",
+ * "modifier": {
+ * "uuid": "413a10a0-bf0b-47db-a9a9-2eb3dda3bbaf",
+ * "name": "Test",
+ * "value": -1.0,
+ * "operation": "ADDITION"
+ * },
+ * "item": {
+ * "items": [
+ * "minecraft:diamond_helmet",
+ * "minecraft:iron_helmet",
+ * "minecraft:leather_helmet"
+ * ]
+ * },
+ * "slot": "HEAD"
+ * }
+ *
+ *
+ * You can also use tags:
+ *
+ * {
+ * "attribute": "thermoo:generic.heat_resistance",
+ * "modifier": {
+ * "uuid": "413a10a0-bf0b-47db-a9a9-2eb3dda3bbaf",
+ * "name": "Test",
+ * "value": 2.0,
+ * "operation": "ADDITION"
+ * },
+ * "item": {
+ * "tag": "scorchful:turtle_armor"
+ * },
+ * "slot": "HEAD"
+ * }
+ *
+ *
+ * This class is experimental and subject to change. Please use the datapack JSON instead of referencing this class directly.
+ *
+ * @param attribute The attribute this modifier affects
+ * @param modifier The modifier that this applies to the attribute
+ * @param itemPredicate A type predicate for items this should apply to
+ * @param slot The slot this should apply to
+ */
+@ApiStatus.Experimental
+public record ItemAttributeModifier(
+ EntityAttribute attribute,
+ EntityAttributeModifier modifier,
+ ItemTypePredicate itemPredicate,
+ EquipmentSlot slot
+) {
+
+ public static final Codec CODEC = RecordCodecBuilder.create(
+ instance -> instance.group(
+ Registries.ATTRIBUTE.getCodec()
+ .fieldOf("attribute")
+ .forGetter(ItemAttributeModifier::attribute),
+ ThermooCodecs.ATTRIBUTE_MODIFIER_CODEC
+ .fieldOf("modifier")
+ .forGetter(ItemAttributeModifier::modifier),
+ ItemTypePredicate.CODEC
+ .fieldOf("item")
+ .forGetter(ItemAttributeModifier::itemPredicate),
+ ThermooCodecs.EQUIPMENT_SLOT_CODEC
+ .fieldOf("slot")
+ .forGetter(ItemAttributeModifier::slot)
+ ).apply(instance, ItemAttributeModifier::new)
+ );
+
+ public void apply(
+ ItemStack stack,
+ EquipmentSlot slot,
+ Multimap attributeModifiers
+ ) {
+ if (this.slot == slot && this.itemPredicate.test(stack)) {
+ attributeModifiers.put(this.attribute, this.modifier);
+ }
+ }
+
+ public record ItemTypePredicate(
+ @Nullable List- items,
+ @Nullable TagKey
- itemTag
+ ) implements Predicate {
+
+ public static final Codec CODEC = Codec.either(
+ RecordCodecBuilder.create(
+ instance -> instance.group(
+ Codec.list(Registries.ITEM.getCodec())
+ .fieldOf("items")
+ .forGetter(ItemTypePredicate::items)
+ ).apply(instance, items -> new ItemTypePredicate(items, null))
+ ),
+ RecordCodecBuilder.create(
+ instance -> instance.group(
+ TagKey.codec(RegistryKeys.ITEM)
+ .fieldOf("tag")
+ .forGetter(ItemTypePredicate::itemTag)
+ ).apply(instance, tag -> new ItemTypePredicate(null, tag))
+ )
+ ).xmap(
+ either -> either.left().orElseGet(() -> either.right().orElseThrow()),
+ Either::left
+ );
+
+ @Override
+ public boolean test(ItemStack stack) {
+
+ if (this.items == null && this.itemTag == null) {
+ return false;
+ }
+
+ if (this.items != null && !this.items.contains(stack.getItem())) {
+ return false;
+ }
+
+ if (this.itemTag != null && !stack.isIn(this.itemTag)) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/attribute/package-info.java b/src/main/java/com/github/thedeathlycow/thermoo/api/attribute/package-info.java
new file mode 100644
index 00000000..36cfd2b6
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/attribute/package-info.java
@@ -0,0 +1,4 @@
+/**
+ *
+ */
+package com.github.thedeathlycow.thermoo.api.attribute;
\ No newline at end of file
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/client/StatusBarOverlayRenderEvents.java b/src/main/java/com/github/thedeathlycow/thermoo/api/client/StatusBarOverlayRenderEvents.java
new file mode 100644
index 00000000..96267484
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/client/StatusBarOverlayRenderEvents.java
@@ -0,0 +1,58 @@
+package com.github.thedeathlycow.thermoo.api.client;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.entity.player.PlayerEntity;
+import org.joml.Vector2i;
+
+/**
+ * Event for rendering temperature overlays on status bar.
+ */
+@Environment(EnvType.CLIENT)
+public class StatusBarOverlayRenderEvents {
+
+ /**
+ * Invoked after the player health bar is drawn. Does not include information on the Absorption bar.
+ *
+ * Integrates with Colorful Hearts and Overflowing Bars for now - but this integration will be removed in the
+ * future.
+ *
+ * Custom heart types, like Frozen Hearts, should be handled separately.
+ */
+ public static final Event AFTER_HEALTH_BAR = EventFactory.createArrayBacked(
+ RenderHealthBarCallback.class,
+ callbacks -> (context, player, heartPositions, displayHealth, maxDisplayHeath) -> {
+ for (RenderHealthBarCallback callback : callbacks) {
+ callback.render(context, player, heartPositions, displayHealth, maxDisplayHeath);
+ }
+ }
+ );
+
+ @FunctionalInterface
+ public interface RenderHealthBarCallback {
+
+ /**
+ * Note that {@code displayHealth} and {@code maxDisplayHealth} are not always the same as health and max health. Mods that
+ * override the health bar rendering like Colorful Hearts may change these values.
+ *
+ * @param context DrawContext for the HUD
+ * @param player The player rendering hearts for
+ * @param heartPositions An array of heart positions on the HUD. Elements may be null, indicating that a heart
+ * should not be rendered at this index.
+ * @param displayHealth How many half hearts are to be displayed
+ * @param maxDisplayHealth The maximum number of half hearts to be displayed
+ */
+ void render(
+ DrawContext context,
+ PlayerEntity player,
+ Vector2i[] heartPositions,
+ int displayHealth,
+ int maxDisplayHealth
+ );
+
+ }
+
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/client/package-info.java b/src/main/java/com/github/thedeathlycow/thermoo/api/client/package-info.java
new file mode 100644
index 00000000..7853a85d
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/client/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * Relates to any client APIs provided by Thermoo
+ */
+@Environment(EnvType.CLIENT)
+package com.github.thedeathlycow.thermoo.api.client;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
\ No newline at end of file
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/command/EnvironmentCommand.java b/src/main/java/com/github/thedeathlycow/thermoo/api/command/EnvironmentCommand.java
index 8af72fd9..879a53f9 100644
--- a/src/main/java/com/github/thedeathlycow/thermoo/api/command/EnvironmentCommand.java
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/command/EnvironmentCommand.java
@@ -1,11 +1,16 @@
package com.github.thedeathlycow.thermoo.api.command;
import com.github.thedeathlycow.thermoo.api.temperature.EnvironmentManager;
+import com.github.thedeathlycow.thermoo.api.util.TemperatureConverter;
+import com.github.thedeathlycow.thermoo.api.util.TemperatureUnit;
import com.github.thedeathlycow.thermoo.impl.Thermoo;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.command.argument.BlockPosArgumentType;
import net.minecraft.command.argument.EntityArgumentType;
+import net.minecraft.server.command.FillCommand;
import net.minecraft.server.command.ServerCommandSource;
+import net.minecraft.server.command.SetBlockCommand;
+import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import org.jetbrains.annotations.Contract;
@@ -64,6 +69,24 @@ private static LiteralArgumentBuilder buildCommand() {
);
}
)
+ .then(
+ argument("unit", TemperatureUnitArgumentType.temperatureUnit())
+ .executes(
+ context -> {
+ return executeCheckTemperature(
+ context.getSource(),
+ EntityArgumentType.getEntity(
+ context,
+ "target"
+ ).getBlockPos(),
+ TemperatureUnitArgumentType.getTemperatureUnit(
+ context,
+ "unit"
+ )
+ );
+ }
+ )
+ )
)
.then(
argument("location", BlockPosArgumentType.blockPos())
@@ -71,13 +94,31 @@ private static LiteralArgumentBuilder buildCommand() {
context -> {
return executeCheckTemperature(
context.getSource(),
- BlockPosArgumentType.getBlockPos(
+ BlockPosArgumentType.getLoadedBlockPos(
context,
"location"
)
);
}
)
+ .then(
+ argument("unit", TemperatureUnitArgumentType.temperatureUnit())
+ .executes(
+ context -> {
+ return executeCheckTemperature(
+ context.getSource(),
+ BlockPosArgumentType.getLoadedBlockPos(
+ context,
+ "location"
+ ),
+ TemperatureUnitArgumentType.getTemperatureUnit(
+ context,
+ "unit"
+ )
+ );
+ }
+ )
+ )
);
return literal("thermoo").then(
@@ -111,7 +152,7 @@ private static int executeCheckTemperature(ServerCommandSource source, BlockPos
source.sendFeedback(
() -> Text.translatableWithFallback(
"commands.thermoo.environment.checktemperature.success",
- "The passive temperature change at %s, %s, %s (%s) is %d",
+ "The passive temperature change at %s, %s, %s (%s) is %s",
location.getX(),
location.getY(),
location.getZ(),
@@ -123,4 +164,36 @@ private static int executeCheckTemperature(ServerCommandSource source, BlockPos
return temperatureChange;
}
+
+ private static int executeCheckTemperature(ServerCommandSource source, BlockPos location, TemperatureUnit unit) {
+
+ int temperatureTick = EnvironmentManager.INSTANCE.getController().getLocalTemperatureChange(
+ source.getWorld(),
+ location
+ );
+
+
+ var biome = source.getWorld().getBiome(location).getKey().orElse(null);
+
+ double temperature = TemperatureConverter.temperatureTickToAmbientTemperature(
+ temperatureTick,
+ new TemperatureConverter.Settings(unit, 1, 0)
+ );
+
+ source.sendFeedback(
+ () -> Text.translatableWithFallback(
+ "commands.thermoo.environment.checktemperature.unit.success",
+ "The temperature at %s, %s, %s (%s) is %s°%s",
+ location.getX(),
+ location.getY(),
+ location.getZ(),
+ biome == null ? "unknown" : biome.getValue(),
+ String.format("%.2f", temperature),
+ unit.getUnitSymbol()
+ ),
+ false
+ );
+
+ return (int) temperature;
+ }
}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/command/TemperatureUnitArgumentType.java b/src/main/java/com/github/thedeathlycow/thermoo/api/command/TemperatureUnitArgumentType.java
new file mode 100644
index 00000000..0c8382bc
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/command/TemperatureUnitArgumentType.java
@@ -0,0 +1,24 @@
+package com.github.thedeathlycow.thermoo.api.command;
+
+import com.github.thedeathlycow.thermoo.api.util.TemperatureUnit;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.serialization.Codec;
+import net.minecraft.command.argument.EnumArgumentType;
+import net.minecraft.server.command.ServerCommandSource;
+import net.minecraft.util.StringIdentifiable;
+
+public class TemperatureUnitArgumentType extends EnumArgumentType {
+ public static final Codec CODEC = StringIdentifiable.createCodec(TemperatureUnit::values);
+
+ private TemperatureUnitArgumentType() {
+ super(CODEC, TemperatureUnit::values);
+ }
+
+ public static TemperatureUnitArgumentType temperatureUnit() {
+ return new TemperatureUnitArgumentType();
+ }
+
+ public static TemperatureUnit getTemperatureUnit(CommandContext context, String id) {
+ return context.getArgument(id, TemperatureUnit.class);
+ }
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/season/ThermooSeasonEvents.java b/src/main/java/com/github/thedeathlycow/thermoo/api/season/ThermooSeasonEvents.java
new file mode 100644
index 00000000..dd3f9cdf
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/season/ThermooSeasonEvents.java
@@ -0,0 +1,45 @@
+package com.github.thedeathlycow.thermoo.api.season;
+
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+import net.minecraft.world.World;
+
+import java.util.Optional;
+
+/**
+ * Events related to Seasons mod integration in Thermoo. Thermoo does not add seasonal functionality by itself, seasons
+ * must be implemented by another mod like Fabric Seasons or Serene Seasons. This only provides the ability to query
+ * seasons if you want to use them.
+ */
+public class ThermooSeasonEvents {
+
+ /**
+ * Retrieves the current season. This event just places season integration into
+ * a common source.
+ *
+ * If any listener returns a non-empty season, then all further processing is cancelled and that season is returned.
+ *
+ * Returns empty by default.
+ */
+ public static final Event GET_CURRENT_SEASON = EventFactory.createArrayBacked(
+ CurrentSeasonCallback.class,
+ callbacks -> world -> {
+ for (CurrentSeasonCallback callback : callbacks) {
+ Optional season = callback.getCurrentSeason(world);
+ if (season.isPresent()) {
+ return season;
+ }
+ }
+
+ return Optional.empty();
+ }
+ );
+
+ @FunctionalInterface
+ public interface CurrentSeasonCallback {
+
+ Optional getCurrentSeason(World world);
+
+ }
+
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/season/ThermooSeasons.java b/src/main/java/com/github/thedeathlycow/thermoo/api/season/ThermooSeasons.java
new file mode 100644
index 00000000..cfa6b985
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/season/ThermooSeasons.java
@@ -0,0 +1,31 @@
+package com.github.thedeathlycow.thermoo.api.season;
+
+import net.minecraft.world.World;
+
+import java.util.Optional;
+
+/**
+ * Mod-agnostic Seasons enum. Thermoo does not provide seasons itself, but this can be used to better integrate with any
+ * mods that do provide seasons.
+ */
+public enum ThermooSeasons {
+ SPRING,
+ SUMMER,
+ AUTUMN,
+ WINTER;
+
+ /**
+ * Shorthand for invoking {@link ThermooSeasonEvents#GET_CURRENT_SEASON}.
+ *
+ * Retrieves the current season, if a season mod is loaded. Thermoo does not add seasons by itself, seasons must be
+ * implemented by another mod like Fabric Seasons or Serene Seasons. This event just places season integration into
+ * a common source.
+ *
+ * @param world The current world / level to get the season from.
+ * @return Returns the current season if a Seasons mod is installed, or empty if no seasons mod is installed.
+ */
+ public static Optional getCurrentSeason(World world) {
+ return ThermooSeasonEvents.GET_CURRENT_SEASON.invoker().getCurrentSeason(world);
+ }
+
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/season/package-info.java b/src/main/java/com/github/thedeathlycow/thermoo/api/season/package-info.java
new file mode 100644
index 00000000..8d1e4cfd
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/season/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Provides a common source for integrations with seasons mods like Fabric Seasons and Serene Seasons
+ */
+package com.github.thedeathlycow.thermoo.api.season;
\ No newline at end of file
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/temperature/effects/SequenceTemperatureEffect.java b/src/main/java/com/github/thedeathlycow/thermoo/api/temperature/effects/SequenceTemperatureEffect.java
new file mode 100644
index 00000000..f428e471
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/temperature/effects/SequenceTemperatureEffect.java
@@ -0,0 +1,54 @@
+package com.github.thedeathlycow.thermoo.api.temperature.effects;
+
+import com.google.gson.*;
+import net.minecraft.entity.EntityType;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.server.world.ServerWorld;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Applies multiple child temperature effects at once. Useful for when you want to apply several different temperature
+ * effects under the same base set of conditions, without the overhead of checking those conditions multiple times.
+ */
+public class SequenceTemperatureEffect extends TemperatureEffect {
+
+ @Override
+ public void apply(LivingEntity victim, ServerWorld serverWorld, Config config) {
+ for (ConfiguredTemperatureEffect> child : config.children()) {
+ EntityType> childType = child.getEntityType();
+ if (childType == null || victim.getType() == childType) {
+ child.applyIfPossible(victim);
+ }
+ }
+ }
+
+ @Override
+ public boolean shouldApply(LivingEntity victim, Config config) {
+ return true;
+ }
+
+ @Override
+ public Config configFromJson(JsonElement json, JsonDeserializationContext context) throws JsonSyntaxException {
+ return Config.fromJson(json, context);
+ }
+
+ public record Config(List> children) {
+
+ public static Config fromJson(JsonElement json, JsonDeserializationContext context) throws JsonSyntaxException {
+ JsonObject object = json.getAsJsonObject();
+
+ JsonArray jsonChildren = object.get("children").getAsJsonArray();
+ List> children = new ArrayList<>(jsonChildren.size());
+ for (JsonElement jsonChild : jsonChildren) {
+ ConfiguredTemperatureEffect> child = context.deserialize(jsonChild, ConfiguredTemperatureEffect.class);
+ children.add(child);
+ }
+
+ return new Config(children);
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/temperature/effects/TemperatureEffects.java b/src/main/java/com/github/thedeathlycow/thermoo/api/temperature/effects/TemperatureEffects.java
index 755238f6..8c6ce1f8 100644
--- a/src/main/java/com/github/thedeathlycow/thermoo/api/temperature/effects/TemperatureEffects.java
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/temperature/effects/TemperatureEffects.java
@@ -20,14 +20,20 @@ public final class TemperatureEffects {
public static final TemperatureEffect> EMPTY = new EmptyTemperatureEffect();
/**
- * Applies {@link net.minecraft.entity.effect.StatusEffect}s to entities based on their temperature
+ * A meta temperature effect that allows multiple child effects to be applied under the same conditions.
*/
- public static final TemperatureEffect> STATUS_EFFECT = new StatusEffectTemperatureEffect();
+ public static final TemperatureEffect> SEQUENCE = new SequenceTemperatureEffect();
/**
- * Applies scaled {@link net.minecraft.entity.attribute.EntityAttributeModifier}s to entities based on their
+ * Applies {@linkplain net.minecraft.entity.effect.StatusEffect status effects} to entities based on their
* temperature
*/
+ public static final TemperatureEffect> STATUS_EFFECT = new StatusEffectTemperatureEffect();
+
+ /**
+ * Applies scaled {@linkplain net.minecraft.entity.attribute.EntityAttributeModifier attribute modifiers} to
+ * entities based on their temperature
+ */
public static final TemperatureEffect> SCALING_ATTRIBUTE_MODIFIER = new ScalingAttributeModifierTemperatureEffect();
/**
@@ -49,6 +55,7 @@ public final class TemperatureEffects {
/**
* Returns all currently loaded {@link ConfiguredTemperatureEffect}s that are mapped to the {@code entity}'s type.
+ *
* @param entity The entity to fetch the effects for
* @return Returns the effects loaded for the entity type
*/
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/util/TemperatureConverter.java b/src/main/java/com/github/thedeathlycow/thermoo/api/util/TemperatureConverter.java
new file mode 100644
index 00000000..72a6f897
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/util/TemperatureConverter.java
@@ -0,0 +1,106 @@
+package com.github.thedeathlycow.thermoo.api.util;
+
+import net.minecraft.util.math.MathHelper;
+
+/**
+ * Helper API for conversions between normal temperature units (like Celsius and Fahrenheit) to per-tick temperature
+ * point changes.
+ */
+public class TemperatureConverter {
+
+ /**
+ * Converts an ambient Celsius temperature value to a per tick Thermoo passive temperature point change.
+ *
+ * The result is based on a linear scale. For example:
+ *
+ * 5C - 14C => -1 temp/tick
+ *
+ * 15C - 24C => 0 temp/tick
+ *
+ * 25C - 34C => +1 temp/tick
+ *
+ * etc
+ *
+ * @param temperatureValue The input temperature value, in Celsius
+ * @return An equivalent per-tick temperature point change
+ */
+ public static int celsiusToTemperatureTick(double temperatureValue) {
+ return ambientTemperatureToTemperatureTick(temperatureValue, Settings.DEFAULT);
+ }
+
+ /**
+ * Converts an ambient temperature value in Celsius, Kelvin, Fahrenheit or Rankine to a per tick Thermoo temperature
+ * point change. Using the {@linkplain Settings#DEFAULT default settings}, this converts the temperature value to a
+ * temperature point change following a linear scale where 15C-24C equates to 0 temp/tick, and then every +10C adds
+ * +1 temp/tick (and vice versa).
+ *
+ * With the settings, the scale the effect of changes in ambient temperature. For example a scale of 2 will mean that
+ * every +5C adds +1 temp/tick. The base shift effects where the 0 base is. For example, a base shift of +5 means that
+ * the 20C - 29C will be 0 temp/tick.
+ *
+ * You can also change the unit to other measurements like Fahrenheit or Kelvin, but the scale is still based in Celsius.
+ * So for example with default settings 59F to 75.2F equates to 0 temp/tick, and increases of 18F will add +1 temp/tick.
+ *
+ * @param temperatureValue The ambient temperature, in Celsius.
+ * @param settings Allows you to adjust the unit, scale, and base of the temperature conversion.
+ * @see #temperatureTickToAmbientTemperature(int, Settings)
+ */
+ public static int ambientTemperatureToTemperatureTick(double temperatureValue, Settings settings) {
+ double celsiusTemperature = settings.unit.toCelsius(temperatureValue);
+
+ return MathHelper.floor(settings.scale / 10.0 * (celsiusTemperature - (15.0 + settings.baseShift)));
+ }
+
+ /**
+ * Converts a per-tick thermoo temperature point change to an ambient temperature in Celsius.
+ *
+ * Performs the inverse calculation of {@link #celsiusToTemperatureTick(double)}, but returns the median of the input
+ * temperature range.
+ *
+ * So for example:
+ *
+ * -1 temp/tick => 10C
+ *
+ * 0 temp/tick => 20C,
+ *
+ * +1 temp/tick => 30C.
+ *
+ * @param temperatureTick The thermoo temperature point change per tick value
+ * @return Returns the ambient temperature in Celsius for the given temperature point change.
+ * @see #ambientTemperatureToTemperatureTick(double, Settings)
+ */
+ public static double temperatureTickToCelsius(int temperatureTick) {
+ return temperatureTickToAmbientTemperature(temperatureTick, Settings.DEFAULT);
+ }
+
+
+ /**
+ * Converts a per-tick thermoo temperature point change to an ambient temperature in Celsius.
+ *
+ * Performs the inverse calculation of {@link #ambientTemperatureToTemperatureTick(double, Settings)}, but returns
+ * the median of the range.
+ *
+ * So for example, 0 temp/tick => 20C, -1 temp/tick => 10C, and +1 temp/tick => 30C.
+ *
+ * @param temperatureTick The thermoo temperature point change per tick value
+ * @param settings Allows you to adjust the unit, scale, and base of the temperature conversion.
+ * @return Returns the ambient temperature in Celsius for the given temperature point change.
+ * @see #ambientTemperatureToTemperatureTick(double, Settings)
+ */
+ public static double temperatureTickToAmbientTemperature(int temperatureTick, Settings settings) {
+ double celsiusTemperature = (10 * temperatureTick) / settings.scale + 20.0 + settings.baseShift;
+ return settings.unit.fromCelsius(celsiusTemperature);
+ }
+
+ public record Settings(
+ TemperatureUnit unit,
+ double scale,
+ double baseShift
+ ) {
+ public static final Settings DEFAULT = new Settings(TemperatureUnit.CELSIUS, 1.0, 0);
+ }
+
+ private TemperatureConverter() {
+
+ }
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/api/util/TemperatureUnit.java b/src/main/java/com/github/thedeathlycow/thermoo/api/util/TemperatureUnit.java
new file mode 100644
index 00000000..00079a9a
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/api/util/TemperatureUnit.java
@@ -0,0 +1,88 @@
+package com.github.thedeathlycow.thermoo.api.util;
+
+import net.minecraft.util.StringIdentifiable;
+
+import java.util.function.DoubleUnaryOperator;
+
+/**
+ * Defines the basic units of temperature and allows for conversions between them.
+ */
+public enum TemperatureUnit implements StringIdentifiable {
+
+ CELSIUS(
+ "C",
+ celsiusValue -> celsiusValue,
+ celsiusValue -> celsiusValue
+ ),
+ KELVIN(
+ "K",
+ kelvinValue -> kelvinValue - 273.15,
+ celsiusValue -> celsiusValue + 273.15
+ ),
+ FAHRENHEIT(
+ "F",
+ fahrenheitValue -> (fahrenheitValue - 32.0) * 5.0 / 9.0,
+ celsiusValue -> (9.0 / 5.0) * celsiusValue + 32.0
+ ),
+ RANKINE(
+ "R",
+ rankineValue -> FAHRENHEIT.toCelsius(rankineValue - 459.67),
+ celsiusValue -> FAHRENHEIT.fromCelsius(celsiusValue) + 459.67
+ );
+
+ private final String unitSymbol;
+
+ private final DoubleUnaryOperator toCelsius;
+
+ private final DoubleUnaryOperator fromCelsius;
+
+ TemperatureUnit(String unitSymbol, DoubleUnaryOperator toCelsius, DoubleUnaryOperator fromCelsius) {
+ this.unitSymbol = unitSymbol;
+ this.toCelsius = toCelsius;
+ this.fromCelsius = fromCelsius;
+ }
+
+ public String getUnitSymbol() {
+ return this.unitSymbol;
+ }
+
+ /**
+ * Converts a temperature value in this unit to Celsius.
+ *
+ * @param value A temperature value in this unit
+ * @return Returns the equivalent temperature value in Celsius
+ */
+ public double toCelsius(double value) {
+ return this.toCelsius.applyAsDouble(value);
+ }
+
+ /**
+ * Converts a temperature value in Celsius to this unit.
+ *
+ * @param celsiusValue A temperature value in Celsius
+ * @return Returns the equivalent temperature value in this unit
+ */
+ public double fromCelsius(double celsiusValue) {
+ return this.fromCelsius.applyAsDouble(celsiusValue);
+ }
+
+ /**
+ * Converts a temperature in some other unit to this unit.
+ *
+ * @param inputValue The input temperature value in the other unit
+ * @param inputUnit The other unit
+ * @return Returns the equivalent temperature value in this unit.
+ */
+ public double convertTemperature(double inputValue, TemperatureUnit inputUnit) {
+ if (this == inputUnit) {
+ return inputValue;
+ }
+ double inputCelsius = inputUnit.toCelsius(inputValue);
+ return this.fromCelsius(inputCelsius);
+ }
+
+ @Override
+ public String asString() {
+ return this.toString().toLowerCase();
+ }
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/impl/ItemAttributeModifierManager.java b/src/main/java/com/github/thedeathlycow/thermoo/impl/ItemAttributeModifierManager.java
new file mode 100644
index 00000000..7b5cfb94
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/impl/ItemAttributeModifierManager.java
@@ -0,0 +1,85 @@
+package com.github.thedeathlycow.thermoo.impl;
+
+import com.github.thedeathlycow.thermoo.api.ThermooRegistryKeys;
+import com.github.thedeathlycow.thermoo.api.attribute.ItemAttributeModifier;
+import com.google.common.collect.Multimap;
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
+import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
+import net.fabricmc.fabric.api.event.registry.DynamicRegistryView;
+import net.fabricmc.fabric.api.item.v1.ModifyItemAttributeModifiersCallback;
+import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.entity.attribute.EntityAttribute;
+import net.minecraft.entity.attribute.EntityAttributeModifier;
+import net.minecraft.item.ItemStack;
+import net.minecraft.registry.DynamicRegistryManager;
+import net.minecraft.registry.Registry;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Applies {@link com.github.thedeathlycow.thermoo.api.attribute.ItemAttributeModifier}s to the default attributes of
+ * item stacks
+ */
+public class ItemAttributeModifierManager implements ModifyItemAttributeModifiersCallback {
+
+ public static final ItemAttributeModifierManager INSTANCE = new ItemAttributeModifierManager();
+
+ @Nullable
+ private DynamicRegistryManager manager;
+
+ public void registerToEventsCommon() {
+ ServerLifecycleEvents.SERVER_STARTING.register(server -> {
+ this.manager = server.getRegistryManager();
+ });
+
+ ServerLifecycleEvents.SERVER_STARTED.register(server -> {
+ if (this.manager == null) {
+ return;
+ }
+
+ Registry registry = this.manager.get(ThermooRegistryKeys.ITEM_ATTRIBUTE_MODIFIER);
+ Thermoo.LOGGER.info("Loaded {} items attribute modifier(s)", registry != null ? registry.size() : 0);
+ });
+
+ ServerLifecycleEvents.SERVER_STOPPING.register(server -> {
+ this.manager = null;
+ });
+ ModifyItemAttributeModifiersCallback.EVENT.register(this);
+ }
+
+ public void registerToEventsClient() {
+ ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
+ if (this.manager == null) {
+ this.manager = handler.getRegistryManager();
+ }
+ });
+ ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> {
+ this.manager = null;
+ });
+ }
+
+ @Override
+ public void modifyAttributeModifiers(
+ ItemStack stack,
+ EquipmentSlot slot,
+ Multimap attributeModifiers
+ ) {
+ if (manager != null) {
+ manager.getOptional(ThermooRegistryKeys.ITEM_ATTRIBUTE_MODIFIER)
+ .ifPresent(
+ registry -> {
+ registry.streamEntries().forEach(
+ entry -> {
+ entry.value().apply(stack, slot, attributeModifiers);
+ }
+ );
+ }
+ );
+ } else {
+ Thermoo.LOGGER.info("Manager is null");
+ }
+ }
+
+ private ItemAttributeModifierManager() {
+
+ }
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/impl/Thermoo.java b/src/main/java/com/github/thedeathlycow/thermoo/impl/Thermoo.java
index e853ecda..7845cef7 100644
--- a/src/main/java/com/github/thedeathlycow/thermoo/impl/Thermoo.java
+++ b/src/main/java/com/github/thedeathlycow/thermoo/impl/Thermoo.java
@@ -1,13 +1,22 @@
package com.github.thedeathlycow.thermoo.impl;
+import com.github.thedeathlycow.thermoo.api.ThermooRegistryKeys;
+import com.github.thedeathlycow.thermoo.api.attribute.ItemAttributeModifier;
import com.github.thedeathlycow.thermoo.api.command.EnvironmentCommand;
import com.github.thedeathlycow.thermoo.api.command.HeatingModeArgumentType;
import com.github.thedeathlycow.thermoo.api.command.TemperatureCommand;
+import com.github.thedeathlycow.thermoo.api.command.TemperatureUnitArgumentType;
+import com.github.thedeathlycow.thermoo.api.season.ThermooSeasonEvents;
+import com.github.thedeathlycow.thermoo.api.season.ThermooSeasons;
import com.github.thedeathlycow.thermoo.api.temperature.EnvironmentManager;
+import io.github.lucaargolo.seasons.FabricSeasons;
+import io.github.lucaargolo.seasons.utils.Season;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
+import net.fabricmc.fabric.api.event.registry.DynamicRegistries;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
+import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.command.argument.serialize.ConstantArgumentSerializer;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
@@ -15,21 +24,25 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Optional;
+
public class Thermoo implements ModInitializer {
public static final String MODID = "thermoo";
public static final Logger LOGGER = LoggerFactory.getLogger(MODID);
- public static final int CONFIG_VERSION = 0;
-
@Override
public void onInitialize() {
-
ArgumentTypeRegistry.registerArgumentType(
Thermoo.id("heating_mode"),
HeatingModeArgumentType.class,
ConstantArgumentSerializer.of(HeatingModeArgumentType::heatingMode)
);
+ ArgumentTypeRegistry.registerArgumentType(
+ Thermoo.id("temperature_unit"),
+ TemperatureUnitArgumentType.class,
+ ConstantArgumentSerializer.of(TemperatureUnitArgumentType::temperatureUnit)
+ );
CommandRegistrationCallback.EVENT.register(
(dispatcher, registryAccess, environment) -> {
@@ -38,16 +51,44 @@ public void onInitialize() {
}
);
+ DynamicRegistries.registerSynced(
+ ThermooRegistryKeys.ITEM_ATTRIBUTE_MODIFIER,
+ ItemAttributeModifier.CODEC,
+ DynamicRegistries.SyncOption.SKIP_WHEN_EMPTY
+ );
+
+ ItemAttributeModifierManager.INSTANCE.registerToEventsCommon();
+
ThermooCommonRegisters.registerAttributes();
ThermooCommonRegisters.registerTemperatureEffects();
ResourceManagerHelper serverManager = ResourceManagerHelper.get(ResourceType.SERVER_DATA);
serverManager.registerReloadListener(TemperatureEffectLoader.INSTANCE);
+ registerFabricSeasonsIntegration();
LOGGER.info("Creating environment manager {}", EnvironmentManager.INSTANCE);
LOGGER.info("Thermoo initialized");
}
+ private static void registerFabricSeasonsIntegration() {
+ if (FabricLoader.getInstance().isModLoaded(ThermooIntegrations.FABRIC_SEASONS_ID)) {
+ LOGGER.warn("Registering builtin Fabric Seasons integration with Thermoo. " +
+ "Note that this integration will be removed as a builtin feature in the future.");
+ ThermooSeasonEvents.GET_CURRENT_SEASON.register(world -> {
+ Season fabricSeason = FabricSeasons.getCurrentSeason(world);
+ return Optional.ofNullable(
+ switch (fabricSeason) {
+ case WINTER -> ThermooSeasons.WINTER;
+ case SUMMER -> ThermooSeasons.SUMMER;
+ case FALL -> ThermooSeasons.AUTUMN;
+ case SPRING -> ThermooSeasons.SPRING;
+ default -> null;
+ }
+ );
+ });
+ }
+ }
+
/**
* Creates a new {@link Identifier} under the namespace {@value #MODID}
*
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/impl/ThermooClient.java b/src/main/java/com/github/thedeathlycow/thermoo/impl/ThermooClient.java
new file mode 100644
index 00000000..88de8d40
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/impl/ThermooClient.java
@@ -0,0 +1,12 @@
+package com.github.thedeathlycow.thermoo.impl;
+
+import net.fabricmc.api.ClientModInitializer;
+
+public class ThermooClient implements ClientModInitializer {
+
+
+ @Override
+ public void onInitializeClient() {
+ ItemAttributeModifierManager.INSTANCE.registerToEventsClient();
+ }
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/impl/ThermooCommonRegisters.java b/src/main/java/com/github/thedeathlycow/thermoo/impl/ThermooCommonRegisters.java
index 0fb6d9fe..2eca7727 100644
--- a/src/main/java/com/github/thedeathlycow/thermoo/impl/ThermooCommonRegisters.java
+++ b/src/main/java/com/github/thedeathlycow/thermoo/impl/ThermooCommonRegisters.java
@@ -13,6 +13,7 @@ public class ThermooCommonRegisters {
@SuppressWarnings("deprecation")
public static void registerTemperatureEffects() {
registerTemperatureEffect("empty", TemperatureEffects.EMPTY);
+ registerTemperatureEffect("sequence", TemperatureEffects.SEQUENCE);
registerTemperatureEffect("status_effect", TemperatureEffects.STATUS_EFFECT);
registerTemperatureEffect("scaling_attribute_modifier", TemperatureEffects.SCALING_ATTRIBUTE_MODIFIER);
registerTemperatureEffect("damage", TemperatureEffects.DAMAGE);
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/impl/ThermooIntegrations.java b/src/main/java/com/github/thedeathlycow/thermoo/impl/ThermooIntegrations.java
new file mode 100644
index 00000000..12babe39
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/impl/ThermooIntegrations.java
@@ -0,0 +1,18 @@
+package com.github.thedeathlycow.thermoo.impl;
+
+import net.fabricmc.loader.api.FabricLoader;
+
+public class ThermooIntegrations {
+
+ public static final String COLORFUL_HEARTS_ID = "colorfulhearts";
+
+ public static final String OVERFLOWING_BARS_ID = "overflowingbars";
+
+ public static final String FABRIC_SEASONS_ID = "seasons";
+
+ public static boolean isModLoaded(String id) {
+ return FabricLoader.getInstance().isModLoaded(id);
+ }
+
+
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/impl/client/HeartOverlayImpl.java b/src/main/java/com/github/thedeathlycow/thermoo/impl/client/HeartOverlayImpl.java
new file mode 100644
index 00000000..cfc3c409
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/impl/client/HeartOverlayImpl.java
@@ -0,0 +1,33 @@
+package com.github.thedeathlycow.thermoo.impl.client;
+
+import net.minecraft.util.Util;
+import org.jetbrains.annotations.Nullable;
+import org.joml.Vector2i;
+
+import java.util.Arrays;
+
+public class HeartOverlayImpl {
+
+ public static final HeartOverlayImpl INSTANCE = new HeartOverlayImpl();
+
+ private static final int MAX_OVERLAY_HEARTS = 20;
+
+ private final Vector2i[] heartPositions = Util.make(() -> {
+ var positions = new Vector2i[MAX_OVERLAY_HEARTS];
+ Arrays.fill(positions, null);
+ return positions;
+ });
+
+ public void setHeartPosition(int index, int heartX, int heartY) {
+ heartPositions[index] = new Vector2i(heartX, heartY);
+ }
+
+ public Vector2i[] getHeartPositions() {
+ return heartPositions;
+ }
+
+ private HeartOverlayImpl() {
+
+ }
+
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/mixin/client/compat/Plugin.java b/src/main/java/com/github/thedeathlycow/thermoo/mixin/client/compat/Plugin.java
new file mode 100644
index 00000000..47b5db13
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/mixin/client/compat/Plugin.java
@@ -0,0 +1,65 @@
+package com.github.thedeathlycow.thermoo.mixin.client.compat;
+
+import net.fabricmc.loader.api.FabricLoader;
+import org.objectweb.asm.tree.ClassNode;
+import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
+import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
+
+import java.util.List;
+import java.util.Set;
+
+public class Plugin implements IMixinConfigPlugin {
+ private static final String COMPAT_PACKAGE_ROOT;
+ private static final String COMPAT_PRESENT_KEY = "present";
+ private static final FabricLoader LOADER = FabricLoader.getInstance();
+
+ static {
+ // Shorthand getting the plugin package to ensure not making trouble with other mixins
+ COMPAT_PACKAGE_ROOT = Plugin.class.getPackageName();
+ }
+
+ @Override
+ public void onLoad(String mixinPackage) {
+ }
+
+ @Override
+ public String getRefMapperConfig() {
+ return null;
+ }
+
+ @Override
+ public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
+ if (!mixinClassName.startsWith(COMPAT_PACKAGE_ROOT)) {
+ return true; // We do not meddle with the others' work
+ }
+ String[] compatRoot = COMPAT_PACKAGE_ROOT.split("\\.");
+ String[] mixinPath = mixinClassName.split("\\.");
+ // The id of the mod the mixin depends on
+ String compatModId = mixinPath[compatRoot.length];
+ // Whether the mixin is for when the mod is loaded or not
+ boolean isPresentMixin = mixinPath[compatRoot.length + 1].equals(COMPAT_PRESENT_KEY);
+
+ if (isPresentMixin) {
+ // We only load the mixin if the mod we want to be present is found
+ return LOADER.isModLoaded(compatModId);
+ }
+ return !LOADER.isModLoaded(compatModId);
+ }
+
+ @Override
+ public void acceptTargets(Set myTargets, Set otherTargets) {
+ }
+
+ @Override
+ public List getMixins() {
+ return null;
+ }
+
+ @Override
+ public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
+ }
+
+ @Override
+ public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
+ }
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/mixin/client/compat/colorfulhearts/present/HeartRendererMixin.java b/src/main/java/com/github/thedeathlycow/thermoo/mixin/client/compat/colorfulhearts/present/HeartRendererMixin.java
new file mode 100644
index 00000000..8a1b079f
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/mixin/client/compat/colorfulhearts/present/HeartRendererMixin.java
@@ -0,0 +1,87 @@
+package com.github.thedeathlycow.thermoo.mixin.client.compat.colorfulhearts.present;
+
+import com.github.thedeathlycow.thermoo.api.client.StatusBarOverlayRenderEvents;
+import com.github.thedeathlycow.thermoo.impl.ThermooIntegrations;
+import com.github.thedeathlycow.thermoo.impl.client.HeartOverlayImpl;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.entity.player.PlayerEntity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+import terrails.colorfulhearts.heart.Heart;
+import terrails.colorfulhearts.heart.HeartType;
+import terrails.colorfulhearts.render.HeartRenderer;
+
+import java.util.Arrays;
+
+@Environment(EnvType.CLIENT)
+@Mixin(value = HeartRenderer.class, remap = false)
+public class HeartRendererMixin {
+
+ @Inject(method = "renderPlayerHearts",
+ at = @At(
+ value = "INVOKE",
+ target = "Lterrails/colorfulhearts/heart/Heart;draw(Lnet/minecraft/client/util/math/MatrixStack;IIZZLterrails/colorfulhearts/heart/HeartType;)V",
+ remap = true,
+ shift = At.Shift.AFTER
+ ),
+ locals = LocalCapture.CAPTURE_FAILEXCEPTION
+ )
+ private void captureHeartPositions(
+ DrawContext guiGraphics,
+ PlayerEntity player,
+ int x, int y,
+ int maxHealth, int currentHealth,
+ int displayHealth, int absorption,
+ boolean renderHighlight,
+ CallbackInfo ci,
+ int healthHearts, int displayHealthHearts,
+ boolean absorptionSameRow,
+ int regenIndex,
+ HeartType heartType,
+ int index,
+ Heart heart,
+ int xPos, int yPos,
+ boolean highlightHeart
+ ) {
+ if (ThermooIntegrations.isModLoaded(ThermooIntegrations.OVERFLOWING_BARS_ID)) {
+ return;
+ }
+ HeartOverlayImpl.INSTANCE.setHeartPosition(index, xPos, yPos);
+ }
+
+ @Inject(
+ method = "renderPlayerHearts",
+ at = @At(
+ value = "INVOKE",
+ target = "Lcom/mojang/blaze3d/systems/RenderSystem;disableBlend()V",
+ shift = At.Shift.BEFORE
+ )
+ )
+ private void drawColdHeartOverlayBar(
+ DrawContext drawContext,
+ PlayerEntity player,
+ int x, int y,
+ int maxHealth, int currentHealth, int displayHealth, int absorption,
+ boolean renderHighlight,
+ CallbackInfo ci
+ ) {
+ if (ThermooIntegrations.isModLoaded(ThermooIntegrations.OVERFLOWING_BARS_ID)) {
+ return;
+ }
+ StatusBarOverlayRenderEvents.AFTER_HEALTH_BAR.invoker()
+ .render(
+ drawContext,
+ player,
+ HeartOverlayImpl.INSTANCE.getHeartPositions(),
+ displayHealth,
+ 20
+ );
+ Arrays.fill(HeartOverlayImpl.INSTANCE.getHeartPositions(), null);
+ }
+
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/mixin/client/compat/overflowingbars/absent/InGameHudMixin.java b/src/main/java/com/github/thedeathlycow/thermoo/mixin/client/compat/overflowingbars/absent/InGameHudMixin.java
new file mode 100644
index 00000000..4e5da449
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/mixin/client/compat/overflowingbars/absent/InGameHudMixin.java
@@ -0,0 +1,87 @@
+package com.github.thedeathlycow.thermoo.mixin.client.compat.overflowingbars.absent;
+
+import com.github.thedeathlycow.thermoo.api.client.StatusBarOverlayRenderEvents;
+import com.github.thedeathlycow.thermoo.impl.client.HeartOverlayImpl;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.hud.InGameHud;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.util.math.MathHelper;
+import org.joml.Vector2i;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+import java.util.Arrays;
+
+@Mixin(InGameHud.class)
+public class InGameHudMixin {
+ @Inject(
+ method = "renderHealthBar",
+ at = @At(
+ value = "INVOKE",
+ target = "Lnet/minecraft/client/gui/hud/InGameHud;drawHeart(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/client/gui/hud/InGameHud$HeartType;IIIZZ)V",
+ ordinal = 0,
+ shift = At.Shift.AFTER
+ ),
+ locals = LocalCapture.CAPTURE_FAILEXCEPTION
+ )
+ private void captureHeartPositions(
+ DrawContext context,
+ PlayerEntity player,
+ int x, int y,
+ int lines,
+ int regeneratingHeartIndex,
+ float maxHealth,
+ int lastHealth,
+ int health,
+ int absorption,
+ boolean blinking,
+ CallbackInfo ci,
+ // local captures
+ InGameHud.HeartType heartType,
+ int i, int j, int k, int l,
+ int m, // index of heart
+ int n, int o,
+ int p, int q // position of heart to capture
+ ) {
+ HeartOverlayImpl.INSTANCE.setHeartPosition(m, p, q);
+ }
+
+ @Inject(
+ method = "renderHealthBar",
+ at = @At(
+ value = "TAIL"
+ )
+ )
+ private void drawHeartOverlayBar(
+ DrawContext context,
+ PlayerEntity player,
+ int x, int y,
+ int lines,
+ int regeneratingHeartIndex,
+ float maxHealth,
+ int lastHealth,
+ int health,
+ int absorption,
+ boolean blinking,
+ CallbackInfo ci
+ ) {
+
+ Vector2i[] heartPositions = HeartOverlayImpl.INSTANCE.getHeartPositions();
+ int displayHealth = Math.min(health, heartPositions.length);
+ int maxDisplayHealth = Math.min(MathHelper.ceil(maxHealth), heartPositions.length);
+
+ StatusBarOverlayRenderEvents.AFTER_HEALTH_BAR.invoker()
+ .render(
+ context,
+ player,
+ heartPositions,
+ displayHealth,
+ maxDisplayHealth
+ );
+ Arrays.fill(HeartOverlayImpl.INSTANCE.getHeartPositions(), null);
+ }
+
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/mixin/client/compat/overflowingbars/present/HealthBarRendererMixin.java b/src/main/java/com/github/thedeathlycow/thermoo/mixin/client/compat/overflowingbars/present/HealthBarRendererMixin.java
new file mode 100644
index 00000000..5d4aa081
--- /dev/null
+++ b/src/main/java/com/github/thedeathlycow/thermoo/mixin/client/compat/overflowingbars/present/HealthBarRendererMixin.java
@@ -0,0 +1,86 @@
+package com.github.thedeathlycow.thermoo.mixin.client.compat.overflowingbars.present;
+
+import com.github.thedeathlycow.thermoo.api.client.StatusBarOverlayRenderEvents;
+import com.github.thedeathlycow.thermoo.impl.client.HeartOverlayImpl;
+import fuzs.overflowingbars.client.handler.HealthBarRenderer;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.entity.player.PlayerEntity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+import java.util.Arrays;
+
+@Environment(EnvType.CLIENT)
+@Mixin(value = HealthBarRenderer.class, remap = false)
+public class HealthBarRendererMixin {
+
+ @Inject(
+ method = "renderHearts",
+ at = @At(
+ value = "INVOKE",
+ target = "Lfuzs/overflowingbars/client/handler/HealthBarRenderer;renderHeart(Lnet/minecraft/client/gui/DrawContext;Lfuzs/overflowingbars/client/handler/HealthBarRenderer$HeartType;IIZZZ)V",
+ ordinal = 0,
+ shift = At.Shift.AFTER,
+ remap = true
+ ),
+ locals = LocalCapture.CAPTURE_FAILEXCEPTION
+ )
+ private void captureHeartPosition(
+ DrawContext guiGraphics,
+ PlayerEntity player,
+ int posX,
+ int posY,
+ int heartOffsetByRegen,
+ float maxHealth,
+ int currentHealth,
+ int displayHealth,
+ int currentAbsorptionHealth,
+ boolean blink,
+ CallbackInfo ci,
+ boolean hardcore,
+ int normalHearts,
+ int maxAbsorptionHearts,
+ int absorptionHearts,
+ int currentHeart,
+ int currentPosX,
+ int currentPosY
+ ) {
+ HeartOverlayImpl.INSTANCE.setHeartPosition(currentHeart, currentPosX, currentPosY);
+ }
+
+ @Inject(
+ method = "renderHearts",
+ at = @At(
+ value = "TAIL"
+ )
+ )
+ private void renderColdHeartOverlayBar(
+ DrawContext guiGraphics,
+ PlayerEntity player,
+ int posX,
+ int posY,
+ int heartOffsetByRegen,
+ float maxHealth,
+ int currentHealth,
+ int displayHealth,
+ int currentAbsorptionHealth,
+ boolean blink,
+ CallbackInfo ci
+ ) {
+ StatusBarOverlayRenderEvents.AFTER_HEALTH_BAR.invoker()
+ .render(
+ guiGraphics,
+ player,
+ HeartOverlayImpl.INSTANCE.getHeartPositions(),
+ displayHealth,
+ 20
+ );
+ Arrays.fill(HeartOverlayImpl.INSTANCE.getHeartPositions(), null);
+ }
+
+}
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/mixin/EntityPredicateMixin.java b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/EntityPredicateMixin.java
similarity index 95%
rename from src/main/java/com/github/thedeathlycow/thermoo/mixin/EntityPredicateMixin.java
rename to src/main/java/com/github/thedeathlycow/thermoo/mixin/common/EntityPredicateMixin.java
index ecddf041..a7e3a6ae 100644
--- a/src/main/java/com/github/thedeathlycow/thermoo/mixin/EntityPredicateMixin.java
+++ b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/EntityPredicateMixin.java
@@ -1,4 +1,4 @@
-package com.github.thedeathlycow.thermoo.mixin;
+package com.github.thedeathlycow.thermoo.mixin.common;
import com.github.thedeathlycow.thermoo.api.predicate.TemperaturePredicate;
import com.github.thedeathlycow.thermoo.api.temperature.TemperatureAware;
@@ -10,6 +10,7 @@
import net.minecraft.util.JsonHelper;
import net.minecraft.util.math.Vec3d;
import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@@ -18,6 +19,7 @@
@Mixin(EntityPredicate.class)
public class EntityPredicateMixin {
+ @Unique
private TemperaturePredicate thermoo$temperaturePredicate = TemperaturePredicate.ANY;
@Inject(
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/mixin/EnvironmentAwareEntityMixin.java b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/EnvironmentAwareEntityMixin.java
similarity index 99%
rename from src/main/java/com/github/thedeathlycow/thermoo/mixin/EnvironmentAwareEntityMixin.java
rename to src/main/java/com/github/thedeathlycow/thermoo/mixin/common/EnvironmentAwareEntityMixin.java
index 01023489..e2876415 100644
--- a/src/main/java/com/github/thedeathlycow/thermoo/mixin/EnvironmentAwareEntityMixin.java
+++ b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/EnvironmentAwareEntityMixin.java
@@ -1,4 +1,4 @@
-package com.github.thedeathlycow.thermoo.mixin;
+package com.github.thedeathlycow.thermoo.mixin.common;
import com.github.thedeathlycow.thermoo.api.ThermooAttributes;
import com.github.thedeathlycow.thermoo.api.ThermooTags;
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/mixin/HotFloorMixin.java b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/HotFloorMixin.java
similarity index 96%
rename from src/main/java/com/github/thedeathlycow/thermoo/mixin/HotFloorMixin.java
rename to src/main/java/com/github/thedeathlycow/thermoo/mixin/common/HotFloorMixin.java
index 36d62f71..b7ab5ff3 100644
--- a/src/main/java/com/github/thedeathlycow/thermoo/mixin/HotFloorMixin.java
+++ b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/HotFloorMixin.java
@@ -1,4 +1,4 @@
-package com.github.thedeathlycow.thermoo.mixin;
+package com.github.thedeathlycow.thermoo.mixin.common;
import com.github.thedeathlycow.thermoo.api.temperature.EnvironmentManager;
import com.github.thedeathlycow.thermoo.api.temperature.HeatingModes;
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/mixin/LivingEntityAttributeMixin.java b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/LivingEntityAttributeMixin.java
similarity index 97%
rename from src/main/java/com/github/thedeathlycow/thermoo/mixin/LivingEntityAttributeMixin.java
rename to src/main/java/com/github/thedeathlycow/thermoo/mixin/common/LivingEntityAttributeMixin.java
index f99791fc..ccd3e0e3 100644
--- a/src/main/java/com/github/thedeathlycow/thermoo/mixin/LivingEntityAttributeMixin.java
+++ b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/LivingEntityAttributeMixin.java
@@ -1,4 +1,4 @@
-package com.github.thedeathlycow.thermoo.mixin;
+package com.github.thedeathlycow.thermoo.mixin.common;
import com.github.thedeathlycow.thermoo.api.ThermooAttributes;
import com.github.thedeathlycow.thermoo.api.temperature.EnvironmentController;
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/mixin/LivingEntityEnvironmentTickMixin.java b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/LivingEntityEnvironmentTickMixin.java
similarity index 95%
rename from src/main/java/com/github/thedeathlycow/thermoo/mixin/LivingEntityEnvironmentTickMixin.java
rename to src/main/java/com/github/thedeathlycow/thermoo/mixin/common/LivingEntityEnvironmentTickMixin.java
index 6e0e3ebe..f8d0771f 100644
--- a/src/main/java/com/github/thedeathlycow/thermoo/mixin/LivingEntityEnvironmentTickMixin.java
+++ b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/LivingEntityEnvironmentTickMixin.java
@@ -1,4 +1,4 @@
-package com.github.thedeathlycow.thermoo.mixin;
+package com.github.thedeathlycow.thermoo.mixin.common;
import com.github.thedeathlycow.thermoo.impl.LivingEntityEnvironmentTickImpl;
import net.minecraft.entity.LivingEntity;
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/mixin/PlayerTemperatureTickMixin.java b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/PlayerTemperatureTickMixin.java
similarity index 97%
rename from src/main/java/com/github/thedeathlycow/thermoo/mixin/PlayerTemperatureTickMixin.java
rename to src/main/java/com/github/thedeathlycow/thermoo/mixin/common/PlayerTemperatureTickMixin.java
index 7bf08387..863529c0 100644
--- a/src/main/java/com/github/thedeathlycow/thermoo/mixin/PlayerTemperatureTickMixin.java
+++ b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/PlayerTemperatureTickMixin.java
@@ -1,4 +1,4 @@
-package com.github.thedeathlycow.thermoo.mixin;
+package com.github.thedeathlycow.thermoo.mixin.common;
import com.github.thedeathlycow.thermoo.api.temperature.EnvironmentManager;
import com.github.thedeathlycow.thermoo.api.temperature.HeatingModes;
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/mixin/TemperatureEffectTickerMixin.java b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/TemperatureEffectTickerMixin.java
similarity index 96%
rename from src/main/java/com/github/thedeathlycow/thermoo/mixin/TemperatureEffectTickerMixin.java
rename to src/main/java/com/github/thedeathlycow/thermoo/mixin/common/TemperatureEffectTickerMixin.java
index 2bb54d5d..eb7eee12 100644
--- a/src/main/java/com/github/thedeathlycow/thermoo/mixin/TemperatureEffectTickerMixin.java
+++ b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/TemperatureEffectTickerMixin.java
@@ -1,4 +1,4 @@
-package com.github.thedeathlycow.thermoo.mixin;
+package com.github.thedeathlycow.thermoo.mixin.common;
import com.github.thedeathlycow.thermoo.api.temperature.effects.TemperatureEffects;
import net.minecraft.entity.Entity;
diff --git a/src/main/java/com/github/thedeathlycow/thermoo/mixin/package-info.java b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/package-info.java
similarity index 73%
rename from src/main/java/com/github/thedeathlycow/thermoo/mixin/package-info.java
rename to src/main/java/com/github/thedeathlycow/thermoo/mixin/common/package-info.java
index 36a6ebac..c7228224 100644
--- a/src/main/java/com/github/thedeathlycow/thermoo/mixin/package-info.java
+++ b/src/main/java/com/github/thedeathlycow/thermoo/mixin/common/package-info.java
@@ -2,6 +2,6 @@
* Mixin package for Thermoo. Code here should not be relied on by API users.
*/
@ApiStatus.Internal
-package com.github.thedeathlycow.thermoo.mixin;
+package com.github.thedeathlycow.thermoo.mixin.common;
import org.jetbrains.annotations.ApiStatus;
diff --git a/src/main/resources/assets/thermoo/icon.png b/src/main/resources/assets/thermoo/icon.png
index 8febce05..0db142b5 100644
Binary files a/src/main/resources/assets/thermoo/icon.png and b/src/main/resources/assets/thermoo/icon.png differ
diff --git a/src/main/resources/assets/thermoo/lang/en_us.json b/src/main/resources/assets/thermoo/lang/en_us.json
index 8d45795a..4c32cac5 100644
--- a/src/main/resources/assets/thermoo/lang/en_us.json
+++ b/src/main/resources/assets/thermoo/lang/en_us.json
@@ -18,5 +18,7 @@
"commands.thermoo.temperature.set.success.multiple": "Set the temperature of %s entities to %d",
"commands.thermoo.environment.printcontroller.success": "Controller logged to console",
- "commands.thermoo.environment.checktemperature.success": "The passive temperature change at %s, %s, %s (%s) is %d"
+ "commands.thermoo.environment.checktemperature.success": "The passive temperature change at %s, %s, %s (%s) is %s",
+ "commands.thermoo.environment.checktemperature.unit.success": "The temperature at %s, %s, %s (%s) is %s°%s"
+
}
\ No newline at end of file
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 4cc3d2dc..41219058 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -18,22 +18,31 @@
"main": [
"com.github.thedeathlycow.thermoo.impl.Thermoo"
],
+ "client": [
+ "com.github.thedeathlycow.thermoo.impl.ThermooClient"
+ ],
"cardinal-components-entity": [
"com.github.thedeathlycow.thermoo.impl.component.ThermooComponents"
]
},
"mixins": [
- "thermoo.mixins.json"
+ "thermoo.mixins.json",
+ {
+ "config": "thermoo.client.mixins.json",
+ "environment": "client"
+ }
],
"depends": {
- "fabricloader": ">=0.14.19",
- "fabric-api": "*",
- "minecraft": ">=1.20",
+ "fabricloader": ">=0.15.7",
+ "fabric-api": ">=0.91.0",
+ "minecraft": "~1.20.1",
"java": ">=17"
},
"suggests": {
- "frostiful": "*"
+ "frostiful": "*",
+ "scorchful": "*"
},
+ "accessWidener": "thermoo.accesswidener",
"custom": {
"loom:injected_interfaces": {
"net/minecraft/class_1309": [
diff --git a/src/main/resources/thermoo.accesswidener b/src/main/resources/thermoo.accesswidener
new file mode 100644
index 00000000..d9a4ac53
--- /dev/null
+++ b/src/main/resources/thermoo.accesswidener
@@ -0,0 +1,5 @@
+accessWidener v1 named
+### Classes ###
+
+# Heart Type needed to mixin to render health bar
+accessible class net/minecraft/client/gui/hud/InGameHud$HeartType
\ No newline at end of file
diff --git a/src/main/resources/thermoo.client.mixins.json b/src/main/resources/thermoo.client.mixins.json
new file mode 100644
index 00000000..d7969c8f
--- /dev/null
+++ b/src/main/resources/thermoo.client.mixins.json
@@ -0,0 +1,14 @@
+{
+ "required": true,
+ "package": "com.github.thedeathlycow.thermoo.mixin.client",
+ "compatibilityLevel": "JAVA_17",
+ "plugin": "com.github.thedeathlycow.thermoo.mixin.client.compat.Plugin",
+ "client": [
+ "compat.colorfulhearts.present.HeartRendererMixin",
+ "compat.overflowingbars.absent.InGameHudMixin",
+ "compat.overflowingbars.present.HealthBarRendererMixin"
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/thermoo.mixins.json b/src/main/resources/thermoo.mixins.json
index bc638167..861c8623 100644
--- a/src/main/resources/thermoo.mixins.json
+++ b/src/main/resources/thermoo.mixins.json
@@ -1,7 +1,7 @@
{
"required": true,
"minVersion": "0.8",
- "package": "com.github.thedeathlycow.thermoo.mixin",
+ "package": "com.github.thedeathlycow.thermoo.mixin.common",
"compatibilityLevel": "JAVA_17",
"mixins": [
"EntityPredicateMixin",
@@ -12,9 +12,7 @@
"PlayerTemperatureTickMixin",
"TemperatureEffectTickerMixin"
],
- "client": [
- ],
"injectors": {
"defaultRequire": 1
- }
+ }
}
diff --git a/src/testmod/java/com/github/thedeathlycow/thermoo/testmod/ThermooTestModClient.java b/src/testmod/java/com/github/thedeathlycow/thermoo/testmod/ThermooTestModClient.java
new file mode 100644
index 00000000..98e54234
--- /dev/null
+++ b/src/testmod/java/com/github/thedeathlycow/thermoo/testmod/ThermooTestModClient.java
@@ -0,0 +1,62 @@
+package com.github.thedeathlycow.thermoo.testmod;
+
+import com.github.thedeathlycow.thermoo.api.client.StatusBarOverlayRenderEvents;
+import com.github.thedeathlycow.thermoo.impl.Thermoo;
+import net.fabricmc.api.ClientModInitializer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.math.MathHelper;
+import org.jetbrains.annotations.NotNull;
+import org.joml.Vector2i;
+
+public class ThermooTestModClient implements ClientModInitializer {
+
+ public static final Identifier HEART_OVERLAY_TEXTURE = Thermoo.id("textures/gui/fire_heart_overlay.png");
+
+ private static final int TEXTURE_WIDTH = 18;
+ private static final int TEXTURE_HEIGHT = 30;
+
+ @Override
+ public void onInitializeClient() {
+ StatusBarOverlayRenderEvents.AFTER_HEALTH_BAR.register(ThermooTestModClient::renderFireHeartBar);
+ }
+
+ public static void renderFireHeartBar(
+ DrawContext context,
+ PlayerEntity player,
+ Vector2i[] heartPositions,
+ int displayHealth,
+ int maxDisplayHealth
+ ) {
+ int fireHeartPoints = getNumFirePoints(player, maxDisplayHealth);
+ int fireHearts = getNumFireHeartsFromPoints(fireHeartPoints, maxDisplayHealth);
+
+ for (int m = 0; m < fireHearts; m++) {
+ // is half heart if this is the last heart being rendered and we have an odd
+ // number of frozen health points
+ int x = heartPositions[m].x;
+ int y = heartPositions[m].y - 1;
+ boolean isHalfHeart = m + 1 >= fireHearts && (fireHeartPoints & 1) == 1; // is odd check
+
+ int u = isHalfHeart ? 9 : 0;
+
+ context.drawTexture(HEART_OVERLAY_TEXTURE, x, y, u, 0, 9, 10, TEXTURE_WIDTH, TEXTURE_HEIGHT);
+ }
+ }
+
+ private static int getNumFirePoints(@NotNull PlayerEntity player, int maxDisplayHealth) {
+ float tempScale = player.thermoo$getTemperatureScale();
+ if (tempScale <= 0f) {
+ return 0;
+ }
+ return (int) (tempScale * maxDisplayHealth);
+ }
+
+ private static int getNumFireHeartsFromPoints(int fireHealthPoints, int maxDisplayHealth) {
+ // number of whole hearts
+ int frozenHealthHearts = MathHelper.ceil(fireHealthPoints / 2.0f);
+
+ return Math.min(maxDisplayHealth / 2, frozenHealthHearts);
+ }
+}
diff --git a/src/testmod/resources/assets/thermoo/textures/gui/fire_heart_overlay.png b/src/testmod/resources/assets/thermoo/textures/gui/fire_heart_overlay.png
new file mode 100644
index 00000000..95e27290
Binary files /dev/null and b/src/testmod/resources/assets/thermoo/textures/gui/fire_heart_overlay.png differ
diff --git a/src/testmod/resources/data/thermoo-test/predicates/is_cold.json b/src/testmod/resources/data/thermoo-test/predicates/is_cold.json
new file mode 100644
index 00000000..7c8189fa
--- /dev/null
+++ b/src/testmod/resources/data/thermoo-test/predicates/is_cold.json
@@ -0,0 +1,11 @@
+{
+ "condition": "minecraft:entity_properties",
+ "entity": "this",
+ "predicate": {
+ "thermoo.temperature": {
+ "scale": {
+ "max": 0
+ }
+ }
+ }
+}
diff --git a/src/testmod/resources/data/thermoo-test/predicates/is_warm.json b/src/testmod/resources/data/thermoo-test/predicates/is_warm.json
new file mode 100644
index 00000000..15a2027c
--- /dev/null
+++ b/src/testmod/resources/data/thermoo-test/predicates/is_warm.json
@@ -0,0 +1,11 @@
+{
+ "condition": "minecraft:entity_properties",
+ "entity": "this",
+ "predicate": {
+ "thermoo.temperature": {
+ "scale": {
+ "min": 0
+ }
+ }
+ }
+}
diff --git a/src/testmod/resources/data/thermoo-test/thermoo/item_attribute_modifier/diamond_weapons.json b/src/testmod/resources/data/thermoo-test/thermoo/item_attribute_modifier/diamond_weapons.json
new file mode 100644
index 00000000..494d4187
--- /dev/null
+++ b/src/testmod/resources/data/thermoo-test/thermoo/item_attribute_modifier/diamond_weapons.json
@@ -0,0 +1,16 @@
+{
+ "attribute": "minecraft:generic.max_health",
+ "modifier": {
+ "uuid": "413a10a0-bf0b-47db-a9a9-2eb3dda3bbaf",
+ "name": "Test",
+ "value": 1.0,
+ "operation": "ADDITION"
+ },
+ "item": {
+ "items": [
+ "minecraft:diamond_axe",
+ "minecraft:diamond_sword"
+ ]
+ },
+ "slot": "MAINHAND"
+}
\ No newline at end of file
diff --git a/src/testmod/resources/data/thermoo-test/thermoo/temperature_effects/sequence_test.json b/src/testmod/resources/data/thermoo-test/thermoo/temperature_effects/sequence_test.json
new file mode 100644
index 00000000..b43898e2
--- /dev/null
+++ b/src/testmod/resources/data/thermoo-test/thermoo/temperature_effects/sequence_test.json
@@ -0,0 +1,36 @@
+{
+ "type": "thermoo:sequence",
+ "entity": {
+ "condition": "minecraft:reference",
+ "name": "thermoo-test:is_warm"
+ },
+ "config": {
+ "children": [
+ {
+ "type": "thermoo:damage",
+ "entity_type": "minecraft:player",
+ "config": {
+ "amount": 1,
+ "damage_interval": 20,
+ "damage_type": "minecraft:in_fire"
+ }
+ },
+ {
+ "type": "thermoo:status_effect",
+ "entity": {
+ "condition": "minecraft:reference",
+ "name": "thermoo-test:is_cold"
+ },
+ "config": {
+ "effects": [
+ {
+ "amplifier": 0,
+ "duration": 200,
+ "effect": "minecraft:nausea"
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/testmod/resources/fabric.mod.json b/src/testmod/resources/fabric.mod.json
index 263fab89..ecc4d0ef 100644
--- a/src/testmod/resources/fabric.mod.json
+++ b/src/testmod/resources/fabric.mod.json
@@ -9,6 +9,9 @@
"main": [
"com.github.thedeathlycow.thermoo.testmod.ThermooTestMod"
],
+ "client": [
+ "com.github.thedeathlycow.thermoo.testmod.ThermooTestModClient"
+ ],
"fabric-gametest": [
]
}