Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recipe Remainder API #158

Merged
merged 34 commits into from Oct 8, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ad74dce
Beginning of the recipe remainder api, impl take 2
OroArmor Mar 4, 2022
0e73972
Use a bit more of my brain to make things work better now, still need…
OroArmor Mar 4, 2022
379f1f0
did some reworking and everything works that doesnt have a todo.
OroArmor Mar 5, 2022
10fdb76
cant remember
OroArmor Apr 20, 2022
398622b
Merge remote-tracking branch 'QuiltMC/1.19' into recipe-remainder-api
Platymemo Jul 30, 2022
dbe696c
prevent crash on craft in furnace with pre-existing fuel time
Platymemo Jul 31, 2022
2422f53
properly re-ordered recipe remainder logic
Platymemo Jul 31, 2022
6ba5a9c
apply suggestions
Platymemo Jul 31, 2022
2aaead1
add fuel test
Platymemo Jul 31, 2022
399a6b6
allow partial returns
Platymemo Jul 31, 2022
4d7bd39
Merge remote-tracking branch 'QuiltMC/1.19' into recipe-remainder-api
Platymemo Aug 3, 2022
4eaff80
Merge remote-tracking branch 'QuiltMC/1.19' into recipe-remainder-api
Platymemo Aug 6, 2022
d15e2fe
remove SharedConstantsMixin
Platymemo Aug 6, 2022
7291e8c
apply suggestions
Platymemo Aug 6, 2022
52de650
Merge remote-tracking branch 'QuiltMC/1.19' into recipe-remainder-api
Platymemo Sep 2, 2022
bf320f4
small fixes
Platymemo Sep 2, 2022
67d4531
brewing remainder test
Platymemo Sep 2, 2022
dc5ebd7
update package-info and apply licences
Platymemo Sep 3, 2022
40da666
code cleanup
Platymemo Sep 3, 2022
25a2dbd
remove invalid contracts
Platymemo Sep 4, 2022
ac991ea
move recipe remainder tests
Platymemo Sep 4, 2022
293bb3b
fix recipe remainder order of operations
Platymemo Sep 4, 2022
e0e617c
apply licenses
Platymemo Sep 4, 2022
c02450a
refactoring and docs
Platymemo Sep 8, 2022
011635a
Merge remote-tracking branch 'QuiltMC/1.19' into recipe-remainder-api
Platymemo Sep 8, 2022
0a957b2
ApiStatus annotations
Platymemo Sep 8, 2022
a73ff10
apply suggestion
Platymemo Sep 8, 2022
6e11ae8
apply suggestions
Platymemo Sep 12, 2022
b82c9a1
Merge branch '1.19' into recipe-remainder-api
EnnuiL Sep 28, 2022
63632bc
checkstyle pass
Platymemo Sep 29, 2022
a287b68
revert bad checkstyle
Platymemo Sep 29, 2022
f6f0f40
Apply suggestions
Platymemo Oct 5, 2022
1ace64d
update recipe remainder names
Platymemo Oct 5, 2022
83ad373
Merge branch '1.19' into recipe-remainder-api
EnnuiL Oct 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions library/item/item_setting/build.gradle
Expand Up @@ -12,6 +12,15 @@ qslModule {
core {
api("qsl_base")
testmodOnly("resource_loader")
testmodOnly("lifecycle_events")
testmodOnly("networking")
}
data {
testmodOnly("registry_entry_attachments")
Platymemo marked this conversation as resolved.
Show resolved Hide resolved
testmodOnly("tags")
}
item {
testmodOnly("item_content_registry")
}
}
}
Expand Up @@ -19,6 +19,7 @@
import java.util.function.Supplier;

import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;

import net.minecraft.item.Item;

