CraftingManager.java
package com.hypixel.hytale.builtin.crafting.component;
import com.google.gson.JsonArray;
import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin;
import com.hypixel.hytale.builtin.crafting.CraftingPlugin;
import com.hypixel.hytale.builtin.crafting.state.BenchState;
import com.hypixel.hytale.builtin.crafting.window.BenchWindow;
import com.hypixel.hytale.builtin.crafting.window.CraftingWindow;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.event.IEventDispatcher;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.protocol.BenchRequirement;
import com.hypixel.hytale.protocol.BenchType;
import com.hypixel.hytale.protocol.GameMode;
import com.hypixel.hytale.protocol.ItemQuantity;
import com.hypixel.hytale.protocol.ItemResourceType;
import com.hypixel.hytale.protocol.SoundCategory;
import com.hypixel.hytale.protocol.packets.interface_.NotificationStyle;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.Bench;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.BenchTierLevel;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.BenchUpgradeRequirement;
import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe;
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData;
import com.hypixel.hytale.server.core.entity.entities.player.windows.MaterialExtraResourcesSection;
import com.hypixel.hytale.server.core.event.events.ecs.CraftRecipeEvent;
import com.hypixel.hytale.server.core.event.events.player.PlayerCraftEvent;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.core.inventory.MaterialQuantity;
import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer;
import com.hypixel.hytale.server.core.inventory.container.DelegateItemContainer;
import com.hypixel.hytale.server.core.inventory.container.EmptyItemContainer;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer;
import com.hypixel.hytale.server.core.inventory.container.filter.FilterType;
import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction;
import com.hypixel.hytale.server.core.inventory.transaction.MaterialSlotTransaction;
import com.hypixel.hytale.server.core.inventory.transaction.MaterialTransaction;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.SoundUtil;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.meta.BlockState;
import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule;
import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerState;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.util.NotificationUtil;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bson.BsonDocument;
public class CraftingManager implements Component<EntityStore> {
@Nonnull
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
@Nonnull
private final BlockingQueue<CraftingJob> queuedCraftingJobs = new LinkedBlockingQueue();
@Nullable
private BenchUpgradingJob upgradingJob;
private int x;
private int y;
private int z;
@Nullable
private BlockType blockType;
@Nonnull
public static ComponentType<EntityStore, CraftingManager> getComponentType() {
return CraftingPlugin.get().getCraftingManagerComponentType();
}
public CraftingManager() {
}
private CraftingManager(@Nonnull CraftingManager other) {
this.x = other.x;
this.y = other.y;
this.z = other.z;
this.blockType = other.blockType;
this.queuedCraftingJobs.addAll(other.queuedCraftingJobs);
this.upgradingJob = other.upgradingJob;
}
public boolean hasBenchSet() {
return this.blockType != null;
}
public void setBench(int x, int y, int z, @Nonnull BlockType blockType) {
Bench bench = blockType.getBench();
Objects.requireNonNull(bench, "blockType isn't a bench!");
if (bench.getType() != BenchType.Crafting && bench.getType() != BenchType.DiagramCrafting && bench.getType() != BenchType.StructuralCrafting && bench.getType() != BenchType.Processing) {
throw new IllegalArgumentException("blockType isn't a crafting bench!");
} else if (this.blockType != null) {
throw new IllegalArgumentException("Bench blockType is already set! Must be cleared (close UI).");
} else if (!this.queuedCraftingJobs.isEmpty()) {
throw new IllegalArgumentException("Queue already has jobs!");
} else if (this.upgradingJob != null) {
throw new IllegalArgumentException("Upgrading job is already set!");
} else {
this.x = x;
this.y = y;
this.z = z;
this.blockType = blockType;
}
}
public boolean clearBench(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store) {
boolean result = this.cancelAllCrafting(ref, store);
this.x = 0;
this.y = 0;
this.z = 0;
this.blockType = null;
this.upgradingJob = null;
return result;
}
public boolean craftItem(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> store, @Nonnull CraftingRecipe recipe, int quantity, @Nonnull ItemContainer itemContainer) {
if (this.upgradingJob != null) {
return false;
} else {
Objects.requireNonNull(recipe, "Recipe can't be null");
CraftRecipeEvent.Pre preEvent = new CraftRecipeEvent.Pre(recipe, quantity);
store.invoke(ref, preEvent);
if (preEvent.isCancelled()) {
return false;
} else if (!this.isValidBenchForRecipe(ref, store, recipe)) {
return false;
} else {
World world = ((EntityStore)store.getExternalData()).getWorld();
Player playerComponent = (Player)store.getComponent(ref, Player.getComponentType());
assert playerComponent != null;
if (playerComponent.getGameMode() != GameMode.Creative && !removeInputFromInventory(itemContainer, recipe, quantity)) {
PlayerRef playerRefComponent = (PlayerRef)store.getComponent(ref, PlayerRef.getComponentType());
assert playerRefComponent != null;
String translationKey = getRecipeOutputTranslationKey(recipe);
NotificationUtil.sendNotification(playerRefComponent.getPacketHandler(), Message.translation("server.general.crafting.missingIngredient").param("item", Message.translation(translationKey)), NotificationStyle.Danger);
LOGGER.at(Level.FINE).log("Missing items required to craft the item: %s", recipe);
return false;
} else {
CraftRecipeEvent.Post postEvent = new CraftRecipeEvent.Post(recipe, quantity);
store.invoke(ref, postEvent);
if (postEvent.isCancelled()) {
return true;
} else {
giveOutput(ref, store, recipe, quantity);
IEventDispatcher<PlayerCraftEvent, PlayerCraftEvent> dispatcher = HytaleServer.get().getEventBus().dispatchFor(PlayerCraftEvent.class, world.getName());
if (dispatcher.hasListener()) {
dispatcher.dispatch(new PlayerCraftEvent(ref, playerComponent, recipe, quantity));
}
return true;
}
}
}
}
}
private static String getRecipeOutputTranslationKey(CraftingRecipe recipe) {
String itemId = recipe.getPrimaryOutput().getItemId();
Item item = (Item)Item.getAssetMap().getAsset(itemId);
return item != null ? item.getTranslationKey() : null;
}
public boolean queueCraft(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> store, @Nonnull CraftingWindow window, int transactionId, @Nonnull CraftingRecipe recipe, int quantity, @Nonnull ItemContainer inputItemContainer, @Nonnull InputRemovalType inputRemovalType) {
if (this.upgradingJob != null) {
return false;
} else {
Objects.requireNonNull(recipe, "Recipe can't be null");
if (!this.isValidBenchForRecipe(ref, store, recipe)) {
return false;
} else {
float recipeTime = recipe.getTimeSeconds();
if (recipeTime > 0.0F) {
int level = this.getBenchTierLevel(store);
if (level > 1) {
BenchTierLevel tierLevelData = this.getBenchTierLevelData(level);
if (tierLevelData != null) {
recipeTime -= recipeTime * tierLevelData.getCraftingTimeReductionModifier();
}
}
}
this.queuedCraftingJobs.offer(new CraftingJob(window, transactionId, recipe, quantity, recipeTime, inputItemContainer, inputRemovalType));
return true;
}
}
}
public void tick(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> store, float dt) {
if (this.upgradingJob != null) {
if (dt > 0.0F) {
BenchUpgradingJob var10000 = this.upgradingJob;
var10000.timeSecondsCompleted += dt;
}
this.upgradingJob.window.updateBenchUpgradeJob(this.upgradingJob.computeLoadingPercent());
if (this.upgradingJob.timeSecondsCompleted >= this.upgradingJob.timeSeconds) {
this.upgradingJob.window.updateBenchTierLevel(this.finishTierUpgrade(ref, store));
this.upgradingJob = null;
}
} else {
Player playerComponent = (Player)store.getComponent(ref, Player.getComponentType());
assert playerComponent != null;
PlayerRef playerRefComponent = (PlayerRef)store.getComponent(ref, PlayerRef.getComponentType());
assert playerRefComponent != null;
while(dt > 0.0F && !this.queuedCraftingJobs.isEmpty()) {
CraftingJob currentJob = (CraftingJob)this.queuedCraftingJobs.peek();
boolean isCreativeMode = playerComponent.getGameMode() == GameMode.Creative;
if (currentJob != null && currentJob.quantityStarted < currentJob.quantity && currentJob.quantityStarted <= currentJob.quantityCompleted) {
LOGGER.at(Level.FINE).log("Removing Items for next quantity: %s", currentJob);
int currentItemId = currentJob.quantityStarted++;
if (!isCreativeMode && !removeInputFromInventory(currentJob, currentItemId)) {
String translationKey = getRecipeOutputTranslationKey(currentJob.recipe);
NotificationUtil.sendNotification(playerRefComponent.getPacketHandler(), Message.translation("server.general.crafting.missingIngredient").param("item", Message.translation(translationKey)), NotificationStyle.Danger);
LOGGER.at(Level.FINE).log("Missing items required to craft the item: %s", currentJob);
currentJob = null;
this.queuedCraftingJobs.poll();
}
if (!isCreativeMode && currentJob != null && currentJob.quantityStarted < currentJob.quantity && currentJob.quantityStarted <= currentJob.quantityCompleted) {
NotificationUtil.sendNotification(playerRefComponent.getPacketHandler(), Message.translation("server.general.crafting.failedTakingCorrectQuantity"), NotificationStyle.Danger);
LOGGER.at(Level.SEVERE).log("Failed to remove the correct quantity of input, removing crafting job %s", currentJob);
currentJob = null;
this.queuedCraftingJobs.poll();
}
}
if (currentJob != null) {
currentJob.timeSecondsCompleted += dt;
float percent = currentJob.timeSeconds <= 0.0F ? 1.0F : currentJob.timeSecondsCompleted / currentJob.timeSeconds;
if (percent > 1.0F) {
percent = 1.0F;
}
currentJob.window.updateCraftingJob(percent);
LOGGER.at(Level.FINEST).log("Update time: %s", currentJob);
dt = 0.0F;
if (currentJob.timeSecondsCompleted >= currentJob.timeSeconds) {
dt = currentJob.timeSecondsCompleted - currentJob.timeSeconds;
int currentCompletedItemId = currentJob.quantityCompleted++;
currentJob.timeSecondsCompleted = 0.0F;
LOGGER.at(Level.FINE).log("Crafted 1 Quantity: %s", currentJob);
if (currentJob.quantityCompleted == currentJob.quantity) {
giveOutput(ref, store, currentJob, currentCompletedItemId);
LOGGER.at(Level.FINE).log("Crafting Finished: %s", currentJob);
this.queuedCraftingJobs.poll();
} else {
if (currentJob.quantityCompleted > currentJob.quantity) {
this.queuedCraftingJobs.poll();
throw new RuntimeException("QuantityCompleted is greater than the Quality! " + String.valueOf(currentJob));
}
giveOutput(ref, store, currentJob, currentCompletedItemId);
}
if (this.queuedCraftingJobs.isEmpty()) {
currentJob.window.setBlockInteractionState("default", ((EntityStore)store.getExternalData()).getWorld(), 6);
}
}
}
}
}
}
public boolean cancelAllCrafting(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> store) {
LOGGER.at(Level.FINE).log("Cancel Crafting!");
ObjectList<CraftingJob> oldJobs = new ObjectArrayList<CraftingJob>(this.queuedCraftingJobs.size());
this.queuedCraftingJobs.drainTo(oldJobs);
if (!oldJobs.isEmpty()) {
CraftingJob currentJob = (CraftingJob)oldJobs.getFirst();
LOGGER.at(Level.FINE).log("Refunding Items for: %s", currentJob);
refundInputToInventory(ref, store, currentJob, currentJob.quantityStarted - 1);
return true;
} else {
return false;
}
}
private boolean isValidBenchForRecipe(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> store, @Nonnull CraftingRecipe recipe) {
Player playerComponent = (Player)store.getComponent(ref, Player.getComponentType());
assert playerComponent != null;
PlayerConfigData playerConfigData = playerComponent.getPlayerConfigData();
String primaryOutputItemId = recipe.getPrimaryOutput() != null ? recipe.getPrimaryOutput().getItemId() : null;
if (!recipe.isKnowledgeRequired() || primaryOutputItemId != null && playerConfigData.getKnownRecipes().contains(primaryOutputItemId)) {
World world = ((EntityStore)store.getExternalData()).getWorld();
if (recipe.getRequiredMemoriesLevel() > 1 && MemoriesPlugin.get().getMemoriesLevel(world.getGameplayConfig()) < recipe.getRequiredMemoriesLevel()) {
LOGGER.at(Level.WARNING).log("Attempted to craft %s but doesn't have the required world memories level!", recipe.getId());
return false;
} else {
BenchType benchType = this.blockType != null ? this.blockType.getBench().getType() : BenchType.Crafting;
String benchName = this.blockType != null ? this.blockType.getBench().getId() : "Fieldcraft";
boolean meetsRequirements = false;
BlockState state = world.getState(this.x, this.y, this.z, true);
int benchTierLevel = state instanceof BenchState ? ((BenchState)state).getTierLevel() : 0;
BenchRequirement[] requirements = recipe.getBenchRequirement();
if (requirements != null) {
for(BenchRequirement benchRequirement : requirements) {
if (benchRequirement.type == benchType && benchName.equals(benchRequirement.id) && benchRequirement.requiredTierLevel <= benchTierLevel) {
meetsRequirements = true;
break;
}
}
}
if (!meetsRequirements) {
LOGGER.at(Level.WARNING).log("Attempted to craft %s using %s, %s but requires bench %s but a bench is NOT set!", recipe.getId(), benchType, benchName, requirements);
return false;
} else if (benchType == BenchType.Crafting && !"Fieldcraft".equals(benchName)) {
CraftingJob craftingJob = (CraftingJob)this.queuedCraftingJobs.peek();
return craftingJob == null || craftingJob.recipe.getId().equals(recipe.getId());
} else {
return true;
}
}
} else {
LOGGER.at(Level.WARNING).log("%s - Attempted to craft %s but doesn't know the recipe!", recipe.getId());
return false;
}
}
private static void giveOutput(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> store, @Nonnull CraftingJob job, int currentItemId) {
job.removedItems.remove(currentItemId);
String recipeId = job.recipe.getId();
CraftingRecipe recipeAsset = (CraftingRecipe)CraftingRecipe.getAssetMap().getAsset(recipeId);
if (recipeAsset == null) {
throw new RuntimeException("A non-existent item ID was provided! " + recipeId);
} else {
giveOutput(ref, store, (CraftingRecipe)recipeAsset, 1);
}
}
private static void giveOutput(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> store, @Nonnull CraftingRecipe craftingRecipe, int quantity) {
Player player = (Player)store.getComponent(ref, Player.getComponentType());
List<ItemStack> itemStacks = getOutputItemStacks(craftingRecipe, quantity);
SimpleItemContainer.addOrDropItemStacks(store, ref, player.getInventory().getCombinedArmorHotbarStorage(), itemStacks);
}
private static boolean removeInputFromInventory(@Nonnull CraftingJob job, int currentItemId) {
Objects.requireNonNull(job, "Job can't be null!");
CraftingRecipe craftingRecipe = job.recipe;
Objects.requireNonNull(craftingRecipe, "CraftingRecipe can't be null!");
List<MaterialQuantity> materialsToRemove = getInputMaterials(craftingRecipe);
if (materialsToRemove.isEmpty()) {
return true;
} else {
LOGGER.at(Level.FINEST).log("Removing Materials: %s - %s", job, materialsToRemove);
ObjectList<ItemStack> itemStackList = new ObjectArrayList<ItemStack>();
boolean succeeded;
switch (job.inputRemovalType.ordinal()) {
case 0:
ListTransaction<MaterialTransaction> materialTransactions = job.inputItemContainer.removeMaterials(materialsToRemove, true, true, true);
for(MaterialTransaction transaction : materialTransactions.getList()) {
for(MaterialSlotTransaction slotTransaction : transaction.getList()) {
if (!ItemStack.isEmpty(slotTransaction.getOutput())) {
itemStackList.add(slotTransaction.getOutput());
}
}
}
succeeded = materialTransactions.succeeded();
break;
case 1:
ListTransaction<MaterialSlotTransaction> materialTransactions = job.inputItemContainer.removeMaterialsOrdered(materialsToRemove, true, true, true);
for(MaterialSlotTransaction transaction : materialTransactions.getList()) {
if (!ItemStack.isEmpty(transaction.getOutput())) {
itemStackList.add(transaction.getOutput());
}
}
succeeded = materialTransactions.succeeded();
break;
default:
throw new IllegalArgumentException("Unknown enum: " + String.valueOf(job.inputRemovalType));
}
job.removedItems.put(currentItemId, itemStackList);
job.window.invalidateExtraResources();
return succeeded;
}
}
private static boolean removeInputFromInventory(@Nonnull ItemContainer itemContainer, @Nonnull CraftingRecipe craftingRecipe, int quantity) {
List<MaterialQuantity> materialsToRemove = getInputMaterials(craftingRecipe, quantity);
if (materialsToRemove.isEmpty()) {
return true;
} else {
LOGGER.at(Level.FINEST).log("Removing Materials: %s - %s", craftingRecipe, materialsToRemove);
ListTransaction<MaterialTransaction> materialTransactions = itemContainer.removeMaterials(materialsToRemove, true, true, true);
return materialTransactions.succeeded();
}
}
private static void refundInputToInventory(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> store, @Nonnull CraftingJob job, int currentItemId) {
Objects.requireNonNull(job, "Job can't be null!");
List<ItemStack> itemStacks = (List)job.removedItems.get(currentItemId);
if (itemStacks != null) {
Player player = (Player)store.getComponent(ref, Player.getComponentType());
SimpleItemContainer.addOrDropItemStacks(store, ref, player.getInventory().getCombinedHotbarFirst(), itemStacks);
}
}
@Nonnull
public static List<ItemStack> getOutputItemStacks(@Nonnull CraftingRecipe recipe) {
return getOutputItemStacks(recipe, 1);
}
@Nonnull
public static List<ItemStack> getOutputItemStacks(@Nonnull CraftingRecipe recipe, int quantity) {
Objects.requireNonNull(recipe);
MaterialQuantity[] output = recipe.getOutputs();
if (output == null) {
return List.of();
} else {
ObjectList<ItemStack> outputItemStacks = new ObjectArrayList<ItemStack>();
for(MaterialQuantity outputMaterial : output) {
outputItemStacks.add(getOutputItemStack(outputMaterial, quantity));
}
return outputItemStacks;
}
}
@Nonnull
public static ItemStack getOutputItemStack(@Nonnull MaterialQuantity outputMaterial, @Nonnull String id) {
return getOutputItemStack(outputMaterial, 1);
}
@Nonnull
public static ItemStack getOutputItemStack(@Nonnull MaterialQuantity outputMaterial, int quantity) {
String itemId = outputMaterial.getItemId();
int materialQuantity = outputMaterial.getQuantity() <= 0 ? 1 : outputMaterial.getQuantity();
return new ItemStack(itemId, materialQuantity * quantity, outputMaterial.getMetadata());
}
@Nonnull
public static List<MaterialQuantity> getInputMaterials(@Nonnull CraftingRecipe recipe) {
return getInputMaterials((CraftingRecipe)recipe, 1);
}
@Nonnull
private static List<MaterialQuantity> getInputMaterials(@Nonnull MaterialQuantity[] input) {
return getInputMaterials((MaterialQuantity[])input, 1);
}
@Nonnull
public static List<MaterialQuantity> getInputMaterials(@Nonnull CraftingRecipe recipe, int quantity) {
Objects.requireNonNull(recipe);
return recipe.getInput() == null ? Collections.emptyList() : getInputMaterials(recipe.getInput(), quantity);
}
@Nonnull
private static List<MaterialQuantity> getInputMaterials(@Nonnull MaterialQuantity[] input, int quantity) {
ObjectList<MaterialQuantity> materials = new ObjectArrayList<MaterialQuantity>();
for(MaterialQuantity craftingMaterial : input) {
String itemId = craftingMaterial.getItemId();
String resourceTypeId = craftingMaterial.getResourceTypeId();
int materialQuantity = craftingMaterial.getQuantity();
BsonDocument metadata = craftingMaterial.getMetadata();
materials.add(new MaterialQuantity(itemId, resourceTypeId, (String)null, materialQuantity * quantity, metadata));
}
return materials;
}
public static boolean matches(@Nonnull MaterialQuantity craftingMaterial, @Nonnull ItemStack itemStack) {
String itemId = craftingMaterial.getItemId();
if (itemId != null) {
return itemId.equals(itemStack.getItemId());
} else {
String resourceTypeId = craftingMaterial.getResourceTypeId();
if (resourceTypeId != null && itemStack.getItem().getResourceTypes() != null) {
for(ItemResourceType itemResourceType : itemStack.getItem().getResourceTypes()) {
if (itemResourceType.id.equals(resourceTypeId)) {
return true;
}
}
}
return false;
}
}
@Nonnull
public static JsonArray generateInventoryHints(@Nonnull List<CraftingRecipe> recipes, int inputSlotIndex, @Nonnull ItemContainer container) {
JsonArray inventoryHints = new JsonArray();
short storageSlotIndex = 0;
for(short bound = container.getCapacity(); storageSlotIndex < bound; ++storageSlotIndex) {
ItemStack itemStack = container.getItemStack(storageSlotIndex);
if (itemStack != null && !itemStack.isEmpty() && matchesAnyRecipe(recipes, inputSlotIndex, itemStack)) {
inventoryHints.add((Number)storageSlotIndex);
}
}
return inventoryHints;
}
public static boolean matchesAnyRecipe(@Nonnull List<CraftingRecipe> recipes, int inputSlotIndex, @Nonnull ItemStack slotItemStack) {
for(CraftingRecipe recipe : recipes) {
MaterialQuantity[] input = recipe.getInput();
if (inputSlotIndex < input.length) {
MaterialQuantity slotCraftingMaterial = input[inputSlotIndex];
if (slotCraftingMaterial.getItemId() != null && slotCraftingMaterial.getItemId().equals(slotItemStack.getItemId())) {
return true;
}
if (slotCraftingMaterial.getResourceTypeId() != null && slotItemStack.getItem().getResourceTypes() != null) {
for(ItemResourceType itemResourceType : slotItemStack.getItem().getResourceTypes()) {
if (slotCraftingMaterial.getResourceTypeId().equals(itemResourceType.id)) {
return true;
}
}
}
}
}
return false;
}
public boolean startTierUpgrade(Ref<EntityStore> ref, ComponentAccessor<EntityStore> store, @Nonnull BenchWindow window) {
if (this.upgradingJob != null) {
return false;
} else {
BenchUpgradeRequirement requirements = this.getBenchUpgradeRequierement(this.getBenchTierLevel(store));
if (requirements == null) {
return false;
} else {
List<MaterialQuantity> input = getInputMaterials(requirements.getInput());
if (input.isEmpty()) {
return false;
} else {
Player player = (Player)store.getComponent(ref, Player.getComponentType());
if (player.getGameMode() != GameMode.Creative) {
CombinedItemContainer combined = new CombinedItemContainer(new ItemContainer[]{player.getInventory().getCombinedBackpackStorageHotbar(), window.getExtraResourcesSection().getItemContainer()});
if (!combined.canRemoveMaterials(input)) {
return false;
}
}
this.upgradingJob = new BenchUpgradingJob(window, requirements.getTimeSeconds());
this.cancelAllCrafting(ref, store);
return true;
}
}
}
}
private int finishTierUpgrade(Ref<EntityStore> ref, ComponentAccessor<EntityStore> store) {
if (this.upgradingJob == null) {
return 0;
} else {
BlockState state = ((EntityStore)store.getExternalData()).getWorld().getState(this.x, this.y, this.z, true);
BenchState benchState = state instanceof BenchState ? (BenchState)state : null;
if (benchState != null && benchState.getTierLevel() != 0) {
BenchUpgradeRequirement requirements = this.getBenchUpgradeRequierement(benchState.getTierLevel());
if (requirements == null) {
return benchState.getTierLevel();
} else {
List<MaterialQuantity> input = getInputMaterials(requirements.getInput());
if (input.isEmpty()) {
return benchState.getTierLevel();
} else {
Player player = (Player)store.getComponent(ref, Player.getComponentType());
boolean canUpgrade = player.getGameMode() == GameMode.Creative;
if (!canUpgrade) {
CombinedItemContainer combined = new CombinedItemContainer(new ItemContainer[]{player.getInventory().getCombinedBackpackStorageHotbar(), this.upgradingJob.window.getExtraResourcesSection().getItemContainer()});
combined = new CombinedItemContainer(new ItemContainer[]{combined, this.upgradingJob.window.getExtraResourcesSection().getItemContainer()});
ListTransaction<MaterialTransaction> materialTransactions = combined.removeMaterials(input);
if (materialTransactions.succeeded()) {
List<ItemStack> consumed = new ObjectArrayList<ItemStack>();
for(MaterialTransaction transaction : materialTransactions.getList()) {
for(MaterialSlotTransaction matSlot : transaction.getList()) {
consumed.add(matSlot.getOutput());
}
}
benchState.addUpgradeItems(consumed);
canUpgrade = true;
}
}
if (canUpgrade) {
benchState.setTierLevel(benchState.getTierLevel() + 1);
if (benchState.getBench().getBenchUpgradeCompletedSoundEventIndex() != 0) {
SoundUtil.playSoundEvent3d(benchState.getBench().getBenchUpgradeCompletedSoundEventIndex(), SoundCategory.SFX, (double)this.x + 0.5, (double)this.y + 0.5, (double)this.z + 0.5, store);
}
}
return benchState.getTierLevel();
}
}
} else {
return 0;
}
}
}
private BenchTierLevel getBenchTierLevelData(int level) {
if (this.blockType == null) {
return null;
} else {
Bench bench = this.blockType.getBench();
return bench == null ? null : bench.getTierLevel(level);
}
}
private BenchUpgradeRequirement getBenchUpgradeRequierement(int tierLevel) {
BenchTierLevel tierData = this.getBenchTierLevelData(tierLevel);
return tierData == null ? null : tierData.getUpgradeRequirement();
}
private int getBenchTierLevel(ComponentAccessor<EntityStore> store) {
BlockState state = ((EntityStore)store.getExternalData()).getWorld().getState(this.x, this.y, this.z, true);
return state instanceof BenchState ? ((BenchState)state).getTierLevel() : 0;
}
protected static List<ItemContainer> getContainersAroundBench(@Nonnull BenchState benchState) {
List<ItemContainer> containers = new ObjectArrayList<ItemContainer>();
World world = benchState.getChunk().getWorld();
Store<ChunkStore> store = world.getChunkStore().getStore();
int limit = world.getGameplayConfig().getCraftingConfig().getBenchMaterialChestLimit();
double horizontalRadius = (double)world.getGameplayConfig().getCraftingConfig().getBenchMaterialHorizontalChestSearchRadius();
double verticalRadius = (double)world.getGameplayConfig().getCraftingConfig().getBenchMaterialVerticalChestSearchRadius();
Vector3d blockPos = benchState.getBlockPosition().toVector3d();
BlockBoundingBoxes hitboxAsset = (BlockBoundingBoxes)BlockBoundingBoxes.getAssetMap().getAsset(benchState.getBlockType().getHitboxTypeIndex());
BlockBoundingBoxes.RotatedVariantBoxes rotatedHitbox = hitboxAsset.get(benchState.getRotationIndex());
Box boundingBox = rotatedHitbox.getBoundingBox();
double benchWidth = boundingBox.width();
double benchHeight = boundingBox.height();
double benchDepth = boundingBox.depth();
double extraSearchRadius = Math.max(benchWidth, Math.max(benchDepth, benchHeight)) - 1.0;
SpatialResource<Ref<ChunkStore>, ChunkStore> blockStateSpatialStructure = (SpatialResource)store.getResource(BlockStateModule.get().getItemContainerSpatialResourceType());
ObjectList<Ref<ChunkStore>> results = SpatialResource.getThreadLocalReferenceList();
blockStateSpatialStructure.getSpatialStructure().ordered3DAxis(blockPos, horizontalRadius + extraSearchRadius, verticalRadius + extraSearchRadius, horizontalRadius + extraSearchRadius, results);
if (!results.isEmpty()) {
double minX = blockPos.x + boundingBox.min.x - horizontalRadius;
double minY = blockPos.y + boundingBox.min.y - verticalRadius;
double minZ = blockPos.z + boundingBox.min.z - horizontalRadius;
double maxX = blockPos.x + boundingBox.max.x + horizontalRadius;
double maxY = blockPos.y + boundingBox.max.y + verticalRadius;
double maxZ = blockPos.z + boundingBox.max.z + horizontalRadius;
for(Ref<ChunkStore> ref : results) {
BlockState state = BlockState.getBlockState(ref, ref.getStore());
if (state instanceof ItemContainerState) {
ItemContainerState chest = (ItemContainerState)state;
Vector3d chestPos = chest.getCenteredBlockPosition();
if (chestPos.x >= minX && chestPos.x <= maxX && chestPos.y >= minY && chestPos.y <= maxY && chestPos.z >= minZ && chestPos.z <= maxZ) {
containers.add(chest.getItemContainer());
if (containers.size() >= limit) {
break;
}
}
}
}
}
return containers;
}
public static void feedExtraResourcesSection(BenchState benchState, MaterialExtraResourcesSection extraResourcesSection) {
List<ItemContainer> chests = getContainersAroundBench(benchState);
ItemContainer itemContainer = EmptyItemContainer.INSTANCE;
if (!chests.isEmpty()) {
itemContainer = new CombinedItemContainer((ItemContainer[])chests.stream().map((container) -> {
DelegateItemContainer<ItemContainer> delegate = new DelegateItemContainer<ItemContainer>(container);
delegate.setGlobalFilter(FilterType.ALLOW_OUTPUT_ONLY);
return delegate;
}).toArray((x$0) -> new ItemContainer[x$0]));
}
Map<String, ItemQuantity> materials = new Object2ObjectOpenHashMap<String, ItemQuantity>();
for(ItemContainer chest : chests) {
chest.forEach((i, itemStack) -> {
if (CraftingPlugin.isValidUpgradeMaterialForBench(benchState, itemStack) || CraftingPlugin.isValidCraftingMaterialForBench(benchState, itemStack)) {
ItemQuantity var10000 = (ItemQuantity)materials.computeIfAbsent(itemStack.getItemId(), (k) -> new ItemQuantity(itemStack.getItemId(), 0));
var10000.quantity += itemStack.getQuantity();
}
});
}
extraResourcesSection.setItemContainer(itemContainer);
extraResourcesSection.setExtraMaterials((ItemQuantity[])materials.values().toArray(new ItemQuantity[0]));
extraResourcesSection.setValid(true);
}
@Nonnull
public String toString() {
String var10000 = String.valueOf(this.queuedCraftingJobs);
return "CraftingManager{queuedCraftingJobs=" + var10000 + ", x=" + this.x + ", y=" + this.y + ", z=" + this.z + ", blockType=" + String.valueOf(this.blockType) + "}";
}
@Nonnull
public Component<EntityStore> clone() {
return new CraftingManager(this);
}
private static class CraftingJob {
@Nonnull
private final CraftingWindow window;
private final int transactionId;
@Nonnull
private final CraftingRecipe recipe;
private final int quantity;
private final float timeSeconds;
@Nonnull
private final ItemContainer inputItemContainer;
@Nonnull
private final InputRemovalType inputRemovalType;
@Nonnull
private final Int2ObjectMap<List<ItemStack>> removedItems = new Int2ObjectOpenHashMap<List<ItemStack>>();
private int quantityStarted;
private int quantityCompleted;
private float timeSecondsCompleted;
public CraftingJob(@Nonnull CraftingWindow window, int transactionId, @Nonnull CraftingRecipe recipe, int quantity, float timeSeconds, @Nonnull ItemContainer inputItemContainer, @Nonnull InputRemovalType inputRemovalType) {
this.window = window;
this.transactionId = transactionId;
this.recipe = recipe;
this.quantity = quantity;
this.timeSeconds = timeSeconds;
this.inputItemContainer = inputItemContainer;
this.inputRemovalType = inputRemovalType;
}
@Nonnull
public String toString() {
String var10000 = String.valueOf(this.window);
return "CraftingJob{window=" + var10000 + ", transactionId=" + this.transactionId + ", recipe=" + String.valueOf(this.recipe) + ", quantity=" + this.quantity + ", timeSeconds=" + this.timeSeconds + ", inputItemContainer=" + String.valueOf(this.inputItemContainer) + ", inputRemovalType=" + String.valueOf(this.inputRemovalType) + ", removedItems=" + String.valueOf(this.removedItems) + ", quantityStarted=" + this.quantityStarted + ", quantityCompleted=" + this.quantityCompleted + ", timeSecondsCompleted=" + this.timeSecondsCompleted + "}";
}
}
private static class BenchUpgradingJob {
@Nonnull
private final BenchWindow window;
private final float timeSeconds;
private float timeSecondsCompleted;
private float lastSentPercent;
private BenchUpgradingJob(@Nonnull BenchWindow window, float timeSeconds) {
this.window = window;
this.timeSeconds = timeSeconds;
}
public String toString() {
String var10000 = String.valueOf(this.window);
return "BenchUpgradingJob{window=" + var10000 + ", timeSeconds=" + this.timeSeconds + "}";
}
public float computeLoadingPercent() {
return this.timeSeconds <= 0.0F ? 1.0F : Math.min(this.timeSecondsCompleted / this.timeSeconds, 1.0F);
}
}
public static enum InputRemovalType {
NORMAL,
ORDERED;
private InputRemovalType() {
}
}
}