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": [ ] }