Expand Down
Expand Up @@ -22,8 +22,7 @@
* A list of the {@link CustomItemSetting}s that are provided by Quilt
*/
public final class QuiltCustomItemSettings {
private QuiltCustomItemSettings() {
}
private QuiltCustomItemSettings() {}
Platymemo marked this conversation as resolved.
Show resolved Hide resolved

/**
* The {@link CustomItemSetting} in charge of handing {@link EquipmentSlotProvider}s
Expand All @@ -33,4 +32,18 @@ private QuiltCustomItemSettings() {
* The {@link CustomItemSetting} in charge of handing {@link CustomDamageHandler}s
*/
public static final CustomItemSetting<CustomDamageHandler> CUSTOM_DAMAGE_HANDLER = CustomItemSettingImpl.CUSTOM_DAMAGE_HANDLER;
/**
Leo40Git marked this conversation as resolved.
Show resolved Hide resolved
* The {@link CustomItemSetting} in charge of handing {@link RecipeRemainderProvider}s. This setting should be used when implementing custom crafting systems to properly handle remainders. <p>
* The setting is currently used in the following places:
* <ul>
* <li>Crafting Table</li>
* <li>Furnace Fuel</li>
* <li>Furnace Ingredient</li>
* <li>Loom Dye Input</li>
* <li>Brewing Stand Ingredient</li>
* <li>Smithing Table Addition</li>
* <li>Stone Cutter Ingredient</li>
* </ul>
*/
public static final CustomItemSetting<RecipeRemainderProvider> RECIPE_REMAINDER_PROVIDER = CustomItemSettingImpl.RECIPE_REMAINDER_PROVIDER;
}
Expand Up @@ -17,6 +17,8 @@

package org.quiltmc.qsl.item.setting.api;

import org.jetbrains.annotations.Contract;

