Skip to content

Commit

Permalink
feat: refactored the requirements system, updated changelog, bumped v…
Browse files Browse the repository at this point in the history
…ersion
  • Loading branch information
MichaelHillcox committed Feb 25, 2024
1 parent d06f1e0 commit c13a318
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 42 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
# Changelog
All notable changes to this project will be documented in this file.

## [5.3.0]

### Added

- A new requirements system that has a lot more customizable
- You can now require any item to be held [#24](https://github.com/nanite/SquatGrow/issues/24)
- You can now require equipment to be worn
- You can now require the required equipment or held item to be enchanted [#16](https://github.com/nanite/SquatGrow/issues/16)

### Fixed

- Config system not reloading as it should when you do `/reload`
- Chance config not being computed properly leading to the chance always being 100% [#27](https://github.com/nanite/SquatGrow/issues/27)

### Deprecated

- The old `requireHoe` and `hoeTakesDamage` config options have been deprecated in favor of the new requirements system

## [5.2.0]

### Added
Expand Down
130 changes: 93 additions & 37 deletions common/src/main/java/dev/wuffs/squatgrow/SquatAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.ItemTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
Expand All @@ -25,7 +27,7 @@

import java.util.*;

import static dev.wuffs.squatgrow.SquatGrow.config;
import static dev.wuffs.squatgrow.SquatGrow.*;

public class SquatAction {
public static void performAction(Level level, Player player) {
Expand All @@ -44,45 +46,31 @@ public static Pair<Boolean, List<ItemStack>> passesRequirements(Player player) {
List<ItemStack> itemsThatHandleDamage = new ArrayList<>();
// Legacy support, if this is enabled, the requirements system is disabled.
if (config.requireHoe) {
ItemStack mainHand = player.getMainHandItem();
if (mainHand.is(ItemTags.HOES)) {
itemsThatHandleDamage.add(mainHand);
return Pair.of(true, itemsThatHandleDamage);
}

ItemStack offHand = player.getOffhandItem();
if (offHand.is(ItemTags.HOES)) {
itemsThatHandleDamage.add(offHand);
// Meh, lists aren't free but emptylist is a constant so it's fine
var matchedItem = getMatchingHeldItem(player, Collections.emptyList(), List.of(ItemTags.HOES));
if (!matchedItem.isEmpty()) {
itemsThatHandleDamage.add(matchedItem);
return Pair.of(true, itemsThatHandleDamage);
}

return Pair.of(false, itemsThatHandleDamage);
}

SquatGrowConfig.Requirements requirements = config.requirements;
if (requirements.enabled) {
if (requirements.enabled && SquatGrow.computedRequirements != null) {
// Easier to compare the originals than it is to compare the computed ones
if (requirements.heldItemRequirement.isEmpty() && requirements.equipmentRequirement.isEmpty()) {
return Pair.of(true, itemsThatHandleDamage);
}

// Let's check the correct things. First, the lighter of the two checks
boolean passesEquipment = false;
if (!requirements.heldItemRequirement.isEmpty()) {
BitSet foundItems = new BitSet();
for (Map.Entry<EquipmentSlot, String> entry : requirements.equipmentRequirement.entrySet()) {
ItemStack stack = player.getItemBySlot(entry.getKey());
if (stack.isEmpty()) {
continue;
}
if (!requirements.equipmentRequirement.isEmpty()) {
var matchingEquipment = matchingEquipmentItem(player, computedRequirements.equipmentRequirementStacks(), computedRequirements.equipmentRequirementTags());

// Now compare the item in the slot to the requirement
if (stack.getItem().arch$registryName().toString().equals(entry.getValue())) {
foundItems.set(entry.getKey().getIndex());
itemsThatHandleDamage.add(stack);
}
}

if (foundItems.cardinality() == requirements.heldItemRequirement.size()) {
// This is safe to do as it will only increment if the equipment is found, and you can only have one item per slot
if (matchingEquipment.size() == requirements.equipmentRequirement.size()) {
itemsThatHandleDamage.addAll(matchingEquipment);
passesEquipment = true;
}
}
Expand All @@ -93,20 +81,22 @@ public static Pair<Boolean, List<ItemStack>> passesRequirements(Player player) {

// Now, the heavier of the two checks
// We can only have gotten here if heldItemRequirement is not empty so no need to check again
BitSet foundItems = new BitSet();
for (String item : requirements.heldItemRequirement) {
ResourceLocation itemLocation = new ResourceLocation(item);
if (player.getMainHandItem().getItem().arch$registryName().equals(itemLocation) || player.getOffhandItem().getItem().arch$registryName().equals(itemLocation)) {
foundItems.set(itemLocation.hashCode());
itemsThatHandleDamage.add(player.getMainHandItem());
}
boolean passedHeldItem = false;
var matchingHeldItem = getMatchingHeldItem(player, computedRequirements.heldItemRequirementStacks(), computedRequirements.heldItemRequirementTags());
if (!matchingHeldItem.isEmpty()) {
itemsThatHandleDamage.add(matchingHeldItem);
passedHeldItem = true;
}

if (!requirements.heldItemRequirement.isEmpty() && !passedHeldItem) {
return Pair.of(false, itemsThatHandleDamage); // If the held item check is required and failed, we can return false
}

var passes = foundItems.cardinality() == requirements.heldItemRequirement.size();
return Pair.of(passes, itemsThatHandleDamage);
return Pair.of(true, itemsThatHandleDamage); // If we got here, we passed both checks
}

return Pair.of(true, itemsThatHandleDamage);
// Nothing is required, so we can return true
return Pair.of(true, Collections.emptyList());
}

public static void grow(Level level, ServerPlayer player, List<ItemStack> itemsToDamage) {
Expand Down Expand Up @@ -213,4 +203,70 @@ private static void addGrowthParticles(ServerLevel level, BlockPos pos, ServerPl
level.playSound(null, immutablePos, SoundEvents.BONE_MEAL_USE, SoundSource.MASTER, 0.5F, 1.0F);
}
}

private static ItemStack getMatchingHeldItem(Player player, List<ItemStack> itemStacks, List<TagKey<Item>> itemTags) {
var mainHand = player.getMainHandItem();
var offHand = player.getOffhandItem();

// Check the main hand first
var matchingItem = compareItemToLists(mainHand, itemStacks, itemTags);
if (!matchingItem.isEmpty()) {
return matchingItem;
}

// Check the offhand next
return compareItemToLists(offHand, itemStacks, itemTags);
}

private static ItemStack compareItemToLists(ItemStack stack, List<ItemStack> itemStacks, List<TagKey<Item>> itemTags) {
for (ItemStack item : itemStacks) {
if (itemStackMatches(stack, item)) {
return stack;
}
}

for (TagKey<Item> tag : itemTags) {
if (itemStackMatches(stack, tag)) {
return stack;
}
}

return ItemStack.EMPTY;
}

private static List<ItemStack> matchingEquipmentItem(Player player, Map<EquipmentSlot, ItemStack> equipmentStacks, Map<EquipmentSlot, TagKey<Item>> equipmentTags) {
List<ItemStack> matchedItems = new ArrayList<>();

for (Map.Entry<EquipmentSlot, ItemStack> entry : equipmentStacks.entrySet()) {
ItemStack itemBySlot = player.getItemBySlot(entry.getKey());
if (itemStackMatches(itemBySlot, entry.getValue())) {
matchedItems.add(itemBySlot);
}
}

for (Map.Entry<EquipmentSlot, TagKey<Item>> entry : equipmentTags.entrySet()) {
ItemStack itemBySlot = player.getItemBySlot(entry.getKey());
if (itemStackMatches(itemBySlot, entry.getValue())) {
matchedItems.add(itemBySlot);
}
}

return matchedItems;
}

private static boolean itemStackMatches(ItemStack stack, TagKey<Item> tag) {
if (computedEnchantment != null && stack.isEnchantable()) {
return stack.is(tag) && EnchantmentHelper.getEnchantments(stack).containsKey(computedEnchantment);
}

return stack.is(tag);
}

private static boolean itemStackMatches(ItemStack stack, ItemStack item) {
if (computedEnchantment != null && stack.isEnchantable()) {
return stack.is(item.getItem()) && EnchantmentHelper.getEnchantments(stack).containsKey(computedEnchantment);
}

return stack.is(item.getItem());
}
}
64 changes: 60 additions & 4 deletions common/src/main/java/dev/wuffs/squatgrow/SquatGrow.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,34 @@
import dev.architectury.event.events.common.LifecycleEvent;
import dev.architectury.registry.ReloadListenerRegistry;
import dev.wuffs.squatgrow.actions.Actions;
import dev.wuffs.squatgrow.config.ComputedRequirements;
import dev.wuffs.squatgrow.config.SquatGrowConfig;
import me.shedaniel.autoconfig.AutoConfig;
import me.shedaniel.autoconfig.ConfigHolder;
import me.shedaniel.autoconfig.serializer.YamlConfigSerializer;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.ResourceManagerReloadListener;
import net.minecraft.tags.TagKey;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

Expand All @@ -33,6 +44,8 @@ public class SquatGrow {
public static final Set<TagKey<Block>> tagCache = new HashSet<>();
public static final Set<String> wildcardCache = new HashSet<>();

public static ComputedRequirements computedRequirements = null;
public static Enchantment computedEnchantment = null;

public static void init() {
configHolder = AutoConfig.register(SquatGrowConfig.class, YamlConfigSerializer::new);
Expand Down Expand Up @@ -66,8 +79,6 @@ private static InteractionResult onConfigChanged(ConfigHolder<SquatGrowConfig> h
tagCache.clear();
wildcardCache.clear();

LOGGER.info("Config loading");

tagCache.addAll(newConfig.ignoreList.stream()
.filter(e -> e.contains("#"))
.map(e -> TagKey.create(Registries.BLOCK, new ResourceLocation(e.replace("#", ""))))
Expand All @@ -76,8 +87,39 @@ private static InteractionResult onConfigChanged(ConfigHolder<SquatGrowConfig> h
wildcardCache.addAll(newConfig.ignoreList.stream().filter(e -> e.contains("*")).map(e -> e.split(":")[0])
.collect(Collectors.toSet()));

LOGGER.info("Tags: " + tagCache);
LOGGER.info("Wildcards: " + wildcardCache);
List<String> heldItemRequirement = newConfig.requirements.heldItemRequirement;
Map<EquipmentSlot, String> equipmentRequirement = newConfig.requirements.equipmentRequirement;

Pair<List<ItemStack>, List<TagKey<Item>>> computedHeldEntries = computeItemsAndTagsFromStringList(heldItemRequirement);

// This is kinda gross, but it does work so /shrug
Map<EquipmentSlot, ItemStack> equipmentRequirementStacks = equipmentRequirement.entrySet().stream()
.filter(e -> !e.getValue().contains("#"))
.collect(Collectors.toMap(Map.Entry::getKey, e -> new ItemStack(BuiltInRegistries.ITEM.get(new ResourceLocation(e.getValue())))));

Map<EquipmentSlot, TagKey<Item>> equipmentRequirementTags = equipmentRequirement.entrySet().stream()
.filter(e -> e.getValue().contains("#"))
.collect(Collectors.toMap(Map.Entry::getKey, e -> TagKey.create(Registries.ITEM, new ResourceLocation(e.getValue().replace("#", "")))));

computedRequirements = new ComputedRequirements(
computedHeldEntries.getLeft(),
computedHeldEntries.getRight(),
equipmentRequirementStacks,
equipmentRequirementTags
);

// This makes me want to puke, defaulted registries suck
if (!newConfig.requirements.requiredEnchantment.isEmpty()) {
ResourceLocation enchantmentRl = new ResourceLocation(newConfig.requirements.requiredEnchantment);
Enchantment enchantment = BuiltInRegistries.ENCHANTMENT.get(enchantmentRl);
// Default for the registry is fortune, we need to make that if we get fortune it matches the enchantmentRl
if (enchantment != Enchantments.BLOCK_FORTUNE || enchantmentRl.equals(new ResourceLocation("minecraft:fortune"))) {
// If the enchantment is not fortune or it is but we want fortune, set it
computedEnchantment = enchantment;
}
} else {
computedEnchantment = null;
}

return InteractionResult.SUCCESS;
}
Expand All @@ -98,4 +140,18 @@ private static boolean isBlockInIgnoreList(BlockState state) {

return tagCache.stream().anyMatch(state::is);
}

private static Pair<List<ItemStack>, List<TagKey<Item>>> computeItemsAndTagsFromStringList(List<String> list) {
List<ItemStack> stacks = list.stream()
.filter(e -> !e.contains("#"))
.map(e -> new ItemStack(BuiltInRegistries.ITEM.get(new ResourceLocation(e))))
.toList();

List<TagKey<Item>> tags = list.stream()
.filter(e -> e.contains("#"))
.map(e -> TagKey.create(Registries.ITEM, new ResourceLocation(e.replace("#", ""))))
.toList();

return Pair.of(stacks, tags);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dev.wuffs.squatgrow.config;

import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;

import java.util.List;
import java.util.Map;

public record ComputedRequirements(
List<ItemStack> heldItemRequirementStacks,
List<TagKey<Item>> heldItemRequirementTags,
Map<EquipmentSlot, ItemStack> equipmentRequirementStacks,
Map<EquipmentSlot, TagKey<Item>> equipmentRequirementTags
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
import me.shedaniel.autoconfig.annotation.Config;
import me.shedaniel.autoconfig.annotation.ConfigEntry;
import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Comment;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;

import java.beans.Transient;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -86,5 +90,10 @@ public static class Requirements {

@Comment("Amount of damage to take when used to grow")
public int durabilityDamage = 1;

@Comment(
"Enchantment required to grow, leave empty to disable\n"
)
public String requiredEnchantment = "";
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ minecraft_version=1.20.1
enabled_platforms=fabric,forge

archives_base_name=squatgrow
mod_version=5.2.0
mod_version=5.3.0
maven_group=dev.wuffs

architectury_version=9.1.12
Expand Down

0 comments on commit c13a318

Please sign in to comment.