package com.hypixel.hytale.builtin.crafting; import com.hypixel.hytale.assetstore.AssetRegistry; import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; import com.hypixel.hytale.assetstore.event.RemovedAssetsEvent; import com.hypixel.hytale.assetstore.map.DefaultAssetMap; import com.hypixel.hytale.builtin.crafting.commands.RecipeCommand; import com.hypixel.hytale.builtin.crafting.component.CraftingManager; import com.hypixel.hytale.builtin.crafting.interaction.LearnRecipeInteraction; import com.hypixel.hytale.builtin.crafting.interaction.OpenBenchPageInteraction; import com.hypixel.hytale.builtin.crafting.interaction.OpenProcessingBenchInteraction; import com.hypixel.hytale.builtin.crafting.state.BenchState; import com.hypixel.hytale.builtin.crafting.state.ProcessingBenchState; import com.hypixel.hytale.builtin.crafting.system.PlayerCraftingSystems; import com.hypixel.hytale.builtin.crafting.window.FieldCraftingWindow; import com.hypixel.hytale.common.util.ArrayUtil; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Archetype; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentAccessor; import com.hypixel.hytale.component.ComponentRegistryProxy; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.system.RefSystem; import com.hypixel.hytale.protocol.BenchRequirement; import com.hypixel.hytale.protocol.BenchType; import com.hypixel.hytale.protocol.ItemResourceType; import com.hypixel.hytale.protocol.packets.interface_.UpdateKnownRecipes; import com.hypixel.hytale.protocol.packets.window.WindowType; import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.Bench; 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.command.system.CommandManager; 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.Window; import com.hypixel.hytale.server.core.inventory.ItemStack; import com.hypixel.hytale.server.core.inventory.MaterialQuantity; import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.meta.BlockStateRegistry; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class CraftingPlugin extends JavaPlugin { private static CraftingPlugin instance; private static final Map registries = new Object2ObjectOpenHashMap(); private static final Map itemGeneratedRecipes = new Object2ObjectOpenHashMap(); private ComponentType craftingManagerComponentType; public CraftingPlugin(@Nonnull JavaPluginInit init) { super(init); instance = this; } @Nullable public static Set getAvailableRecipesForCategory(String benchId, String benchCategoryId) { BenchRecipeRegistry benchRecipeRegistry = registries.get(benchId); return benchRecipeRegistry == null ? null : benchRecipeRegistry.getRecipesForCategory(benchCategoryId); } public static boolean isValidCraftingMaterialForBench(BenchState benchState, ItemStack itemStack) { BenchRecipeRegistry benchRecipeRegistry = registries.get(benchState.getBench().getId()); return benchRecipeRegistry == null ? false : benchRecipeRegistry.isValidCraftingMaterial(itemStack); } public static boolean isValidUpgradeMaterialForBench(BenchState benchState, ItemStack itemStack) { BenchUpgradeRequirement nextLevelUpgradeMaterials = benchState.getNextLevelUpgradeMaterials(); if (nextLevelUpgradeMaterials == null) { return false; } else { for (MaterialQuantity upgradeMaterial : nextLevelUpgradeMaterials.getInput()) { if (itemStack.getItemId().equals(upgradeMaterial.getItemId())) { return true; } ItemResourceType[] resourceTypeId = itemStack.getItem().getResourceTypes(); if (resourceTypeId != null) { for (ItemResourceType resTypeId : resourceTypeId) { if (resTypeId.id.equals(upgradeMaterial.getResourceTypeId())) { return true; } } } } return false; } } @Override protected void setup() { AssetRegistry.getAssetStore(Interaction.class) .loadAssets( "Hytale:Hytale", List.of(OpenBenchPageInteraction.SIMPLE_CRAFTING, OpenBenchPageInteraction.DIAGRAM_CRAFTING, OpenBenchPageInteraction.STRUCTURAL_CRAFTING) ); AssetRegistry.getAssetStore(RootInteraction.class) .loadAssets( "Hytale:Hytale", List.of( OpenBenchPageInteraction.SIMPLE_CRAFTING_ROOT, OpenBenchPageInteraction.DIAGRAM_CRAFTING_ROOT, OpenBenchPageInteraction.STRUCTURAL_CRAFTING_ROOT ) ); ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); this.craftingManagerComponentType = entityStoreRegistry.registerComponent(CraftingManager.class, CraftingManager::new); entityStoreRegistry.registerSystem(new PlayerCraftingSystems.PlayerCraftingSystem(this.craftingManagerComponentType)); entityStoreRegistry.registerSystem(new PlayerCraftingSystems.CraftingManagerAddSystem(this.craftingManagerComponentType)); this.getCodecRegistry(Interaction.CODEC) .register("OpenBenchPage", OpenBenchPageInteraction.class, OpenBenchPageInteraction.CODEC) .register("OpenProcessingBench", OpenProcessingBenchInteraction.class, OpenProcessingBenchInteraction.CODEC); Bench.registerRootInteraction(BenchType.Crafting, OpenBenchPageInteraction.SIMPLE_CRAFTING_ROOT); Bench.registerRootInteraction(BenchType.DiagramCrafting, OpenBenchPageInteraction.DIAGRAM_CRAFTING_ROOT); Bench.registerRootInteraction(BenchType.StructuralCrafting, OpenBenchPageInteraction.STRUCTURAL_CRAFTING_ROOT); BlockStateRegistry blockStateRegistry = this.getBlockStateRegistry(); blockStateRegistry.registerBlockState(ProcessingBenchState.class, "processingBench", ProcessingBenchState.CODEC); blockStateRegistry.registerBlockState(BenchState.class, "crafting", BenchState.CODEC); Window.CLIENT_REQUESTABLE_WINDOW_TYPES.put(WindowType.PocketCrafting, FieldCraftingWindow::new); this.getEventRegistry().register(LoadedAssetsEvent.class, CraftingRecipe.class, CraftingPlugin::onRecipeLoad); this.getEventRegistry().register(RemovedAssetsEvent.class, CraftingRecipe.class, CraftingPlugin::onRecipeRemove); this.getEventRegistry().register(LoadedAssetsEvent.class, Item.class, CraftingPlugin::onItemAssetLoad); this.getEventRegistry().register(RemovedAssetsEvent.class, Item.class, CraftingPlugin::onItemAssetRemove); Interaction.CODEC.register("LearnRecipe", LearnRecipeInteraction.class, LearnRecipeInteraction.CODEC); CommandManager.get().registerSystemCommand(new RecipeCommand()); entityStoreRegistry.registerSystem(new CraftingPlugin.PlayerAddedSystem()); } private static void onItemAssetLoad(LoadedAssetsEvent> event) { List recipesToLoad = new ObjectArrayList(); for (Item item : event.getLoadedAssets().values()) { if (item.hasRecipesToGenerate()) { List generatedRecipes = new ObjectArrayList(); item.collectRecipesToGenerate(generatedRecipes); List generatedIds = new ObjectArrayList(); for (CraftingRecipe generatedRecipe : generatedRecipes) { String id = generatedRecipe.getId(); generatedIds.add(id); } itemGeneratedRecipes.put(item.getId(), generatedIds.toArray(String[]::new)); recipesToLoad.addAll(generatedRecipes); } } if (!recipesToLoad.isEmpty()) { CraftingRecipe.getAssetStore().loadAssets("Hytale:Hytale", recipesToLoad); } } private static void onItemAssetRemove(@Nonnull RemovedAssetsEvent> event) { for (String id : event.getRemovedAssets()) { String[] generatedRecipes = itemGeneratedRecipes.get(id); if (generatedRecipes != null) { CraftingRecipe.getAssetStore().removeAssets(List.of(generatedRecipes)); } } } private static void onRecipeLoad(LoadedAssetsEvent> event) { for (CraftingRecipe recipe : event.getLoadedAssets().values()) { for (BenchRecipeRegistry registry : registries.values()) { registry.removeRecipe(recipe.getId()); } if (recipe.getBenchRequirement() != null) { for (BenchRequirement benchRequirement : recipe.getBenchRequirement()) { BenchRecipeRegistry benchRecipeRegistry = registries.computeIfAbsent(benchRequirement.id, BenchRecipeRegistry::new); benchRecipeRegistry.addRecipe(benchRequirement, recipe); } } } computeBenchRecipeRegistries(); } private static void onRecipeRemove(RemovedAssetsEvent> event) { for (String removedRecipeId : event.getRemovedAssets()) { for (BenchRecipeRegistry registry : registries.values()) { registry.removeRecipe(removedRecipeId); } } computeBenchRecipeRegistries(); } private static void computeBenchRecipeRegistries() { for (BenchRecipeRegistry registry : registries.values()) { registry.recompute(); } } @Nonnull public static List getBenchRecipes(@Nonnull Bench bench) { return getBenchRecipes(bench.getType(), bench.getId()); } @Nonnull public static List getBenchRecipes(BenchType benchType, String name) { return getBenchRecipes(benchType, name, null); } @Nonnull public static List getBenchRecipes(BenchType benchType, String benchId, @Nullable String category) { BenchRecipeRegistry registry = registries.get(benchId); if (registry == null) { return List.of(); } else { List list = new ObjectArrayList(); for (CraftingRecipe recipe : registry.getAllRecipes()) { BenchRequirement[] benchRequirement = recipe.getBenchRequirement(); if (benchRequirement != null) { for (BenchRequirement requirement : benchRequirement) { if (requirement.type == benchType && requirement.id.equals(benchId) && (category == null || hasCategory(recipe, category))) { list.add(recipe); break; } } } } return list; } } private static boolean hasCategory(@Nonnull CraftingRecipe recipe, String category) { for (BenchRequirement benchRequirement : recipe.getBenchRequirement()) { if (benchRequirement.categories != null && ArrayUtil.contains(benchRequirement.categories, category)) { return true; } } return false; } public static boolean learnRecipe(@Nonnull Ref ref, @Nonnull String recipeId, @Nonnull ComponentAccessor componentAccessor) { Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); assert playerComponent != null; PlayerConfigData playerConfigData = playerComponent.getPlayerConfigData(); Set knownRecipes = new HashSet<>(playerConfigData.getKnownRecipes()); if (knownRecipes.add(recipeId)) { playerConfigData.setKnownRecipes(knownRecipes); sendKnownRecipes(ref, componentAccessor); return true; } else { return false; } } public static boolean forgetRecipe(@Nonnull Ref ref, @Nonnull String itemId, @Nonnull ComponentAccessor componentAccessor) { Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); assert playerComponent != null; PlayerConfigData playerConfigData = playerComponent.getPlayerConfigData(); Set knownRecipes = new ObjectOpenHashSet(playerConfigData.getKnownRecipes()); if (knownRecipes.remove(itemId)) { playerConfigData.setKnownRecipes(knownRecipes); sendKnownRecipes(ref, componentAccessor); return true; } else { return false; } } public static void sendKnownRecipes(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); assert playerRefComponent != null; Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); assert playerComponent != null; PlayerConfigData playerConfigData = playerComponent.getPlayerConfigData(); DefaultAssetMap itemAssetMap = Item.getAssetMap(); Map knownRecipes = new Object2ObjectOpenHashMap(); for (String id : playerConfigData.getKnownRecipes()) { Item item = itemAssetMap.getAsset(id); if (item != null) { for (BenchRecipeRegistry registry : registries.values()) { for (String recipeId : registry.getIncomingRecipesForItem(item.getId())) { CraftingRecipe recipe = CraftingRecipe.getAssetMap().getAsset(recipeId); if (recipe != null) { knownRecipes.put(id, recipe.toPacket(id)); } } } } } playerRefComponent.getPacketHandler().writeNoCache(new UpdateKnownRecipes(knownRecipes)); } public ComponentType getCraftingManagerComponentType() { return this.craftingManagerComponentType; } public static CraftingPlugin get() { return instance; } public static class PlayerAddedSystem extends RefSystem { private static final Query QUERY = Archetype.of(Player.getComponentType(), PlayerRef.getComponentType()); @Override public Query getQuery() { return QUERY; } @Override public void onEntityAdded( @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { CraftingPlugin.sendKnownRecipes(ref, commandBuffer); } @Override public void onEntityRemove( @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { } } }