hytale-server/com/hypixel/hytale/builtin/crafting/window/StructuralCraftingWindow.java

339 lines
15 KiB
Java

package com.hypixel.hytale.builtin.crafting.window;
import com.google.gson.JsonArray;
import com.hypixel.hytale.builtin.crafting.CraftingPlugin;
import com.hypixel.hytale.builtin.crafting.component.CraftingManager;
import com.hypixel.hytale.builtin.crafting.state.BenchState;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.event.EventRegistration;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.protocol.BenchRequirement;
import com.hypixel.hytale.protocol.ItemSoundEvent;
import com.hypixel.hytale.protocol.SoundCategory;
import com.hypixel.hytale.protocol.packets.window.ChangeBlockAction;
import com.hypixel.hytale.protocol.packets.window.CraftRecipeAction;
import com.hypixel.hytale.protocol.packets.window.SelectSlotAction;
import com.hypixel.hytale.protocol.packets.window.WindowAction;
import com.hypixel.hytale.protocol.packets.window.WindowType;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.StructuralCraftingBench;
import com.hypixel.hytale.server.core.asset.type.item.config.BlockGroup;
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.asset.type.itemsound.config.ItemSoundSet;
import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.entity.entities.player.windows.ItemContainerWindow;
import com.hypixel.hytale.server.core.inventory.Inventory;
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.ItemContainer;
import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer;
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.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.SoundUtil;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
public class StructuralCraftingWindow extends CraftingWindow implements ItemContainerWindow {
private static final int MAX_OPTIONS = 64;
private final SimpleItemContainer inputContainer;
private final SimpleItemContainer optionsContainer;
private final CombinedItemContainer combinedItemContainer;
private final Int2ObjectMap<String> optionSlotToRecipeMap = new Int2ObjectOpenHashMap();
private int selectedSlot;
@Nullable
private EventRegistration inventoryRegistration;
public StructuralCraftingWindow(BenchState benchState) {
super(WindowType.StructuralCrafting, benchState);
this.inputContainer = new SimpleItemContainer((short)1);
this.inputContainer.registerChangeEvent(e -> this.updateRecipes());
this.inputContainer.setSlotFilter(FilterActionType.ADD, (short)0, this::isValidInput);
this.optionsContainer = new SimpleItemContainer((short)64);
this.optionsContainer.setGlobalFilter(FilterType.DENY_ALL);
this.combinedItemContainer = new CombinedItemContainer(this.inputContainer, this.optionsContainer);
this.windowData.addProperty("selected", this.selectedSlot);
StructuralCraftingBench structuralBench = (StructuralCraftingBench)this.bench;
this.windowData.addProperty("allowBlockGroupCycling", structuralBench.shouldAllowBlockGroupCycling());
this.windowData.addProperty("alwaysShowInventoryHints", structuralBench.shouldAlwaysShowInventoryHints());
}
private boolean isValidInput(FilterActionType filterActionType, ItemContainer itemContainer, short i, ItemStack itemStack) {
if (filterActionType != FilterActionType.ADD) {
return true;
} else {
ObjectList<CraftingRecipe> matchingRecipes = this.getMatchingRecipes(itemStack);
return matchingRecipes != null && !matchingRecipes.isEmpty();
}
}
private static void sortRecipes(ObjectList<CraftingRecipe> matching, StructuralCraftingBench structuralBench) {
matching.sort((a, b) -> {
boolean aHasHeaderCategory = hasHeaderCategory(structuralBench, a);
boolean bHasHeaderCategory = hasHeaderCategory(structuralBench, b);
if (aHasHeaderCategory != bHasHeaderCategory) {
return aHasHeaderCategory ? -1 : 1;
} else {
int categoryA = getSortingPriority(structuralBench, a);
int categoryB = getSortingPriority(structuralBench, b);
int categoryCompare = Integer.compare(categoryA, categoryB);
return categoryCompare != 0 ? categoryCompare : a.getId().compareTo(b.getId());
}
});
}
private static boolean hasHeaderCategory(StructuralCraftingBench bench, CraftingRecipe recipe) {
for (BenchRequirement requirement : recipe.getBenchRequirement()) {
if (requirement.type == bench.getType() && requirement.id.equals(bench.getId()) && requirement.categories != null) {
for (String category : requirement.categories) {
if (bench.isHeaderCategory(category)) {
return true;
}
}
}
}
return false;
}
private static int getSortingPriority(StructuralCraftingBench bench, CraftingRecipe recipe) {
int priority = Integer.MAX_VALUE;
for (BenchRequirement requirement : recipe.getBenchRequirement()) {
if (requirement.type == bench.getType() && requirement.id.equals(bench.getId()) && requirement.categories != null) {
for (String category : requirement.categories) {
priority = Math.min(priority, bench.getCategoryIndex(category));
}
break;
}
}
return priority;
}
@Override
public void handleAction(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull WindowAction action) {
CraftingManager craftingManager = store.getComponent(ref, CraftingManager.getComponentType());
switch (action) {
case SelectSlotAction selectAction:
int newSlot = MathUtil.clamp(selectAction.slot, 0, this.optionsContainer.getCapacity());
if (newSlot != this.selectedSlot) {
this.selectedSlot = newSlot;
this.windowData.addProperty("selected", this.selectedSlot);
this.invalidate();
}
break;
case CraftRecipeAction craftAction:
ItemStack output = this.optionsContainer.getItemStack((short)this.selectedSlot);
if (output != null) {
int quantity = craftAction.quantity;
String recipeId = (String)this.optionSlotToRecipeMap.get(this.selectedSlot);
if (recipeId == null) {
return;
}
CraftingRecipe recipe = CraftingRecipe.getAssetMap().getAsset(recipeId);
if (recipe == null) {
return;
}
MaterialQuantity primaryOutput = recipe.getPrimaryOutput();
String primaryOutputItemId = primaryOutput.getItemId();
if (primaryOutputItemId != null) {
Item primaryOutputItem = Item.getAssetMap().getAsset(primaryOutputItemId);
if (primaryOutputItem != null) {
this.playCraftSound(ref, store, primaryOutputItem);
}
}
craftingManager.queueCraft(ref, store, this, 0, recipe, quantity, this.inputContainer, CraftingManager.InputRemovalType.ORDERED);
this.invalidate();
}
break;
case ChangeBlockAction changeBlockAction:
if (((StructuralCraftingBench)this.bench).shouldAllowBlockGroupCycling()) {
this.changeBlockType(ref, changeBlockAction.down, store);
}
break;
default:
}
}
private void playCraftSound(Ref<EntityStore> ref, Store<EntityStore> store, Item item) {
ItemSoundSet soundSet = ItemSoundSet.getAssetMap().getAsset(item.getItemSoundSetIndex());
if (soundSet != null) {
String dragSound = soundSet.getSoundEventIds().get(ItemSoundEvent.Drop);
if (dragSound != null) {
int dragSoundIndex = SoundEvent.getAssetMap().getIndex(dragSound);
if (dragSoundIndex != 0) {
SoundUtil.playSoundEvent2d(ref, dragSoundIndex, SoundCategory.UI, store);
}
}
}
}
private void changeBlockType(@Nonnull Ref<EntityStore> ref, boolean down, @Nonnull Store<EntityStore> store) {
ItemStack item = this.inputContainer.getItemStack((short)0);
if (item != null) {
BlockGroup set = BlockGroup.findItemGroup(item.getItem());
if (set != null) {
int currentIndex = -1;
for (int i = 0; i < set.size(); i++) {
if (set.get(i).equals(item.getItem().getId())) {
currentIndex = i;
break;
}
}
if (currentIndex != -1) {
int newIndex;
if (down) {
newIndex = (currentIndex - 1 + set.size()) % set.size();
} else {
newIndex = (currentIndex + 1) % set.size();
}
String next = set.get(newIndex);
Item desiredItem = Item.getAssetMap().getAsset(next);
if (desiredItem != null) {
this.inputContainer.replaceItemStackInSlot((short)0, item, new ItemStack(next, item.getQuantity()));
this.playCraftSound(ref, store, desiredItem);
}
}
}
}
}
@Nonnull
@Override
public ItemContainer getItemContainer() {
return this.combinedItemContainer;
}
@Override
public boolean onOpen0() {
super.onOpen0();
PlayerRef playerRef = this.getPlayerRef();
Ref<EntityStore> ref = playerRef.getReference();
Store<EntityStore> store = ref.getStore();
Player player = store.getComponent(ref, Player.getComponentType());
Inventory inventory = player.getInventory();
this.inventoryRegistration = inventory.getCombinedHotbarFirst()
.registerChangeEvent(
event -> {
this.windowData
.add(
"inventoryHints",
CraftingManager.generateInventoryHints(CraftingPlugin.getBenchRecipes(this.bench), 0, inventory.getCombinedHotbarFirst())
);
this.invalidate();
}
);
this.windowData
.add("inventoryHints", CraftingManager.generateInventoryHints(CraftingPlugin.getBenchRecipes(this.bench), 0, inventory.getCombinedHotbarFirst()));
return true;
}
@Override
public void onClose0() {
super.onClose0();
PlayerRef playerRef = this.getPlayerRef();
Ref<EntityStore> ref = playerRef.getReference();
Store<EntityStore> store = ref.getStore();
Player player = store.getComponent(ref, Player.getComponentType());
List<ItemStack> itemStacks = this.inputContainer.dropAllItemStacks();
SimpleItemContainer.addOrDropItemStacks(store, ref, player.getInventory().getCombinedHotbarFirst(), itemStacks);
CraftingManager craftingManager = store.getComponent(ref, CraftingManager.getComponentType());
craftingManager.cancelAllCrafting(ref, store);
if (this.inventoryRegistration != null) {
this.inventoryRegistration.unregister();
this.inventoryRegistration = null;
}
}
private void updateRecipes() {
this.invalidate();
this.optionsContainer.clear();
this.optionSlotToRecipeMap.clear();
ItemStack inputStack = this.inputContainer.getItemStack((short)0);
ObjectList<CraftingRecipe> matchingRecipes = this.getMatchingRecipes(inputStack);
if (matchingRecipes != null) {
StructuralCraftingBench structuralBench = (StructuralCraftingBench)this.bench;
sortRecipes(matchingRecipes, structuralBench);
int dividerIndex;
for (dividerIndex = 0; dividerIndex < matchingRecipes.size(); dividerIndex++) {
CraftingRecipe recipe = (CraftingRecipe)matchingRecipes.get(dividerIndex);
if (!hasHeaderCategory(structuralBench, recipe)) {
break;
}
}
this.windowData.addProperty("dividerIndex", dividerIndex);
this.optionsContainer.clear();
short index = 0;
int i = 0;
for (int bound = matchingRecipes.size(); i < bound; i++) {
CraftingRecipe match = (CraftingRecipe)matchingRecipes.get(i);
for (BenchRequirement requirement : match.getBenchRequirement()) {
if (requirement.type == this.bench.getType() && requirement.id.equals(this.bench.getId())) {
List<ItemStack> output = CraftingManager.getOutputItemStacks(match);
this.optionsContainer.setItemStackForSlot(index, output.getFirst(), false);
this.optionSlotToRecipeMap.put(index, match.getId());
index++;
}
}
}
JsonArray optionSlotRecipes = new JsonArray();
for (int ix = 0; ix < this.optionsContainer.getCapacity(); ix++) {
String recipeId = (String)this.optionSlotToRecipeMap.get(ix);
if (recipeId != null) {
optionSlotRecipes.add(recipeId);
}
}
this.windowData.add("optionSlotRecipes", optionSlotRecipes);
}
}
@NullableDecl
private ObjectList<CraftingRecipe> getMatchingRecipes(@Nullable ItemStack inputStack) {
if (inputStack == null) {
return null;
} else {
List<CraftingRecipe> recipes = CraftingPlugin.getBenchRecipes(this.bench.getType(), this.bench.getId());
if (recipes.isEmpty()) {
return null;
} else {
ObjectList<CraftingRecipe> matchingRecipes = new ObjectArrayList();
int i = 0;
for (int bound = recipes.size(); i < bound; i++) {
CraftingRecipe recipe = recipes.get(i);
List<MaterialQuantity> inputMaterials = CraftingManager.getInputMaterials(recipe);
if (inputMaterials.size() == 1 && CraftingManager.matches(inputMaterials.getFirst(), inputStack)) {
matchingRecipes.add(recipe);
}
}
return matchingRecipes.isEmpty() ? null : matchingRecipes;
}
}
}
}