diff --git a/README.md b/README.md index 59a77e3..a489341 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,29 @@ All fields are optional. } ``` +### Prevent trinket equip/unequip + +This power prevents the player from equipping or unequipping a trinket. + +```jsonc +{ + "type": "shappoli:prevent_trinket_equip", // or shappoli:prevent_trinket_unequip + "slot": { + // Trinket slot, optional + }, + "slots": [ + // List of trinket slots, optional + ], + "item_condition": { + // Item condition, optional + }, + "entity_condition": { + // Entity condition, optional + }, + "allow_in_creative": true // Allow in creative mode, default true +} +``` + ### Modify villager reputation This power modifies the player's reputation with a villager, which is used to determine the prices of trades. diff --git a/src/main/java/com/github/shap_po/shappoli/integration/trinkets/power/BasePreventTrinketPower.java b/src/main/java/com/github/shap_po/shappoli/integration/trinkets/power/BasePreventTrinketPower.java new file mode 100644 index 0000000..281a6aa --- /dev/null +++ b/src/main/java/com/github/shap_po/shappoli/integration/trinkets/power/BasePreventTrinketPower.java @@ -0,0 +1,91 @@ +package com.github.shap_po.shappoli.integration.trinkets.power; + +import com.github.shap_po.shappoli.Shappoli; +import com.github.shap_po.shappoli.integration.trinkets.data.ShappoliTrinketsDataTypes; +import com.github.shap_po.shappoli.integration.trinkets.data.TrinketSlotData; +import com.github.shap_po.shappoli.integration.trinkets.util.TrinketsUtil; +import dev.emi.trinkets.api.SlotReference; +import io.github.apace100.apoli.data.ApoliDataTypes; +import io.github.apace100.apoli.power.Power; +import io.github.apace100.apoli.power.PowerType; +import io.github.apace100.apoli.power.factory.PowerFactory; +import io.github.apace100.calio.data.SerializableData; +import io.github.apace100.calio.data.SerializableDataTypes; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Pair; +import net.minecraft.world.World; + +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; + +public abstract class BasePreventTrinketPower extends Power { + protected final Predicate entityCondition; + protected final Predicate> itemCondition; + protected final List slots; + protected final boolean allowCreative; + + public BasePreventTrinketPower( + PowerType type, + LivingEntity entity, + Predicate entityCondition, + Predicate> itemCondition, + List slots, + boolean allowInCreative + ) { + super(type, entity); + this.entityCondition = entityCondition; + this.itemCondition = itemCondition; + this.slots = slots; + this.allowCreative = allowInCreative; + } + + public boolean doesApply(LivingEntity actor, SlotReference slotReference, ItemStack item) { + if ((actor instanceof PlayerEntity player) && player.isCreative() && allowCreative) { + return false; + } + return ( + (slots.isEmpty() || slots.stream().anyMatch(slot -> slot.test(slotReference))) && + (entityCondition == null || entityCondition.test(actor)) && + (itemCondition == null || itemCondition.test(TrinketsUtil.getItemConditionPair(actor, item))) + ); + } + + @FunctionalInterface + public interface Factory { + BasePreventTrinketPower create( + PowerType type, + LivingEntity entity, + Predicate entityCondition, + Predicate> itemCondition, + List slots, + boolean allowInCreative + ); + } + + + public static PowerFactory createFactory(String identifier, Factory serializerFactory) { + Objects.requireNonNull(serializerFactory); + return new PowerFactory<>( + Shappoli.identifier(identifier), + new SerializableData() + .add("entity_condition", ApoliDataTypes.ENTITY_CONDITION, null) + .add("item_condition", ApoliDataTypes.ITEM_CONDITION, null) + .add("slot", ShappoliTrinketsDataTypes.TRINKET_SLOT, null) + .add("slots", ShappoliTrinketsDataTypes.TRINKET_SLOTS, null) + .add("allow_in_creative", SerializableDataTypes.BOOLEAN, true), + data -> (type, player) -> + serializerFactory.create( + type, + player, + data.get("entity_condition"), + data.get("item_condition"), + TrinketSlotData.getSlots(data), + data.getBoolean("allow_in_creative") + ) + ).allowCondition(); + } +} diff --git a/src/main/java/com/github/shap_po/shappoli/integration/trinkets/power/PreventTrinketEquipPower.java b/src/main/java/com/github/shap_po/shappoli/integration/trinkets/power/PreventTrinketEquipPower.java new file mode 100644 index 0000000..eb8e856 --- /dev/null +++ b/src/main/java/com/github/shap_po/shappoli/integration/trinkets/power/PreventTrinketEquipPower.java @@ -0,0 +1,31 @@ +package com.github.shap_po.shappoli.integration.trinkets.power; + +import com.github.shap_po.shappoli.integration.trinkets.data.TrinketSlotData; +import io.github.apace100.apoli.power.PowerType; +import io.github.apace100.apoli.power.factory.PowerFactory; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Pair; +import net.minecraft.world.World; + +import java.util.List; +import java.util.function.Predicate; + +public class PreventTrinketEquipPower extends BasePreventTrinketPower { + public PreventTrinketEquipPower( + PowerType type, + LivingEntity entity, + Predicate entityCondition, + Predicate> itemCondition, + List slots, + boolean allowCreative + ) { + super(type, entity, entityCondition, itemCondition, slots, allowCreative); + } + + public static PowerFactory createFactory() { + return createFactory("prevent_trinket_equip", PreventTrinketEquipPower::new); + } + +} diff --git a/src/main/java/com/github/shap_po/shappoli/integration/trinkets/power/PreventTrinketUnequipPower.java b/src/main/java/com/github/shap_po/shappoli/integration/trinkets/power/PreventTrinketUnequipPower.java new file mode 100644 index 0000000..8d04f43 --- /dev/null +++ b/src/main/java/com/github/shap_po/shappoli/integration/trinkets/power/PreventTrinketUnequipPower.java @@ -0,0 +1,30 @@ +package com.github.shap_po.shappoli.integration.trinkets.power; + +import com.github.shap_po.shappoli.integration.trinkets.data.TrinketSlotData; +import io.github.apace100.apoli.power.PowerType; +import io.github.apace100.apoli.power.factory.PowerFactory; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Pair; +import net.minecraft.world.World; + +import java.util.List; +import java.util.function.Predicate; + +public class PreventTrinketUnequipPower extends BasePreventTrinketPower { + public PreventTrinketUnequipPower( + PowerType type, + LivingEntity entity, + Predicate entityCondition, + Predicate> itemCondition, + List slots, + boolean allowCreative + ) { + super(type, entity, entityCondition, itemCondition, slots, allowCreative); + } + + public static PowerFactory createFactory() { + return createFactory("prevent_trinket_unequip", PreventTrinketUnequipPower::new); + } +} diff --git a/src/main/java/com/github/shap_po/shappoli/mixin/integration/trinkets/SurvivalTrinketSlotMixin.java b/src/main/java/com/github/shap_po/shappoli/mixin/integration/trinkets/SurvivalTrinketSlotMixin.java new file mode 100644 index 0000000..41dfe39 --- /dev/null +++ b/src/main/java/com/github/shap_po/shappoli/mixin/integration/trinkets/SurvivalTrinketSlotMixin.java @@ -0,0 +1,41 @@ +package com.github.shap_po.shappoli.mixin.integration.trinkets; + +import com.github.shap_po.shappoli.integration.trinkets.power.PreventTrinketUnequipPower; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import dev.emi.trinkets.SurvivalTrinketSlot; +import dev.emi.trinkets.api.SlotReference; +import dev.emi.trinkets.api.TrinketInventory; +import io.github.apace100.apoli.component.PowerHolderComponent; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(SurvivalTrinketSlot.class) +public class SurvivalTrinketSlotMixin extends Slot { + @Shadow + @Final + private TrinketInventory trinketInventory; + @Shadow + @Final + private int slotOffset; + + public SurvivalTrinketSlotMixin(Inventory inventory, int index, int x, int y) { + super(inventory, index, x, y); + } + + @ModifyReturnValue(method = "canTakeItems", at = @At("RETURN"), remap = false) + public boolean shappoli$preventTrinketUnequip(boolean original, PlayerEntity player) { + ItemStack stack = this.getStack(); + SlotReference slotRef = new SlotReference(trinketInventory, slotOffset); + + return !PowerHolderComponent.hasPower(player, PreventTrinketUnequipPower.class, + p -> p.doesApply(player, slotRef, stack) + ) && original; + } + +} diff --git a/src/main/java/com/github/shap_po/shappoli/mixin/integration/trinkets/TrinketSlotMixin.java b/src/main/java/com/github/shap_po/shappoli/mixin/integration/trinkets/TrinketSlotMixin.java new file mode 100644 index 0000000..f545444 --- /dev/null +++ b/src/main/java/com/github/shap_po/shappoli/mixin/integration/trinkets/TrinketSlotMixin.java @@ -0,0 +1,21 @@ +package com.github.shap_po.shappoli.mixin.integration.trinkets; + +import com.github.shap_po.shappoli.integration.trinkets.power.PreventTrinketEquipPower; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import dev.emi.trinkets.TrinketSlot; +import dev.emi.trinkets.api.SlotReference; +import io.github.apace100.apoli.component.PowerHolderComponent; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(TrinketSlot.class) +public interface TrinketSlotMixin { + @ModifyReturnValue(method = "canInsert", at = @At("RETURN"), remap = false) + private static boolean shappoli$preventTrinketEquip(boolean original, ItemStack stack, SlotReference slotRef, LivingEntity entity) { + return !PowerHolderComponent.hasPower(entity, PreventTrinketEquipPower.class, + p -> p.doesApply(entity, slotRef, stack) + ) && original; + } +} diff --git a/src/main/resources/shappoli.mixins.json b/src/main/resources/shappoli.mixins.json index 75c506d..65de4db 100644 --- a/src/main/resources/shappoli.mixins.json +++ b/src/main/resources/shappoli.mixins.json @@ -5,7 +5,9 @@ "compatibilityLevel": "JAVA_17", "mixins": [ "VillagerEntityMixin", - "integration.trinkets.LivingEntityMixin" + "integration.trinkets.LivingEntityMixin", + "integration.trinkets.SurvivalTrinketSlotMixin", + "integration.trinkets.TrinketSlotMixin" ], "injectors": { "defaultRequire": 1 diff --git a/src/test/resources/data/shappoli/powers/trinket/prevent_equip.json b/src/test/resources/data/shappoli/powers/trinket/prevent_equip.json new file mode 100644 index 0000000..5e2045a --- /dev/null +++ b/src/test/resources/data/shappoli/powers/trinket/prevent_equip.json @@ -0,0 +1,35 @@ +{ + "type": "apoli:multiple", + + "prevent_equip": { + "type": "shappoli:prevent_trinket_equip", + "slot": { + "group": "feet" + }, + "slots": [ + { + "name": "necklace" + } + ], + "item_condition": { + "type": "apoli:empty", + "inverted": true + } + }, + + "prevent_unequip": { + "type": "shappoli:prevent_trinket_unequip", + "slot": { + "group": "feet" + }, + "slots": [ + { + "name": "necklace" + } + ], + "item_condition": { + "type": "apoli:empty", + "inverted": true + } + } +}