Skip to content

Commit

Permalink
Item Attribute Modifiers and Other New APIs (#8)
Browse files Browse the repository at this point in the history
* item attribute modifier codec

* implement modifiers on event callback

* use a simpler item predicate type and fix encoding

* add javadoc

* sequence temperature effect

* celsius to temp tick api

* temperature unit api

* add units to checktemperature command

* set version 2.2

* fix wrong example slot in javadoc

* require checktemperature to be a loaded blockpos

* display temperature to 2 decimal places

* client api and mixin setup

* heart render event for vanilla

* add compats for overflowing bars and colorful hearts

* common seasons integration events

* move common mixins to common mixin package

* fire hearts in testmod

* add fabric seasons integration

* update icon

* make heart overlay event class more generic

* write clearer javadoc for season events
  • Loading branch information
TheDeathlyCow authored Mar 25, 2024
1 parent d3f1882 commit 94ef6e0
Show file tree
Hide file tree
Showing 50 changed files with 1,467 additions and 32 deletions.
20 changes: 19 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id 'fabric-loom' version '1.2-SNAPSHOT'
id 'fabric-loom' version '1.5-SNAPSHOT'
id 'maven-publish'
}

Expand All @@ -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 {
Expand All @@ -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.

Expand Down Expand Up @@ -86,6 +101,9 @@ subprojects {
}

loom {

accessWidenerPath = file("src/main/resources/thermoo.accesswidener")

interfaceInjection {
enableDependencyInterfaceInjection = true
}
Expand Down
Binary file added docs/requires-thermoo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 11 additions & 4 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* Exposed in API for the convenience of API users.
*/
@ApiStatus.Experimental
public class ThermooCodecs {

public static final Codec<EquipmentSlot> EQUIPMENT_SLOT_CODEC = createEnumCodec(EquipmentSlot.class);

public static final Codec<EntityAttributeModifier.Operation> ENTITY_ATTRIBUTE_OPERATION_CODEC = createEnumCodec(EntityAttributeModifier.Operation.class);

public static final Codec<EntityAttributeModifier> 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 <E> The enum type
* @return Returns a codec for the enum class
*/
public static <E extends Enum<E>> Codec<E> createEnumCodec(Class<E> 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() {

}

}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,6 +11,8 @@ public final class ThermooRegistryKeys {

public static final RegistryKey<Registry<TemperatureEffect<?>>> TEMPERATURE_EFFECT = createRegistryKey("temperature_effects");

public static final RegistryKey<Registry<ItemAttributeModifier>> ITEM_ATTRIBUTE_MODIFIER = createRegistryKey("item_attribute_modifier");

private static <T> RegistryKey<Registry<T>> createRegistryKey(String registryId) {
return RegistryKey.ofRegistry(Thermoo.id(registryId));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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/<entry namespace>/thermoo/item_attribute_modifier/<entry path>}. They are synchronized to clients so that
* they will properly show up in the item tooltip.
* <p>
* Example JSON file:
* <pre>
* {
* "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"
* }
* </pre>
* <p>
* You can also use tags:
* <pre>
* {
* "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"
* }
* </pre>
* <p>
* 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<ItemAttributeModifier> 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<EntityAttribute, EntityAttributeModifier> attributeModifiers
) {
if (this.slot == slot && this.itemPredicate.test(stack)) {
attributeModifiers.put(this.attribute, this.modifier);
}
}

public record ItemTypePredicate(
@Nullable List<Item> items,
@Nullable TagKey<Item> itemTag
) implements Predicate<ItemStack> {

public static final Codec<ItemTypePredicate> CODEC = Codec.<ItemTypePredicate, ItemTypePredicate>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;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
*
*/
package com.github.thedeathlycow.thermoo.api.attribute;
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* Integrates with Colorful Hearts and Overflowing Bars for now - but this integration will be removed in the
* future.
* <p>
* Custom heart types, like Frozen Hearts, should be handled separately.
*/
public static final Event<RenderHealthBarCallback> 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
);

}

}
Original file line number Diff line number Diff line change
@@ -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;
Loading

0 comments on commit 94ef6e0

Please sign in to comment.