888 lines
40 KiB
Java
888 lines
40 KiB
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 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<EntityStore> {
|
|
@Nonnull
|
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
|
@Nonnull
|
|
private final BlockingQueue<CraftingManager.CraftingJob> 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<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 = 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<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.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 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<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> 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<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> store) {
|
|
LOGGER.at(Level.FINE).log("Cancel Crafting!");
|
|
ObjectList<CraftingManager.CraftingJob> 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<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> 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<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> 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<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> store, @Nonnull CraftingRecipe craftingRecipe, int quantity
|
|
) {
|
|
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 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<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();
|
|
|
|
boolean succeeded = switch (job.inputRemovalType) {
|
|
case NORMAL -> {
|
|
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());
|
|
}
|
|
}
|
|
}
|
|
|
|
yield materialTransactions.succeeded();
|
|
}
|
|
case ORDERED -> {
|
|
ListTransaction<MaterialSlotTransaction> 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<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 CraftingManager.CraftingJob job, int currentItemId
|
|
) {
|
|
Objects.requireNonNull(job, "Job can't be null!");
|
|
List<ItemStack> itemStacks = (List<ItemStack>)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<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();
|
|
|
|
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(recipe, 1);
|
|
}
|
|
|
|
@Nonnull
|
|
private static List<MaterialQuantity> getInputMaterials(@Nonnull MaterialQuantity[] input) {
|
|
return getInputMaterials(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();
|
|
|
|
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<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(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 (itemResourceType.id.equals(slotCraftingMaterial.getResourceTypeId())) {
|
|
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 = 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<EntityStore> ref, ComponentAccessor<EntityStore> 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<MaterialQuantity> 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<MaterialTransaction> materialTransactions = combined.removeMaterials(input);
|
|
if (materialTransactions.succeeded()) {
|
|
List<ItemStack> 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<EntityStore> 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<ItemContainer> getContainersAroundBench(@Nonnull BenchState benchState) {
|
|
List<ItemContainer> containers = new ObjectArrayList();
|
|
World world = benchState.getChunk().getWorld();
|
|
Store<ChunkStore> 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<Ref<ChunkStore>, ChunkStore> blockStateSpatialStructure = 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;
|
|
ObjectListIterator var35 = results.iterator();
|
|
|
|
while (var35.hasNext()) {
|
|
Ref<ChunkStore> ref = (Ref<ChunkStore>)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<ItemContainer> chests = getContainersAroundBench(benchState);
|
|
ItemContainer itemContainer = EmptyItemContainer.INSTANCE;
|
|
if (!chests.isEmpty()) {
|
|
itemContainer = new CombinedItemContainer(chests.stream().map(container -> {
|
|
DelegateItemContainer<ItemContainer> delegate = new DelegateItemContainer<>(container);
|
|
delegate.setGlobalFilter(FilterType.ALLOW_OUTPUT_ONLY);
|
|
return delegate;
|
|
}).toArray(ItemContainer[]::new));
|
|
}
|
|
|
|
Map<String, ItemQuantity> 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<EntityStore> 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<List<ItemStack>> 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;
|
|
}
|
|
}
|