841 lines
38 KiB
Java
841 lines
38 KiB
Java
package com.hypixel.hytale.builtin.crafting.state;
|
|
|
|
import com.google.common.flogger.LazyArgs;
|
|
import com.hypixel.hytale.builtin.crafting.CraftingPlugin;
|
|
import com.hypixel.hytale.builtin.crafting.component.CraftingManager;
|
|
import com.hypixel.hytale.builtin.crafting.window.ProcessingBenchWindow;
|
|
import com.hypixel.hytale.codec.Codec;
|
|
import com.hypixel.hytale.codec.KeyedCodec;
|
|
import com.hypixel.hytale.codec.builder.BuilderCodec;
|
|
import com.hypixel.hytale.component.AddReason;
|
|
import com.hypixel.hytale.component.ArchetypeChunk;
|
|
import com.hypixel.hytale.component.CommandBuffer;
|
|
import com.hypixel.hytale.component.ComponentAccessor;
|
|
import com.hypixel.hytale.component.Holder;
|
|
import com.hypixel.hytale.component.Ref;
|
|
import com.hypixel.hytale.component.Store;
|
|
import com.hypixel.hytale.event.EventPriority;
|
|
import com.hypixel.hytale.logger.HytaleLogger;
|
|
import com.hypixel.hytale.math.util.MathUtil;
|
|
import com.hypixel.hytale.math.vector.Vector3d;
|
|
import com.hypixel.hytale.math.vector.Vector3f;
|
|
import com.hypixel.hytale.math.vector.Vector3i;
|
|
import com.hypixel.hytale.protocol.SoundCategory;
|
|
import com.hypixel.hytale.protocol.Transform;
|
|
import com.hypixel.hytale.protocol.packets.worldmap.MapMarker;
|
|
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.RotationTuple;
|
|
import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.BenchTierLevel;
|
|
import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.ProcessingBench;
|
|
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.windows.WindowManager;
|
|
import com.hypixel.hytale.server.core.inventory.ItemStack;
|
|
import com.hypixel.hytale.server.core.inventory.MaterialQuantity;
|
|
import com.hypixel.hytale.server.core.inventory.ResourceQuantity;
|
|
import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer;
|
|
import com.hypixel.hytale.server.core.inventory.container.InternalContainerUtilMaterial;
|
|
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.TestRemoveItemSlotResult;
|
|
import com.hypixel.hytale.server.core.inventory.container.filter.FilterActionType;
|
|
import com.hypixel.hytale.server.core.inventory.container.filter.FilterType;
|
|
import com.hypixel.hytale.server.core.inventory.container.filter.ResourceFilter;
|
|
import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction;
|
|
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.inventory.transaction.ResourceTransaction;
|
|
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
|
|
import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent;
|
|
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.accessor.BlockAccessor;
|
|
import com.hypixel.hytale.server.core.universe.world.chunk.state.TickableBlockState;
|
|
import com.hypixel.hytale.server.core.universe.world.meta.BlockState;
|
|
import com.hypixel.hytale.server.core.universe.world.meta.state.DestroyableBlockState;
|
|
import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerBlockState;
|
|
import com.hypixel.hytale.server.core.universe.world.meta.state.MarkerBlockState;
|
|
import com.hypixel.hytale.server.core.universe.world.meta.state.PlacedByBlockState;
|
|
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.universe.world.worldmap.WorldMapManager;
|
|
import com.hypixel.hytale.server.core.util.PositionUtil;
|
|
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
import java.util.logging.Level;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
import org.bson.BsonDocument;
|
|
|
|
public class ProcessingBenchState
|
|
extends BenchState
|
|
implements TickableBlockState,
|
|
ItemContainerBlockState,
|
|
DestroyableBlockState,
|
|
MarkerBlockState,
|
|
PlacedByBlockState {
|
|
public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
|
public static final boolean EXACT_RESOURCE_AMOUNTS = true;
|
|
public static final Codec<ProcessingBenchState> CODEC = BuilderCodec.builder(ProcessingBenchState.class, ProcessingBenchState::new, BenchState.CODEC)
|
|
.append(new KeyedCodec<>("InputContainer", ItemContainer.CODEC), (state, o) -> state.inputContainer = o, state -> state.inputContainer)
|
|
.add()
|
|
.append(new KeyedCodec<>("FuelContainer", ItemContainer.CODEC), (state, o) -> state.fuelContainer = o, state -> state.fuelContainer)
|
|
.add()
|
|
.append(new KeyedCodec<>("OutputContainer", ItemContainer.CODEC), (state, o) -> state.outputContainer = o, state -> state.outputContainer)
|
|
.add()
|
|
.append(new KeyedCodec<>("Progress", Codec.DOUBLE), (state, d) -> state.inputProgress = d.floatValue(), state -> (double)state.inputProgress)
|
|
.add()
|
|
.append(new KeyedCodec<>("FuelTime", Codec.DOUBLE), (state, d) -> state.fuelTime = d.floatValue(), state -> (double)state.fuelTime)
|
|
.add()
|
|
.append(new KeyedCodec<>("Active", Codec.BOOLEAN), (state, b) -> state.active = b, state -> state.active)
|
|
.add()
|
|
.append(new KeyedCodec<>("NextExtra", Codec.INTEGER), (state, b) -> state.nextExtra = b, state -> state.nextExtra)
|
|
.add()
|
|
.append(new KeyedCodec<>("Marker", WorldMapManager.MarkerReference.CODEC), (state, o) -> state.marker = o, state -> state.marker)
|
|
.add()
|
|
.append(new KeyedCodec<>("RecipeId", Codec.STRING), (state, o) -> state.recipeId = o, state -> state.recipeId)
|
|
.add()
|
|
.build();
|
|
private static final float EJECT_VELOCITY = 2.0F;
|
|
private static final float EJECT_SPREAD_VELOCITY = 1.0F;
|
|
private static final float EJECT_VERTICAL_VELOCITY = 3.25F;
|
|
public static final String PROCESSING = "Processing";
|
|
public static final String PROCESS_COMPLETED = "ProcessCompleted";
|
|
protected WorldMapManager.MarkerReference marker;
|
|
private final Map<UUID, ProcessingBenchWindow> windows = new ConcurrentHashMap<>();
|
|
private ProcessingBench processingBench;
|
|
private ItemContainer inputContainer;
|
|
private ItemContainer fuelContainer;
|
|
private ItemContainer outputContainer;
|
|
private CombinedItemContainer combinedItemContainer;
|
|
private float inputProgress;
|
|
private float fuelTime;
|
|
private int lastConsumedFuelTotal;
|
|
private int nextExtra = -1;
|
|
private final Set<Short> processingSlots = new HashSet<>();
|
|
private final Set<Short> processingFuelSlots = new HashSet<>();
|
|
@Nullable
|
|
private String recipeId;
|
|
@Nullable
|
|
private CraftingRecipe recipe;
|
|
private boolean active = false;
|
|
|
|
@Override
|
|
public boolean initialize(@Nonnull BlockType blockType) {
|
|
if (!super.initialize(blockType)) {
|
|
if (this.bench == null) {
|
|
List<ItemStack> itemStacks = new ObjectArrayList();
|
|
if (this.inputContainer != null) {
|
|
itemStacks.addAll(this.inputContainer.dropAllItemStacks());
|
|
}
|
|
|
|
if (this.fuelContainer != null) {
|
|
itemStacks.addAll(this.fuelContainer.dropAllItemStacks());
|
|
}
|
|
|
|
if (this.outputContainer != null) {
|
|
itemStacks.addAll(this.outputContainer.dropAllItemStacks());
|
|
}
|
|
|
|
World world = this.getChunk().getWorld();
|
|
Store<EntityStore> store = world.getEntityStore().getStore();
|
|
Holder<EntityStore>[] itemEntityHolders = this.ejectItems(store, itemStacks);
|
|
if (itemEntityHolders.length > 0) {
|
|
world.execute(() -> store.addEntities(itemEntityHolders, AddReason.SPAWN));
|
|
}
|
|
}
|
|
|
|
return false;
|
|
} else if (!(this.bench instanceof ProcessingBench)) {
|
|
LOGGER.at(Level.SEVERE).log("Wrong bench type for processing. Got %s", this.bench.getClass().getName());
|
|
return false;
|
|
} else {
|
|
this.processingBench = (ProcessingBench)this.bench;
|
|
if (this.nextExtra == -1) {
|
|
this.nextExtra = this.processingBench.getExtraOutput() != null ? this.processingBench.getExtraOutput().getPerFuelItemsConsumed() : 0;
|
|
}
|
|
|
|
this.setupSlots();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private void setupSlots() {
|
|
List<ItemStack> remainder = new ObjectArrayList();
|
|
int tierLevel = this.getTierLevel();
|
|
ProcessingBench.ProcessingSlot[] input = this.processingBench.getInput(tierLevel);
|
|
short inputSlotsCount = (short)input.length;
|
|
this.inputContainer = ItemContainer.ensureContainerCapacity(this.inputContainer, inputSlotsCount, SimpleItemContainer::getNewContainer, remainder);
|
|
this.inputContainer.registerChangeEvent(EventPriority.LAST, this::onItemChange);
|
|
|
|
for (short slot = 0; slot < inputSlotsCount; slot++) {
|
|
ProcessingBench.ProcessingSlot inputSlot = input[slot];
|
|
String resourceTypeId = inputSlot.getResourceTypeId();
|
|
boolean shouldFilterValidIngredients = inputSlot.shouldFilterValidIngredients();
|
|
if (resourceTypeId != null) {
|
|
this.inputContainer.setSlotFilter(FilterActionType.ADD, slot, new ResourceFilter(new ResourceQuantity(resourceTypeId, 1)));
|
|
} else if (shouldFilterValidIngredients) {
|
|
ObjectArrayList<MaterialQuantity> validIngredients = new ObjectArrayList();
|
|
|
|
for (CraftingRecipe recipe : CraftingPlugin.getBenchRecipes(this.bench.getType(), this.bench.getId())) {
|
|
if (!recipe.isRestrictedByBenchTierLevel(this.bench.getId(), tierLevel)) {
|
|
List<MaterialQuantity> inputMaterials = CraftingManager.getInputMaterials(recipe);
|
|
validIngredients.addAll(inputMaterials);
|
|
}
|
|
}
|
|
|
|
this.inputContainer.setSlotFilter(FilterActionType.ADD, slot, (actionType, container, slotIndex, itemStack) -> {
|
|
if (itemStack == null) {
|
|
return true;
|
|
} else {
|
|
Iterator i$ = validIngredients.iterator();
|
|
|
|
while (i$.hasNext()) {
|
|
MaterialQuantity ingredient = (MaterialQuantity)i$.next();
|
|
if (CraftingManager.matches(ingredient, itemStack)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
input = this.processingBench.getFuel();
|
|
inputSlotsCount = (short)(input != null ? input.length : 0);
|
|
this.fuelContainer = ItemContainer.ensureContainerCapacity(this.fuelContainer, inputSlotsCount, SimpleItemContainer::getNewContainer, remainder);
|
|
this.fuelContainer.registerChangeEvent(EventPriority.LAST, this::onItemChange);
|
|
if (inputSlotsCount > 0) {
|
|
for (int i = 0; i < input.length; i++) {
|
|
ProcessingBench.ProcessingSlot fuel = input[i];
|
|
String resourceTypeId = fuel.getResourceTypeId();
|
|
if (resourceTypeId != null) {
|
|
this.fuelContainer.setSlotFilter(FilterActionType.ADD, (short)i, new ResourceFilter(new ResourceQuantity(resourceTypeId, 1)));
|
|
}
|
|
}
|
|
}
|
|
|
|
short outputSlotsCount = (short)this.processingBench.getOutputSlotsCount(tierLevel);
|
|
this.outputContainer = ItemContainer.ensureContainerCapacity(this.outputContainer, outputSlotsCount, SimpleItemContainer::getNewContainer, remainder);
|
|
this.outputContainer.registerChangeEvent(EventPriority.LAST, this::onItemChange);
|
|
if (outputSlotsCount > 0) {
|
|
this.outputContainer.setGlobalFilter(FilterType.ALLOW_OUTPUT_ONLY);
|
|
}
|
|
|
|
this.combinedItemContainer = new CombinedItemContainer(this.fuelContainer, this.inputContainer, this.outputContainer);
|
|
World world = this.getChunk().getWorld();
|
|
Store<EntityStore> store = world.getEntityStore().getStore();
|
|
Holder<EntityStore>[] itemEntityHolders = this.ejectItems(store, remainder);
|
|
if (itemEntityHolders.length > 0) {
|
|
world.execute(() -> store.addEntities(itemEntityHolders, AddReason.SPAWN));
|
|
}
|
|
|
|
this.inputContainer.registerChangeEvent(EventPriority.LAST, event -> this.updateRecipe());
|
|
if (this.processingBench.getFuel() == null) {
|
|
this.setActive(true);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void tick(float dt, int index, ArchetypeChunk<ChunkStore> archetypeChunk, @Nonnull Store<ChunkStore> store, CommandBuffer<ChunkStore> commandBuffer) {
|
|
World world = store.getExternalData().getWorld();
|
|
Store<EntityStore> entityStore = world.getEntityStore().getStore();
|
|
BlockType blockType = this.getBlockType();
|
|
String currentState = BlockAccessor.getCurrentInteractionState(blockType);
|
|
List<ItemStack> outputItemStacks = null;
|
|
List<MaterialQuantity> inputMaterials = null;
|
|
this.processingSlots.clear();
|
|
this.checkForRecipeUpdate();
|
|
if (this.recipe != null) {
|
|
outputItemStacks = CraftingManager.getOutputItemStacks(this.recipe);
|
|
if (!this.outputContainer.canAddItemStacks(outputItemStacks, false, false)) {
|
|
if ("Processing".equals(currentState)) {
|
|
this.setBlockInteractionState("default", blockType);
|
|
this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore);
|
|
} else if ("ProcessCompleted".equals(currentState)) {
|
|
this.setBlockInteractionState("default", blockType);
|
|
this.playSound(world, this.processingBench.getEndSoundEventIndex(), entityStore);
|
|
}
|
|
|
|
this.setActive(false);
|
|
return;
|
|
}
|
|
|
|
inputMaterials = CraftingManager.getInputMaterials(this.recipe);
|
|
List<TestRemoveItemSlotResult> result = this.inputContainer.getSlotMaterialsToRemove(inputMaterials, true, true);
|
|
if (result.isEmpty()) {
|
|
if ("Processing".equals(currentState)) {
|
|
this.setBlockInteractionState("default", blockType);
|
|
this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore);
|
|
} else if ("ProcessCompleted".equals(currentState)) {
|
|
this.setBlockInteractionState("default", blockType);
|
|
this.playSound(world, this.processingBench.getEndSoundEventIndex(), entityStore);
|
|
}
|
|
|
|
this.inputProgress = 0.0F;
|
|
this.setActive(false);
|
|
this.recipeId = null;
|
|
this.recipe = null;
|
|
return;
|
|
}
|
|
|
|
for (TestRemoveItemSlotResult item : result) {
|
|
this.processingSlots.addAll(item.getPickedSlots());
|
|
}
|
|
|
|
this.sendProcessingSlots();
|
|
} else {
|
|
if (this.processingBench.getFuel() == null) {
|
|
if ("Processing".equals(currentState)) {
|
|
this.setBlockInteractionState("default", blockType);
|
|
this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore);
|
|
} else if ("ProcessCompleted".equals(currentState)) {
|
|
this.setBlockInteractionState("default", blockType);
|
|
this.playSound(world, this.processingBench.getEndSoundEventIndex(), entityStore);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
boolean allowNoInputProcessing = this.processingBench.shouldAllowNoInputProcessing();
|
|
if (!allowNoInputProcessing && "Processing".equals(currentState)) {
|
|
this.setBlockInteractionState("default", blockType);
|
|
this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore);
|
|
} else if ("ProcessCompleted".equals(currentState)) {
|
|
this.setBlockInteractionState("default", blockType);
|
|
this.playSound(world, this.processingBench.getEndSoundEventIndex(), entityStore);
|
|
this.setActive(false);
|
|
this.sendProgress(0.0F);
|
|
return;
|
|
}
|
|
|
|
this.sendProgress(0.0F);
|
|
if (!allowNoInputProcessing) {
|
|
this.setActive(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
boolean needsUpdate = false;
|
|
if (this.fuelTime > 0.0F && this.active) {
|
|
this.fuelTime -= dt;
|
|
if (this.fuelTime < 0.0F) {
|
|
this.fuelTime = 0.0F;
|
|
}
|
|
|
|
needsUpdate = true;
|
|
}
|
|
|
|
ProcessingBench.ProcessingSlot[] fuelSlots = this.processingBench.getFuel();
|
|
boolean hasFuelSlots = fuelSlots != null && fuelSlots.length > 0;
|
|
if ((this.processingBench.getMaxFuel() <= 0 || this.fuelTime < this.processingBench.getMaxFuel()) && !this.fuelContainer.isEmpty()) {
|
|
if (!hasFuelSlots) {
|
|
return;
|
|
}
|
|
|
|
if (this.active) {
|
|
if (this.fuelTime > 0.0F) {
|
|
for (int i = 0; i < fuelSlots.length; i++) {
|
|
ItemStack itemInSlot = this.fuelContainer.getItemStack((short)i);
|
|
if (itemInSlot != null) {
|
|
this.processingFuelSlots.add((short)i);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (this.fuelTime < 0.0F) {
|
|
this.fuelTime = 0.0F;
|
|
}
|
|
|
|
this.processingFuelSlots.clear();
|
|
|
|
for (int ix = 0; ix < fuelSlots.length; ix++) {
|
|
ProcessingBench.ProcessingSlot fuelSlot = fuelSlots[ix];
|
|
String resourceTypeId = fuelSlot.getResourceTypeId() != null ? fuelSlot.getResourceTypeId() : "Fuel";
|
|
ResourceQuantity resourceQuantity = new ResourceQuantity(resourceTypeId, 1);
|
|
ItemStack slot = this.fuelContainer.getItemStack((short)ix);
|
|
if (slot != null) {
|
|
double fuelQuality = slot.getItem().getFuelQuality();
|
|
ResourceTransaction transaction = this.fuelContainer.removeResource(resourceQuantity, true, true, true);
|
|
this.processingFuelSlots.add((short)ix);
|
|
if (transaction.getRemainder() <= 0) {
|
|
ProcessingBench.ExtraOutput extra = this.processingBench.getExtraOutput();
|
|
if (extra != null && !extra.isIgnoredFuelSource(slot.getItem())) {
|
|
this.nextExtra--;
|
|
if (this.nextExtra <= 0) {
|
|
this.nextExtra = extra.getPerFuelItemsConsumed();
|
|
ObjectArrayList<ItemStack> extraItemStacks = new ObjectArrayList(extra.getOutputs().length);
|
|
|
|
for (MaterialQuantity e : extra.getOutputs()) {
|
|
extraItemStacks.add(e.toItemStack());
|
|
}
|
|
|
|
ListTransaction<ItemStackTransaction> addTransaction = this.outputContainer.addItemStacks(extraItemStacks, false, false, false);
|
|
List<ItemStack> remainderItems = new ObjectArrayList();
|
|
|
|
for (ItemStackTransaction itemStackTransaction : addTransaction.getList()) {
|
|
ItemStack remainder = itemStackTransaction.getRemainder();
|
|
if (remainder != null && !remainder.isEmpty()) {
|
|
remainderItems.add(remainder);
|
|
}
|
|
}
|
|
|
|
if (!remainderItems.isEmpty()) {
|
|
LOGGER.at(Level.WARNING).log("Dropping excess items at %s", this.getBlockPosition());
|
|
Holder<EntityStore>[] itemEntityHolders = this.ejectItems(entityStore, remainderItems);
|
|
entityStore.addEntities(itemEntityHolders, AddReason.SPAWN);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.fuelTime = (float)(this.fuelTime + transaction.getConsumed() * fuelQuality);
|
|
needsUpdate = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (needsUpdate) {
|
|
this.updateFuelValues();
|
|
}
|
|
|
|
if (!hasFuelSlots || this.active && !(this.fuelTime <= 0.0F)) {
|
|
if (!"Processing".equals(currentState)) {
|
|
this.setBlockInteractionState("Processing", blockType);
|
|
}
|
|
|
|
if (this.recipe != null && (this.fuelTime > 0.0F || this.processingBench.getFuel() == null)) {
|
|
this.inputProgress += dt;
|
|
}
|
|
|
|
if (this.recipe != null) {
|
|
float recipeTime = this.recipe.getTimeSeconds();
|
|
float craftingTimeReductionModifier = this.getCraftingTimeReductionModifier();
|
|
if (craftingTimeReductionModifier > 0.0F) {
|
|
recipeTime -= recipeTime * craftingTimeReductionModifier;
|
|
}
|
|
|
|
if (this.inputProgress > recipeTime) {
|
|
if (recipeTime > 0.0F) {
|
|
this.inputProgress -= recipeTime;
|
|
float progressPercent = this.inputProgress / recipeTime;
|
|
this.sendProgress(progressPercent);
|
|
} else {
|
|
this.inputProgress = 0.0F;
|
|
this.sendProgress(0.0F);
|
|
}
|
|
|
|
LOGGER.at(Level.FINE).log("Do Process for %s %s", this.recipeId, this.recipe);
|
|
if (inputMaterials != null) {
|
|
List<ItemStack> remainderItems = new ObjectArrayList();
|
|
int success = 0;
|
|
IntArrayList slots = new IntArrayList();
|
|
|
|
for (int j = 0; j < this.inputContainer.getCapacity(); j++) {
|
|
slots.add(j);
|
|
}
|
|
|
|
for (MaterialQuantity material : inputMaterials) {
|
|
for (int ixx = 0; ixx < slots.size(); ixx++) {
|
|
int slot = slots.getInt(ixx);
|
|
MaterialSlotTransaction transaction = this.inputContainer.removeMaterialFromSlot((short)slot, material, true, true, true);
|
|
if (transaction.succeeded()) {
|
|
success++;
|
|
slots.removeInt(ixx);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ListTransaction<ItemStackTransaction> addTransaction = this.outputContainer.addItemStacks(outputItemStacks, false, false, false);
|
|
if (!addTransaction.succeeded()) {
|
|
return;
|
|
}
|
|
|
|
for (ItemStackTransaction itemStackTransactionx : addTransaction.getList()) {
|
|
ItemStack remainder = itemStackTransactionx.getRemainder();
|
|
if (remainder != null && !remainder.isEmpty()) {
|
|
remainderItems.add(remainder);
|
|
}
|
|
}
|
|
|
|
if (success == inputMaterials.size()) {
|
|
this.setBlockInteractionState("ProcessCompleted", blockType);
|
|
this.playSound(world, this.bench.getCompletedSoundEventIndex(), entityStore);
|
|
if (!remainderItems.isEmpty()) {
|
|
LOGGER.at(Level.WARNING).log("Dropping excess items at %s", this.getBlockPosition());
|
|
Holder<EntityStore>[] itemEntityHolders = this.ejectItems(entityStore, remainderItems);
|
|
entityStore.addEntities(itemEntityHolders, AddReason.SPAWN);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
List<ItemStack> remainderItems = new ObjectArrayList();
|
|
ListTransaction<MaterialTransaction> transaction = this.inputContainer.removeMaterials(inputMaterials, true, true, true);
|
|
if (!transaction.succeeded()) {
|
|
LOGGER.at(Level.WARNING).log("Failed to remove input materials at %s", this.getBlockPosition());
|
|
this.setBlockInteractionState("default", blockType);
|
|
this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore);
|
|
return;
|
|
}
|
|
|
|
this.setBlockInteractionState("ProcessCompleted", blockType);
|
|
this.playSound(world, this.bench.getCompletedSoundEventIndex(), entityStore);
|
|
ListTransaction<ItemStackTransaction> addTransactionx = this.outputContainer.addItemStacks(outputItemStacks, false, false, false);
|
|
if (addTransactionx.succeeded()) {
|
|
return;
|
|
}
|
|
|
|
LOGGER.at(Level.WARNING).log("Dropping excess items at %s", this.getBlockPosition());
|
|
|
|
for (ItemStackTransaction itemStackTransactionxx : addTransactionx.getList()) {
|
|
ItemStack remainder = itemStackTransactionxx.getRemainder();
|
|
if (remainder != null && !remainder.isEmpty()) {
|
|
remainderItems.add(remainder);
|
|
}
|
|
}
|
|
|
|
Holder<EntityStore>[] itemEntityHolders = this.ejectItems(entityStore, remainderItems);
|
|
entityStore.addEntities(itemEntityHolders, AddReason.SPAWN);
|
|
} else if (this.recipe != null && recipeTime > 0.0F) {
|
|
float progressPercent = this.inputProgress / recipeTime;
|
|
this.sendProgress(progressPercent);
|
|
} else {
|
|
this.sendProgress(0.0F);
|
|
}
|
|
}
|
|
} else {
|
|
this.lastConsumedFuelTotal = 0;
|
|
if ("Processing".equals(currentState)) {
|
|
this.setBlockInteractionState("default", blockType);
|
|
this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore);
|
|
if (this.processingBench.getFuel() != null) {
|
|
this.setActive(false);
|
|
}
|
|
} else if ("ProcessCompleted".equals(currentState)) {
|
|
this.setBlockInteractionState("default", blockType);
|
|
this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore);
|
|
if (this.processingBench.getFuel() != null) {
|
|
this.setActive(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private float getCraftingTimeReductionModifier() {
|
|
BenchTierLevel levelData = this.bench.getTierLevel(this.getTierLevel());
|
|
return levelData != null ? levelData.getCraftingTimeReductionModifier() : 0.0F;
|
|
}
|
|
|
|
@Nonnull
|
|
private Holder<EntityStore>[] ejectItems(@Nonnull ComponentAccessor<EntityStore> accessor, @Nonnull List<ItemStack> itemStacks) {
|
|
if (itemStacks.isEmpty()) {
|
|
return Holder.emptyArray();
|
|
} else {
|
|
RotationTuple rotation = RotationTuple.get(this.getRotationIndex());
|
|
Vector3d frontDir = new Vector3d(0.0, 0.0, 1.0);
|
|
rotation.yaw().rotateY(frontDir, frontDir);
|
|
BlockType blockType = this.getBlockType();
|
|
Vector3d dropPosition;
|
|
if (blockType == null) {
|
|
dropPosition = this.getBlockPosition().toVector3d().add(0.5, 0.0, 0.5);
|
|
} else {
|
|
BlockBoundingBoxes hitboxAsset = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex());
|
|
if (hitboxAsset == null) {
|
|
dropPosition = this.getBlockPosition().toVector3d().add(0.5, 0.0, 0.5);
|
|
} else {
|
|
double depth = hitboxAsset.get(0).getBoundingBox().depth();
|
|
double frontOffset = depth / 2.0 + 0.1F;
|
|
dropPosition = this.getCenteredBlockPosition();
|
|
dropPosition.add(frontDir.x * frontOffset, 0.0, frontDir.z * frontOffset);
|
|
}
|
|
}
|
|
|
|
ThreadLocalRandom random = ThreadLocalRandom.current();
|
|
ObjectArrayList<Holder<EntityStore>> result = new ObjectArrayList(itemStacks.size());
|
|
|
|
for (ItemStack item : itemStacks) {
|
|
float velocityX = (float)(frontDir.x * 2.0 + 2.0 * (random.nextDouble() - 0.5));
|
|
float velocityZ = (float)(frontDir.z * 2.0 + 2.0 * (random.nextDouble() - 0.5));
|
|
Holder<EntityStore> holder = ItemComponent.generateItemDrop(accessor, item, dropPosition, Vector3f.ZERO, velocityX, 3.25F, velocityZ);
|
|
if (holder != null) {
|
|
result.add(holder);
|
|
}
|
|
}
|
|
|
|
return (Holder<EntityStore>[])result.toArray(Holder[]::new);
|
|
}
|
|
}
|
|
|
|
private void sendProgress(float progress) {
|
|
this.windows.forEach((uuid, window) -> window.setProgress(progress));
|
|
}
|
|
|
|
private void sendProcessingSlots() {
|
|
this.windows.forEach((uuid, window) -> window.setProcessingSlots(this.processingSlots));
|
|
}
|
|
|
|
private void sendProcessingFuelSlots() {
|
|
this.windows.forEach((uuid, window) -> window.setProcessingFuelSlots(this.processingFuelSlots));
|
|
}
|
|
|
|
public boolean isActive() {
|
|
return this.active;
|
|
}
|
|
|
|
public boolean setActive(boolean active) {
|
|
if (this.active != active) {
|
|
if (active && this.processingBench.getFuel() != null && this.fuelContainer.isEmpty()) {
|
|
return false;
|
|
} else {
|
|
this.active = active;
|
|
if (!active) {
|
|
this.processingSlots.clear();
|
|
this.processingFuelSlots.clear();
|
|
this.sendProcessingSlots();
|
|
this.sendProcessingFuelSlots();
|
|
}
|
|
|
|
this.updateRecipe();
|
|
this.windows.forEach((uuid, window) -> window.setActive(active));
|
|
this.markNeedsSave();
|
|
return true;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void updateFuelValues() {
|
|
if (this.fuelTime > this.lastConsumedFuelTotal) {
|
|
this.lastConsumedFuelTotal = MathUtil.ceil(this.fuelTime);
|
|
}
|
|
|
|
float fuelPercent = this.lastConsumedFuelTotal > 0 ? this.fuelTime / this.lastConsumedFuelTotal : 0.0F;
|
|
this.windows.forEach((uuid, window) -> {
|
|
window.setFuelTime(fuelPercent);
|
|
window.setMaxFuel(this.lastConsumedFuelTotal);
|
|
window.setProcessingFuelSlots(this.processingFuelSlots);
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy() {
|
|
super.onDestroy();
|
|
WindowManager.closeAndRemoveAll(this.windows);
|
|
if (this.combinedItemContainer != null) {
|
|
List<ItemStack> itemStacks = this.combinedItemContainer.dropAllItemStacks();
|
|
this.dropFuelItems(itemStacks);
|
|
World world = this.getChunk().getWorld();
|
|
Store<EntityStore> entityStore = world.getEntityStore().getStore();
|
|
Vector3d dropPosition = this.getBlockPosition().toVector3d().add(0.5, 0.0, 0.5);
|
|
Holder<EntityStore>[] itemEntityHolders = ItemComponent.generateItemDrops(entityStore, itemStacks, dropPosition, Vector3f.ZERO);
|
|
if (itemEntityHolders.length > 0) {
|
|
world.execute(() -> entityStore.addEntities(itemEntityHolders, AddReason.SPAWN));
|
|
}
|
|
}
|
|
|
|
if (this.marker != null) {
|
|
this.marker.remove();
|
|
}
|
|
}
|
|
|
|
public CombinedItemContainer getItemContainer() {
|
|
return this.combinedItemContainer;
|
|
}
|
|
|
|
private void checkForRecipeUpdate() {
|
|
if (this.recipe == null && this.recipeId != null) {
|
|
this.updateRecipe();
|
|
}
|
|
}
|
|
|
|
private void updateRecipe() {
|
|
List<CraftingRecipe> recipes = CraftingPlugin.getBenchRecipes(this.bench.getType(), this.bench.getId());
|
|
if (recipes.isEmpty()) {
|
|
this.clearRecipe();
|
|
} else {
|
|
List<CraftingRecipe> matching = new ObjectArrayList();
|
|
|
|
for (CraftingRecipe recipe : recipes) {
|
|
if (!recipe.isRestrictedByBenchTierLevel(this.bench.getId(), this.getTierLevel())) {
|
|
MaterialQuantity[] input = recipe.getInput();
|
|
int matches = 0;
|
|
IntArrayList slots = new IntArrayList();
|
|
|
|
for (int j = 0; j < this.inputContainer.getCapacity(); j++) {
|
|
slots.add(j);
|
|
}
|
|
|
|
for (MaterialQuantity craftingMaterial : input) {
|
|
String itemId = craftingMaterial.getItemId();
|
|
String resourceTypeId = craftingMaterial.getResourceTypeId();
|
|
int materialQuantity = craftingMaterial.getQuantity();
|
|
BsonDocument metadata = craftingMaterial.getMetadata();
|
|
MaterialQuantity material = new MaterialQuantity(itemId, resourceTypeId, null, materialQuantity, metadata);
|
|
|
|
for (int k = 0; k < slots.size(); k++) {
|
|
int j = slots.getInt(k);
|
|
int out = InternalContainerUtilMaterial.testRemoveMaterialFromSlot(this.inputContainer, (short)j, material, material.getQuantity(), true);
|
|
if (out == 0) {
|
|
matches++;
|
|
slots.removeInt(k);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (matches == input.length) {
|
|
matching.add(recipe);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (matching.isEmpty()) {
|
|
this.clearRecipe();
|
|
} else {
|
|
matching.sort(Comparator.comparingInt(o -> CraftingManager.getInputMaterials(o).size()));
|
|
Collections.reverse(matching);
|
|
if (this.recipeId != null) {
|
|
for (CraftingRecipe rec : matching) {
|
|
if (Objects.equals(this.recipeId, rec.getId())) {
|
|
LOGGER.at(Level.FINE).log("%s - Keeping existing Recipe %s %s", LazyArgs.lazy(this::getBlockPosition), this.recipeId, rec);
|
|
this.recipe = rec;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
CraftingRecipe recipex = matching.getFirst();
|
|
if (this.recipeId == null || !Objects.equals(this.recipeId, recipex.getId())) {
|
|
this.inputProgress = 0.0F;
|
|
this.sendProgress(0.0F);
|
|
}
|
|
|
|
this.recipeId = recipex.getId();
|
|
this.recipe = recipex;
|
|
LOGGER.at(Level.FINE).log("%s - Found Recipe %s %s", LazyArgs.lazy(this::getBlockPosition), this.recipeId, this.recipe);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void clearRecipe() {
|
|
this.recipeId = null;
|
|
this.recipe = null;
|
|
this.lastConsumedFuelTotal = 0;
|
|
this.inputProgress = 0.0F;
|
|
this.sendProgress(0.0F);
|
|
LOGGER.at(Level.FINE).log("%s - Cleared Recipe", LazyArgs.lazy(this::getBlockPosition));
|
|
}
|
|
|
|
public void dropFuelItems(@Nonnull List<ItemStack> itemStacks) {
|
|
String fuelDropItemId = this.processingBench.getFuelDropItemId();
|
|
if (fuelDropItemId != null) {
|
|
Item item = Item.getAssetMap().getAsset(fuelDropItemId);
|
|
int dropAmount = (int)this.fuelTime;
|
|
this.fuelTime = 0.0F;
|
|
|
|
while (dropAmount > 0) {
|
|
int quantity = Math.min(dropAmount, item.getMaxStack());
|
|
itemStacks.add(new ItemStack(fuelDropItemId, quantity));
|
|
dropAmount -= quantity;
|
|
}
|
|
} else {
|
|
LOGGER.at(Level.WARNING).log("No FuelDropItemId defined for %s fuel value of %s will be lost!", this.bench.getId(), this.fuelTime);
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public CraftingRecipe getRecipe() {
|
|
return this.recipe;
|
|
}
|
|
|
|
@Nonnull
|
|
public Map<UUID, ProcessingBenchWindow> getWindows() {
|
|
return this.windows;
|
|
}
|
|
|
|
public float getInputProgress() {
|
|
return this.inputProgress;
|
|
}
|
|
|
|
public void onItemChange(ItemContainer.ItemContainerChangeEvent event) {
|
|
this.markNeedsSave();
|
|
}
|
|
|
|
public void setBlockInteractionState(@Nonnull String state, @Nonnull BlockType blockType) {
|
|
this.getChunk().setBlockInteractionState(this.getBlockPosition(), blockType, state);
|
|
}
|
|
|
|
@Override
|
|
public void setMarker(WorldMapManager.MarkerReference marker) {
|
|
this.marker = marker;
|
|
this.markNeedsSave();
|
|
}
|
|
|
|
@Override
|
|
public void placedBy(
|
|
@Nonnull Ref<EntityStore> playerRef,
|
|
@Nonnull String blockTypeKey,
|
|
@Nonnull BlockState blockState,
|
|
@Nonnull ComponentAccessor<EntityStore> componentAccessor
|
|
) {
|
|
if (blockTypeKey.equals(this.processingBench.getIconItem()) && this.processingBench.getIcon() != null) {
|
|
Player playerComponent = componentAccessor.getComponent(playerRef, Player.getComponentType());
|
|
|
|
assert playerComponent != null;
|
|
|
|
TransformComponent transformComponent = componentAccessor.getComponent(playerRef, TransformComponent.getComponentType());
|
|
|
|
assert transformComponent != null;
|
|
|
|
Transform transformPacket = PositionUtil.toTransformPacket(transformComponent.getTransform());
|
|
transformPacket.orientation.yaw = 0.0F;
|
|
transformPacket.orientation.pitch = 0.0F;
|
|
transformPacket.orientation.roll = 0.0F;
|
|
MapMarker marker = new MapMarker(
|
|
this.processingBench.getIconId() + "-" + UUID.randomUUID(),
|
|
this.processingBench.getIconName(),
|
|
this.processingBench.getIcon(),
|
|
transformPacket,
|
|
null
|
|
);
|
|
((MarkerBlockState)blockState).setMarker(WorldMapManager.createPlayerMarker(playerRef, marker, componentAccessor));
|
|
}
|
|
}
|
|
|
|
private void playSound(@Nonnull World world, int soundEventIndex, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
|
|
if (soundEventIndex != 0) {
|
|
Vector3i pos = this.getBlockPosition();
|
|
SoundUtil.playSoundEvent3d(soundEventIndex, SoundCategory.SFX, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, componentAccessor);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onTierLevelChange() {
|
|
super.onTierLevelChange();
|
|
this.setupSlots();
|
|
}
|
|
}
|