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 it.unimi.dsi.fastutil.objects.ObjectListIterator; 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 { @Nonnull private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); @Nonnull private final BlockingQueue queuedCraftingJobs = new LinkedBlockingQueue<>(); @Nullable private CraftingManager.BenchUpgradingJob upgradingJob; private int x; private int y; private int z; @Nullable private BlockType blockType; @Nonnull public static ComponentType 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 ref, @Nonnull Store 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 ref, @Nonnull ComponentAccessor 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 = store.getExternalData().getWorld(); Player playerComponent = store.getComponent(ref, Player.getComponentType()); assert playerComponent != null; if (playerComponent.getGameMode() != GameMode.Creative && !removeInputFromInventory(itemContainer, recipe, quantity)) { PlayerRef playerRefComponent = 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 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.getAssetMap().getAsset(itemId); return item != null ? item.getTranslationKey() : null; } public boolean queueCraft( @Nonnull Ref ref, @Nonnull ComponentAccessor store, @Nonnull CraftingWindow window, int transactionId, @Nonnull CraftingRecipe recipe, int quantity, @Nonnull ItemContainer inputItemContainer, @Nonnull CraftingManager.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 CraftingManager.CraftingJob(window, transactionId, recipe, quantity, recipeTime, inputItemContainer, inputRemovalType)); return true; } } } public void tick(@Nonnull Ref ref, @Nonnull ComponentAccessor store, float dt) { if (this.upgradingJob != null) { if (dt > 0.0F) { this.upgradingJob.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 = store.getComponent(ref, Player.getComponentType()); assert playerComponent != null; PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); assert playerRefComponent != null; while (dt > 0.0F && !this.queuedCraftingJobs.isEmpty()) { CraftingManager.CraftingJob currentJob = 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! " + currentJob); } giveOutput(ref, store, currentJob, currentCompletedItemId); } if (this.queuedCraftingJobs.isEmpty()) { currentJob.window.setBlockInteractionState("default", store.getExternalData().getWorld(), 6); } } } } } } public boolean cancelAllCrafting(@Nonnull Ref ref, @Nonnull ComponentAccessor store) { LOGGER.at(Level.FINE).log("Cancel Crafting!"); ObjectList oldJobs = new ObjectArrayList(this.queuedCraftingJobs.size()); this.queuedCraftingJobs.drainTo(oldJobs); if (!oldJobs.isEmpty()) { CraftingManager.CraftingJob currentJob = (CraftingManager.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 ref, @Nonnull ComponentAccessor store, @Nonnull CraftingRecipe recipe) { Player playerComponent = 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 = 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)) { CraftingManager.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 ref, @Nonnull ComponentAccessor store, @Nonnull CraftingManager.CraftingJob job, int currentItemId ) { job.removedItems.remove(currentItemId); String recipeId = job.recipe.getId(); CraftingRecipe recipeAsset = CraftingRecipe.getAssetMap().getAsset(recipeId); if (recipeAsset == null) { throw new RuntimeException("A non-existent item ID was provided! " + recipeId); } else { giveOutput(ref, store, recipeAsset, 1); } } private static void giveOutput( @Nonnull Ref ref, @Nonnull ComponentAccessor store, @Nonnull CraftingRecipe craftingRecipe, int quantity ) { Player player = store.getComponent(ref, Player.getComponentType()); List itemStacks = getOutputItemStacks(craftingRecipe, quantity); SimpleItemContainer.addOrDropItemStacks(store, ref, player.getInventory().getCombinedArmorHotbarStorage(), itemStacks); } private static boolean removeInputFromInventory(@Nonnull CraftingManager.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 materialsToRemove = getInputMaterials(craftingRecipe); if (materialsToRemove.isEmpty()) { return true; } else { LOGGER.at(Level.FINEST).log("Removing Materials: %s - %s", job, materialsToRemove); ObjectList itemStackList = new ObjectArrayList(); boolean succeeded = switch (job.inputRemovalType) { case NORMAL -> { ListTransaction 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()); } } } yield materialTransactions.succeeded(); } case ORDERED -> { ListTransaction materialTransactions = job.inputItemContainer .removeMaterialsOrdered(materialsToRemove, true, true, true); for (MaterialSlotTransaction transaction : materialTransactions.getList()) { if (!ItemStack.isEmpty(transaction.getOutput())) { itemStackList.add(transaction.getOutput()); } } yield materialTransactions.succeeded(); } default -> throw new IllegalArgumentException("Unknown enum: " + 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 materialsToRemove = getInputMaterials(craftingRecipe, quantity); if (materialsToRemove.isEmpty()) { return true; } else { LOGGER.at(Level.FINEST).log("Removing Materials: %s - %s", craftingRecipe, materialsToRemove); ListTransaction materialTransactions = itemContainer.removeMaterials(materialsToRemove, true, true, true); return materialTransactions.succeeded(); } } private static void refundInputToInventory( @Nonnull Ref ref, @Nonnull ComponentAccessor store, @Nonnull CraftingManager.CraftingJob job, int currentItemId ) { Objects.requireNonNull(job, "Job can't be null!"); List itemStacks = (List)job.removedItems.get(currentItemId); if (itemStacks != null) { Player player = store.getComponent(ref, Player.getComponentType()); SimpleItemContainer.addOrDropItemStacks(store, ref, player.getInventory().getCombinedHotbarFirst(), itemStacks); } } @Nonnull public static List getOutputItemStacks(@Nonnull CraftingRecipe recipe) { return getOutputItemStacks(recipe, 1); } @Nonnull public static List getOutputItemStacks(@Nonnull CraftingRecipe recipe, int quantity) { Objects.requireNonNull(recipe); MaterialQuantity[] output = recipe.getOutputs(); if (output == null) { return List.of(); } else { ObjectList outputItemStacks = new ObjectArrayList(); 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 getInputMaterials(@Nonnull CraftingRecipe recipe) { return getInputMaterials(recipe, 1); } @Nonnull private static List getInputMaterials(@Nonnull MaterialQuantity[] input) { return getInputMaterials(input, 1); } @Nonnull public static List getInputMaterials(@Nonnull CraftingRecipe recipe, int quantity) { Objects.requireNonNull(recipe); return recipe.getInput() == null ? Collections.emptyList() : getInputMaterials(recipe.getInput(), quantity); } @Nonnull private static List getInputMaterials(@Nonnull MaterialQuantity[] input, int quantity) { ObjectList materials = new ObjectArrayList(); 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, 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 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(storageSlotIndex); } } return inventoryHints; } public static boolean matchesAnyRecipe(@Nonnull List 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 (itemResourceType.id.equals(slotCraftingMaterial.getResourceTypeId())) { return true; } } } } } return false; } public boolean startTierUpgrade(Ref ref, ComponentAccessor 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 input = getInputMaterials(requirements.getInput()); if (input.isEmpty()) { return false; } else { Player player = store.getComponent(ref, Player.getComponentType()); if (player.getGameMode() != GameMode.Creative) { CombinedItemContainer combined = new CombinedItemContainer( player.getInventory().getCombinedBackpackStorageHotbar(), window.getExtraResourcesSection().getItemContainer() ); if (!combined.canRemoveMaterials(input)) { return false; } } this.upgradingJob = new CraftingManager.BenchUpgradingJob(window, requirements.getTimeSeconds()); this.cancelAllCrafting(ref, store); return true; } } } } private int finishTierUpgrade(Ref ref, ComponentAccessor store) { if (this.upgradingJob == null) { return 0; } else { BlockState state = 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 input = getInputMaterials(requirements.getInput()); if (input.isEmpty()) { return benchState.getTierLevel(); } else { Player player = store.getComponent(ref, Player.getComponentType()); boolean canUpgrade = player.getGameMode() == GameMode.Creative; if (!canUpgrade) { CombinedItemContainer combined = new CombinedItemContainer( player.getInventory().getCombinedBackpackStorageHotbar(), this.upgradingJob.window.getExtraResourcesSection().getItemContainer() ); combined = new CombinedItemContainer(combined, this.upgradingJob.window.getExtraResourcesSection().getItemContainer()); ListTransaction materialTransactions = combined.removeMaterials(input); if (materialTransactions.succeeded()) { List consumed = new ObjectArrayList(); 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, this.x + 0.5, this.y + 0.5, 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 store) { BlockState state = store.getExternalData().getWorld().getState(this.x, this.y, this.z, true); return state instanceof BenchState ? ((BenchState)state).getTierLevel() : 0; } protected static List getContainersAroundBench(@Nonnull BenchState benchState) { List containers = new ObjectArrayList(); World world = benchState.getChunk().getWorld(); Store store = world.getChunkStore().getStore(); int limit = world.getGameplayConfig().getCraftingConfig().getBenchMaterialChestLimit(); double horizontalRadius = world.getGameplayConfig().getCraftingConfig().getBenchMaterialHorizontalChestSearchRadius(); double verticalRadius = world.getGameplayConfig().getCraftingConfig().getBenchMaterialVerticalChestSearchRadius(); Vector3d blockPos = benchState.getBlockPosition().toVector3d(); BlockBoundingBoxes hitboxAsset = 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, ChunkStore> blockStateSpatialStructure = store.getResource(BlockStateModule.get().getItemContainerSpatialResourceType()); ObjectList> 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; ObjectListIterator var35 = results.iterator(); while (var35.hasNext()) { Ref ref = (Ref)var35.next(); if (BlockState.getBlockState(ref, ref.getStore()) instanceof ItemContainerState chest) { 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 chests = getContainersAroundBench(benchState); ItemContainer itemContainer = EmptyItemContainer.INSTANCE; if (!chests.isEmpty()) { itemContainer = new CombinedItemContainer(chests.stream().map(container -> { DelegateItemContainer delegate = new DelegateItemContainer<>(container); delegate.setGlobalFilter(FilterType.ALLOW_OUTPUT_ONLY); return delegate; }).toArray(ItemContainer[]::new)); } Map materials = new Object2ObjectOpenHashMap(); for (ItemContainer chest : chests) { chest.forEach((i, itemStack) -> { if (CraftingPlugin.isValidUpgradeMaterialForBench(benchState, itemStack) || CraftingPlugin.isValidCraftingMaterialForBench(benchState, itemStack)) { ItemQuantity var10000 = materials.computeIfAbsent(itemStack.getItemId(), k -> new ItemQuantity(itemStack.getItemId(), 0)); var10000.quantity = var10000.quantity + itemStack.getQuantity(); } }); } extraResourcesSection.setItemContainer(itemContainer); extraResourcesSection.setExtraMaterials(materials.values().toArray(new ItemQuantity[0])); extraResourcesSection.setValid(true); } @Nonnull @Override public String toString() { return "CraftingManager{queuedCraftingJobs=" + this.queuedCraftingJobs + ", x=" + this.x + ", y=" + this.y + ", z=" + this.z + ", blockType=" + this.blockType + "}"; } @Nonnull @Override public Component clone() { return new CraftingManager(this); } 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; } @Override public String toString() { return "BenchUpgradingJob{window=" + this.window + ", timeSeconds=" + this.timeSeconds + "}"; } public float computeLoadingPercent() { return this.timeSeconds <= 0.0F ? 1.0F : Math.min(this.timeSecondsCompleted / this.timeSeconds, 1.0F); } } 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 CraftingManager.InputRemovalType inputRemovalType; @Nonnull private final Int2ObjectMap> removedItems = new Int2ObjectOpenHashMap(); 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 CraftingManager.InputRemovalType inputRemovalType ) { this.window = window; this.transactionId = transactionId; this.recipe = recipe; this.quantity = quantity; this.timeSeconds = timeSeconds; this.inputItemContainer = inputItemContainer; this.inputRemovalType = inputRemovalType; } @Nonnull @Override public String toString() { return "CraftingJob{window=" + this.window + ", transactionId=" + this.transactionId + ", recipe=" + this.recipe + ", quantity=" + this.quantity + ", timeSeconds=" + this.timeSeconds + ", inputItemContainer=" + this.inputItemContainer + ", inputRemovalType=" + this.inputRemovalType + ", removedItems=" + this.removedItems + ", quantityStarted=" + this.quantityStarted + ", quantityCompleted=" + this.quantityCompleted + ", timeSecondsCompleted=" + this.timeSecondsCompleted + "}"; } } public static enum InputRemovalType { NORMAL, ORDERED; } }