import net.minecraft.entity.EquipmentSlot;
import net.minecraft.item.FoodComponent;
import net.minecraft.item.Item;
Expand All @@ -39,6 +41,7 @@ public class QuiltItemSettings extends Item.Settings {
* @param equipmentSlotProvider the {@link EquipmentSlotProvider}
* @return this
*/
@Contract("_->this")
public QuiltItemSettings equipmentSlot(EquipmentSlotProvider equipmentSlotProvider) {
return this.customSetting(QuiltCustomItemSettings.EQUIPMENT_SLOT_PROVIDER, equipmentSlotProvider);
}
Expand All @@ -49,6 +52,7 @@ public QuiltItemSettings equipmentSlot(EquipmentSlotProvider equipmentSlotProvid
* @param equipmentSlot the {@link EquipmentSlot}
* @return this
*/
@Contract("_->this")
public QuiltItemSettings equipmentSlot(EquipmentSlot equipmentSlot) {
return this.customSetting(QuiltCustomItemSettings.EQUIPMENT_SLOT_PROVIDER, itemStack -> equipmentSlot);
}
Expand All @@ -61,17 +65,72 @@ public QuiltItemSettings equipmentSlot(EquipmentSlot equipmentSlot) {
* @return this
* @see CustomDamageHandler
*/
@Contract("_->this")
public QuiltItemSettings customDamage(CustomDamageHandler handler) {
return this.customSetting(QuiltCustomItemSettings.CUSTOM_DAMAGE_HANDLER, handler);
}

/**
* Sets the stack-aware recipe remainder provider of the item.
*/
@Contract("_->this")
public QuiltItemSettings recipeRemainder(RecipeRemainderProvider provider) {
return this.customSetting(QuiltCustomItemSettings.RECIPE_REMAINDER_PROVIDER, provider);
}

/**
* Sets the stack-aware recipe remainder to damage the item by 1 every time it is used in crafting.
*/
@Contract("->this")
public QuiltItemSettings damageIfUsedInCrafting() {
Platymemo marked this conversation as resolved.
Show resolved Hide resolved
return this.damageIfUsedInCrafting(1);
}

/**
* Sets the stack-aware recipe remainder to return the item itself.
*/
@Contract("->this")
public QuiltItemSettings returnSelfInCrafting() {
Platymemo marked this conversation as resolved.
Show resolved Hide resolved
return this.damageIfUsedInCrafting(0);
}

/**
* Sets the stack-aware recipe remainder to damage the item by a certain amount every time it is used in crafting.
*
* @param by the amount
*/
@Contract("_->this")
public QuiltItemSettings damageIfUsedInCrafting(int by) {
Platymemo marked this conversation as resolved.
Show resolved Hide resolved
if (by == 0) {
return this.recipeRemainder((original, recipe) -> original.copy());
}

return this.recipeRemainder((original, recipe) -> {
if (!original.isDamageable()) {
return original.copy();
}

ItemStack copy = original.copy();

copy.setDamage(copy.getDamage() + by);

if (copy.getDamage() >= copy.getMaxDamage()) {
copy.setCount(0);
return ItemStack.EMPTY;
}

return copy;
});
}

/**
* Sets a custom setting of the item.
*
* @param setting the unique type for this setting
* @param value the object containing the setting itself
* @return this builder
*/
@Contract("_, _->this")
public <T> QuiltItemSettings customSetting(CustomItemSetting<T> setting, T value) {
if (!(setting instanceof CustomItemSettingImpl)) {
throw new UnsupportedOperationException("CustomItemSetting should not be custom class " + setting.getClass().getSimpleName());
Expand All @@ -84,48 +143,56 @@ public <T> QuiltItemSettings customSetting(CustomItemSetting<T> setting, T value
// Overrides of vanilla methods

@Override
@Contract("_->this")
public QuiltItemSettings food(FoodComponent foodComponent) {
super.food(foodComponent);
return this;
}

@Override
@Contract("_->this")
public QuiltItemSettings maxCount(int maxCount) {
super.maxCount(maxCount);
return this;
}

@Override
@Contract("_->this")
public QuiltItemSettings maxDamageIfAbsent(int maxDamage) {
super.maxDamageIfAbsent(maxDamage);
return this;
}

@Override
@Contract("_->this")
public QuiltItemSettings maxDamage(int maxDamage) {
super.maxDamage(maxDamage);
return this;
}

@Override
@Contract("_->this")
public QuiltItemSettings recipeRemainder(Item recipeRemainder) {
super.recipeRemainder(recipeRemainder);
return this;
}

@Override
@Contract("_->this")
public QuiltItemSettings group(ItemGroup group) {
super.group(group);
return this;
}

@Override
@Contract("_->this")
public QuiltItemSettings rarity(Rarity rarity) {
super.rarity(rarity);
return this;
}

@Override
@Contract("->this")
public QuiltItemSettings fireproof() {
super.fireproof();
return this;
Expand Down
@@ -0,0 +1,62 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qsl.item.setting.api;

import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;

import net.minecraft.inventory.Inventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.recipe.Recipe;
import net.minecraft.util.collection.DefaultedList;

import org.quiltmc.qsl.item.setting.impl.CustomItemSettingImpl;

/**
* Allows an item to conditionally specify the recipe remainder.
* The recipe remainder is an {@link ItemStack} instead of an {@link Item}.
* This can be used to allow your item to get damaged instead of
* getting removed when used in crafting.
*
* <p>Recipe remainder providers can be set with {@link QuiltItemSettings#recipeRemainder(RecipeRemainderProvider)}.</p>
*/
@FunctionalInterface
public interface RecipeRemainderProvider {
/**
* An {@link ItemStack} aware version of {@link Item#getRecipeRemainder()}.
*
* @param original the input item stack
* @param recipe the recipe being used
* @return the recipe remainder
*/
@Contract(value = "_, _ -> new")
ItemStack getRecipeRemainder(ItemStack original, @Nullable Recipe<?> recipe);

static DefaultedList<ItemStack> getRemainingStacks(Inventory inventory, Recipe<?> recipe, DefaultedList<ItemStack> defaultedList) {
for (int i = 0; i < defaultedList.size(); ++i) {
ItemStack stack = inventory.getStack(i);
ItemStack remainder = CustomItemSettingImpl.RECIPE_REMAINDER_PROVIDER.get(stack.getItem()).getRecipeRemainder(stack, recipe);

if (!remainder.isEmpty()) {
defaultedList.set(i, remainder);
}
}

return defaultedList;
}
}
Expand Up @@ -6,7 +6,10 @@
* {@link net.minecraft.item.Item.Settings} are ways to add specific traits to {@link net.minecraft.item.Item}s without creating a subclass.
* In addition, these traits are applicable to all {@link net.minecraft.item.Item}s, not just a specific subclass.
* <p>
* This API adds two new settings for items, {@link org.quiltmc.qsl.item.setting.api.QuiltCustomItemSettings#CUSTOM_DAMAGE_HANDLER} and {@link org.quiltmc.qsl.item.setting.api.QuiltCustomItemSettings#EQUIPMENT_SLOT_PROVIDER}.
* This API adds three new settings for items,
* {@link org.quiltmc.qsl.item.setting.api.QuiltItemSettings#customDamage(org.quiltmc.qsl.item.setting.api.CustomDamageHandler)},
* {@link org.quiltmc.qsl.item.setting.api.QuiltItemSettings#equipmentSlot(org.quiltmc.qsl.item.setting.api.EquipmentSlotProvider)}, and
* {@link org.quiltmc.qsl.item.setting.api.QuiltItemSettings#recipeRemainder(org.quiltmc.qsl.item.setting.api.RecipeRemainderProvider)}.
* <p>
* These custom settings make use of the {@link org.quiltmc.qsl.item.setting.api.CustomItemSetting} API provided.
* This API allows mods to specify their own custom settings in an API.
Expand Down
Expand Up @@ -26,15 +26,28 @@
import java.util.function.Supplier;

import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;

import org.quiltmc.qsl.item.setting.api.CustomDamageHandler;
import org.quiltmc.qsl.item.setting.api.CustomItemSetting;
import org.quiltmc.qsl.item.setting.api.EquipmentSlotProvider;
import org.quiltmc.qsl.item.setting.api.RecipeRemainderProvider;

public class CustomItemSettingImpl<T> implements CustomItemSetting<T> {
public static final CustomItemSetting<EquipmentSlotProvider> EQUIPMENT_SLOT_PROVIDER = CustomItemSetting.create(() -> null);
public static final CustomItemSetting<CustomDamageHandler> CUSTOM_DAMAGE_HANDLER = CustomItemSetting.create(() -> null);

@SuppressWarnings("ConstantConditions")
public static final CustomItemSetting<RecipeRemainderProvider> RECIPE_REMAINDER_PROVIDER = new CustomItemSettingImpl<>(() -> (original, recipe) -> original.getItem().hasRecipeRemainder() ? original.getItem().getRecipeRemainder().getDefaultStack() : ItemStack.EMPTY) {
@Override
public void apply(Item.Settings settings, Item item) {
if (item.hasRecipeRemainder()) {
throw new IllegalArgumentException("Item cannot have a standard recipe remainder and a custom recipe remainder");
}
super.apply(settings, item);
}
};

private static final Map<Item.Settings, Collection<CustomItemSettingImpl<?>>> CUSTOM_SETTINGS = new WeakHashMap<>();

private final Map<Item.Settings, T> customSettings = new WeakHashMap<>();
Expand Down
@@ -0,0 +1,70 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qsl.item.setting.impl;

import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.recipe.Recipe;
import net.minecraft.util.ItemScatterer;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

public class RecipeRemainderLogicHandler {
private static boolean tryReturnItemToInventory(ItemStack remainder, DefaultedList<ItemStack> inventory, int slot) {
ItemStack leftovers = inventory.get(slot);
if (leftovers.isEmpty()) {
inventory.set(slot, remainder);
return false;
} else if (ItemStack.canCombine(remainder, leftovers)) {
int toTake = Math.min(leftovers.getMaxCount() - leftovers.getCount(), remainder.getCount());
remainder.decrement(toTake);
leftovers.increment(toTake);
return !remainder.isEmpty();
}
return true;
}

public static ItemStack getRemainder(ItemStack usedItem, Recipe<?> recipe) {
ItemStack remainder = CustomItemSettingImpl.RECIPE_REMAINDER_PROVIDER.get(usedItem.getItem()).getRecipeRemainder(
usedItem,
recipe
);

return remainder.isEmpty() ? ItemStack.EMPTY : remainder;
}

public static void handleRemainderForNonPlayerCraft(ItemStack remainder, DefaultedList<ItemStack> inventory, int slot, World world, BlockPos location) {
if (remainder.isEmpty()) {
return;
}

if (tryReturnItemToInventory(remainder, inventory, slot)) {
ItemScatterer.spawn(world, location.getX(), location.getY(), location.getZ(), remainder);
}
}

public static void handleRemainderForPlayerCraft(ItemStack remainder, DefaultedList<ItemStack> inventory, int slot, PlayerEntity player) {
if (remainder.isEmpty()) {
return;
}

if (tryReturnItemToInventory(remainder, inventory, slot)) {
player.getInventory().offerOrDrop(remainder);
}
}
}