diff --git a/plugins/factorymod-paper/.editorconfig b/plugins/factorymod-paper/.editorconfig new file mode 100644 index 000000000..9887763dc --- /dev/null +++ b/plugins/factorymod-paper/.editorconfig @@ -0,0 +1,25 @@ +# Editorconfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +indent_style = tab +indent_size = 4 +end_of_line = lf +insert_final_newline = true +continuation_indent_size = 8 + +[*.java] +indent_style = tab +indent_size = 4 + +[*.xml] +indent_style = tab +indent_size = 2 + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/plugins/factorymod-paper/LICENSE.txt b/plugins/factorymod-paper/LICENSE.txt new file mode 100644 index 000000000..e83d6ca52 --- /dev/null +++ b/plugins/factorymod-paper/LICENSE.txt @@ -0,0 +1,27 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3)The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/plugins/factorymod-paper/README.md b/plugins/factorymod-paper/README.md new file mode 100644 index 000000000..71bd0fae2 --- /dev/null +++ b/plugins/factorymod-paper/README.md @@ -0,0 +1,47 @@ +# FactoryMod + +FactoryMod is a Minecraft plugin with specialized crafting machines that can consume and produce any goods. From this basis FactoryMod's flexible configurability means admins can drastically improve upon a vanilla Minecraft economy: + +* Tech trees with precise control over depth and difficulty +* Encouraging cooperation through permanence of capital +* Or simply a way for players to obtain survival unobtainable items + +FactoryMod is currently running on [r/civclassics](https://old.reddit.com/r/civclassics/) at mc.civclassic.com (1.16.5) + +--- +Any number of factories can be configured, each with unique purposes, and upgradable to other factories. Factories are a static structure created by placing a furnace, crafting bench, and chest together. The ingredients and outputs for a recipe are placed inside the chest. Clicking on the crafting table with a stick allows for selection of a recipe while clicking on the furnace with a stick runs the factory. Before a factory can be run it must be created : the setup cost of goods is deposited in the chest. Factories have a configurable repair cost, which essentially works as an upkeep cost. + +The diamond pick factory in the Civclassic config is an example of a simple factory, that for input diamonds creates diamond picks at a 1/3 vanilla cost. + + diamond_pick: + type: FCC + name: Diamond Pickaxe Smith + citadelBreakReduction: 1.0 + setupcost: + diamond: + material: DIAMOND + amount: 96 + recipes: + - make_diamond_pick + - repair_diamond_pick_factory + +Lower in the config file each recipe is defined: + + make_diamond_pick: + production_time: 4s + name: Make Diamond Pick + type: PRODUCTION + input: + diamond: + material: DIAMOND + amount: 15 + output: + diamond_pick: + material: DIAMOND_PICKAXE + amount: 15 + +Factories can do more than increase yield. The preset compactor factory turns a stack of items into a single lored item or visa versa. This gives another method for transporting bulk goods. There are many more configurable options, such as how much of startup cost factories return when broken. For a more comprehensive (WIP) users guide see [Civclassic Wiki](https://civclassic.miraheze.org/wiki/Comprehensive_Guide#Factorymod) + +--- + +Parts of this README adapted from earlier guides such as [Civcraft FactoryMod](https://github.com/civcraft/FactoryMod/wiki) diff --git a/plugins/factorymod-paper/build.gradle.kts b/plugins/factorymod-paper/build.gradle.kts new file mode 100644 index 000000000..0167d76a5 --- /dev/null +++ b/plugins/factorymod-paper/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("io.papermc.paperweight.userdev") +} + +version = "3.1.0" + +dependencies { + paperweight { + paperDevBundle("1.18.2-R0.1-SNAPSHOT") + } + + compileOnly(project(":plugins:civmodcore-paper")) + compileOnly(project(":plugins:namelayer-paper")) + compileOnly(project(":plugins:citadel-paper")) +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/ConfigParser.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/ConfigParser.java new file mode 100644 index 000000000..57a90e6be --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/ConfigParser.java @@ -0,0 +1,1046 @@ +package com.github.igotyou.FactoryMod; + +import com.github.igotyou.FactoryMod.eggs.FurnCraftChestEgg; +import com.github.igotyou.FactoryMod.eggs.IFactoryEgg; +import com.github.igotyou.FactoryMod.eggs.PipeEgg; +import com.github.igotyou.FactoryMod.eggs.SorterEgg; +import com.github.igotyou.FactoryMod.listeners.NetherPortalListener; +import com.github.igotyou.FactoryMod.recipes.AOERepairRecipe; +import com.github.igotyou.FactoryMod.recipes.CompactingRecipe; +import com.github.igotyou.FactoryMod.recipes.DecompactingRecipe; +import com.github.igotyou.FactoryMod.recipes.DeterministicEnchantingRecipe; +import com.github.igotyou.FactoryMod.recipes.DummyParsingRecipe; +import com.github.igotyou.FactoryMod.recipes.FactoryMaterialReturnRecipe; +import com.github.igotyou.FactoryMod.recipes.IRecipe; +import com.github.igotyou.FactoryMod.recipes.InputRecipe; +import com.github.igotyou.FactoryMod.recipes.LoreEnchantRecipe; +import com.github.igotyou.FactoryMod.recipes.PlayerHeadRecipe; +import com.github.igotyou.FactoryMod.recipes.PrintBookRecipe; +import com.github.igotyou.FactoryMod.recipes.PrintNoteRecipe; +import com.github.igotyou.FactoryMod.recipes.PrintingPlateJsonRecipe; +import com.github.igotyou.FactoryMod.recipes.PrintingPlateRecipe; +import com.github.igotyou.FactoryMod.recipes.ProductionRecipe; +import com.github.igotyou.FactoryMod.recipes.PylonRecipe; +import com.github.igotyou.FactoryMod.recipes.RandomOutputRecipe; +import com.github.igotyou.FactoryMod.recipes.RecipeScalingUpgradeRecipe; +import com.github.igotyou.FactoryMod.recipes.RepairRecipe; +import com.github.igotyou.FactoryMod.recipes.Upgraderecipe; +import com.github.igotyou.FactoryMod.recipes.WordBankRecipe; +import com.github.igotyou.FactoryMod.recipes.scaling.ProductionRecipeModifier; +import com.github.igotyou.FactoryMod.structures.BlockFurnaceStructure; +import com.github.igotyou.FactoryMod.structures.FurnCraftChestStructure; +import com.github.igotyou.FactoryMod.structures.PipeStructure; +import com.github.igotyou.FactoryMod.utility.FactoryGarbageCollector; +import com.github.igotyou.FactoryMod.utility.FactoryModGUI; +import org.apache.commons.lang.WordUtils; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; +import vg.civcraft.mc.civmodcore.config.ConfigHelper; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +import static vg.civcraft.mc.civmodcore.config.ConfigHelper.parseTime; +import static vg.civcraft.mc.civmodcore.config.ConfigHelper.parseTimeAsTicks; + +public class ConfigParser { + private FactoryMod plugin; + private HashMap recipes; + private FactoryModManager manager; + private int defaultUpdateTime; + private ItemStack defaultFuel; + private int defaultFuelConsumptionTime; + private double defaultReturnRate; + private HashMap upgradeEggs; + private HashMap> recipeLists; + private HashMap recipeScalingUpgradeMapping; + private long defaultBreakGracePeriod; + private int defaultDamagePerBreakPeriod; + private boolean useYamlIdentifers; + private int defaultHealth; + private HashSet forceRecipes; + private boolean forceIncludeAll; + + public ConfigParser(FactoryMod plugin) { + this.plugin = plugin; + } + + /** + * Parses the whole config and creates a manager containing everything that was + * parsed from the config + * + * @return manager with everything contained in the config + */ + public FactoryModManager parse() { + plugin.saveDefaultConfig(); + plugin.reloadConfig(); + FileConfiguration config = plugin.getConfig(); + boolean citadelEnabled = plugin.getServer().getPluginManager().isPluginEnabled("Citadel"); + boolean nameLayerEnabled = plugin.getServer().getPluginManager().isPluginEnabled("NameLayer"); + boolean logInventories = config.getBoolean("log_inventories", true); + Material factoryInteractionMaterial = Material.STICK; + try { + factoryInteractionMaterial = Material + .getMaterial(config.getString("factory_interaction_material", "STICK")); + } catch (IllegalArgumentException iae) { + plugin.warning(config.getString("factory_interaction_material") + + " is not a valid material for factory_interaction_material"); + } + boolean disableNether = config.getBoolean("disable_nether", false); + if (disableNether) { + plugin.getServer().getPluginManager().registerEvents(new NetherPortalListener(), plugin); + } + useYamlIdentifers = config.getBoolean("use_recipe_yamlidentifiers", false); + if (!useYamlIdentifers) { + plugin.warning( + "You have usage of yaml identifiers turned off, names will be used instead to identify factories and recipes. This behavior" + + " is not recommended and not compatible with config inheritation"); + } + defaultUpdateTime = parseTimeAsTicks(config.getString("default_update_time", "250ms")); + defaultHealth = config.getInt("default_health", 10000); + ItemMap dFuel = ConfigHelper.parseItemMap(config.getConfigurationSection("default_fuel")); + if (dFuel.getTotalUniqueItemAmount() > 0) { + defaultFuel = dFuel.getItemStackRepresentation().get(0); + } else { + plugin.warning("No default_fuel specified. Should be an ItemMap."); + } + defaultFuelConsumptionTime = parseTimeAsTicks(config.getString("default_fuel_consumption_intervall", "20")); + defaultReturnRate = config.getDouble("default_return_rate", 0.0); + int redstonePowerOn = config.getInt("redstone_power_on", 7); + int redstoneRecipeChange = config.getInt("redstone_recipe_change", 2); + defaultBreakGracePeriod = parseTime(config.getString("default_break_grace_period")); + defaultDamagePerBreakPeriod = config.getInt("default_decay_amount", 21); + long savingIntervall = parseTimeAsTicks(config.getString("saving_intervall", "15m")); + forceIncludeAll = config.getBoolean("force_include_default", false); + // save factories on a regular base, unless disabled + if (savingIntervall > 0) { + new BukkitRunnable() { + + @Override + public void run() { + FactoryMod.getInstance().getManager().saveFactories(); + + } + }.runTaskTimerAsynchronously(plugin, savingIntervall, savingIntervall); + } + int globalPylonLimit = config.getInt("global_pylon_limit"); + PylonRecipe.setGlobalLimit(globalPylonLimit); + Map factoryRenames = parseRenames(config.getConfigurationSection("renames")); + int maxInputChests = config.getInt("max_input_chests", 10); + int maxOutputChests = config.getInt("max_output_chests", 10); + int maxFuelChests = config.getInt("max_fuel_chests", 10); + int maxTotalIOFChests = config.getInt("max_iof_chests", 15); + + manager = new FactoryModManager(plugin, factoryInteractionMaterial, citadelEnabled, nameLayerEnabled, + redstonePowerOn, redstoneRecipeChange, logInventories, maxInputChests, maxOutputChests, maxFuelChests, + maxTotalIOFChests, factoryRenames); + upgradeEggs = new HashMap<>(); + recipeLists = new HashMap<>(); + recipeScalingUpgradeMapping = new HashMap<>(); + parseFactories(config.getConfigurationSection("factories")); + parseRecipes(config.getConfigurationSection("recipes")); + manager.setForceInclude(forceRecipes); + assignRecipeScalingRecipes(); + assignRecipesToFactories(); + enableFactoryDecay(config); + manager.calculateTotalSetupCosts(); + FactoryModGUI.initUpgradeMapping(manager); + // Some recipes need references to factories and all factories need + // references to recipes, so we parse all factories first, set their + // recipes to null, store the names of the recipes in a map here, parse + // the recipes which can already get the references to the factories and + // then fix the recipe references for the factories + plugin.info("Parsed complete config"); + return manager; + } + + /** + * Parses all recipes and sorts them into a hashmap by their name so they are + * ready to assign them to factories + * + * @param config ConfigurationSection containing the recipe configurations + */ + private void parseRecipes(ConfigurationSection config) { + recipes = new HashMap<>(); + forceRecipes = new HashSet<>(); + List recipeKeys = new LinkedList<>(); + for (String key : config.getKeys(false)) { + ConfigurationSection current = config.getConfigurationSection(key); + if (current == null) { + plugin.warning("Found invalid section that should not exist at " + config.getCurrentPath() + key); + continue; + } + recipeKeys.add(key); + } + while (!recipeKeys.isEmpty()) { + String currentIdent = recipeKeys.get(0); + ConfigurationSection current = config.getConfigurationSection(currentIdent); + if (useYamlIdentifers) { + // no support for inheritation when not using yaml identifiers + boolean foundParent = false; + while (!foundParent) { + // keep track of already parsed sections, so we dont get stuck forever in cyclic + // dependencies + List children = new LinkedList<>(); + children.add(currentIdent); + if (current.isString("inherit")) { + // parent is defined for this recipe + String parent = current.getString("inherit"); + if (recipes.containsKey(parent)) { + // we already parsed the parent, so parsing this recipe is fine + foundParent = true; + } else { + if (!recipeKeys.contains(parent)) { + // specified parent doesnt exist + plugin.warning("The recipe " + currentIdent + " specified " + parent + + " as parent, but this recipe could not be found"); + current = null; + foundParent = true; + } else { + + // specified parent exists, but wasnt parsed yet, so we do it first + if (children.contains(parent)) { + // cyclic dependency + plugin.warning( + "The recipe " + currentIdent + " specified a cyclic dependency with parent " + + parent + " it was skipped"); + current = null; + foundParent = true; + break; + } + currentIdent = parent; + current = config.getConfigurationSection(parent); + } + } + } else { + // no parent is a parent as well + foundParent = true; + } + } + } + recipeKeys.remove(currentIdent); + if (current == null) { + plugin.warning(String.format("Recipe %s unable to be added.", currentIdent)); + continue; + } + IRecipe recipe = parseRecipe(current); + if (recipe == null) { + plugin.warning(String.format("Recipe %s unable to be added.", currentIdent)); + } else { + if (recipes.containsKey(recipe.getIdentifier())) { + plugin.warning("Recipe identifier " + recipe.getIdentifier() + + " was found twice in the config. One instance was skipped"); + } else { + recipes.put(recipe.getIdentifier(), recipe); + manager.registerRecipe(recipe); + } + } + } + } + + /** + * Parses all factories + * + * @param config ConfigurationSection to parse the factories from + * param defaultUpdate default intervall in ticks how often factories update, + * each factory can choose to define an own value or to use + * the default instead + */ + private void parseFactories(ConfigurationSection config) { + if (config == null) { + plugin.getLogger().info("No factory configurations found in config"); + return; + } + for (String key : config.getKeys(false)) { + parseFactory(config.getConfigurationSection(key)); + } + + } + + /** + * Parses a single factory and turns it into a factory egg which is add to the + * manager + * + * @param config ConfigurationSection to parse the factory from + * param defaultUpdate default intervall in ticks how often factories update, + * each factory can choose to define an own value or to use + * the default instead + */ + private void parseFactory(ConfigurationSection config) { + IFactoryEgg egg = null; + String type = config.getString("type"); + if (type == null) { + plugin.warning("No type specified for factory at " + config.getCurrentPath() + ". Skipping it."); + return; + } + switch (type) { + case "FCC": // Furnace, chest, craftingtable + case "FCCUPGRADE": + egg = parseFCCFactory(config); + if (egg == null) { + break; + } + upgradeEggs.put(egg.getName(), egg); + manager.addFactoryEgg(FurnCraftChestStructure.class, ((FurnCraftChestEgg) egg).getSetupCost(), egg); + break; + + case "PIPE": + egg = parsePipe(config); + if (egg == null) { + break; + } + ItemMap pipeSetupCost = ConfigHelper.parseItemMap(config.getConfigurationSection("setupcost")); + if (pipeSetupCost.getTotalUniqueItemAmount() > 0) { + manager.addFactoryEgg(PipeStructure.class, pipeSetupCost, egg); + } else { + plugin.warning(String.format("PIPE %s specified with no setup cost, skipping", egg.getName())); + } + break; + case "SORTER": + egg = parseSorter(config); + if (egg == null) { + break; + } + ItemMap sorterSetupCost = ConfigHelper.parseItemMap(config.getConfigurationSection("setupcost")); + if (sorterSetupCost.getTotalUniqueItemAmount() > 0) { + manager.addFactoryEgg(BlockFurnaceStructure.class, sorterSetupCost, egg); + } else { + plugin.warning(String.format("SORTER %s specified with no setup cost, skipping", egg.getName())); + } + break; + default: + plugin.severe("Could not identify factory type " + config.getString("type")); + } + if (egg != null) { + plugin.info("Parsed factory " + egg.getName()); + } else { + plugin.warning(String.format("Failed to set up factory %s", config.getCurrentPath())); + } + + } + + public SorterEgg parseSorter(ConfigurationSection config) { + String name = config.getString("name"); + double returnRate; + if (config.contains("return_rate")) { + returnRate = config.getDouble("return_rate"); + } else { + returnRate = defaultReturnRate; + } + int update; + if (config.contains("updatetime")) { + update = parseTimeAsTicks(config.getString("updatetime")); + } else { + update = defaultUpdateTime; + } + ItemStack fuel; + if (config.contains("fuel")) { + ItemMap tfuel = ConfigHelper.parseItemMap(config.getConfigurationSection("fuel")); + if (tfuel.getTotalUniqueItemAmount() > 0) { + fuel = tfuel.getItemStackRepresentation().get(0); + } else { + plugin.warning("Custom fuel was specified incorrectly for " + name); + fuel = defaultFuel; + } + } else { + fuel = defaultFuel; + } + int fuelIntervall; + if (config.contains("fuel_consumption_intervall")) { + fuelIntervall = parseTimeAsTicks(config.getString("fuel_consumption_intervall")); + } else { + fuelIntervall = defaultFuelConsumptionTime; + } + int sortTime = parseTimeAsTicks(config.getString("sort_time")); + int sortamount = config.getInt("sort_amount"); + int matsPerSide = config.getInt("maximum_materials_per_side"); + return new SorterEgg(name, update, fuel, fuelIntervall, sortTime, matsPerSide, sortamount, returnRate); + } + + public PipeEgg parsePipe(ConfigurationSection config) { + String name = config.getString("name"); + double returnRate; + if (config.contains("return_rate")) { + returnRate = config.getDouble("return_rate"); + } else { + returnRate = defaultReturnRate; + } + int update; + if (config.contains("updatetime")) { + update = parseTimeAsTicks(config.getString("updatetime")); + } else { + update = defaultUpdateTime; + } + ItemStack fuel; + if (config.contains("fuel")) { + ItemMap tfuel = ConfigHelper.parseItemMap(config.getConfigurationSection("fuel")); + if (tfuel.getTotalUniqueItemAmount() > 0) { + fuel = tfuel.getItemStackRepresentation().get(0); + } else { + plugin.warning("Custom fuel was specified incorrectly for " + name); + fuel = defaultFuel; + } + } else { + fuel = defaultFuel; + } + int fuelIntervall; + if (config.contains("fuel_consumption_intervall")) { + fuelIntervall = parseTimeAsTicks(config.getString("fuel_consumption_intervall")); + } else { + fuelIntervall = defaultFuelConsumptionTime; + } + int transferTimeMultiplier = parseTimeAsTicks(config.getString("transfer_time_multiplier")); + int transferAmount = config.getInt("transfer_amount"); + Material pipeType = Material.getMaterial(config.getString("pipe_type")); + int maxLength = config.getInt("maximum_length"); + return new PipeEgg(name, update, fuel, fuelIntervall, null, transferTimeMultiplier, transferAmount, + returnRate, maxLength, pipeType); + } + + public IFactoryEgg parseFCCFactory(ConfigurationSection config) { + String name = config.getString("name"); + double returnRate; + if (config.contains("return_rate")) { + returnRate = config.getDouble("return_rate"); + } else { + returnRate = defaultReturnRate; + } + int update; + if (config.contains("updatetime")) { + update = parseTimeAsTicks(config.getString("updatetime")); + } else { + update = defaultUpdateTime; + } + ItemStack fuel; + if (config.contains("fuel")) { + ItemMap tfuel = ConfigHelper.parseItemMap(config.getConfigurationSection("fuel")); + if (tfuel.getTotalUniqueItemAmount() > 0) { + fuel = tfuel.getItemStackRepresentation().get(0); + } else { + plugin.warning("Custom fuel was specified incorrectly for " + name); + fuel = defaultFuel; + } + } else { + fuel = defaultFuel; + } + int health; + if (config.contains("health")) { + health = config.getInt("health"); + } else { + health = defaultHealth; + } + int fuelIntervall; + if (config.contains("fuel_consumption_intervall")) { + fuelIntervall = parseTimeAsTicks(config.getString("fuel_consumption_intervall")) / 50; + } else { + fuelIntervall = defaultFuelConsumptionTime; + } + long gracePeriod; + if (config.contains("grace_period")) { + // milliseconds + gracePeriod = parseTime(config.getString("grace_period")); + } else { + gracePeriod = defaultBreakGracePeriod; + } + int healthPerDamageIntervall; + if (config.contains("decay_amount")) { + healthPerDamageIntervall = config.getInt("decay_amount"); + } else { + healthPerDamageIntervall = defaultDamagePerBreakPeriod; + } + double citadelBreakReduction = config.getDouble("citadelBreakReduction", 1.0); + ItemMap setupCost = null; + if (config.isConfigurationSection("setupcost")) { + setupCost = ConfigHelper.parseItemMap(config.getConfigurationSection("setupcost")); + } + FurnCraftChestEgg egg = new FurnCraftChestEgg(name, update, null, fuel, fuelIntervall, returnRate, health, + gracePeriod, healthPerDamageIntervall, citadelBreakReduction, setupCost); + recipeLists.put(egg, config.getStringList("recipes")); + return egg; + } + + public void enableFactoryDecay(ConfigurationSection config) { + long interval = parseTimeAsTicks(config.getString("decay_intervall")); + plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, new FactoryGarbageCollector(), interval, + interval); + } + + /** + * Parses a single recipe + * + * @param config ConfigurationSection to parse the recipe from + * @return The recipe created based on the data parse + */ + private IRecipe parseRecipe(ConfigurationSection config) { + IRecipe result; + IRecipe parentRecipe = null; + if (config.isString("inherit") && useYamlIdentifers) { + parentRecipe = recipes.get(config.get("inherit")); + } + String name = config.getString("name", (parentRecipe != null) ? parentRecipe.getName() : null); + if (name == null) { + plugin.warning("No name specified for recipe at " + config.getCurrentPath() + ". Skipping the recipe."); + return null; + } + // we dont inherit identifier, because that would make no sense + String identifier = config.getString("identifier"); + if (identifier == null) { + if (useYamlIdentifers) { + identifier = config.getName(); + } else { + identifier = name; + } + } + String prodTime = config.getString("production_time"); + if (prodTime == null && parentRecipe == null) { + plugin.warning("No production time specied for recipe " + name + ". Skipping it"); + return null; + } + int productionTime; + if (parentRecipe == null) { + productionTime = parseTimeAsTicks(prodTime); + } else { + productionTime = parentRecipe.getProductionTime(); + } + String type = config.getString("type", (parentRecipe != null) ? parentRecipe.getTypeIdentifier() : null); + if (type == null) { + plugin.warning("No type specified for recipe at " + config.getCurrentPath() + ". Skipping the recipe."); + return null; + } + // Force This Recipe to Show Up Even on Existing Factories (idempotently, ish) + boolean forceAddExisting = config.getBoolean("forceInclude", forceIncludeAll); + if (forceAddExisting) { + this.forceRecipes.add(identifier); + } + ConfigurationSection inputSection = config.getConfigurationSection("input"); + ItemMap input; + if (inputSection == null) { + // no input specified, check parent + if (!(parentRecipe instanceof InputRecipe)) { + // default to empty input + input = new ItemMap(); + } else { + input = ((InputRecipe) parentRecipe).getInput(); + } + } else { + input = ConfigHelper.parseItemMap(inputSection); + } + switch (type) { + case "PRODUCTION": + ConfigurationSection outputSection = config.getConfigurationSection("output"); + ItemMap output; + ItemStack recipeRepresentation; + if (outputSection == null) { + if (!(parentRecipe instanceof ProductionRecipe)) { + output = new ItemMap(); + recipeRepresentation = null; + } else { + output = ((ProductionRecipe) parentRecipe).getOutput(); + recipeRepresentation = ((ProductionRecipe) parentRecipe).getRecipeRepresentation(); + } + } else { + output = ConfigHelper.parseItemMap(outputSection); + recipeRepresentation = parseFirstItem(outputSection); + } + ProductionRecipeModifier modi = parseProductionRecipeModifier(config.getConfigurationSection("modi")); + if (modi == null && parentRecipe instanceof ProductionRecipe) { + modi = ((ProductionRecipe) parentRecipe).getModifier().clone(); + } + result = new ProductionRecipe(identifier, name, productionTime, input, output, recipeRepresentation, modi); + break; + case "COMPACT": + String compactedLore = config.getString("compact_lore", + (parentRecipe instanceof CompactingRecipe) ? ((CompactingRecipe) parentRecipe).getCompactedLore() + : null); + if (compactedLore == null) { + plugin.warning("No special lore specified for compaction recipe " + name + " it was skipped"); + result = null; + break; + } + manager.addCompactLore(compactedLore); + List excluded = new LinkedList<>(); + if (config.isList("excluded_materials")) { + for (String mat : config.getStringList("excluded_materials")) { + try { + excluded.add(Material.valueOf(mat)); + } catch (IllegalArgumentException iae) { + plugin.warning(mat + " is not a valid material to exclude: " + config.getCurrentPath()); + } + } + } else { + if (parentRecipe instanceof CompactingRecipe) { + // copy so they are not using same instance + for (Material m : ((CompactingRecipe) parentRecipe).getExcludedMaterials()) { + excluded.add(m); + } + } + // otherwise just leave list empty, as nothing is specified, which is fine + } + result = new CompactingRecipe(identifier, input, excluded, name, productionTime, compactedLore); + break; + case "DECOMPACT": + String decompactedLore = config.getString("compact_lore", + (parentRecipe instanceof DecompactingRecipe) + ? ((DecompactingRecipe) parentRecipe).getCompactedLore() + : null); + if (decompactedLore == null) { + plugin.warning("No special lore specified for decompaction recipe " + name + " it was skipped"); + result = null; + break; + } + manager.addCompactLore(decompactedLore); + result = new DecompactingRecipe(identifier, input, name, productionTime, decompactedLore); + break; + case "REPAIR": + int health = config.getInt("health_gained", + (parentRecipe instanceof RepairRecipe) ? ((RepairRecipe) parentRecipe).getHealth() : 0); + if (health == 0) { + plugin.warning("Health gained from repair recipe " + name + + " is set to or was defaulted to 0, this might not be what was intended"); + } + result = new RepairRecipe(identifier, name, productionTime, input, health); + break; + case "UPGRADE": + String upgradeName = config.getString("factory"); + IFactoryEgg egg; + if (upgradeName == null) { + if (parentRecipe instanceof Upgraderecipe) { + egg = ((Upgraderecipe) parentRecipe).getEgg(); + } else { + egg = null; + } + } else { + egg = upgradeEggs.get(upgradeName); + } + if (egg == null) { + plugin.warning("Could not find factory " + upgradeName + " for upgrade recipe " + name); + result = null; + } else { + result = new Upgraderecipe(identifier, name, productionTime, input, (FurnCraftChestEgg) egg); + } + break; + case "AOEREPAIR": + // This is untested and should not be used for now + plugin.warning( + "This recipe is not tested or even completly developed, use it with great care and don't expect it to work"); + ItemMap tessence = ConfigHelper.parseItemMap(config.getConfigurationSection("essence")); + if (tessence.getTotalUniqueItemAmount() > 0) { + ItemStack essence = tessence.getItemStackRepresentation().get(0); + int repPerEssence = config.getInt("repair_per_essence"); + int range = config.getInt("range"); + result = new AOERepairRecipe(identifier, name, productionTime, essence, range, repPerEssence); + } else { + plugin.severe("No essence specified for AOEREPAIR " + config.getCurrentPath()); + result = null; + } + break; + case "PYLON": + ConfigurationSection outputSec = config.getConfigurationSection("output"); + ItemMap outputMap; + if (outputSec == null) { + if (!(parentRecipe instanceof PylonRecipe)) { + outputMap = new ItemMap(); + } else { + outputMap = ((PylonRecipe) parentRecipe).getOutput().clone(); + } + } else { + outputMap = ConfigHelper.parseItemMap(outputSec); + } + if (outputMap.getTotalItemAmount() == 0) { + plugin.warning("Pylon recipe " + name + " has an empty output specified"); + } + int weight = config.getInt("weight", + (parentRecipe instanceof PylonRecipe) ? ((PylonRecipe) parentRecipe).getWeight() : 20); + result = new PylonRecipe(identifier, name, productionTime, input, outputMap, weight); + break; + case "ENCHANT": + Enchantment enchant; + if (parentRecipe instanceof DeterministicEnchantingRecipe) { + enchant = ((DeterministicEnchantingRecipe) parentRecipe).getEnchant(); + } else { + enchant = Enchantment.getByKey(NamespacedKey.minecraft(config.getString("enchant", ""))); + } + if (enchant == null) { + plugin.warning( + "No enchant specified for deterministic enchanting recipe " + name + ". It was skipped."); + result = null; + break; + } + int level = config.getInt("level", + (parentRecipe instanceof DeterministicEnchantingRecipe) + ? ((DeterministicEnchantingRecipe) parentRecipe).getLevel() + : 1); + ConfigurationSection toolSection = config.getConfigurationSection("enchant_item"); + ItemMap tool; + if (toolSection == null) { + if (!(parentRecipe instanceof DeterministicEnchantingRecipe)) { + tool = new ItemMap(); + } else { + tool = ((DeterministicEnchantingRecipe) parentRecipe).getTool().clone(); + } + } else { + tool = ConfigHelper.parseItemMap(toolSection); + } + if (tool.getTotalItemAmount() == 0) { + plugin.warning("Deterministic enchanting recipe " + name + + " had no tool to enchant specified, it was skipped"); + result = null; + break; + } + result = new DeterministicEnchantingRecipe(identifier, name, productionTime, input, tool, enchant, level); + break; + case "RANDOM": + ConfigurationSection outputSect = config.getConfigurationSection("outputs"); + Map outputs = new HashMap<>(); + ItemMap displayThis = null; + if (outputSect == null) { + if (parentRecipe instanceof RandomOutputRecipe) { + // clone it + for (Entry entry : ((RandomOutputRecipe) parentRecipe).getOutputs().entrySet()) { + outputs.put(entry.getKey().clone(), entry.getValue()); + } + displayThis = ((RandomOutputRecipe) parentRecipe).getDisplayMap(); + } else { + plugin.severe("No outputs specified for random recipe " + name + " it was skipped"); + result = null; + break; + } + } else { + double totalChance = 0.0; + String displayMap = outputSect.getString("display"); + for (String key : outputSect.getKeys(false)) { + ConfigurationSection keySec = outputSect.getConfigurationSection(key); + if (keySec != null) { + double chance = keySec.getDouble("chance"); + totalChance += chance; + ItemMap im = ConfigHelper.parseItemMap(keySec); + outputs.put(im, chance); + if (key.equals(displayMap)) { + displayThis = im; + plugin.debug("Displaying " + displayMap + " as recipe label"); + } + } + } + if (Math.abs(totalChance - 1.0) > 0.0001) { + plugin.warning( + "Sum of output chances for recipe " + name + " is not 1.0. Total sum is: " + totalChance); + } + } + result = new RandomOutputRecipe(identifier, name, productionTime, input, outputs, displayThis); + break; + case "COSTRETURN": + double factor = config.getDouble("factor", + (parentRecipe instanceof FactoryMaterialReturnRecipe) + ? ((FactoryMaterialReturnRecipe) parentRecipe).getFactor() + : 1.0); + result = new FactoryMaterialReturnRecipe(identifier, name, productionTime, input, factor); + break; + case "LOREENCHANT": + ConfigurationSection toolSec = config.getConfigurationSection("loredItem"); + ItemMap toolMap; + if (toolSec == null) { + if (!(parentRecipe instanceof LoreEnchantRecipe)) { + toolMap = new ItemMap(); + } else { + toolMap = ((LoreEnchantRecipe) parentRecipe).getTool().clone(); + } + } else { + toolMap = ConfigHelper.parseItemMap(toolSec); + } + if (toolMap.getTotalItemAmount() == 0) { + plugin.warning("Lore enchanting recipe " + name + " had no tool to enchant specified, it was skipped"); + result = null; + break; + } + List appliedLore = config.getStringList("appliedLore"); + if (appliedLore == null || appliedLore.isEmpty()) { + if (parentRecipe instanceof LoreEnchantRecipe) { + appliedLore = ((LoreEnchantRecipe) parentRecipe).getAppliedLore(); + } else { + plugin.warning("No lore to apply found for lore enchanting recipe " + name + ". It was skipped"); + result = null; + break; + } + } + List overwrittenLore = config.getStringList("overwrittenLore"); + if (overwrittenLore == null || overwrittenLore.isEmpty()) { + if (parentRecipe instanceof LoreEnchantRecipe) { + overwrittenLore = ((LoreEnchantRecipe) parentRecipe).getOverwrittenLore(); + } else { + // having no lore to be overwritten is completly fine + overwrittenLore = new LinkedList<>(); + } + } + result = new LoreEnchantRecipe(identifier, name, productionTime, input, toolMap, appliedLore, + overwrittenLore); + break; + case "RECIPEMODIFIERUPGRADE": + int rank = config.getInt("rank"); + String toUpgrade = config.getString("recipeUpgraded"); + if (toUpgrade == null) { + plugin.warning("No recipe to upgrade specified at " + config.getCurrentPath()); + return null; + } + String followUpRecipe = config.getString("followUpRecipe"); + result = new RecipeScalingUpgradeRecipe(identifier, name, productionTime, input, null, rank, null); + String[] data = { toUpgrade, followUpRecipe }; + recipeScalingUpgradeMapping.put((RecipeScalingUpgradeRecipe) result, data); + break; + case "DUMMY": + result = new DummyParsingRecipe(identifier, name, productionTime, null); + break; + case "PRINTINGPLATE": + ConfigurationSection printingPlateOutputSection = config.getConfigurationSection("output"); + ItemMap printingPlateOutput; + if (printingPlateOutputSection == null) { + if (!(parentRecipe instanceof PrintingPlateRecipe)) { + printingPlateOutput = new ItemMap(); + } else { + printingPlateOutput = ((PrintingPlateRecipe) parentRecipe).getOutput(); + } + } else { + printingPlateOutput = ConfigHelper.parseItemMap(printingPlateOutputSection); + } + result = new PrintingPlateRecipe(identifier, name, productionTime, input, printingPlateOutput); + break; + case "PRINTINGPLATEJSON": + ConfigurationSection printingPlateJsonOutputSection = config.getConfigurationSection("output"); + ItemMap printingPlateJsonOutput; + if (printingPlateJsonOutputSection == null) { + if (!(parentRecipe instanceof PrintingPlateJsonRecipe)) { + printingPlateJsonOutput = new ItemMap(); + } else { + printingPlateJsonOutput = ((PrintingPlateJsonRecipe) parentRecipe).getOutput(); + } + } else { + printingPlateJsonOutput = ConfigHelper.parseItemMap(printingPlateJsonOutputSection); + } + result = new PrintingPlateJsonRecipe(identifier, name, productionTime, input, printingPlateJsonOutput); + break; + case "PRINTBOOK": + ItemMap printBookPlate = ConfigHelper.parseItemMap(config.getConfigurationSection("printingplate")); + int printBookOutputAmount = config.getInt("outputamount", 1); + result = new PrintBookRecipe(identifier, name, productionTime, input, printBookPlate, + printBookOutputAmount); + break; + case "PRINTNOTE": + ItemMap printNotePlate = ConfigHelper.parseItemMap(config.getConfigurationSection("printingplate")); + int printBookNoteAmount = config.getInt("outputamount", 1); + boolean secureNote = config.getBoolean("securenote", false); + String noteTitle = config.getString("title"); + result = new PrintNoteRecipe(identifier, name, productionTime, input, printNotePlate, printBookNoteAmount, + secureNote, noteTitle); + break; + case "WORDBANK": + String key = config.getString("seed", "defaultSeed"); + if ("defaultSeed".equals(key)) { + plugin.getLogger().warning("Word bank recipe is using default seed, this is not secure and allows predicting output"); + } + String path = config.getString("wordListFile", "words.txt"); + List words = loadWordList(path); + if (words == null) { + plugin.severe("Could not load word file " + path); + result = null; + break; + } else { + plugin.info("Loaded " + words.size() + " words for word bank recipe"); + } + List colors = new ArrayList<>(); + if (!config.isList("colors")) { + colors = Arrays.asList(ChatColor.YELLOW, ChatColor.LIGHT_PURPLE, ChatColor.BLUE, ChatColor.RED, + ChatColor.GREEN, ChatColor.AQUA, ChatColor.WHITE); + } else { + for (String colorString : config.getStringList("colors")) { + try { + ChatColor col = ChatColor.valueOf(colorString.toUpperCase()); + colors.add(col); + } catch (IllegalArgumentException e) { + plugin.severe("Could not parse color " + colorString + " at " + config.getCurrentPath()); + result = null; + break; + } + } + } + int wordCount = config.getInt("word_count", 2); + result = new WordBankRecipe(identifier, name, productionTime, key, words, colors, wordCount); + break; + case "PLAYERHEAD": + result = new PlayerHeadRecipe(identifier, name, productionTime, input); + break; + default: + plugin.severe("Could not identify type " + config.getString("type") + " as a valid recipe identifier"); + result = null; + } + if (result != null) { + int interval = 0; + if (config.getString("fuel_consumption_intervall") == null) { + interval = this.defaultFuelConsumptionTime; + } else { + interval = parseTimeAsTicks(config.getString("fuel_consumption_intervall")); + } + ((InputRecipe) result) + .setFuelConsumptionIntervall(interval); + plugin.info("Parsed recipe " + name); + } + return result; + } + + private static ItemStack parseFirstItem(ConfigurationSection config) { + if (config == null) { + return null; + } + + for (String key : config.getKeys(false)) { + ConfigurationSection current = config.getConfigurationSection(key); + List list = ConfigHelper.parseItemMapDirectly(current).getItemStackRepresentation(); + return list.isEmpty() ? null : list.get(0); + } + + return null; + } + + private Map parseRenames(ConfigurationSection config) { + Map renames = new TreeMap<>(); + if (config != null) { + for (String key : config.getKeys(false)) { + String oldName = config.getConfigurationSection(key).getString("oldName"); + if (oldName == null) { + plugin.warning("No old name specified for factory rename at " + + config.getConfigurationSection(key).getCurrentPath()); + } + String newName = config.getConfigurationSection(key).getString("newName"); + if (newName == null) { + plugin.warning("No new name specified for factory rename at " + + config.getConfigurationSection(key).getCurrentPath()); + } + renames.put(oldName, newName); + } + } + return renames; + } + + public void assignRecipesToFactories() { + HashSet usedRecipes = new HashSet<>(); + for (Entry> entry : recipeLists.entrySet()) { + if (entry.getKey() instanceof FurnCraftChestEgg) { + List recipeList = new LinkedList<>(); + for (String recipeName : entry.getValue()) { + IRecipe rec = recipes.get(recipeName); + if (rec instanceof DummyParsingRecipe) { + plugin.warning("You can't add dummy recipes to factories! Maybe you are the dummy here?"); + continue; + } + if (rec != null) { + recipeList.add(rec); + usedRecipes.add(rec); + } else { + plugin.warning("Could not find specified recipe " + recipeName + " for factory " + + entry.getKey().getName()); + } + } + ((FurnCraftChestEgg) entry.getKey()).setRecipes(recipeList); + } + } + for (IRecipe reci : recipes.values()) { + if (!usedRecipes.contains(reci)) { + plugin.warning( + "The recipe " + reci.getName() + " is specified in the config, but not used in any factory"); + } + } + } + + private ProductionRecipeModifier parseProductionRecipeModifier(ConfigurationSection config) { + ProductionRecipeModifier modi = new ProductionRecipeModifier(); + if (config == null) { + return null; + } + for (String key : config.getKeys(false)) { + ConfigurationSection current = config.getConfigurationSection(key); + if (current == null) { + plugin.warning("Found invalid config value at " + config.getCurrentPath() + " " + key + + ". Only identifiers for recipe modifiers allowed at this level"); + continue; + } + int minimumRunAmount = current.getInt("minimumRunAmount"); + int maximumRunAmount = current.getInt("maximumRunAmount"); + double minimumMultiplier = current.getDouble("baseMultiplier"); + double maximumMultiplier = current.getDouble("maximumMultiplier"); + int rank = current.getInt("rank"); + modi.addConfig(minimumRunAmount, maximumRunAmount, minimumMultiplier, maximumMultiplier, rank); + } + return modi; + } + + private void assignRecipeScalingRecipes() { + for (Entry entry : recipeScalingUpgradeMapping.entrySet()) { + IRecipe prod = recipes.get(entry.getValue()[0]); + if (prod == null) { + plugin.warning("The recipe " + entry.getValue()[0] + ", which the recipe " + entry.getKey().getName() + + " is supposed to upgrade doesnt exist"); + continue; + } + if (!(prod instanceof ProductionRecipe)) { + plugin.warning("The recipe " + entry.getKey().getName() + + " has a non production recipe specified as recipe to upgrade, this doesnt work"); + continue; + } + entry.getKey().setUpgradedRecipe((ProductionRecipe) prod); + String followUp = entry.getValue()[1]; + if (followUp != null) { + IRecipe followRecipe = recipes.get(followUp); + if (followRecipe == null) { + plugin.warning("The recipe " + entry.getValue()[0] + ", which the recipe " + + entry.getKey().getName() + " is supposed to use as follow up recipe doesnt exist"); + continue; + } + if (!(followRecipe instanceof RecipeScalingUpgradeRecipe)) { + plugin.warning("The recipe " + entry.getKey().getName() + + " has a non recipe scaling upgrade recipe specified as recipe to follow up with, this doesnt work"); + continue; + } + entry.getKey().setFollowUpRecipe((RecipeScalingUpgradeRecipe) followRecipe); + } + } + } + + private List loadWordList(String fileName) { + File file = new File(plugin.getDataFolder(), fileName); + List result = new ArrayList<>(); + boolean parsingYet = false; + try { + for (String line : Files.readAllLines(file.toPath())) { + if (!parsingYet) { + if (line.startsWith("---")) { + parsingYet = true; + } + } else { + result.add(WordUtils.capitalize(line.trim())); + } + } + } catch (IOException e) { + plugin.getLogger().severe("Failed to load file: " + e.getMessage()); + return null; + } + if (result.isEmpty()) { + return null; + } + return result; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/FactoryMod.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/FactoryMod.java new file mode 100644 index 000000000..895202dfa --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/FactoryMod.java @@ -0,0 +1,60 @@ +package com.github.igotyou.FactoryMod; + +import com.github.igotyou.FactoryMod.commands.FMCommandManager; +import com.github.igotyou.FactoryMod.listeners.CitadelListener; +import com.github.igotyou.FactoryMod.listeners.CompactItemListener; +import com.github.igotyou.FactoryMod.listeners.FactoryModListener; +import com.github.igotyou.FactoryMod.utility.FactoryModPermissionManager; +import vg.civcraft.mc.civmodcore.ACivMod; + +public class FactoryMod extends ACivMod { + private FactoryModManager manager; + private static FactoryMod plugin; + private FactoryModPermissionManager permissionManager; + private FMCommandManager commandManager; + + @Override + public void onEnable() { + super.onEnable(); + plugin = this; + ConfigParser cp = new ConfigParser(this); + manager = cp.parse(); + manager.loadFactories(); + if (manager.isCitadelEnabled()) { + permissionManager = new FactoryModPermissionManager(); + } + commandManager = new FMCommandManager(this); + registerListeners(); + info("Successfully enabled"); + } + + @Override + public void onDisable() { + manager.shutDown(); + plugin.info("Shutting down"); + } + + public FactoryModManager getManager() { + return manager; + } + + public static FactoryMod getInstance() { + return plugin; + } + + public FactoryModPermissionManager getPermissionManager() { + return permissionManager; + } + + private void registerListeners() { + plugin.getServer().getPluginManager() + .registerEvents(new FactoryModListener(manager), plugin); + plugin.getServer() + .getPluginManager() + .registerEvents( + new CompactItemListener(), plugin); + if (manager.isCitadelEnabled()) { + plugin.getServer().getPluginManager().registerEvents(new CitadelListener(), plugin); + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/FactoryModManager.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/FactoryModManager.java new file mode 100644 index 000000000..affa8dead --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/FactoryModManager.java @@ -0,0 +1,583 @@ +package com.github.igotyou.FactoryMod; + +import com.github.igotyou.FactoryMod.eggs.FurnCraftChestEgg; +import com.github.igotyou.FactoryMod.eggs.IFactoryEgg; +import com.github.igotyou.FactoryMod.eggs.PipeEgg; +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.recipes.IRecipe; +import com.github.igotyou.FactoryMod.recipes.Upgraderecipe; +import com.github.igotyou.FactoryMod.structures.BlockFurnaceStructure; +import com.github.igotyou.FactoryMod.structures.FurnCraftChestStructure; +import com.github.igotyou.FactoryMod.structures.MultiBlockStructure; +import com.github.igotyou.FactoryMod.structures.PipeStructure; +import com.github.igotyou.FactoryMod.utility.FactoryModGUI; +import com.github.igotyou.FactoryMod.utility.FileHandler; +import com.github.igotyou.FactoryMod.utility.LoggingUtils; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Chest; +import org.bukkit.block.Dispenser; +import org.bukkit.block.Dropper; +import org.bukkit.entity.Player; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; + +/** + * Manager class which handles all factories, their locations and their creation + * + */ +public class FactoryModManager { + private FactoryMod plugin; + private FileHandler fileHandler; + private HashMap, HashMap> factoryCreationRecipes; + private HashMap totalSetupCosts; + private HashMap locations; + private HashMap eggs; + private HashSet factories; + private Map recipes; + private HashSet possibleCenterBlocks; + private HashSet possibleInteractionBlock; + private Material factoryInteractionMaterial; + private boolean citadelEnabled; + private boolean logInventories; + private int redstonePowerOn; + private int redstoneRecipeChange; + private int maxInputChests; + private int maxOutputChests; + private int maxFuelChests; + private int maxTotalIOFChests; + private Set compactLore; + private Set forceInclude; + private FactoryModPlayerSettings playerSettings; + + public FactoryModManager(FactoryMod plugin, Material factoryInteractionMaterial, boolean citadelEnabled, + boolean nameLayerEnabled, int redstonePowerOn, int redstoneRecipeChange, boolean logInventories, + int maxInputChests, int maxOutputChests, int maxFuelChests, int maxTotalIOFChests, + Map factoryRenames) { + this.plugin = plugin; + this.factoryInteractionMaterial = factoryInteractionMaterial; + this.citadelEnabled = citadelEnabled; + this.redstonePowerOn = redstonePowerOn; + this.redstoneRecipeChange = redstoneRecipeChange; + this.maxInputChests = maxInputChests; + this.maxOutputChests = maxOutputChests; + this.maxFuelChests = maxFuelChests; + this.maxTotalIOFChests = maxTotalIOFChests; + this.fileHandler = new FileHandler(this, factoryRenames); + + factoryCreationRecipes = new HashMap<>(); + locations = new HashMap<>(); + eggs = new HashMap<>(); + possibleCenterBlocks = new HashSet<>(); + possibleInteractionBlock = new HashSet<>(); + factories = new HashSet<>(); + totalSetupCosts = new HashMap<>(); + recipes = new HashMap<>(); + compactLore = new HashSet<>(); + forceInclude = new HashSet<>(); + playerSettings = new FactoryModPlayerSettings(plugin); + + // Normal furnace, craftingtable, chest factories + possibleCenterBlocks.add(Material.CRAFTING_TABLE); + possibleInteractionBlock.add(Material.CRAFTING_TABLE); + possibleInteractionBlock.add(Material.FURNACE); + possibleInteractionBlock.add(Material.CHEST); + possibleInteractionBlock.add(Material.TRAPPED_CHEST); + + // sorter + possibleCenterBlocks.add(Material.DROPPER); + possibleInteractionBlock.add(Material.DROPPER); + + // pipe + possibleCenterBlocks.add(Material.DISPENSER); + possibleInteractionBlock.add(Material.DISPENSER); + } + + /** + * Sets the lore used for compacting recipes. This is needed for the compact + * item listeners + * + * @param lore Lore used for compacting items + */ + public void addCompactLore(String lore) { + compactLore.add(lore); + } + + public boolean logInventories() { + return logInventories; + } + + /** + * @return Lore given to compacted items + */ + public boolean isCompactLore(String lore) { + return compactLore.contains(lore); + } + + /** + * Gets the setupcost for a specific factory + * + * @param c Class of the structure type the factory is using + * @param name Name of the factory + * @return Setupcost if the factory if it was found or null if it wasnt + */ + public ItemMap getSetupCost(Class c, String name) { + for (Entry entry : factoryCreationRecipes.get(c).entrySet()) { + if (entry.getValue().getName().equals(name)) { + return entry.getKey(); + } + } + return null; + } + + /** + * Adds a factory and the locations of its blocks to the manager + * + * @param f Factory to add + */ + public void addFactory(Factory f) { + factories.add(f); + for (Location loc : f.getMultiBlockStructure().getAllBlocks()) { + locations.put(loc, f); + } + } + + /** + * @return Whether citadel is enabled on the server + */ + public boolean isCitadelEnabled() { + return citadelEnabled; + } + + public int getMaxInputChests() { + return maxInputChests; + } + + public int getMaxOutputChests() { + return maxOutputChests; + } + + public int getMaxFuelChests() { + return maxFuelChests; + } + + /** + * @return The maximum total number of inputs, outputs, and fuel inputs a factory has. A chest with multiple IOF + * settings enabled is counted for each. + */ + public int getMaxTotalIOFChests() { + return maxTotalIOFChests; + } + + /** + * @return Which material is used to interact with factories, stick by default + */ + public Material getFactoryInteractionMaterial() { + return factoryInteractionMaterial; + } + + /** + * Removes a factory from the manager + * + * @param f Factory to remove + */ + public void removeFactory(Factory f) { + if (f.isActive()) { + f.deactivate(); + } + factories.remove(f); + FurnCraftChestFactory.removePylon(f); + for (Location b : f.getMultiBlockStructure().getAllBlocks()) { + locations.remove(b); + } + } + + /** + * Tries to get the factory which has a part at the given location + * + * @param loc Location which is supposed to be part of a factory + * @return The factory which had a block at the given location or null if there + * was no factory + */ + public Factory getFactoryAt(Location loc) { + return getFactoryAt(loc.getBlock()); + } + + /** + * Tries to get the factory which has a part at the given block + * + * @param b Block which is supposed to be part of a factory + * @return The factory which had a block at the given location or null if there + * was no factory + */ + public Factory getFactoryAt(Block b) { + return locations.get(b.getLocation()); + } + + /** + * Checks whether a part of a factory is at the given location + * + * @param loc Location to check + * @return True if there is a factory block, false if not + */ + public boolean factoryExistsAt(Location loc) { + return getFactoryAt(loc) != null; + } + + /** + * Attempts to create a factory with the given block as new center block. If all + * blocks for a specific structure are there and other conditions needed for the + * factory type are fullfilled, the factory is created and added to the manager + * + * @param b Center block + * @param p Player attempting to create the factory + */ + public void attemptCreation(Block b, Player p) { + // this method should probably be taken apart and the individual logic should be + // exported in + // a class that fits each factory type + if (!factoryExistsAt(b.getLocation())) { + // Cycle through possible structures here + if (b.getType() == Material.CRAFTING_TABLE) { + FurnCraftChestStructure fccs = new FurnCraftChestStructure(b); + if (fccs.isComplete()) { + if (fccs.blockedByExistingFactory()) { + p.sendMessage(ChatColor.RED + + "At least one of the blocks of this factory is already part of another factory"); + return; + } + HashMap eggs = factoryCreationRecipes.get(FurnCraftChestStructure.class); + if (eggs != null) { + IFactoryEgg egg = null; + for (Entry entry : eggs.entrySet()) { + if (entry.getKey() + .containedExactlyIn(((Chest) (fccs.getChest().getState())).getInventory())) { + egg = entry.getValue(); + break; + } + } + if (egg != null) { + Factory f = egg.hatch(fccs, p); + if (f != null) { + // Trigger lazy-initialize default crafting table IOSelector + ((FurnCraftChestFactory)f).getTableIOSelector(); + ((Chest) (fccs.getChest().getState())).getInventory().clear(); + addFactory(f); + p.sendMessage(ChatColor.GREEN + "Successfully created " + f.getName()); + LoggingUtils.log(f.getLogData() + " was created by " + p.getName()); + } + } else { + p.sendMessage(ChatColor.RED + "There is no factory with the given creation materials"); + FactoryModGUI gui = new FactoryModGUI(p); + gui.showFactoryOverview(true); + } + } + return; + } + } + if (b.getType() == Material.DISPENSER) { + PipeStructure ps = new PipeStructure(b); + if (ps.isComplete()) { + if (ps.blockedByExistingFactory()) { + p.sendMessage(ChatColor.RED + + "At least one of the blocks of this factory is already part of another factory"); + return; + } + HashMap eggs = factoryCreationRecipes.get(PipeStructure.class); + if (eggs != null) { + IFactoryEgg egg = null; + for (Entry entry : eggs.entrySet()) { + if (entry.getKey() + .containedExactlyIn((((Dispenser) (ps.getStart().getState())).getInventory()))) { + egg = entry.getValue(); + break; + } + } + if (egg != null) { + if (ps.getPipeType() != ((PipeEgg) egg).getPipeType()) { + p.sendMessage(ChatColor.RED + "You dont have the right block for this pipe"); + return; + } + if (ps.getLength() > ((PipeEgg) egg).getMaximumLength()) { + p.sendMessage(ChatColor.RED + "You cant make pipes of this type, which are that long"); + return; + } + Factory f = egg.hatch(ps, p); + if (f != null) { + ((Dispenser) (ps.getStart().getState())).getInventory().clear(); + addFactory(f); + p.sendMessage(ChatColor.GREEN + "Successfully created " + f.getName()); + LoggingUtils.log(f.getLogData() + " was created by " + p.getName()); + } + + } else { + p.sendMessage(ChatColor.RED + "There is no pipe with the given creation materials"); + } + } + return; + } else { + p.sendMessage(ChatColor.RED + "This pipe is not set up the right way"); + } + } + if (b.getType() == Material.DROPPER) { + BlockFurnaceStructure bfs = new BlockFurnaceStructure(b); + if (bfs.isComplete()) { + if (bfs.blockedByExistingFactory()) { + p.sendMessage(ChatColor.RED + + "At least one of the blocks of this factory is already part of another factory"); + return; + } + HashMap eggs = factoryCreationRecipes.get(BlockFurnaceStructure.class); + if (eggs != null) { + IFactoryEgg egg = null; + for (Entry entry : eggs.entrySet()) { + if (entry.getKey().containedExactlyIn( + ((Dropper) (bfs.getCenter().getBlock().getState())).getInventory())) { + egg = entry.getValue(); + break; + } + } + if (egg != null) { + Factory f = egg.hatch(bfs, p); + if (f != null) { + ((Dropper) (bfs.getCenter().getBlock().getState())).getInventory().clear(); + addFactory(f); + p.sendMessage(ChatColor.GREEN + "Successfully created " + f.getName()); + LoggingUtils.log(f.getLogData() + " was created by " + p.getName()); + } + + } else { + p.sendMessage(ChatColor.RED + "There is no sorter with the given creation materials"); + } + } + } else { + p.sendMessage(ChatColor.RED + "This sorter is not set up the right way"); + } + } + } + } + + public void calculateTotalSetupCosts() { + for (HashMap maps : factoryCreationRecipes.values()) { + for (Entry entry : maps.entrySet()) { + totalSetupCosts.put(entry.getValue(), entry.getKey()); + } + } + for (IFactoryEgg egg : this.eggs.values()) { + totalSetupCosts.put(egg, calculateTotalSetupCost(egg)); + } + } + + private ItemMap calculateTotalSetupCost(IFactoryEgg egg) { + ItemMap map = null; + map = totalSetupCosts.get(egg); + if (map != null) { + return map; + } + for (IFactoryEgg superEgg : this.eggs.values()) { + if (superEgg instanceof FurnCraftChestEgg) { + for (IRecipe recipe : ((FurnCraftChestEgg) superEgg).getRecipes()) { + if (recipe instanceof Upgraderecipe && ((Upgraderecipe) recipe).getEgg() == egg) { + map = calculateTotalSetupCost(superEgg); + if (map == null) { + plugin.warning("Could not calculate total setupcost for " + egg.getName() + + ". It's parent factory " + superEgg.getName() + " is impossible to set up"); + break; + } + map = map.clone(); // so we dont mess with the original + // setup costs + map.merge(((Upgraderecipe) recipe).getInput()); + return map; + } + } + + } + } + return map; + } + + /** + * Gets all the factories within a certain range of a given location + * + * @param l Location on which the search is centered + * @param range maximum distance from the center allowed + * @return All of the factories which are less or equal than the given range + * away from the given location + */ + public List getNearbyFactories(Location l, int range) { + List facs = new LinkedList<>(); + for (Factory f : factories) { + if (f.getMultiBlockStructure().getCenter().distance(l) <= range) { + facs.add(f); + } + } + return facs; + } + + public ItemMap getTotalSetupCost(Factory f) { + return getTotalSetupCost(getEgg(f.getName())); + } + + public ItemMap getTotalSetupCost(IFactoryEgg e) { + return totalSetupCosts.get(e); + } + + /** + * Adds a factory egg to the manager and associates it with a specific setup + * cost in items and a specific MultiBlockStructure which is the physical + * representation of the factory created by the egg. See the docu for the eggs + * for more info on those + * + * @param blockStructureClass Class inheriting from MultiBlockStructure, which + * physically represents the factories created by the + * egg + * @param recipe Item cost to create the factory + * @param egg Encapsulates the factory itself + */ + public void addFactoryEgg(Class blockStructureClass, ItemMap recipe, + IFactoryEgg egg) { + if (recipe != null) { + HashMap eggs = factoryCreationRecipes.computeIfAbsent(blockStructureClass, + a -> new HashMap()); + eggs.put(recipe, egg); + } + this.eggs.put(egg.getName().toLowerCase(), egg); + } + + public void saveFactories() { + plugin.info("Attempting to save factory data"); + fileHandler.save(getAllFactories()); + } + + public void loadFactories() { + plugin.info("Attempting to load factory data"); + fileHandler.load(eggs); + } + + /** + * Called when the plugin is deactivated to first save all factories and then + * deactivate them, so the deactivated block state is saved + */ + public void shutDown() { + saveFactories(); + for (Factory f : factories) { + f.deactivate(); + } + } + + /** + * Checks whether a specific material is a possible center block for a factory + * and whether a factory could potentionally created from a block with this + * material + * + * @param m Material to check + * @return true if the material could be the one of a possible center block, + * false if not + */ + public boolean isPossibleCenterBlock(Material m) { + return possibleCenterBlocks.contains(m); + } + + /** + * Checks whether the given material is an interaction material and whether a + * reaction should be tried to get when one of those blocks is part of a factory + * and interacted with + * + * @param m Material to check + * @return True if the material is a possible interaction material, false if not + */ + public boolean isPossibleInteractionBlock(Material m) { + return possibleInteractionBlock.contains(m); + } + + /** + * Gets a specific factory egg based on it's name + * + * @param name Name of the egg + * @return The egg with the given name or null if no such egg exists + */ + public IFactoryEgg getEgg(String name) { + return eggs.get(name.toLowerCase()); + } + + /** + * Gets the Redstone power level necessary to active a factory. Fall below this + * level and the factory will deactivate. + * + * @return The power level on which factory activation or de-activation hinges + */ + public int getRedstonePowerOn() { + return this.redstonePowerOn; + } + + /** + * Gets the Redstone power change necessary to alter the recipe setting of a + * factory. Any change {@code >=} this level, either positive or negative, will attempt + * to alter the recipe (implementation depending). + * + * @return The amount of Redstone power change necessary to alter recipe setting + * of a factory. + */ + public int getRedstoneRecipeChange() { + return this.redstoneRecipeChange; + } + + /** + * Gets all factories which currently exist. Do not mess with the hashset + * returned as it is used in other places + * + * @return All existing factory instances + */ + public HashSet getAllFactories() { + synchronized (factories) { + return new HashSet<>(factories); + } + } + + /** + * Gets the recipe with the given identifier, if it exists + * + * @param identifier Identifier of the recipe + * @return Recipe with the given identifier or null if either the recipe doesn't + * exist or the given string was null + */ + public IRecipe getRecipe(String identifier) { + if (identifier == null) { + return null; + } + return recipes.get(identifier); + } + + /** + * Registers a recipe and add it to the recipe tracking. + */ + public void registerRecipe(IRecipe recipe) { + recipes.put(recipe.getIdentifier(), recipe); + } + + public void setForceInclude(HashSet forceRecipes) { + this.forceInclude.addAll(forceRecipes); + } + + public boolean isForceInclude(String identifier) { + return this.forceInclude.contains(identifier); + } + + public Collection getAllFactoryEggs() { + return eggs.values(); + } + + public FactoryModPlayerSettings getPlayerSettings() { + return playerSettings; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/FactoryModPlayerSettings.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/FactoryModPlayerSettings.java new file mode 100644 index 000000000..4408fa49b --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/FactoryModPlayerSettings.java @@ -0,0 +1,68 @@ +package com.github.igotyou.FactoryMod; + +import java.util.UUID; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.players.settings.impl.EnumSetting; + +public class FactoryModPlayerSettings { + + private final FactoryMod plugin; + EnumSetting ioDirectionSetting; + + public FactoryModPlayerSettings(FactoryMod plugin) { + this.plugin = plugin; + initSettings(); + } + + private void initSettings() { + MenuSection menu = PlayerSettingAPI.getMainMenu().createMenuSection( + "FactoryMod", + "FactoryMod settings", + new ItemStack(Material.FURNACE) + ); + + ioDirectionSetting = new EnumSetting<>( + plugin, + IoConfigDirectionMode.VISUAL_RELATIVE, + "Factory IOConfig Mode", + "ioconfig_visual_mode", + new ItemStack(Material.HOPPER), + "Change how the factory IO config appears", + true, + IoConfigDirectionMode.class + ); + PlayerSettingAPI.registerSetting(ioDirectionSetting, menu); + } + + public IoConfigDirectionMode getIoDirectionMode(UUID id) { + return ioDirectionSetting.getValue(id); + } + + public enum IoConfigDirectionMode { + + VISUAL_RELATIVE( + "Relative Directions", + new String[] { + "The furnace shows the", + "front of the factory." + }), + CARDINAL( + "Cardinal Directions", + new String[] { + "Cardinals (for those too", + "familiar with map mods.)" + }); + + public final String simpleDescription; + public final String[] fullDescription; + + private IoConfigDirectionMode(String simpleDescription, String[] fullDescription) { + this.simpleDescription = simpleDescription; + this.fullDescription = fullDescription; + } + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/CheatOutput.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/CheatOutput.java new file mode 100644 index 000000000..6a66fd6d0 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/CheatOutput.java @@ -0,0 +1,51 @@ +package com.github.igotyou.FactoryMod.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.FactoryModManager; +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.recipes.IRecipe; +import com.github.igotyou.FactoryMod.recipes.ProductionRecipe; +import java.util.List; +import java.util.Set; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class CheatOutput extends BaseCommand { + + @CommandAlias("fmco") + @CommandPermission("fm.op") + @Description("Gives you the output of the selected recipe in the factory you are looking at") + public void execute(Player sender) { + Set transparent = null; + List view = ((Player) sender).getLineOfSight(transparent, 10); + FactoryModManager manager = FactoryMod.getInstance().getManager(); + Factory exis = manager.getFactoryAt(view.get(view.size() - 1)); + if (exis != null && exis instanceof FurnCraftChestFactory) { + FurnCraftChestFactory fcc = (FurnCraftChestFactory) exis; + if (fcc.getCurrentRecipe() == null) { + sender.sendMessage(ChatColor.RED + "This factory has no recipe selected"); + return; + } + IRecipe rec = fcc.getCurrentRecipe(); + if (!(rec instanceof ProductionRecipe)) { + sender.sendMessage(ChatColor.RED + "The selected recipe is not a production recipe"); + return; + } + ProductionRecipe prod = (ProductionRecipe) rec; + for (ItemStack is : prod.getOutput().getItemStackRepresentation()) { + sender.getInventory().addItem(is); + } + sender.sendMessage(ChatColor.GREEN + "Gave you all items for recipe " + ChatColor.GREEN + prod.getName()); + } else { + sender.sendMessage(ChatColor.RED + "You are not looking at a valid factory"); + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/Create.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/Create.java new file mode 100644 index 000000000..a60eda36d --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/Create.java @@ -0,0 +1,107 @@ +package com.github.igotyou.FactoryMod.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Syntax; +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.FactoryModManager; +import com.github.igotyou.FactoryMod.eggs.FurnCraftChestEgg; +import com.github.igotyou.FactoryMod.eggs.IFactoryEgg; +import com.github.igotyou.FactoryMod.eggs.PipeEgg; +import com.github.igotyou.FactoryMod.eggs.SorterEgg; +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.structures.BlockFurnaceStructure; +import com.github.igotyou.FactoryMod.structures.FurnCraftChestStructure; +import com.github.igotyou.FactoryMod.structures.PipeStructure; +import java.util.List; +import java.util.Set; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Chest; +import org.bukkit.block.Furnace; +import org.bukkit.entity.Player; + +public class Create extends BaseCommand { + + @CommandAlias("fmc") + @CommandPermission("fm.op") + @Syntax("") + @Description("Creates a factory at the blocks you are looking at") + @CommandCompletion("@FM_Factories") + public void execute(Player sender, String factoryName) { + FactoryModManager manager = FactoryMod.getInstance().getManager(); + IFactoryEgg egg = manager.getEgg(factoryName); + if (egg == null) { + sender.sendMessage(ChatColor.RED + "This factory does not exist"); + return; + } + Set transparent = null; + List view = sender.getLineOfSight(transparent, 10); + Factory exis = manager.getFactoryAt(view.get(view.size() - 1)); + if (exis != null) { + manager.removeFactory(exis); + } + if (egg instanceof FurnCraftChestEgg) { + FurnCraftChestEgg fcce = (FurnCraftChestEgg) egg; + if (view.get(view.size() - 1).getType() == Material.CRAFTING_TABLE) { + FurnCraftChestStructure fccs = new FurnCraftChestStructure(view.get(view.size() - 1)); + if (!fccs.isComplete()) { + sender.sendMessage( + ChatColor.RED + "The required block structure for this factory doesn't exist here"); + return; + } + BlockState chestBS = fccs.getChest().getState(); + if (((Chest) chestBS).getCustomName() == null) { + ((Chest) chestBS).setCustomName(fcce.getName()); + chestBS.update(true); + } + BlockState furnaceBS = fccs.getFurnace().getState(); + if (((Furnace) furnaceBS).getCustomName() == null) { + ((Furnace) furnaceBS).setCustomName(fcce.getName()); + furnaceBS.update(true); + } + manager.addFactory(fcce.hatch(fccs, (Player) sender)); + sender.sendMessage(ChatColor.GREEN + "Created " + egg.getName()); + } else { + sender.sendMessage(ChatColor.RED + "You are not looking at the right block for this factory"); + } + return; + } + if (egg instanceof PipeEgg) { + PipeEgg fcce = (PipeEgg) egg; + if (view.get(view.size() - 1).getType() == Material.DISPENSER) { + PipeStructure fccs = new PipeStructure(view.get(view.size() - 1)); + if (!fccs.isComplete()) { + sender.sendMessage( + ChatColor.RED + "The required block structure for this factory doesn't exist here"); + return; + } + manager.addFactory(fcce.hatch(fccs, (Player) sender)); + sender.sendMessage(ChatColor.GREEN + "Created " + egg.getName()); + } else { + sender.sendMessage(ChatColor.RED + "You are not looking at the right block for this factory"); + } + return; + } + if (egg instanceof SorterEgg) { + SorterEgg fcce = (SorterEgg) egg; + if (view.get(view.size() - 1).getType() == Material.DROPPER) { + BlockFurnaceStructure fccs = new BlockFurnaceStructure(view.get(view.size() - 1)); + if (!fccs.isComplete()) { + sender.sendMessage( + ChatColor.RED + "The required block structure for this factory doesn't exist here"); + return; + } + manager.addFactory(fcce.hatch(fccs, (Player) sender)); + sender.sendMessage(ChatColor.GREEN + "Created " + egg.getName()); + } else { + sender.sendMessage(ChatColor.RED + "You are not looking at the right block for this factory"); + } + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/FMCommandManager.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/FMCommandManager.java new file mode 100644 index 000000000..f2a7112b7 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/FMCommandManager.java @@ -0,0 +1,33 @@ +package com.github.igotyou.FactoryMod.commands; + +import co.aikar.commands.BukkitCommandCompletionContext; +import co.aikar.commands.CommandCompletions; +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.eggs.IFactoryEgg; +import javax.annotation.Nonnull; +import org.bukkit.plugin.Plugin; +import vg.civcraft.mc.civmodcore.commands.CommandManager; + +public class FMCommandManager extends CommandManager { + + public FMCommandManager(Plugin plugin) { + super(plugin); + init(); + } + + @Override + public void registerCommands() { + registerCommand(new CheatOutput()); + registerCommand(new Create()); + registerCommand(new FactoryMenu()); + registerCommand(new ItemUseMenu()); + registerCommand(new RunAmountSetterCommand()); + } + + @Override + public void registerCompletions(@Nonnull CommandCompletions completions) { + super.registerCompletions(completions); + completions.registerCompletion("FM_Factories", (context) -> FactoryMod.getInstance().getManager().getAllFactoryEggs().stream().map( + IFactoryEgg::getName).toList()); + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/FactoryMenu.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/FactoryMenu.java new file mode 100644 index 000000000..929040cf6 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/FactoryMenu.java @@ -0,0 +1,37 @@ +package com.github.igotyou.FactoryMod.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Syntax; +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.eggs.FurnCraftChestEgg; +import com.github.igotyou.FactoryMod.eggs.IFactoryEgg; +import com.github.igotyou.FactoryMod.utility.FactoryModGUI; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public class FactoryMenu extends BaseCommand { + + @CommandAlias("fm") + @Syntax("[factory]") + @Description("Opens a GUI allowing you to browse through all factories") + @CommandCompletion("@FM_Factories") + public void execute(Player sender, @Optional String factoryName) { + if (factoryName == null) { + FactoryModGUI gui = new FactoryModGUI(sender); + gui.showFactoryOverview(true); + } else { + IFactoryEgg egg = FactoryMod.getInstance().getManager().getEgg(factoryName); + if (egg == null) { + sender.sendMessage(ChatColor.RED + "The factory " + factoryName + " does not exist"); + return; + } + FactoryModGUI gui = new FactoryModGUI(sender); + gui.showForFactory((FurnCraftChestEgg) egg); + } + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/ItemUseMenu.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/ItemUseMenu.java new file mode 100644 index 000000000..ba375f16c --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/ItemUseMenu.java @@ -0,0 +1,35 @@ +package com.github.igotyou.FactoryMod.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Syntax; +import com.github.igotyou.FactoryMod.utility.ItemUseGUI; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class ItemUseMenu extends BaseCommand { + + @CommandAlias("item") + @Syntax("[material]") + @Description("Opens a GUI allowing you to browse all recipes which use or output the item in your main hand") + @CommandCompletion("@materials") + public void execute(Player p, @Optional String material) { + if (material == null) { + ItemUseGUI gui = new ItemUseGUI(p); + gui.showItemOverview(p.getInventory().getItemInMainHand()); + } else { + Material mat = Material.getMaterial(material); + if (mat == null) { + p.sendMessage(ChatColor.RED + "The item " + material + " does not exist"); + return; + } + ItemUseGUI gui = new ItemUseGUI(p); + gui.showItemOverview(new ItemStack(mat)); + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/RunAmountSetterCommand.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/RunAmountSetterCommand.java new file mode 100644 index 000000000..ed4af0cb2 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/commands/RunAmountSetterCommand.java @@ -0,0 +1,43 @@ +package com.github.igotyou.FactoryMod.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Syntax; +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.FactoryModManager; +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import java.util.Set; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; + +public class RunAmountSetterCommand extends BaseCommand { + + @CommandAlias("fmsrc") + @Syntax("") + @Description("Sets the amount of runs for the currently selected recipe in the factory you are looking at") + @CommandPermission("fm.op") + public void execute(Player sender, String runCount) { + int newAmount; + try { + newAmount = Integer.parseInt(runCount); + } + catch(NumberFormatException e) { + sender.sendMessage(ChatColor.RED + runCount + " is not a number"); + return; + } + FactoryModManager manager = FactoryMod.getInstance().getManager(); + for(Block b : sender.getLineOfSight((Set )null, 15)) { + Factory f = manager.getFactoryAt(b); + if (f instanceof FurnCraftChestFactory) { + FurnCraftChestFactory fccf = (FurnCraftChestFactory) f; + fccf.setRunCount(fccf.getCurrentRecipe(), newAmount); + sender.sendMessage(ChatColor.GREEN + "Set runcount for recipe " + fccf.getCurrentRecipe().getName() + " in " + fccf.getName() + " to "+ newAmount); + } + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/eggs/FurnCraftChestEgg.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/eggs/FurnCraftChestEgg.java new file mode 100644 index 000000000..84f956c70 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/eggs/FurnCraftChestEgg.java @@ -0,0 +1,166 @@ +package com.github.igotyou.FactoryMod.eggs; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.interactionManager.FurnCraftChestInteractionManager; +import com.github.igotyou.FactoryMod.powerManager.FurnacePowerManager; +import com.github.igotyou.FactoryMod.recipes.IRecipe; +import com.github.igotyou.FactoryMod.repairManager.PercentageHealthRepairManager; +import com.github.igotyou.FactoryMod.structures.FurnCraftChestStructure; +import com.github.igotyou.FactoryMod.structures.MultiBlockStructure; +import java.util.ArrayList; +import java.util.List; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; + +public class FurnCraftChestEgg implements IFactoryEgg { + private String name; + private int updateTime; + private List recipes; + private ItemStack fuel; + private int fuelConsumptionIntervall; + private int maximumHealth; + private long breakGracePeriod; + private int healthPerDamagePeriod; + private double returnRateOnDestruction; + private double citadelBreakReduction; + private ItemMap setupCost; + + public FurnCraftChestEgg(String name, int updateTime, + List recipes, ItemStack fuel, + int fuelConsumptionIntervall, double returnRateOnDestruction, int maximumHealth, long breakGracePeriod, int healthPerDamagePeriod, double citadelBreakReduction, ItemMap setupCost) { + this.name = name; + this.updateTime = updateTime; + this.recipes = recipes; + this.fuel = fuel; + this.breakGracePeriod = breakGracePeriod; + this.healthPerDamagePeriod = healthPerDamagePeriod; + this.fuelConsumptionIntervall = fuelConsumptionIntervall; + this.returnRateOnDestruction = returnRateOnDestruction; + this.maximumHealth = maximumHealth; + this.citadelBreakReduction = citadelBreakReduction; + this.setupCost = setupCost; + } + + @Override + public Factory hatch(MultiBlockStructure mbs, Player p) { + FurnCraftChestStructure fccs = (FurnCraftChestStructure) mbs; + FurnacePowerManager fpm = new FurnacePowerManager(fccs.getFurnace(), + fuel, fuelConsumptionIntervall); + FurnCraftChestInteractionManager fccim = new FurnCraftChestInteractionManager(); + PercentageHealthRepairManager phrm = new PercentageHealthRepairManager(maximumHealth, maximumHealth, 0, healthPerDamagePeriod, breakGracePeriod); + FurnCraftChestFactory fccf = new FurnCraftChestFactory(fccim, phrm, + fpm, fccs, updateTime, name, recipes, citadelBreakReduction); + fccim.setFactory(fccf); + phrm.setFactory(fccf); + if (!recipes.isEmpty()) { + fccf.setRecipe(recipes.get(0)); + } + return fccf; + } + + @Override + public String getName() { + return name; + } + + public int getUpdateTime() { + return updateTime; + } + + public ItemStack getFuel() { + return fuel; + } + + public List getRecipes() { + return recipes; + } + + public void setRecipes(List recipes) { + this.recipes = recipes; + } + + @Override + public double getReturnRate() { + return returnRateOnDestruction; + } + + public int getFuelConsumptionIntervall() { + return fuelConsumptionIntervall; + } + + public int getMaximumHealth() { + return maximumHealth; + } + + public int getDamagePerDamagingPeriod() { + return healthPerDamagePeriod; + } + + public long getBreakGracePeriod() { + return breakGracePeriod; + } + + public Factory revive(List blocks, int health, + String selectedRecipe, int productionTimer, long breakTime, List recipeStrings) { + FurnCraftChestStructure fccs = new FurnCraftChestStructure(blocks); + FurnacePowerManager fpm = new FurnacePowerManager(fccs.getFurnace(), + fuel, fuelConsumptionIntervall); + FurnCraftChestInteractionManager fccim = new FurnCraftChestInteractionManager(); + PercentageHealthRepairManager phrm = new PercentageHealthRepairManager(health, maximumHealth, breakTime, healthPerDamagePeriod, breakGracePeriod); + List currRecipes = new ArrayList<> (); + for(String recName : recipeStrings) { + boolean found = false; + for(IRecipe exRec : currRecipes) { + if (exRec.getIdentifier().equals(recName)) { + found = true; + break; + } + } + if (!found) { + IRecipe rec = FactoryMod.getInstance().getManager().getRecipe(recName); + if (rec == null) { + FactoryMod.getInstance().warning("Factory at " + blocks.get(0).toString() + " had recipe " + recName + " saved, but it could not be loaded from the config"); + } + else { + currRecipes.add(rec); + } + } + } + FurnCraftChestFactory fccf = new FurnCraftChestFactory(fccim, phrm, + fpm, fccs, updateTime, name, currRecipes, citadelBreakReduction); + fccim.setFactory(fccf); + phrm.setFactory(fccf); + for (IRecipe recipe : currRecipes) { + if (recipe.getName().equals(selectedRecipe)) { + fccf.setRecipe(recipe); + } + } + if (fccf.getCurrentRecipe() == null && !currRecipes.isEmpty()) { + fccf.setRecipe(currRecipes.get(0)); + } + if (productionTimer != 0) { + fccf.attemptToActivate(null, true); + if (fccf.isActive()) { + fccf.setProductionTimer(productionTimer); + } + } + return fccf; + } + + @Override + public Class getMultiBlockStructure() { + return FurnCraftChestStructure.class; + } + + public ItemMap getSetupCost() { + return setupCost; + } + + public double getCitadelBreakReduction() { + return citadelBreakReduction; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/eggs/IFactoryEgg.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/eggs/IFactoryEgg.java new file mode 100644 index 000000000..8b44ce464 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/eggs/IFactoryEgg.java @@ -0,0 +1,67 @@ +package com.github.igotyou.FactoryMod.eggs; + +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.structures.MultiBlockStructure; +import org.bukkit.entity.Player; + +/** + * This represents the design pattern "Factory", but because that word was + * already taken in the context of this plugin I had to come up with another + * analogy instead, I decided to go with eggs, dont blame me. Any class + * implementing this interface should be representing a specific combination of + * managers and MultiBlockStructures and contain any information needed to + * create a new factory object of the type which the egg is representing. + * + */ +public interface IFactoryEgg { + /** + * Called whenever a factory is supposed to be created after all the + * required checks are already done. + * + * @param mbs + * Physical representation of the factory which should be created + * @param p + * Player creating the factory + * @return The created factory object + */ + public Factory hatch(MultiBlockStructure mbs, Player p); + + /** + * Each egg has a name which is also carried over to identify the type of + * the factory at a later point. There can be many instances of the same egg + * with different names, but there should never be multiple instances with + * the same name + * + * @return name of this egg and the factory it is creating + */ + public String getName(); + + /** + * When destroyed completly a factory may return a part of it's setup cost. + * This value specifies how much of the setup cost is returned (as a + * multiplier) + * + * @return Multiplier of the setupcost which is returned upon destruction + */ + public double getReturnRate(); + + /** + * All the factories created by a specific egg will be represented through a + * specific MultiBlockStructure and this is the getter for it + * + * @return Structure class of the factories created by this egg + */ + public Class getMultiBlockStructure(); + + /** + * Java wont let me specify a method here without specifying its parameters + * so it's commented out, because parameters may vary for each + * implementation, but this is needed to make the whole thing work. This + * method is called when reconstructing an factory object with data provided + * from the database, so this method should provide the possibility to + * create a factory in the same specific state which is represented by the + * data pulled from the database, for example in terms of selected recipe + * and repair value + */ + // public Factory revive(); +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/eggs/PipeEgg.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/eggs/PipeEgg.java new file mode 100644 index 000000000..28b514470 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/eggs/PipeEgg.java @@ -0,0 +1,125 @@ +package com.github.igotyou.FactoryMod.eggs; + +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.factories.Pipe; +import com.github.igotyou.FactoryMod.interactionManager.IInteractionManager; +import com.github.igotyou.FactoryMod.interactionManager.PipeInteractionManager; +import com.github.igotyou.FactoryMod.powerManager.FurnacePowerManager; +import com.github.igotyou.FactoryMod.powerManager.IPowerManager; +import com.github.igotyou.FactoryMod.repairManager.IRepairManager; +import com.github.igotyou.FactoryMod.repairManager.NoRepairDestroyOnBreakManager; +import com.github.igotyou.FactoryMod.structures.MultiBlockStructure; +import com.github.igotyou.FactoryMod.structures.PipeStructure; +import java.util.List; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class PipeEgg implements IFactoryEgg { + private String name; + private int updateTime; + private ItemStack fuel; + private int fuelConsumptionIntervall; + private List allowedMaterials; + private int transferTimeMultiplier; + private int transferAmount; + private double returnRate; + private int maximumLength; + private Material pipeType; + + public PipeEgg(String name, int updateTime, ItemStack fuel, + int fuelConsumptionIntervall, List allowedMaterials, + int transferTimeMultiplier, int transferAmount, double returnRate, int maximumLength, Material pipeType) { + this.name = name; + this.fuel = fuel; + this.updateTime = updateTime; + this.fuelConsumptionIntervall = fuelConsumptionIntervall; + this.transferTimeMultiplier = transferTimeMultiplier; + this.transferAmount = transferAmount; + this.allowedMaterials = allowedMaterials; + this.returnRate = returnRate; + this.maximumLength = maximumLength; + this.pipeType = pipeType; + } + + public String getName() { + return name; + } + + public int getUpdateTime() { + return updateTime; + } + + public int getFuelConsumptionIntervall() { + return fuelConsumptionIntervall; + } + + public ItemStack getFuel() { + return fuel; + } + + public List getAllowedMaterials() { + return allowedMaterials; + } + + public double getReturnRate() { + return returnRate; + } + + public int getTransferAmount() { + return transferAmount; + } + + public int getTransferTimeMultiplier() { + return transferTimeMultiplier; + } + + public int getMaximumLength() { + return maximumLength; + } + + public Material getPipeType() { + return pipeType; + } + + public Factory hatch(MultiBlockStructure mbs, Player p) { + IInteractionManager im = new PipeInteractionManager(); + IRepairManager rm = new NoRepairDestroyOnBreakManager(); + IPowerManager pm = new FurnacePowerManager( + ((PipeStructure) mbs).getFurnace(), fuel, + fuelConsumptionIntervall); + Pipe pipe = new Pipe(im, rm, pm, mbs, updateTime, name, + transferTimeMultiplier, transferAmount); + ((PipeInteractionManager) im).setPipe(pipe); + ((NoRepairDestroyOnBreakManager)rm).setFactory(pipe); + return pipe; + } + + public Factory revive(List blocks, List allowedMaterials, + int runTime) { + MultiBlockStructure ps = new PipeStructure(blocks); + PipeInteractionManager im = new PipeInteractionManager(); + IRepairManager rm = new NoRepairDestroyOnBreakManager(); + IPowerManager pm = new FurnacePowerManager( + ((PipeStructure) ps).getFurnace(), fuel, + fuelConsumptionIntervall); + Pipe pipe = new Pipe(im, rm, pm, ps, updateTime, name, + transferTimeMultiplier, transferAmount); + ((PipeStructure) ps).setPipeType(pipeType); + ((PipeInteractionManager) im).setPipe(pipe); + ((NoRepairDestroyOnBreakManager)rm).setFactory(pipe); + pipe.setAllowedMaterials(allowedMaterials); + if (runTime != 0) { + pipe.attemptToActivate(null, true); + if (pipe.isActive()) { + pipe.setRunTime(runTime); + } + } + return pipe; + } + + public Class getMultiBlockStructure() { + return PipeStructure.class; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/eggs/SorterEgg.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/eggs/SorterEgg.java new file mode 100644 index 000000000..02d411d65 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/eggs/SorterEgg.java @@ -0,0 +1,115 @@ +package com.github.igotyou.FactoryMod.eggs; + +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.factories.Sorter; +import com.github.igotyou.FactoryMod.interactionManager.IInteractionManager; +import com.github.igotyou.FactoryMod.interactionManager.SorterInteractionManager; +import com.github.igotyou.FactoryMod.powerManager.FurnacePowerManager; +import com.github.igotyou.FactoryMod.powerManager.IPowerManager; +import com.github.igotyou.FactoryMod.repairManager.IRepairManager; +import com.github.igotyou.FactoryMod.repairManager.NoRepairDestroyOnBreakManager; +import com.github.igotyou.FactoryMod.structures.BlockFurnaceStructure; +import com.github.igotyou.FactoryMod.structures.MultiBlockStructure; +import java.util.List; +import java.util.Map; +import org.bukkit.Location; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; + +public class SorterEgg implements IFactoryEgg { + private String name; + private int updateTime; + private ItemStack fuel; + private int fuelConsumptionIntervall; + private int sortTime; + private int matsPerSide; + private int sortAmount; + private double returnRate; + + public SorterEgg(String name, int updateTime, ItemStack fuel, + int fuelConsumptionIntervall, int sortTime, int matsPerSide, + int sortAmount, double returnRate) { + this.name = name; + this.fuel = fuel; + this.updateTime = updateTime; + this.fuelConsumptionIntervall = fuelConsumptionIntervall; + this.sortTime = sortTime; + this.sortAmount = sortAmount; + this.returnRate = returnRate; + this.matsPerSide = matsPerSide; + } + + public Factory hatch(MultiBlockStructure mbs, Player p) { + IInteractionManager im = new SorterInteractionManager(); + IRepairManager rm = new NoRepairDestroyOnBreakManager(); + IPowerManager pm = new FurnacePowerManager( + ((BlockFurnaceStructure) mbs).getFurnace(), fuel, + fuelConsumptionIntervall); + Sorter sorter = new Sorter(im, rm, pm, mbs, updateTime, name, sortTime, + matsPerSide, sortAmount); + ((NoRepairDestroyOnBreakManager) rm).setFactory(sorter); + ((SorterInteractionManager) im).setSorter(sorter); + return sorter; + } + + public Factory revive(List blocks, + Map assignments, int runTime) { + MultiBlockStructure ps = new BlockFurnaceStructure(blocks); + SorterInteractionManager im = new SorterInteractionManager(); + IRepairManager rm = new NoRepairDestroyOnBreakManager(); + IPowerManager pm = new FurnacePowerManager( + ((BlockFurnaceStructure) ps).getFurnace(), fuel, + fuelConsumptionIntervall); + Sorter sorter = new Sorter(im, rm, pm, ps, updateTime, name, sortTime, + matsPerSide, sortAmount); + ((SorterInteractionManager) im).setSorter(sorter); + ((NoRepairDestroyOnBreakManager) rm).setFactory(sorter); + sorter.setAssignments(assignments); + if (runTime != 0) { + sorter.attemptToActivate(null, true); + if (sorter.isActive()) { + sorter.setRunTime(runTime); + } + } + return sorter; + } + + public String getName() { + return name; + } + + public int getUpdateTime() { + return updateTime; + } + + public ItemStack getFuel() { + return fuel; + } + + public double getReturnRate() { + return returnRate; + } + + public int getFuelConsumptionIntervall() { + return fuelConsumptionIntervall; + } + + public int getSortTime() { + return sortTime; + } + + public int getMaterialsPerSide() { + return matsPerSide; + } + + public int getSortAmount() { + return sortAmount; + } + + public Class getMultiBlockStructure() { + return BlockFurnaceStructure.class; + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/events/FactoryActivateEvent.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/events/FactoryActivateEvent.java new file mode 100644 index 000000000..74f1c55c4 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/events/FactoryActivateEvent.java @@ -0,0 +1,63 @@ +package com.github.igotyou.FactoryMod.events; + +import com.github.igotyou.FactoryMod.factories.Factory; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * Event called when any type of factory is being activated. Cancelling this + * event will prevent the factory from starting up, no additional message will + * be sent to the player informing him about the cancelling, this will be left + * up to the listener cancelling the activation + * + */ +public class FactoryActivateEvent extends Event implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private Factory fac; + private Player activator; + + private boolean cancelled; + + public FactoryActivateEvent(Factory f, Player activator) { + this.fac = f; + this.activator = activator; + } + + /** + * @return The factory being activated + */ + public Factory getFactory() { + return fac; + } + + /** + * @return The player activating the factory or null if it was not activated by + * a player + */ + public Player getActivator() { + return activator; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/events/ItemTransferEvent.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/events/ItemTransferEvent.java new file mode 100644 index 000000000..68b8f8d09 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/events/ItemTransferEvent.java @@ -0,0 +1,49 @@ +package com.github.igotyou.FactoryMod.events; + +import com.github.igotyou.FactoryMod.factories.Factory; +import org.bukkit.block.Block; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +/** + * Event called when any factory is moving around items. This will (hopefully) + * make this plugin more compatible with other plugins which have listeners for + * item move events + * + */ +public class ItemTransferEvent extends InventoryMoveItemEvent { + private Factory f; + private Block fromBlock; + private Block toBlock; + + public ItemTransferEvent(Factory f, Inventory fromInventory, + Inventory toInventory, Block fromBlock, Block toBlock, + ItemStack trans) { + super(fromInventory, trans, toInventory, true); + this.f = f; + this.fromBlock = fromBlock; + this.toBlock = toBlock; + } + + /** + * @return The factory causing the transfer + */ + public Factory getFactory() { + return f; + } + + /** + * @return The source block from which the transfer is originating + */ + public Block getSourceBlock() { + return fromBlock; + } + + /** + * @return The target block to which the transfer is going + */ + public Block getTargetBlock() { + return toBlock; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/events/RecipeExecuteEvent.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/events/RecipeExecuteEvent.java new file mode 100644 index 000000000..8df8eeabd --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/events/RecipeExecuteEvent.java @@ -0,0 +1,61 @@ +package com.github.igotyou.FactoryMod.events; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.recipes.InputRecipe; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * Event called when executing a recipe in a FurnCraftChestFactory + */ +public class RecipeExecuteEvent extends Event implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private FurnCraftChestFactory fccf; + private InputRecipe rec; + + private boolean cancelled; + + public RecipeExecuteEvent(FurnCraftChestFactory fccf, InputRecipe rec) { + this.rec = rec; + this.fccf = fccf; + } + + /** + * @return The factory executing the recipe + */ + public FurnCraftChestFactory getFactory() { + return fccf; + } + + /** + * @return The recipe being executed + */ + public InputRecipe getRecipe() { + return rec; + } + + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/Factory.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/Factory.java new file mode 100644 index 000000000..b15875a1a --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/Factory.java @@ -0,0 +1,159 @@ +package com.github.igotyou.FactoryMod.factories; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.interactionManager.IInteractionManager; +import com.github.igotyou.FactoryMod.powerManager.IPowerManager; +import com.github.igotyou.FactoryMod.repairManager.IRepairManager; +import com.github.igotyou.FactoryMod.structures.MultiBlockStructure; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Furnace; +import org.bukkit.entity.Player; + +/** + * Super class for any sort of factory created by this plugin + * + */ +public abstract class Factory implements Runnable { + protected IInteractionManager im; + protected IRepairManager rm; + protected IPowerManager pm; + protected boolean active; + protected MultiBlockStructure mbs; + protected int updateTime; + protected String name; + protected int threadId; + + public Factory(IInteractionManager im, IRepairManager rm, IPowerManager pm, MultiBlockStructure mbs, + int updateTime, String name) { + this.im = im; + this.rm = rm; + this.mbs = mbs; + this.pm = pm; + this.updateTime = updateTime; + this.name = name; + } + + /** + * @return The manager which handles health, repairs and decay of the + * factory + */ + public IRepairManager getRepairManager() { + return rm; + } + + /** + * @return The manager which handles any sort of player interaction with the + * factory + */ + public IInteractionManager getInteractionManager() { + return im; + } + + /** + * @return The manager which handles power and it's consumption for this + * factory + */ + public IPowerManager getPowerManager() { + return pm; + } + + /** + * @return Whether this factory is currently turned on + */ + public boolean isActive() { + return active; + } + + /** + * @return The physical structure representing this factory + */ + public MultiBlockStructure getMultiBlockStructure() { + return mbs; + } + + /** + * @return How often this factory is updated when it's turned on, measured + * in ticks + */ + public int getUpdateTime() { + return updateTime; + } + + /** + * Names are not unique for factory instances, but simply describe a broader + * functionality group. Factories implemented by the same class can have + * different names, but factories with the same name should have the exact + * same functionality + * + * @return name of this factory + */ + public String getName() { + return name; + } + + /** + * Activates this factory + */ + public abstract void activate(); + + /** + * Deactivates this factory + */ + public abstract void deactivate(); + + /** + * Attempts to turn this factory on and does any checks needed + * + * @param p + * Player turning the factory on or null if something other than + * a player is attempting to turn it on + * @param onStartUp + * Whether this factory is just being reactivated after a + * restart/reload and any permissions checks should be bypassed + */ + public abstract void attemptToActivate(Player p, boolean onStartUp); + + public void scheduleUpdate() { + threadId = FactoryMod.getInstance().getServer().getScheduler() + .scheduleSyncDelayedTask(FactoryMod.getInstance(), this, (long) updateTime); + } + + public void turnFurnaceOn(Block f) { + if (f.getType() != Material.FURNACE) { + return; + } + Bukkit.getScheduler().runTask(FactoryMod.getInstance(), () -> { + if (this.isActive()) { + Furnace furnace = (Furnace) f.getState(); + furnace.setBurnTime(Short.MAX_VALUE); + furnace.update(); + } + }); + } + + public String getLogData() { + return name + " at " + mbs.getCenter().toString(); + } + + public void turnFurnaceOff(Block f) { + if (f.getType() != Material.FURNACE) { + return; + } + FactoryMod fmPlugin = FactoryMod.getInstance(); + if (fmPlugin.isEnabled()) { + Bukkit.getScheduler().runTask(FactoryMod.getInstance(), () -> { + if (!this.isActive()) { + Furnace furnace = (Furnace) f.getState(); + furnace.setBurnTime((short) 0); + furnace.update(); + } + }); + } else if (!this.isActive()) { + Furnace furnace = (Furnace) f.getState(); + furnace.setBurnTime((short) 0); + furnace.update(); + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/FurnCraftChestFactory.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/FurnCraftChestFactory.java new file mode 100644 index 000000000..12fd044f4 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/FurnCraftChestFactory.java @@ -0,0 +1,773 @@ +package com.github.igotyou.FactoryMod.factories; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.events.FactoryActivateEvent; +import com.github.igotyou.FactoryMod.events.RecipeExecuteEvent; +import com.github.igotyou.FactoryMod.interactionManager.IInteractionManager; +import com.github.igotyou.FactoryMod.powerManager.FurnacePowerManager; +import com.github.igotyou.FactoryMod.utility.Direction; +import com.github.igotyou.FactoryMod.utility.IIOFInventoryProvider; +import com.github.igotyou.FactoryMod.powerManager.IPowerManager; +import com.github.igotyou.FactoryMod.recipes.IRecipe; +import com.github.igotyou.FactoryMod.recipes.InputRecipe; +import com.github.igotyou.FactoryMod.recipes.PylonRecipe; +import com.github.igotyou.FactoryMod.recipes.RecipeScalingUpgradeRecipe; +import com.github.igotyou.FactoryMod.recipes.RepairRecipe; +import com.github.igotyou.FactoryMod.recipes.Upgraderecipe; +import com.github.igotyou.FactoryMod.repairManager.IRepairManager; +import com.github.igotyou.FactoryMod.repairManager.PercentageHealthRepairManager; +import com.github.igotyou.FactoryMod.structures.FurnCraftChestStructure; +import com.github.igotyou.FactoryMod.utility.IOSelector; +import com.github.igotyou.FactoryMod.utility.LoggingUtils; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; + +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Chest; +import org.bukkit.block.Furnace; +import org.bukkit.entity.Player; +import org.bukkit.inventory.FurnaceInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; +import vg.civcraft.mc.citadel.ReinforcementLogic; +import vg.civcraft.mc.citadel.model.Reinforcement; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; +import vg.civcraft.mc.namelayer.NameAPI; +import vg.civcraft.mc.namelayer.permission.PermissionType; + +/** + * Represents a "classic" factory, which consists of a furnace as powersource, a + * crafting table as main interaction element between the furnace and the chest, + * which is used as inventory holder + * + */ +public class FurnCraftChestFactory extends Factory implements IIOFInventoryProvider { + protected int currentProductionTimer = 0; + protected List recipes; + protected IRecipe currentRecipe; + protected Map runCount; + protected Map recipeLevel; + private UUID activator; + private double citadelBreakReduction; + private boolean autoSelect; + private @Nullable IOSelector furnaceIoSelector; + private @Nullable IOSelector tableIoSelector; + private UiMenuMode uiMenuMode; + + private static HashSet pylonFactories; + + public FurnCraftChestFactory(IInteractionManager im, IRepairManager rm, IPowerManager ipm, + FurnCraftChestStructure mbs, int updateTime, String name, List recipes, + double citadelBreakReduction) { + super(im, rm, ipm, mbs, updateTime, name); + if (ipm instanceof FurnacePowerManager) { + ((FurnacePowerManager) ipm).setIofProvider(this); + } + this.active = false; + this.runCount = new HashMap<>(); + this.recipeLevel = new HashMap<>(); + this.recipes = new ArrayList<>(); + this.citadelBreakReduction = citadelBreakReduction; + this.autoSelect = false; + this.uiMenuMode = UiMenuMode.SIMPLE; + for (IRecipe rec : recipes) { + addRecipe(rec); + } + if (pylonFactories == null) { + pylonFactories = new HashSet<>(); + } + for (IRecipe rec : recipes) { + if (rec instanceof PylonRecipe) { + pylonFactories.add(this); + break; + } + } + } + + /** + * @return Inventory of the chest or null if there is no chest where one + * should be + */ + public Inventory getInventory() { + if (getChest().getType() != Material.CHEST && getChest().getType() != Material.TRAPPED_CHEST) { + return null; + } + Chest chestBlock = (Chest) (getChest().getState()); + return chestBlock.getInventory(); + } + + public Inventory getInputInventory() { + List invs = new ArrayList<>(12); + getInventoriesForIoType(invs, iosel -> iosel::getInputs); + return new MultiInventoryWrapper(invs); + } + + public Inventory getOutputInventory() { + List invs = new ArrayList<>(12); + getInventoriesForIoType(invs, iosel -> iosel::getOutputs); + return new MultiInventoryWrapper(invs); + } + + public Inventory getFuelInventory() { + if (!getFurnaceIOSelector().hasFuel() && !getTableIOSelector().hasFuel()) { + return getFurnaceInventory(); + } + ArrayList invs = new ArrayList<>(13); + getInventoriesForIoType(invs, iosel -> iosel::getFuel); + invs.add(getFurnaceInventory()); + return new MultiInventoryWrapper(invs); + } + + private void getInventoriesForIoType(List combinedInvList, + Function>> ioTypeFunc) { + FurnCraftChestStructure fccs = (FurnCraftChestStructure) getMultiBlockStructure(); + Block fblock = getFurnace(); + BlockFace facing = getFacing(); + for (BlockFace relativeFace : ioTypeFunc.apply(getFurnaceIOSelector()).apply(facing)) { + Block relBlock = fblock.getRelative(relativeFace); + if (relBlock.getType() == Material.CHEST || relBlock.getType() == Material.TRAPPED_CHEST) { + combinedInvList.add(((Chest) relBlock.getState()).getInventory()); + } + } + Block tblock = fccs.getCraftingTable(); + for (BlockFace relativeFace : ioTypeFunc.apply(getTableIOSelector()).apply(facing)) { + Block relBlock = tblock.getRelative(relativeFace); + if (relBlock.getType() == Material.CHEST || relBlock.getType() == Material.TRAPPED_CHEST) { + combinedInvList.add(((Chest) relBlock.getState()).getInventory()); + } + } + } + + @Override + public int getInputCount() { + return (furnaceIoSelector == null ? 0 : furnaceIoSelector.getInputCount()) + + (tableIoSelector == null ? 0 : tableIoSelector.getInputCount()); + } + + @Override + public int getOutputCount() { + return (furnaceIoSelector == null ? 0 : furnaceIoSelector.getOutputCount()) + + (tableIoSelector == null ? 0 : tableIoSelector.getOutputCount()); + } + + @Override + public int getFuelCount() { + return (furnaceIoSelector == null ? 0 : furnaceIoSelector.getFuelCount()) + + (tableIoSelector == null ? 0 : tableIoSelector.getFuelCount()); + } + + public void setFurnaceIOSelector(IOSelector ioSelector) { + this.furnaceIoSelector = ioSelector; + } + + public IOSelector getFurnaceIOSelector() { + if (furnaceIoSelector == null) { + furnaceIoSelector = new IOSelector(); + } + return furnaceIoSelector; + } + + public void setTableIOSelector(IOSelector ioSelector) { + this.tableIoSelector = ioSelector; + } + + public IOSelector getTableIOSelector() { + if (tableIoSelector == null) { + tableIoSelector = new IOSelector(); + BlockFace front = getFacing(); + BlockFace chestDir = getFurnace().getFace(getCraftingTable()); + if (chestDir != null && front != null) { + Direction defaultDir = Direction.getDirection(front, chestDir); + tableIoSelector.setState(defaultDir, IOSelector.IOState.BOTH); + } + } + return tableIoSelector; + } + + public void setUiMenuMode(UiMenuMode uiMenuMode) { + this.uiMenuMode = uiMenuMode; + } + + public UiMenuMode getUiMenuMode() { + return uiMenuMode; + } + + /** + * @return the direction the furnace (and thus this factory) is facing. + */ + public @Nullable BlockFace getFacing() { + Block fblock = getFurnace(); + if (fblock.getType() != Material.FURNACE) { + return null; + } + Furnace fstate = (Furnace) fblock.getState(); + org.bukkit.block.data.type.Furnace fdata = (org.bukkit.block.data.type.Furnace) fstate.getBlockData(); + return fdata.getFacing(); + } + + /** + * @return Inventory of the furnace or null if there is no furnace where one + * should be + */ + public FurnaceInventory getFurnaceInventory() { + if (getFurnace().getType() != Material.FURNACE) { + return null; + } + Furnace furnaceBlock = (Furnace) (getFurnace().getState()); + return furnaceBlock.getInventory(); + } + + /** + * Sets autoselect mode for this factory + * + * @param mode + * Whether autoselect should be set to true or false + */ + public void setAutoSelect(boolean mode) { + this.autoSelect = mode; + } + + /** + * @return Whether the factory is in auto select mode + */ + public boolean isAutoSelect() { + return autoSelect; + } + + /** + * Attempts to turn the factory on and does all the checks needed to ensure + * that the factory is allowed to turn on + */ + @Override + public void attemptToActivate(Player p, boolean onStartUp) { + LoggingUtils.debug((p != null ? p.getName() : "Redstone") + " is attempting to activate " + getLogData()); + mbs.recheckComplete(); + //dont activate twice + if (active) { + return; + } + //ensure factory is physically complete + if (!mbs.isComplete()) { + rm.breakIt(); + return; + } + + //If Autoselect is on + if (autoSelect) { + //If the factory is in disrepair and we got autoSelect on, we want to repair it + if (rm.inDisrepair() && !(currentRecipe instanceof RepairRecipe)) { + IRecipe autoRepair = getRepairRecipe(); + //Just incase any factory for some reason cannot be repaired. + if (autoRepair == null) { + if (p != null) { + p.sendMessage(ChatColor.RED + "The factory doesn't have a repair recipe."); + } + return; + } else { + if (p != null) { + p.sendMessage(ChatColor.GOLD + "Automatically selected recipe " + autoRepair.getName()); + } + setRecipe(autoRepair); + } + } + + // If we run out of input materials, try to auto-select a new recipe + if (!hasInputMaterials()) { + IRecipe autoSelected = getAutoSelectRecipe(); + if (autoSelected == null) { + if (p != null) { + p.sendMessage(ChatColor.RED + "Not enough materials available to run any recipe"); + } + return; + } else { + if (p != null) { + p.sendMessage(ChatColor.GOLD + "Automatically selected recipe " + autoSelected.getName()); + } + setRecipe(autoSelected); + } + } + } else { + //We are running the factory manually, so we just do the usual checks + if (!hasInputMaterials()) { + if (p != null) { + p.sendMessage(ChatColor.RED + "Not enough materials available"); + } + return; + } + //The factory is broken and needs to be repaired + if (rm.inDisrepair() && !(currentRecipe instanceof RepairRecipe)) { + if (p != null) { + p.sendMessage(ChatColor.RED + "This factory is in disrepair, you have to repair it before using it"); + } + return; + } + //The factory is at full health, so there's no reason to repair it. + if (rm.atFullHealth() && (currentRecipe instanceof RepairRecipe) ) { + if (p != null) { + p.sendMessage(ChatColor.GREEN + "This factory is already at full health"); + } + return; + } + } + + //ensure we have fuel + if (!pm.powerAvailable(1)) { + if (p != null) { + ItemStack fuel = ((FurnacePowerManager) pm).getFuel().clone(); + if (fuel != null) { + p.sendMessage(ChatColor.RED + "Failed to activate factory, there is no fuel (" + ItemUtils.getItemName(fuel) + ") in the furnace"); + }else{ + p.sendMessage(ChatColor.RED + "Failed to activate factory, there is no fuel in the furnace"); + } + } + return; + } + + // Ensure the recipe effect can be applied + var effectFeasibility = currentRecipe.evaluateEffectFeasibility(getInputInventory(), getOutputInventory()); + if (!(effectFeasibility.isFeasible())) { + LoggingUtils.log(String.format("Skipping activation of recipe [%s], since the effect wasn't feasible.", currentRecipe.getName())); + if (p != null) { + p.sendMessage(String.format("%sUnable to activate recipe because %s.",ChatColor.RED, effectFeasibility.reasonSnippet())); + } + return; + } + + if (!onStartUp && currentRecipe instanceof Upgraderecipe && FactoryMod.getInstance().getManager().isCitadelEnabled()) { + // only allow permitted members to upgrade the factory + Reinforcement rein = ReinforcementLogic.getReinforcementAt(mbs.getCenter()); + if (rein != null) { + if (p == null) { + return; + } + if (!NameAPI.getGroupManager().hasAccess(rein.getGroup().getName(), p.getUniqueId(), + PermissionType.getPermission("UPGRADE_FACTORY"))) { + p.sendMessage(ChatColor.RED + "You dont have permission to upgrade this factory"); + return; + } + } + } + FactoryActivateEvent fae = new FactoryActivateEvent(this, p); + Bukkit.getPluginManager().callEvent(fae); + if (fae.isCancelled()) { + return; + } + if (p != null) { + int consumptionIntervall = ((InputRecipe) currentRecipe).getFuelConsumptionIntervall() > 0 ? ((InputRecipe) currentRecipe) + .getFuelConsumptionIntervall() : pm.getPowerConsumptionIntervall(); + if (((FurnacePowerManager) pm).getFuelAmountAvailable() < (currentRecipe.getProductionTime() / consumptionIntervall)) { + p.sendMessage(ChatColor.RED + + "You don't have enough fuel, the factory will run out of it before completing"); + } + p.sendMessage(ChatColor.GREEN + "Activated " + name + " with recipe: " + currentRecipe.getName()); + activator = p.getUniqueId(); + } + activate(); + } + + /** + * Actually turns the factory on, never use this directly unless you know + * what you are doing, use attemptToActivate() instead to ensure the factory + * is allowed to turn on + */ + @Override + public void activate() { + LoggingUtils.log("Activating " + getLogData() + ", because of " + (activator != null ? + Bukkit.getPlayer(activator) : "Redstone")); + active = true; + pm.setPowerCounter(0); + turnFurnaceOn(getFurnace()); + // reset the production timer + currentProductionTimer = 0; + run(); + } + + /** + * Turns the factory off. + */ + @Override + public void deactivate() { + if (active) { + LoggingUtils.log("Deactivating " + getLogData()); + Bukkit.getScheduler().cancelTask(threadId); + turnFurnaceOff(getFurnace()); + active = false; + // reset the production timer + currentProductionTimer = 0; + activator = null; + } + } + + /** + * @return The furnace of this factory + */ + public Block getFurnace() { + return ((FurnCraftChestStructure) mbs).getFurnace(); + } + + /** + * @return The crafting table of this factory + */ + public Block getCraftingTable() { + return ((FurnCraftChestStructure) mbs).getCraftingTable(); + } + + /** + * @return The chest of this factory + */ + public Block getChest() { + return ((FurnCraftChestStructure) mbs).getChest(); + } + + /** + * @return How long the factory has been running in ticks + */ + public int getRunningTime() { + return currentProductionTimer; + } + + public void setRunCount(IRecipe r, Integer count) { + if (recipes.contains(r)) { + runCount.put(r, count); + } + } + + public void setRecipeLevel(IRecipe r, Integer level) { + if (recipes.contains(r)) { + recipeLevel.put(r, level); + } + } + + /** + * @return UUID of the person who activated the factory or null if the + * factory is off or was triggered by redstone + */ + public UUID getActivator() { + return activator; + } + + public void setActivator(UUID uuid) { + this.activator = uuid; + } + + /** + * Called by the manager each update cycle + */ + @Override + public void run() { + if (active && mbs.isComplete()) { + // if the materials required to produce the current recipe are in + // the factory inventory + if (hasInputMaterials()) { + // if the factory has been working for less than the required + // time for the recipe + if (currentProductionTimer < currentRecipe.getProductionTime()) { + int consumptionIntervall = ((InputRecipe) currentRecipe).getFuelConsumptionIntervall() > 0 + ? ((InputRecipe) currentRecipe).getFuelConsumptionIntervall() + : pm.getPowerConsumptionIntervall(); + + int powerCounter = pm.getPowerCounter() + updateTime; + int fuelCount = powerCounter / consumptionIntervall; + + // if the factory power source inventory has enough fuel for + // at least 1 energyCycle + if (pm.powerAvailable(fuelCount)) { + // check whether the furnace is on, minecraft sometimes + // turns it off + turnFurnaceOn(getFurnace()); + // if we need to consume fuel - then do it + if (fuelCount >= 1) { + // remove fuel. + pm.consumePower(fuelCount); + // update power counter to remained time + pm.setPowerCounter(powerCounter % consumptionIntervall); + } + // if we don't need to consume fuel, just increase the + // energy timer + else { + pm.increasePowerCounter(updateTime); + } + // increase the production timer + currentProductionTimer += updateTime; + // schedule next update + scheduleUpdate(); + } + // if there is no fuel Available turn off the factory + else { + sendActivatorMessage(ChatColor.GOLD + name + " deactivated, because it ran out of fuel"); + deactivate(); + } + } + + // if the production timer has reached the recipes production + // time remove input from chest, and add output material + else { + LoggingUtils.log("Executing recipe " + currentRecipe.getName() + " for " + getLogData()); + RecipeExecuteEvent ree = new RecipeExecuteEvent(this, (InputRecipe) currentRecipe); + Bukkit.getPluginManager().callEvent(ree); + if (ree.isCancelled()) { + LoggingUtils.log("Executing recipe " + currentRecipe.getName() + " for " + getLogData() + + " was cancelled over the event"); + deactivate(); + return; + } + sendActivatorMessage(ChatColor.GOLD + currentRecipe.getName() + " in " + name + " completed"); + if (currentRecipe instanceof Upgraderecipe || currentRecipe instanceof RecipeScalingUpgradeRecipe) { + // this if else might look a bit weird, but because + // upgrading changes the current recipe and a lot of + // other stuff, this is needed + currentRecipe.applyEffect(getInputInventory(), getOutputInventory(), this); + deactivate(); + return; + } else { + if (currentRecipe.applyEffect(getInputInventory(), getOutputInventory(), this)) { + runCount.put(currentRecipe, runCount.get(currentRecipe) + 1); + } else { + sendActivatorMessage(ChatColor.RED + currentRecipe.getName() + " in " + name + " deactivated because it ran out of storage space"); + deactivate(); + return; + } + } + currentProductionTimer = 0; + if (currentRecipe instanceof RepairRecipe && rm.atFullHealth()) { + // already at full health, dont try to repair further + sendActivatorMessage(ChatColor.GOLD + name + " repaired to full health"); + deactivate(); + return; + } + if (pm.powerAvailable(1)) { + //not enough materials, but if auto select is on, we might find another recipe to run + if (!hasInputMaterials() && isAutoSelect()) { + IRecipe nextOne = getAutoSelectRecipe(); + if (nextOne != null) { + sendActivatorMessage(ChatColor.GREEN + name + " automatically switched to recipe " + nextOne.getName() + " and began running it"); + currentRecipe = nextOne; + } + else { + deactivate(); + return; + } + } + pm.setPowerCounter(0); + scheduleUpdate(); + // keep going + } else { + deactivate(); + } + } + } else { + IRecipe nextOne; + if (isAutoSelect() && (nextOne = getAutoSelectRecipe()) != null) { + sendActivatorMessage(ChatColor.GREEN + name + " automatically switched to recipe " + nextOne.getName() + " and began running it"); + currentRecipe = nextOne; + scheduleUpdate(); + // don't setPowerCounter to 0, fuel has been consumed, let it be used for the new recipe + } else { + sendActivatorMessage(ChatColor.GOLD + name + " deactivated, because it ran out of required materials"); + deactivate(); + } + } + } else { + sendActivatorMessage(ChatColor.GOLD + name + " deactivated, because the factory was destroyed"); + deactivate(); + } + } + + /** + * @return All the recipes which are available for this instance + */ + public List getRecipes() { + return recipes; + } + + /** + * Pylon recipes have a special functionality, which requires them to know + * all other factories with pylon recipes on the map. Because of that all of + * those factories are kept in a separated hashset, which is provided by + * this method + * + * @return All factories with a pylon recipe + */ + public static HashSet getPylonFactories() { + return pylonFactories; + } + + /** + * @return The recipe currently selected in this instance + */ + public IRecipe getCurrentRecipe() { + return currentRecipe; + } + + /** + * Changes the current recipe for this factory to the given one + * + * @param pr + * Recipe to switch to + */ + public void setRecipe(IRecipe pr) { + if (recipes.contains(pr)) { + currentRecipe = pr; + } + } + + public int getRunCount(IRecipe r) { + return runCount.get(r); + } + + public int getRecipeLevel(IRecipe r) { + Integer level = recipeLevel.get(r); + return level != null ? level: 1; + } + + private void sendActivatorMessage(String msg) { + if (activator != null) { + Player p = Bukkit.getPlayer(activator); + if (p != null) { + p.sendMessage(msg); + } + } + } + + /** + * Adds the given recipe to this factory + * + * @param rec + * Recipe to add + */ + public void addRecipe(IRecipe rec) { + recipes.add(rec); + runCount.put(rec, 0); + recipeLevel.put(rec, 1); + } + + /** + * Removes the given recipe from this factory + * + * @param rec + * Recipe to remove + */ + public void removeRecipe(IRecipe rec) { + recipes.remove(rec); + runCount.remove(rec); + recipeLevel.remove(rec); + } + + /** + * Sets the internal production timer + * + * @param timer + * New timer + */ + public void setProductionTimer(int timer) { + this.currentProductionTimer = timer; + } + + /** + * @return Whether enough materials are available to run the currently + * selected recipe at least once + */ + public boolean hasInputMaterials() { + return currentRecipe.enoughMaterialAvailable(getInputInventory()); + } + + /** + * Auto-selects a recipe which has enough materials to run given the current state of the factory. + * @return null if no suitable recipe was found + */ + public IRecipe getAutoSelectRecipe() { + var selectedRecipe = recipes.stream() + .filter(it -> { + // We want to select a repair recipe if and only if the factory is in disrepair + if (rm.inDisrepair()) { + return it instanceof RepairRecipe; + } else { + return !(it instanceof RepairRecipe); + } + }) + .filter(it -> it.enoughMaterialAvailable(getInputInventory())) + .findFirst() + .orElse(null); + + if (selectedRecipe != null) { + LoggingUtils.log("Auto-selected recipe " + selectedRecipe.getName()); + } + + return selectedRecipe; + } + + /** + * @return yields a repair type recipe for repairing the factory if any exists, returns null if none exists + * + */ + public IRecipe getRepairRecipe() { + for (IRecipe rec : recipes) { + if (rec instanceof RepairRecipe) { + return rec; + } + } + return null; + } + + public static void removePylon(Factory f) { + pylonFactories.remove(f); + } + + public void upgrade(String name, List recipes, ItemStack fuel, int fuelConsumptionIntervall, + int updateTime, int maximumHealth, int damageAmountPerDecayIntervall, long gracePeriod, + double citadelBreakReduction) { + LoggingUtils.log("Upgrading " + getLogData() + " to " + name); + pylonFactories.remove(this); + deactivate(); + this.name = name; + this.recipes = recipes; + this.updateTime = updateTime; + this.citadelBreakReduction = citadelBreakReduction; + this.pm = new FurnacePowerManager(getFurnace(), fuel, fuelConsumptionIntervall); + this.rm = new PercentageHealthRepairManager(maximumHealth, maximumHealth, 0, damageAmountPerDecayIntervall, + gracePeriod); + if (!recipes.isEmpty()) { + setRecipe(recipes.get(0)); + } else { + currentRecipe = null; + } + runCount = new HashMap<>(); + for (IRecipe rec : recipes) { + runCount.put(rec, 0); + } + for (IRecipe rec : recipes) { + if (rec instanceof PylonRecipe) { + pylonFactories.add(this); + break; + } + } + } + + public double getCitadelBreakReduction() { + return citadelBreakReduction; + } + + public enum UiMenuMode { + SIMPLE(Material.PAPER, "Show simple menu"), + IOCONFIG(Material.HOPPER, "Show IO config menu"); + + public final Material uiMaterial; + public final String uiDescription; + + private UiMenuMode(Material uiMaterial, String uiDescription) { + this.uiMaterial = uiMaterial; + this.uiDescription = uiDescription; + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/Pipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/Pipe.java new file mode 100644 index 000000000..35cb81a28 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/Pipe.java @@ -0,0 +1,218 @@ +package com.github.igotyou.FactoryMod.factories; + +import com.github.igotyou.FactoryMod.events.FactoryActivateEvent; +import com.github.igotyou.FactoryMod.events.ItemTransferEvent; +import com.github.igotyou.FactoryMod.interactionManager.IInteractionManager; +import com.github.igotyou.FactoryMod.powerManager.IPowerManager; +import com.github.igotyou.FactoryMod.repairManager.IRepairManager; +import com.github.igotyou.FactoryMod.structures.MultiBlockStructure; +import com.github.igotyou.FactoryMod.structures.PipeStructure; +import com.github.igotyou.FactoryMod.utility.LoggingUtils; +import java.util.LinkedList; +import java.util.List; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; + +public class Pipe extends Factory { + private List allowedMaterials; + private int transferAmount; + private int transferTimeMultiplier; + private int runTime; + + public Pipe(IInteractionManager im, IRepairManager rm, IPowerManager pm, MultiBlockStructure mbs, int updateTime, + String name, int transferTimeMultiplier, int transferAmount) { + super(im, rm, pm, mbs, updateTime, name); + this.transferTimeMultiplier = transferTimeMultiplier; + this.transferAmount = transferAmount; + allowedMaterials = null; + runTime = 0; + } + + public void attemptToActivate(Player p, boolean onStartUp) { + LoggingUtils.log((p != null ? p.getName() : "Redstone") + "is attempting to activate " + getLogData()); + mbs.recheckComplete(); + if (mbs.isComplete()) { + if (transferMaterialsAvailable()) { + if (pm.powerAvailable(1)) { + FactoryActivateEvent fae = new FactoryActivateEvent(this, p); + Bukkit.getPluginManager().callEvent(fae); + if (fae.isCancelled()) { + LoggingUtils.log("Activating for " + getLogData() + " was cancelled by the event"); + return; + } + if (p != null) { + p.sendMessage(ChatColor.GREEN + "Activated pipe transfer"); + } + activate(); + } else { + if (p != null) { + p.sendMessage(ChatColor.RED + "Failed to activate pipe, there is no fuel in the furnace"); + } + } + } else { + if (p != null) { + p.sendMessage(ChatColor.RED + "No items available to transfer"); + } + } + } else { + rm.breakIt(); + p.sendMessage(ChatColor.RED + "Failed to activate pipe, it is missing blocks"); + } + } + + public void activate() { + LoggingUtils.log("Activating " + getLogData()); + active = true; + pm.setPowerCounter(0); + turnFurnaceOn(((PipeStructure) mbs).getFurnace()); + // reset the production timer + runTime = 0; + run(); + } + + public void deactivate() { + LoggingUtils.log("Deactivating " + getLogData()); + active = false; + Bukkit.getScheduler().cancelTask(threadId); + turnFurnaceOff(((PipeStructure) mbs).getFurnace()); + runTime = 0; + } + + public void run() { + int powerCounter = pm.getPowerCounter() + updateTime; + int fuelCount = powerCounter / pm.getPowerConsumptionIntervall(); + + if (active && mbs.isComplete() && pm.powerAvailable(fuelCount) && transferMaterialsAvailable()) { + if (runTime >= getTransferTime()) { + transfer(); + runTime = 0; + if (transferMaterialsAvailable()) { + scheduleUpdate(); + } else { + deactivate(); + } + } else { + Block furnace = ((PipeStructure) mbs).getFurnace(); + turnFurnaceOn(furnace); + // if we need to consume fuel - then do it + if (fuelCount >= 1) { + // remove fuel. + pm.consumePower(fuelCount); + // update power counter to remained time + pm.setPowerCounter(powerCounter % pm.getPowerConsumptionIntervall()); + } + // if we don't need to consume fuel, just increase the + // energy timer + else { + pm.increasePowerCounter(updateTime); + } + // increase the production timer + runTime += updateTime; + // schedule next update + scheduleUpdate(); + } + } else { + deactivate(); + } + } + + public void transfer() { + LoggingUtils.log("Attempting to transfer for " + getLogData()); + mbs.recheckComplete(); + if (mbs.isComplete()) { + Inventory sourceInventory = ((InventoryHolder) (((PipeStructure) mbs).getStart().getState())) + .getInventory(); + Inventory targetInventory = ((InventoryHolder) (((PipeStructure) mbs).getEnd().getState())).getInventory(); + int leftToRemove = transferAmount; + for (ItemStack is : sourceInventory.getContents()) { + if (is != null && is.getType() != Material.AIR && is.getAmount() != 0 + && (allowedMaterials == null || allowedMaterials.contains(is.getType()))) { + int removeAmount = Math.min(leftToRemove, is.getAmount()); + ItemStack removing = is.clone(); + removing.setAmount(removeAmount); + ItemMap removeMap = new ItemMap(removing); + if (removeMap.fitsIn(targetInventory)) { + ItemTransferEvent ite = new ItemTransferEvent(this, sourceInventory, targetInventory, + ((PipeStructure) mbs).getStart(), ((PipeStructure) mbs).getEnd(), removing); + Bukkit.getPluginManager().callEvent(ite); + if (ite.isCancelled()) { + LoggingUtils.log("Transfer for " + removing.toString() + " was cancelled over the event"); + continue; + } + LoggingUtils.logInventory(sourceInventory, + "Origin inventory before transfer for " + getLogData()); + LoggingUtils.logInventory(targetInventory, + "Target inventory before transfer for " + getLogData()); + if (removeMap.removeSafelyFrom(sourceInventory)) { + targetInventory.addItem(removing); + } + LoggingUtils.logInventory(sourceInventory, + "Origin inventory after transfer for " + getLogData()); + LoggingUtils.logInventory(targetInventory, + "Target inventory after transfer for " + getLogData()); + leftToRemove -= removeAmount; + } else { + break; + } + if (leftToRemove <= 0) { + break; + } + } + } + } + } + + public void setRunTime(int time) { + this.runTime = time; + } + + public int getTransferTime() { + return transferTimeMultiplier * ((PipeStructure) mbs).getLength(); + } + + public boolean transferMaterialsAvailable() { + Block start = ((PipeStructure) mbs).getStart(); + if (start != null && start.getState() instanceof InventoryHolder) { + Inventory i = ((InventoryHolder) start.getState()).getInventory(); + for (ItemStack is : i.getContents()) { + if (is != null && (allowedMaterials == null || allowedMaterials.contains(is.getType()))) { + return true; + } + } + } + return false; + } + + public void setAllowedMaterials(List mats) { + allowedMaterials = mats; + } + + public List getAllowedMaterials() { + return allowedMaterials; + } + + public void addAllowedMaterial(Material m) { + if (allowedMaterials == null) { + allowedMaterials = new LinkedList(); + } + allowedMaterials.add(m); + } + + public void removeAllowedMaterial(Material m) { + allowedMaterials.remove(m); + if (allowedMaterials.size() == 0) { + allowedMaterials = null; + } + } + + public int getRunTime() { + return runTime; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/Sorter.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/Sorter.java new file mode 100644 index 000000000..95f57fe0f --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/Sorter.java @@ -0,0 +1,245 @@ +package com.github.igotyou.FactoryMod.factories; + +import com.github.igotyou.FactoryMod.events.FactoryActivateEvent; +import com.github.igotyou.FactoryMod.events.ItemTransferEvent; +import com.github.igotyou.FactoryMod.interactionManager.IInteractionManager; +import com.github.igotyou.FactoryMod.powerManager.IPowerManager; +import com.github.igotyou.FactoryMod.repairManager.IRepairManager; +import com.github.igotyou.FactoryMod.structures.BlockFurnaceStructure; +import com.github.igotyou.FactoryMod.structures.MultiBlockStructure; +import com.github.igotyou.FactoryMod.utility.LoggingUtils; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.world.WorldUtils; + +public class Sorter extends Factory { + private Map assignedMaterials; + private int runTime; + private int matsPerSide; + private int sortTime; + private int sortAmount; + + public Sorter(IInteractionManager im, IRepairManager rm, IPowerManager pm, MultiBlockStructure mbs, int updateTime, + String name, int sortTime, int matsPerSide, int sortAmount) { + super(im, rm, pm, mbs, updateTime, name); + assignedMaterials = new HashMap(); + this.sortTime = sortTime; + this.sortAmount = sortAmount; + runTime = 0; + this.matsPerSide = matsPerSide; + for (BlockFace bf : WorldUtils.ALL_SIDES) { + assignedMaterials.put(bf, new ItemMap()); + } + } + + public void attemptToActivate(Player p, boolean onStartUp) { + LoggingUtils.log((p != null ? p.getName() : "Redstone") + "is attempting to activate " + getLogData()); + mbs.recheckComplete(); + if (mbs.isComplete()) { + if (pm.powerAvailable(1)) { + if (sortableMaterialsAvailable()) { + FactoryActivateEvent fae = new FactoryActivateEvent(this, p); + Bukkit.getPluginManager().callEvent(fae); + if (fae.isCancelled()) { + LoggingUtils.log("Activating of " + getLogData() + " was cancelled by the event"); + return; + } + if (p != null) { + p.sendMessage(ChatColor.GREEN + "Activated " + name); + } + activate(); + } else { + if (p != null) { + p.sendMessage(ChatColor.RED + "Nothing to sort available"); + } + } + } else { + if (p != null) { + p.sendMessage(ChatColor.RED + "No fuel available"); + } + } + } else { + rm.breakIt(); + } + } + + public void setAssignments(Map assigns) { + this.assignedMaterials = assigns; + } + + public void activate() { + LoggingUtils.log("Activating " + getLogData()); + LoggingUtils.logInventory(mbs.getCenter().getBlock()); + turnFurnaceOn(((BlockFurnaceStructure) mbs).getFurnace()); + active = true; + run(); + } + + public void deactivate() { + LoggingUtils.log("Deactivating " + getLogData()); + LoggingUtils.logInventory(mbs.getCenter().getBlock()); + Bukkit.getScheduler().cancelTask(threadId); + turnFurnaceOff(((BlockFurnaceStructure) mbs).getFurnace()); + active = false; + } + + public void run() { + int powerCounter = pm.getPowerCounter() + updateTime; + int fuelCount = powerCounter / pm.getPowerConsumptionIntervall(); + + if (active && mbs.isComplete() && pm.powerAvailable(fuelCount) && sortableMaterialsAvailable()) { + if (runTime >= sortTime) { + mbs.recheckComplete(); + if (!mbs.isComplete()) { + deactivate(); + return; + } + sortStack(); + runTime = 0; + if (sortableMaterialsAvailable()) { + scheduleUpdate(); + } else { + deactivate(); + } + } else { + Block furnace = ((BlockFurnaceStructure) mbs).getFurnace(); + turnFurnaceOn(furnace); + // if we need to consume fuel - then do it + if (fuelCount >= 1) { + // remove fuel. + pm.consumePower(fuelCount); + // update power counter to remained time + pm.setPowerCounter(powerCounter % pm.getPowerConsumptionIntervall()); + } else { + pm.increasePowerCounter(updateTime); + } + runTime += updateTime; + scheduleUpdate(); + } + } else { + deactivate(); + } + } + + public boolean assigned(ItemStack is) { + return getSide(is) != null; + } + + public BlockFace getSide(ItemStack is) { + for (Entry entry : assignedMaterials.entrySet()) { + if (entry.getValue().getAmount(is) != 0) { + return entry.getKey(); + } + } + return null; + } + + public void addAssignment(BlockFace bf, ItemStack is) { + assignedMaterials.get(bf).addItemStack(is.clone()); + } + + public ItemMap getItemsForSide(BlockFace face) { + return assignedMaterials.get(face); + } + + public void removeAssignment(ItemStack is) { + for (Entry entry : assignedMaterials.entrySet()) { + if (entry.getValue().getAmount(is) != 0) { + entry.getValue().removeItemStackCompletely(is); + break; + } + } + } + + public void sortStack() { + LoggingUtils.log("Attempting to sort " + getLogData()); + Block center = mbs.getCenter().getBlock(); + Inventory inv = getCenterInventory(); + int leftToSort = sortAmount; + for (BlockFace bf : WorldUtils.ALL_SIDES) { + if (center.getRelative(bf).getState() instanceof InventoryHolder) { + Block b = center.getRelative(bf); + if (b.getType() == Material.CHEST || b.getType() == Material.TRAPPED_CHEST) { + // load adjacent chunk for double chest + MultiBlockStructure.getAdjacentBlocks(b); + } + Inventory relInv = ((InventoryHolder) center.getRelative(bf).getState()).getInventory(); + ItemMap im = assignedMaterials.get(bf); + for (ItemStack is : inv.getContents()) { + if (is != null && is.getType() != Material.AIR && im != null && im.getAmount(is) != 0) { + int removeAmount = Math.min(leftToSort, is.getAmount()); + ItemStack rem = is.clone(); + rem.setAmount(removeAmount); + if (new ItemMap(is).fitsIn(relInv)) { + ItemTransferEvent ite = new ItemTransferEvent(this, inv, relInv, center, + center.getRelative(bf), rem); + Bukkit.getPluginManager().callEvent(ite); + if (ite.isCancelled()) { + LoggingUtils.log("Sorting for " + rem.toString() + " in " + getLogData() + + " was cancelled over the event"); + continue; + } + LoggingUtils.log("Moving " + rem.toString() + " from " + mbs.getCenter().toString() + " to " + + center.getRelative(bf).getLocation().toString()); + LoggingUtils.logInventory(inv, "Origin inventory before transfer for " + getLogData()); + LoggingUtils.logInventory(relInv, "Target inventory before transfer for " + getLogData()); + inv.removeItem(rem); + relInv.addItem(rem); + LoggingUtils.logInventory(inv, "Origin inventory after transfer for " + getLogData()); + LoggingUtils.logInventory(relInv, "Target inventory after transfer for " + getLogData()); + leftToSort -= removeAmount; + break; + } + } + if (leftToSort <= 0) { + break; + } + } + } + if (leftToSort <= 0) { + break; + } + } + } + + public void setRunTime(int runtime) { + this.runTime = runtime; + } + + public int getRunTime() { + return runTime; + } + + public Inventory getCenterInventory() { + return ((InventoryHolder) mbs.getCenter().getBlock().getState()).getInventory(); + } + + private boolean sortableMaterialsAvailable() { + for (ItemStack is : getCenterInventory()) { + if (is != null && is.getType() != Material.AIR) { + for (Entry entry : assignedMaterials.entrySet()) { + if (mbs.getCenter().getBlock().getRelative(entry.getKey()).getState() instanceof InventoryHolder + && entry.getValue().getAmount(is) != 0) { + return true; + } + } + } + } + return false; + } + + public int getMatsPerSide() { + return matsPerSide; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/interactionManager/FurnCraftChestInteractionManager.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/interactionManager/FurnCraftChestInteractionManager.java new file mode 100644 index 000000000..d6b0bfa53 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/interactionManager/FurnCraftChestInteractionManager.java @@ -0,0 +1,384 @@ +package com.github.igotyou.FactoryMod.interactionManager; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.eggs.FurnCraftChestEgg; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.recipes.IRecipe; +import com.github.igotyou.FactoryMod.recipes.InputRecipe; +import com.github.igotyou.FactoryMod.recipes.ProductionRecipe; +import com.github.igotyou.FactoryMod.repairManager.PercentageHealthRepairManager; +import com.github.igotyou.FactoryMod.structures.FurnCraftChestStructure; +import com.github.igotyou.FactoryMod.structures.MultiBlockStructure; +import com.github.igotyou.FactoryMod.utility.FactoryModGUI; +import com.github.igotyou.FactoryMod.utility.IOConfigSection; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; +import net.kyori.adventure.text.Component; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Furnace; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockRedstoneEvent; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.citadel.ReinforcementLogic; +import vg.civcraft.mc.citadel.model.Reinforcement; +import vg.civcraft.mc.civmodcore.inventory.gui.Clickable; +import vg.civcraft.mc.civmodcore.inventory.gui.ClickableInventory; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.components.ComponableInventory; +import vg.civcraft.mc.civmodcore.inventory.gui.components.Scrollbar; +import vg.civcraft.mc.civmodcore.inventory.gui.components.SlotPredicates; +import vg.civcraft.mc.civmodcore.inventory.gui.components.StaticDisplaySection; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; +import vg.civcraft.mc.namelayer.NameAPI; +import vg.civcraft.mc.namelayer.group.Group; +import vg.civcraft.mc.namelayer.permission.PermissionType; + +public class FurnCraftChestInteractionManager implements IInteractionManager { + + private FurnCraftChestFactory fccf; + private final DecimalFormat decimalFormatting; + + public FurnCraftChestInteractionManager(FurnCraftChestFactory fccf) { + this(); + this.fccf = fccf; + } + + public FurnCraftChestInteractionManager() { + this.decimalFormatting = new DecimalFormat("#.#####"); + } + + public void setFactory(FurnCraftChestFactory fccf) { + this.fccf = fccf; + } + + @Override + public void redStoneEvent(BlockRedstoneEvent e, Block factoryBlock) { + int threshold = FactoryMod.getInstance().getManager().getRedstonePowerOn(); + if (!(factoryBlock.getLocation().equals(fccf.getFurnace().getLocation()) && e.getOldCurrent() >= threshold + && e.getNewCurrent() < threshold)) { + return; + } + if (FactoryMod.getInstance().getManager().isCitadelEnabled()) { + if (!MultiBlockStructure.citadelRedstoneChecks(e.getBlock())) { + return; + } + } + if (fccf.isActive()) { + fccf.deactivate(); + } else { + fccf.attemptToActivate(null, false); + } + } + + @Override + public void blockBreak(Player p, Block b) { + if (p != null && !fccf.getRepairManager().inDisrepair()) { + p.sendMessage(ChatColor.DARK_RED + "You broke the factory, it is in disrepair now"); + } + if (fccf.isActive()) { + fccf.deactivate(); + } + fccf.getRepairManager().breakIt(); + } + + @Override + public void leftClick(Player p, Block b, BlockFace bf) { + if (p.getInventory().getItemInMainHand().getType() != FactoryMod.getInstance().getManager().getFactoryInteractionMaterial()) { + return; + } + if (FactoryMod.getInstance().getManager().isCitadelEnabled()) { + Reinforcement rein = ReinforcementLogic.getReinforcementProtecting(b); + if (rein != null) { + Group g = rein.getGroup(); + if (!NameAPI.getGroupManager().hasAccess(g.getName(), p.getUniqueId(), + PermissionType.getPermission("USE_FACTORY"))) { + p.sendMessage(ChatColor.RED + "You dont have permission to interact with this factory"); + return; + } + } + } + if (b.equals(((FurnCraftChestStructure) fccf.getMultiBlockStructure()).getChest())) { // chest interaction + if (p.isSneaking()) { // sneaking, so showing detailed recipe stuff + ClickableInventory ci = new ClickableInventory(54, fccf.getCurrentRecipe().getName()); + int index = 4; + List inp = ((InputRecipe) fccf.getCurrentRecipe()) + .getInputRepresentation(fccf.getInputInventory(), fccf); + if (inp.size() > 18) { + inp = new ItemMap(inp).getLoredItemCountRepresentation(); + } + for (ItemStack is : inp) { + Clickable c = new Clickable(is) { + @Override + public void clicked(Player arg0) { + // nothing, just supposed to look nice + } + }; + ci.setSlot(c, index); + // weird math to fill up the gui nicely + if ((index % 9) == 4) { + index++; + continue; + } + if ((index % 9) > 4) { + index -= (((index % 9) - 4) * 2); + } else { + if ((index % 9) == 0) { + index += 13; + } else { + index += (((4 - (index % 9)) * 2) + 1); + } + } + + } + index = 49; + List outp = ((InputRecipe) fccf.getCurrentRecipe()) + .getOutputRepresentation(fccf.getOutputInventory(), fccf); + if (outp.size() > 18) { + outp = new ItemMap(outp).getLoredItemCountRepresentation(); + } + for (ItemStack is : outp) { + Clickable c = new Clickable(is) { + @Override + public void clicked(Player arg0) { + // nothing, just supposed to look nice + } + }; + ci.setSlot(c, index); + if ((index % 9) == 4) { + index++; + continue; + } + if ((index % 9) > 4) { + index -= (((index % 9) - 4) * 2); + } else { + if ((index % 9) == 0) { + index -= 13; + } else { + index += (((4 - (index % 9)) * 2) + 1); + } + } + + } + ci.showInventory(p); + + } else { // not sneaking, so just a short sumup + p.sendMessage( + ChatColor.GOLD + fccf.getName() + " currently turned " + (fccf.isActive() ? "on" : "off")); + if (fccf.isActive()) { + p.sendMessage(ChatColor.GOLD + + String.valueOf((fccf.getCurrentRecipe().getProductionTime() - fccf.getRunningTime()) / 20) + + " seconds remaining until current run is complete"); + } + p.sendMessage(ChatColor.GOLD + "Currently selected recipe: " + fccf.getCurrentRecipe().getName()); + p.sendMessage(ChatColor.GOLD + "Currently at " + fccf.getRepairManager().getHealth() + " health"); + if (fccf.getRepairManager().inDisrepair()) { + PercentageHealthRepairManager rm = ((PercentageHealthRepairManager) fccf.getRepairManager()); + long leftTime = rm.getGracePeriod() - (System.currentTimeMillis() - rm.getBreakTime()); + long months = leftTime / (60L * 60L * 24L * 30L * 1000L); + long days = (leftTime - (months * 60L * 60L * 24L * 30L * 1000L)) / (60L * 60L * 24L * 1000L); + long hours = (leftTime - (months * 60L * 60L * 24L * 30L * 1000L) + - (days * 60L * 60L * 24L * 1000L)) / (60L * 60L * 1000L); + String time = (months != 0 ? months + " months, " : "") + (days != 0 ? days + " days, " : "") + + (hours != 0 ? hours + " hours" : ""); + if (time.equals("")) { + time = " less than an hour"; + } + p.sendMessage(ChatColor.GOLD + "It will break permanently in " + time); + } + } + + return; + } + if (b.equals(((FurnCraftChestStructure) fccf.getMultiBlockStructure()).getCraftingTable())) { // crafting table + // interaction + ComponableInventory compInv = buildRecipeInventory(p); + compInv.update(); + compInv.updatePlayerView(); + return; + } + if (b.equals(fccf.getFurnace())) { // furnace interaction + if (fccf.isActive()) { + fccf.deactivate(); + p.sendMessage(ChatColor.RED + "Deactivated " + fccf.getName()); + } else { + fccf.attemptToActivate(p, false); + } + } + } + + private ComponableInventory buildRecipeInventory(Player p) { + ComponableInventory compInv = new ComponableInventory("Select a recipe", 6, p); + + Clickable autoClick = buildAutoSelectToggle(); + Clickable menuC = buildMenuClickable(); + Clickable menuModeButton = buildMenuModeCycleButton(p); + Clickable[] buddons = new Clickable[] { autoClick, menuC, menuModeButton }; + StaticDisplaySection lowerSection = new StaticDisplaySection(buddons); + + Block fblock = fccf.getFurnace(); + switch (fccf.getUiMenuMode()) { + case IOCONFIG: { + Scrollbar recipeScroller = buildRecipeScrollbar(3); + compInv.addComponent(recipeScroller, SlotPredicates.offsetRectangle(3, 9, 0, 0)); + if (fblock.getType() == Material.FURNACE) { + Furnace fstate = (Furnace) fblock.getState(); + org.bukkit.block.data.type.Furnace fdata = (org.bukkit.block.data.type.Furnace) fstate.getBlockData(); + BlockFace facing = fdata.getFacing(); + IOConfigSection furnaceConfigSection = new IOConfigSection( + p, + fccf.getFurnaceIOSelector(), + Material.FURNACE, + fblock, + facing, + fccf); + IOConfigSection tableConfigSection = new IOConfigSection( + p, + fccf.getTableIOSelector(), + Material.CRAFTING_TABLE, + ((FurnCraftChestStructure) fccf.getMultiBlockStructure()).getCraftingTable(), + facing, + fccf); + compInv.addComponent(furnaceConfigSection, SlotPredicates.offsetRectangle(3, 3, 3, 0)); + compInv.addComponent(tableConfigSection, SlotPredicates.offsetRectangle(3, 3, 3, 4)); + } + compInv.addComponent(lowerSection, SlotPredicates.offsetRectangle(3, 1, 3, 8)); + break; + } + default: { + Scrollbar recipeScroller = buildRecipeScrollbar(5); + compInv.addComponent(recipeScroller, SlotPredicates.offsetRectangle(5, 9, 0, 0)); + compInv.addComponent(lowerSection, SlotPredicates.offsetRectangle(1, 3, 5, 6)); + } + } + return compInv; + } + + private Scrollbar buildRecipeScrollbar(int rows) { + rows = Math.max(1, Math.min(5, rows)); + List recipeList = fccf.getRecipes(); + List recipeClickList = new ArrayList<>(recipeList.size()); + for (IRecipe rec : fccf.getRecipes()) { + InputRecipe recipe = (InputRecipe) (rec); + ItemStack recStack = recipe.getRecipeRepresentation(); + int runcount = fccf.getRunCount(recipe); + ItemUtils.addLore(recStack, "",ChatColor.AQUA + "Ran " + String.valueOf(runcount) + " times"); + if (rec == fccf.getCurrentRecipe()) { + ItemUtils.addLore(recStack, ChatColor.GREEN + "Currently selected"); + ItemUtils.addGlow(recStack); + } + if (recipe instanceof ProductionRecipe) { + ProductionRecipe prod = (ProductionRecipe) recipe; + if (prod.getModifier() != null) { + ItemUtils.addLore(recStack, ChatColor.BOLD + " " + ChatColor.GOLD + + fccf.getRecipeLevel(recipe) + " ★"); + ItemUtils.addLore(recStack, ChatColor.GREEN + "Current output multiplier: " + decimalFormatting + .format(prod.getModifier().getFactor(fccf.getRecipeLevel(recipe), runcount))); + } + } + Clickable c = new Clickable(recStack) { + + @Override + public void clicked(Player p) { + if (fccf.isActive()) { + p.sendMessage(ChatColor.RED + "You can't switch recipes while the factory is running"); + } else { + fccf.setRecipe(recipe); + p.sendMessage(ChatColor.GREEN + "Switched recipe to " + recipe.getName()); + } + } + }; + recipeClickList.add(c); + } + Scrollbar recipeScroller = new Scrollbar(recipeClickList, rows * 9); + recipeScroller.setBackwardsClickSlot(rows == 1 ? 0 : 8); + return recipeScroller; + } + + private Clickable buildAutoSelectToggle() { + ItemStack autoSelectStack = new ItemStack(Material.REDSTONE_BLOCK); + ItemUtils.setDisplayName(autoSelectStack, "Toggle auto select"); + ItemUtils.addLore(autoSelectStack, + ChatColor.GOLD + "Make the factory automatically select any", + ChatColor.GOLD + "recipe it can run whenever you activate it", + ChatColor.AQUA + "Click to turn it " + (fccf.isAutoSelect() ? "off" : "on")); + Clickable autoClick = new Clickable(autoSelectStack) { + + @Override + public void clicked(Player p) { + p.sendMessage(ChatColor.GREEN + "Turned auto select " + (fccf.isAutoSelect() ? "off" : "on") + + " for " + fccf.getName()); + fccf.setAutoSelect(!fccf.isAutoSelect()); + } + }; + return autoClick; + } + + private Clickable buildMenuClickable() { + ItemStack menuStack = new ItemStack(Material.PAINTING); + ItemUtils.setDisplayName(menuStack, "Open menu"); + ItemUtils.addLore(menuStack, ChatColor.LIGHT_PURPLE + "Click to open a detailed menu"); + Clickable menuC = new Clickable(menuStack) { + @Override + public void clicked(Player p) { + FactoryModGUI gui = new FactoryModGUI(p); + gui.showForFactory((FurnCraftChestEgg)FactoryMod.getInstance().getManager().getEgg(fccf.getName())); + } + }; + return menuC; + } + + private Clickable buildMenuModeCycleButton(Player p) { + FurnCraftChestFactory.UiMenuMode[] modes = FurnCraftChestFactory.UiMenuMode.values(); + FurnCraftChestFactory.UiMenuMode curMode = fccf.getUiMenuMode(); + FurnCraftChestFactory.UiMenuMode nextMode = modes[(curMode.ordinal() + 1) % modes.length]; + ItemStack display = new ItemStack(nextMode.uiMaterial); + ItemUtils.setComponentDisplayName(display, Component.text(nextMode.uiDescription)); + + Clickable menuModeButton = new Clickable(display) { + private ClickableInventory inventory; + private int slot; + + @Override + protected void clicked(Player player) { + cycleMenuMode(); + ComponableInventory compInv = buildRecipeInventory(p); + compInv.update(); + compInv.updatePlayerView(); + } + + @Override + public void addedToInventory(ClickableInventory inv, int slot) { + this.inventory = inv; + this.slot = slot; + } + + private void cycleMenuMode() { + FurnCraftChestFactory.UiMenuMode innerCurMode = fccf.getUiMenuMode(); + innerCurMode = modes[(innerCurMode.ordinal() + 1) % modes.length]; + fccf.setUiMenuMode(innerCurMode); + + ItemStack innerCurStack = getItemStack(); + FurnCraftChestFactory.UiMenuMode innerNextMode = modes[(innerCurMode.ordinal() + 1) % modes.length]; + innerCurStack.setType(innerNextMode.uiMaterial); + ItemUtils.setComponentDisplayName(innerCurStack, Component.text(innerNextMode.uiDescription)); + + if (inventory != null && inventory.getSlot(slot) == this) { + inventory.setSlot(this, slot); + } + } + }; + return menuModeButton; + } + + @Override + public void rightClick(Player p, Block b, BlockFace bf) { + // Nothing to do here, every block already has a right click + // functionality + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/interactionManager/IInteractionManager.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/interactionManager/IInteractionManager.java new file mode 100644 index 000000000..1a0400a04 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/interactionManager/IInteractionManager.java @@ -0,0 +1,56 @@ +package com.github.igotyou.FactoryMod.interactionManager; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockRedstoneEvent; + +/** + * Handles any interaction with the factory it's associated with + * + */ +public interface IInteractionManager { + /** + * Called if a player right clicks a block, which is part of the factory of + * this manager + * + * @param p + * Player who clicked + * @param b + * Block which was clicked + */ + public void rightClick(Player p, Block b, BlockFace bf); + + /** + * Called if a player left clicks a block, which is part of the factory of + * this manager + * + * @param p + * Player who clicked + * @param b + * Block which was clicked + */ + public void leftClick(Player p, Block b, BlockFace bf); + + /** + * Called if a block, which is part of the factory of this manager is + * broken, this can have various causes such as players, fire or explosions + * + * @param p + * Player who broke the block or null if no player was the direct + * cause + * @param b + * Block which was broken + */ + public void blockBreak(Player p, Block b); + + /** + * Called if a redstone event occurs for any block which is part of the + * factory of this manager + * + * @param e + * Event which occured + */ + public void redStoneEvent(BlockRedstoneEvent e, Block factoryBlock); + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/interactionManager/PipeInteractionManager.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/interactionManager/PipeInteractionManager.java new file mode 100644 index 000000000..5f602a21c --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/interactionManager/PipeInteractionManager.java @@ -0,0 +1,102 @@ +package com.github.igotyou.FactoryMod.interactionManager; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.FactoryModManager; +import com.github.igotyou.FactoryMod.factories.Pipe; +import com.github.igotyou.FactoryMod.repairManager.NoRepairDestroyOnBreakManager; +import com.github.igotyou.FactoryMod.structures.MultiBlockStructure; +import com.github.igotyou.FactoryMod.structures.PipeStructure; +import java.util.List; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockRedstoneEvent; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.citadel.ReinforcementLogic; +import vg.civcraft.mc.citadel.model.Reinforcement; + +public class PipeInteractionManager implements IInteractionManager { + private Pipe pipe; + private FactoryModManager manager; + + public PipeInteractionManager() { + this.manager = FactoryMod.getInstance().getManager(); + } + + public void setPipe(Pipe pipe) { + this.pipe = pipe; + } + + @Override + public void rightClick(Player p, Block b, BlockFace bf) { + // no use for this here + } + + @Override + public void leftClick(Player p, Block b, BlockFace bf) { + ItemStack hand = p.getInventory().getItemInMainHand(); + if (manager.isCitadelEnabled()) { + Reinforcement rein = ReinforcementLogic.getReinforcementProtecting(b); + if (rein != null && !rein.hasPermission(p, FactoryMod.getInstance().getPermissionManager().getUseFactory())) { + p.sendMessage(ChatColor.RED + "You dont have permission to interact with this factory"); + return; + } + } + if (b.equals(((PipeStructure) (pipe.getMultiBlockStructure())).getStart())) { + if (hand.getType() == manager.getFactoryInteractionMaterial()) { + //TODO + } else { + if (pipe.isActive()) { + p.sendMessage( + ChatColor.RED + "You can not modify the allowed materials while the pipe is transferring"); + return; + } + List allowedMats = pipe.getAllowedMaterials(); + if (allowedMats == null || !allowedMats.contains(hand.getType())) { + pipe.addAllowedMaterial(hand.getType()); + p.sendMessage(ChatColor.GREEN + "Added " + hand.getType().toString() + " as allowed material"); + } else { + pipe.removeAllowedMaterial(hand.getType()); + p.sendMessage(ChatColor.GOLD + "Removed " + hand.getType().toString() + " as allowed material"); + } + } + } else if (b.equals(((PipeStructure) (pipe.getMultiBlockStructure())).getFurnace())) { + if (pipe.isActive()) { + pipe.deactivate(); + p.sendMessage(ChatColor.GOLD + pipe.getName() + " has been deactivated"); + } else { + pipe.attemptToActivate(p, false); + } + } + } + + @Override + public void redStoneEvent(BlockRedstoneEvent e, Block factoryBlock) { + int threshold = manager.getRedstonePowerOn(); + if (!factoryBlock.getLocation() + .equals(((PipeStructure) pipe.getMultiBlockStructure()).getFurnace().getLocation())) { + return; + } + if (e.getOldCurrent() >= threshold && e.getNewCurrent() < threshold && pipe.isActive()) { + if ((!manager.isCitadelEnabled() + || MultiBlockStructure.citadelRedstoneChecks(e.getBlock()))) { + pipe.deactivate(); + } + } else if (e.getOldCurrent() < threshold && e.getNewCurrent() >= threshold && !pipe.isActive()) { + if (!manager.isCitadelEnabled() + || MultiBlockStructure.citadelRedstoneChecks(e.getBlock())) { + pipe.attemptToActivate(null, false); + } + } + } + + @Override + public void blockBreak(Player p, Block b) { + ((NoRepairDestroyOnBreakManager) (pipe.getRepairManager())).breakIt(); + if (p != null) { + p.sendMessage(ChatColor.RED + "Pipe was destroyed"); + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/interactionManager/SorterInteractionManager.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/interactionManager/SorterInteractionManager.java new file mode 100644 index 000000000..ac52d6f5e --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/interactionManager/SorterInteractionManager.java @@ -0,0 +1,108 @@ +package com.github.igotyou.FactoryMod.interactionManager; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.FactoryModManager; +import com.github.igotyou.FactoryMod.factories.Sorter; +import com.github.igotyou.FactoryMod.structures.BlockFurnaceStructure; +import com.github.igotyou.FactoryMod.structures.MultiBlockStructure; +import org.bukkit.ChatColor; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockRedstoneEvent; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.citadel.ReinforcementLogic; +import vg.civcraft.mc.citadel.model.Reinforcement; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class SorterInteractionManager implements IInteractionManager { + + private Sorter sorter; + private BlockFurnaceStructure bfs; + private FactoryModManager manager; + + public SorterInteractionManager(Sorter sorter) { + if (sorter != null) { + setSorter(sorter); + } + manager = FactoryMod.getInstance().getManager(); + } + + public SorterInteractionManager() { + this(null); + } + + public void setSorter(Sorter sorter) { + this.sorter = sorter; + this.bfs = (BlockFurnaceStructure) sorter.getMultiBlockStructure(); + } + + @Override + public void blockBreak(Player p, Block b) { + sorter.getRepairManager().breakIt(); + if (p != null) { + p.sendMessage(ChatColor.DARK_RED + "The sorter was destroyed"); + } + } + + @Override + public void rightClick(Player p, Block b, BlockFace bf) { + // not needed here + } + + @Override + public void leftClick(Player p, Block b, BlockFace bf) { + if (manager.isCitadelEnabled()) { + Reinforcement rein = ReinforcementLogic.getReinforcementProtecting(b); + if (rein != null && !rein.hasPermission(p, FactoryMod.getInstance().getPermissionManager().getUseFactory())) { + p.sendMessage(ChatColor.RED + "You dont have permission to interact with this factory"); + return; + } + } + if (b.equals(bfs.getFurnace())) { + if (p.getInventory().getItemInMainHand().getType() + .equals(manager.getFactoryInteractionMaterial())) { + sorter.attemptToActivate(p, false); + } + } else { // center + if (p.isSneaking() && p.getInventory().getItemInMainHand().getType() + .equals(manager.getFactoryInteractionMaterial())) { + //TODO + return; + } + ItemStack is = p.getInventory().getItemInMainHand(); + BlockFace side = sorter.getSide(is); + if (side == null) { + sorter.addAssignment(bf, is); + p.sendMessage(ChatColor.GREEN + "Added " + ItemUtils.getItemName(is) + " to " + bf.toString()); + } else { + if (side == bf) { + sorter.removeAssignment(is); + p.sendMessage(ChatColor.GOLD + "Removed " + ItemUtils.getItemName(is) + " from " + side.toString()); + } else { + p.sendMessage(ChatColor.RED + "This item is already associated with " + side.toString()); + } + } + } + } + + @Override + public void redStoneEvent(BlockRedstoneEvent e, Block factoryBlock) { + int threshold = manager.getRedstonePowerOn(); + if (!factoryBlock.getLocation() + .equals(((BlockFurnaceStructure) sorter.getMultiBlockStructure()).getFurnace().getLocation())) { + return; + } + if (e.getOldCurrent() >= threshold && e.getNewCurrent() < threshold && sorter.isActive()) { + if ((!manager.isCitadelEnabled() + || MultiBlockStructure.citadelRedstoneChecks(e.getBlock()))) { + sorter.deactivate(); + } + } else if (e.getOldCurrent() < threshold && e.getNewCurrent() >= threshold && !sorter.isActive()) { + if (!manager.isCitadelEnabled() + || MultiBlockStructure.citadelRedstoneChecks(e.getBlock())) { + sorter.attemptToActivate(null, false); + } + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/listeners/CitadelListener.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/listeners/CitadelListener.java new file mode 100644 index 000000000..e45c23744 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/listeners/CitadelListener.java @@ -0,0 +1,35 @@ +package com.github.igotyou.FactoryMod.listeners; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.FactoryModManager; +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import java.util.Random; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import vg.civcraft.mc.citadel.events.ReinforcementDamageEvent; + +public class CitadelListener implements Listener { + + private FactoryModManager manager; + private Random rng; + + public CitadelListener() { + this.manager = FactoryMod.getInstance().getManager(); + this.rng = new Random(); + } + + @EventHandler + public void reinDamage(ReinforcementDamageEvent e) { + Factory f = manager.getFactoryAt(e.getReinforcement().getLocation()); + if (!(f instanceof FurnCraftChestFactory)) { + return; + } + FurnCraftChestFactory fccf = (FurnCraftChestFactory) f; + if (fccf.getMultiBlockStructure().getCenter().equals(e.getReinforcement().getLocation())) { + if (rng.nextDouble() > fccf.getCitadelBreakReduction()) { + e.setCancelled(true); + } + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/listeners/CompactItemListener.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/listeners/CompactItemListener.java new file mode 100644 index 000000000..c6a345e58 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/listeners/CompactItemListener.java @@ -0,0 +1,190 @@ +package com.github.igotyou.FactoryMod.listeners; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.events.FactoryActivateEvent; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import io.papermc.paper.event.player.PlayerLoomPatternSelectEvent; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.enchantment.EnchantItemEvent; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.inventory.CraftingInventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.LoomInventory; +import org.bukkit.inventory.meta.ItemMeta; + +/** + * Used to handle events related to items with compacted lore + */ +public class CompactItemListener implements Listener { + + /** + * Prevents players from placing compacted blocks + */ + @EventHandler + public void blockPlaceEvent(BlockPlaceEvent e) { + if (isCompacted(e.getItemInHand())) { + e.setCancelled(true); + e.getPlayer().sendMessage(ChatColor.RED + "You can not place compacted blocks"); + } + + } + + /** + * Prevents players from crafting with compacted materials + */ + @EventHandler + public void craftingEvent(CraftItemEvent e) { + CraftingInventory ci = e.getInventory(); + for (ItemStack is : ci.getMatrix()) { + if (isCompacted(is)) { + e.setCancelled(true); + HumanEntity h = e.getWhoClicked(); + if (h instanceof Player) { + h.sendMessage(ChatColor.RED + "You can not craft with compacted items"); + } + break; + } + } + } + + /** + * Prevents players from enchanting compacted items + */ + @EventHandler + public void enchantEvent(EnchantItemEvent e) { + ItemStack ei = e.getItem(); + if (isCompacted(ei)) { + e.setCancelled(true); + e.getEnchanter().sendMessage(ChatColor.RED + "You can not enchant compacted items"); + } + } + + /** + * Prevents players from eating compacted items + */ + @EventHandler + public void itemConsumeEvent(PlayerItemConsumeEvent e) { + if (isCompacted(e.getItem())) { + e.setCancelled(true); + e.getPlayer().sendMessage(ChatColor.RED + "You can not eat compacted food"); + } + } + + /** + * Prevents players from wordbanking compacted items + */ + @EventHandler + public void wordbankEvent(FactoryActivateEvent e) { + FurnCraftChestFactory fac = ((FurnCraftChestFactory) e.getFactory()); + + if (!fac.getCurrentRecipe().getTypeIdentifier().equals("WORDBANK")) return; + + if (isCompacted(fac.getInputInventory().getItem(0))) { + e.setCancelled(true); + e.getActivator().sendMessage(ChatColor.RED + "You can not wordbank compacted items"); + } + } + + /** + * Prevents players from using compacted items in anvils + */ + @EventHandler + public void anvilEvent(InventoryClickEvent e) { + if (e.getCurrentItem() == null) return; + + if (e.getCurrentItem().getType() == Material.AIR) return; + + if (e.getInventory().getType() == InventoryType.ANVIL) { + if (e.getSlotType() == InventoryType.SlotType.RESULT) { + if (isCompacted(e.getInventory().getItem(0)) || isCompacted(e.getInventory().getItem(1))) { + e.setCancelled(true); + e.getWhoClicked().sendMessage(ChatColor.RED + "You can not use compacted items in an anvil"); + } + } + } + } + + /** + * Prevents players from using compacted items in smithing tables + */ + @EventHandler + public void smithEvent(InventoryClickEvent e) { + if (e.getCurrentItem() == null) return; + + if (e.getCurrentItem().getType() == Material.AIR) return; + + if (e.getInventory().getType() == InventoryType.SMITHING) { + if (e.getSlotType() == InventoryType.SlotType.RESULT) { + if (isCompacted(e.getInventory().getItem(0)) || isCompacted(e.getInventory().getItem(1))) { + e.setCancelled(true); + e.getWhoClicked().sendMessage(ChatColor.RED + "You can not use compacted items in a smithing table"); + } + } + } + } + + /** + * Prevents players from using compacted items in looms + */ + @EventHandler + public void loomEvent(PlayerLoomPatternSelectEvent e) { + LoomInventory li = e.getLoomInventory(); + + for (int i = 0; i < 2; i++) { + if (li.getItem(i) == null) continue; + if (li.getItem(i).getType() == Material.AIR) continue; + if (isCompacted(li.getItem(i))) { + e.setCancelled(true); + e.getPlayer().sendMessage(ChatColor.RED + "You can not use compacted items in a loom"); + } + } + } + + /** + * Prevents players from copying compacted maps in cartography tables + */ + @EventHandler + public void cartographyCopyEvent(InventoryClickEvent e) { + if (e.getCurrentItem() == null) return; + + if (e.getCurrentItem().getType() == Material.AIR) return; + + if (e.getInventory().getType() == InventoryType.CARTOGRAPHY) { + if (e.getSlotType() == InventoryType.SlotType.RESULT) { + if (isCompacted(e.getInventory().getItem(0)) || isCompacted(e.getInventory().getItem(1))) { + e.setCancelled(true); + e.getWhoClicked().sendMessage(ChatColor.RED + "You can not copy compacted maps"); + } + } + } + } + + private boolean isCompacted(ItemStack is) { + if (is == null) { + return false; + } + if (!is.hasItemMeta()) { + return false; + } + ItemMeta im = is.getItemMeta(); + if (!im.hasLore()) { + return false; + } + for (String lore : im.getLore()) { + if (FactoryMod.getInstance().getManager().isCompactLore(lore)) { + return true; + } + } + return false; + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/listeners/FactoryModListener.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/listeners/FactoryModListener.java new file mode 100644 index 000000000..a1204ea40 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/listeners/FactoryModListener.java @@ -0,0 +1,221 @@ +package com.github.igotyou.FactoryMod.listeners; + +import com.github.igotyou.FactoryMod.FactoryModManager; +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.powerManager.FurnacePowerManager; +import com.github.igotyou.FactoryMod.structures.MultiBlockStructure; +import java.util.List; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Furnace; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Directional; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockBurnEvent; +import org.bukkit.event.block.BlockDispenseEvent; +import org.bukkit.event.block.BlockRedstoneEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.FurnaceInventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.world.WorldUtils; + +public class FactoryModListener implements Listener { + private FactoryModManager manager; + + public FactoryModListener(FactoryModManager manager) { + this.manager = manager; + } + + /** + * Called when a block is broken If the block that is destroyed is part of a + * factory, call the required methods. + */ + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void blockBreakEvent(BlockBreakEvent e) { + Block block = e.getBlock(); + if (manager.isPossibleInteractionBlock(block.getType())) { + Factory c = manager.getFactoryAt(block); + if (c != null) { + // let creative player interact without breaking it + if (e.getPlayer().getGameMode() == GameMode.CREATIVE + &&e.getPlayer().getInventory() + .getItemInMainHand().getType() == manager.getFactoryInteractionMaterial()) { + e.setCancelled(true); + return; + } + c.getInteractionManager().blockBreak(e.getPlayer(), block); + } + } + + } + + @EventHandler + public void redstoneChange(BlockRedstoneEvent evt) { + if (evt.getOldCurrent() == evt.getNewCurrent()) { + return; + } + Block powerSource = evt.getBlock(); + Material psType = powerSource.getType(); + if (psType == Material.REPEATER || psType == Material.COMPARATOR) { + BlockData psData = powerSource.getState().getBlockData(); + BlockFace direction = ((Directional) psData).getFacing(); + // repeaters "face" their input apparently + Block poweredBlock = powerSource.getRelative(direction.getOppositeFace()); + Factory f = manager.getFactoryAt(poweredBlock); + if (f != null) { + f.getInteractionManager().redStoneEvent(evt, poweredBlock); + } + } else { + for (BlockFace direction : WorldUtils.ALL_SIDES) { + Block poweredBlock = powerSource.getRelative(direction); + Factory f = manager.getFactoryAt(poweredBlock); + if (f != null) { + f.getInteractionManager().redStoneEvent(evt, poweredBlock); + } + } + } + } + + /** + * Called when a entity explodes(creeper,tnt etc.) Nearly the same as + * blockBreakEvent + */ + @EventHandler(priority=EventPriority.HIGHEST) + public void explosionListener(EntityExplodeEvent e) { + List blocks = e.blockList(); + for (Block block : blocks) { + if (manager.isPossibleInteractionBlock(block.getType())) { + Factory c = manager.getFactoryAt(block); + if (c != null) { + c.getInteractionManager().blockBreak(null, block); + } + } + } + } + + /** + * Called when a block burns Nearly the same as blockBreakEvent + */ + @EventHandler(priority=EventPriority.HIGHEST) + public void burnListener(BlockBurnEvent e) { + Block block = e.getBlock(); + if (manager.isPossibleInteractionBlock(block.getType())) { + Factory c = manager.getFactoryAt(block); + if (c != null) { + c.getInteractionManager().blockBreak(null, block); + } + } + } + + @EventHandler + public void playerInteract(PlayerInteractEvent evt) { + Block block = evt.getClickedBlock(); + Player player = evt.getPlayer(); + if (block != null && manager.isPossibleInteractionBlock(block.getType())) { + BlockFace blockFace = evt.getBlockFace(); + Factory factory = manager.getFactoryAt(block); + if (evt.getAction() == Action.RIGHT_CLICK_BLOCK) { + if (factory != null) { + factory.getInteractionManager().rightClick(player, block, blockFace); + } else { + // check if chest is other half of double chest + if (block.getType() == Material.CHEST || block.getType() == Material.TRAPPED_CHEST) { + for (Block b : MultiBlockStructure.searchForBlockOnSides(block, block.getType())) { + Factory f = manager.getFactoryAt(b); + if (f != null) { + f.getInteractionManager().rightClick(player, b, blockFace); + } + } + } + } + } + if (evt.getAction() == Action.LEFT_CLICK_BLOCK) { + if (factory == null) { + if (manager.isPossibleCenterBlock(block.getType())) { + if (player.getInventory().getItemInMainHand().getType() == manager + .getFactoryInteractionMaterial()) { + manager.attemptCreation(block, player); + } + } else { + // check if chest is other half of double chest + if (block.getType() == Material.CHEST || block.getType() == Material.TRAPPED_CHEST) { + for (Block b : MultiBlockStructure.searchForBlockOnAllSides(block, block.getType())) { + Factory f = manager.getFactoryAt(b); + if (f != null) { + f.getInteractionManager().leftClick(player, b, blockFace); + } + } + } + } + } else { + factory.getInteractionManager().leftClick(player, block, blockFace); + } + + } + } + } + + /** + * Allow shift-clicking valid fuel into the smelting slot of a factories furnace + * @param event + */ + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (event.getAction() == InventoryAction.MOVE_TO_OTHER_INVENTORY) { + boolean clickedTop = event.getRawSlot() < event.getView().getTopInventory().getSize(); + InventoryHolder holder = !clickedTop ? event.getView().getTopInventory().getHolder() : event.getView().getBottomInventory().getHolder(); + if (holder instanceof Furnace) { + Block furnace = ((Furnace) holder).getBlock(); + if (manager.isPossibleInteractionBlock(furnace.getType())) { + Factory factory = manager.getFactoryAt(furnace.getLocation()); + if (factory == null) { + return; + } + ItemStack fuel = ((FurnacePowerManager) factory.getPowerManager()).getFuel(); + if (fuel == null) { + return; + } + FurnaceInventory inv = (FurnaceInventory) holder.getInventory(); + Player p = (Player) event.getWhoClicked(); + ItemStack clicked = event.getCurrentItem(); + moveFuelToSmeltingSlot(inv, p, fuel, clicked); + } + } + } + } + + public void moveFuelToSmeltingSlot(FurnaceInventory inv, Player p, ItemStack fuel, ItemStack clicked) { + if (clicked != null && clicked.getType() == fuel.getType()) { + // Check (bottom) fuel slot is filled + if (inv.getFuel() != null && inv.getFuel().getAmount() == inv.getFuel().getMaxStackSize()) { + ItemStack smeltingSlot = inv.getSmelting(); + // Check (top) smelting slot has space + if (smeltingSlot == null || (smeltingSlot.getType() == fuel.getType() && smeltingSlot.getAmount() < smeltingSlot.getMaxStackSize())) { + int oldSlotAmount = smeltingSlot == null ? 0 : smeltingSlot.getAmount(); + int newSlotAmount = Math.min(oldSlotAmount + clicked.getAmount(), fuel.getMaxStackSize()); + inv.setSmelting(new ItemStack(fuel.getType(), newSlotAmount)); + clicked.setAmount(clicked.getAmount() + (oldSlotAmount - newSlotAmount)); + p.updateInventory(); + } + } + } + } + + @EventHandler + public void blockDispenser(BlockDispenseEvent e) { + if (manager.getFactoryAt(e.getBlock()) != null) { + e.setCancelled(true); + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/listeners/NetherPortalListener.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/listeners/NetherPortalListener.java new file mode 100644 index 000000000..178aa11ab --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/listeners/NetherPortalListener.java @@ -0,0 +1,38 @@ +package com.github.igotyou.FactoryMod.listeners; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityPortalEvent; +import org.bukkit.event.player.PlayerPortalEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; + +/** + * Used to disables vanilla nether portals. This will only be registered as a + * listener if we want to disable portals. + * + */ +public class NetherPortalListener implements Listener { + @EventHandler(priority = EventPriority.NORMAL) + public void handlePortalTelportEvent(PlayerPortalEvent e) { + // Disable normal nether portal teleportation + if (e.getCause() == TeleportCause.NETHER_PORTAL) { + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.NORMAL) + public void playerTeleportEvent(PlayerTeleportEvent e) { + // Disable normal nether portal teleportation + if (e.getCause() == TeleportCause.NETHER_PORTAL) { + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.NORMAL) + public void entityTeleportEvent(EntityPortalEvent event) { + event.setCancelled(true); + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/powerManager/FurnacePowerManager.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/powerManager/FurnacePowerManager.java new file mode 100644 index 000000000..daf8e8399 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/powerManager/FurnacePowerManager.java @@ -0,0 +1,115 @@ +package com.github.igotyou.FactoryMod.powerManager; + +import com.github.igotyou.FactoryMod.utility.IIOFInventoryProvider; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Furnace; +import org.bukkit.inventory.FurnaceInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; + +/** + * Power manager for a FurnCraftChest factory, which uses a specific item in the + * furnace of the factory as fuel + * + */ +public class FurnacePowerManager implements IPowerManager { + private ItemStack fuel; + private int powerCounter; + private int fuelConsumptionIntervall; + private Block furnace; + private IIOFInventoryProvider iofProvider; + + public FurnacePowerManager(Block furnace, ItemStack fuel, + int fuelConsumptionIntervall) { + this.fuel = fuel; + this.fuelConsumptionIntervall = fuelConsumptionIntervall; + this.furnace = furnace; + } + + public FurnacePowerManager(ItemStack fuel, int fuelConsumptionIntervall) { + this.fuel = fuel; + this.fuelConsumptionIntervall = fuelConsumptionIntervall; + } + + public void setIofProvider(IIOFInventoryProvider iofProvider) { + this.iofProvider = iofProvider; + } + + public IIOFInventoryProvider getIofProvider() { + return iofProvider; + } + + public int getPowerCounter() { + return powerCounter; + } + + public boolean powerAvailable(int fuelCount) { + if (iofProvider != null) { + Inventory fuelInv = iofProvider.getFuelInventory(); + if (fuelInv != null) { + ItemMap im = new ItemMap(fuelInv); + return im.getAmount(fuel) >= fuelCount; + } + } + + if (furnace.getType() != Material.FURNACE) { + return false; + } + + if(fuelCount == 0) + fuelCount = 1; + + FurnaceInventory fi = ((Furnace) furnace.getState()).getInventory(); + ItemMap im = new ItemMap(); + im.addItemStack(fi.getFuel()); + im.addItemStack(fi.getSmelting()); + return im.getAmount(fuel) >= fuelCount; + } + + public int getPowerConsumptionIntervall() { + return fuelConsumptionIntervall; + } + + public void increasePowerCounter(int amount) { + powerCounter += amount; + } + + public void setPowerCounter(int amount) { + powerCounter = amount; + } + + public void consumePower(int fuelCount) { + if (iofProvider != null) { + Inventory fuelInv = iofProvider.getFuelInventory(); + if (fuelInv != null) { + for (int i = 0; i < fuelCount; i++) { + fuelInv.removeItem(fuel); + } + return; + } + } + + FurnaceInventory fi = ((Furnace) furnace.getState()).getInventory(); + + for(int i = 0; i < fuelCount; i++) + fi.removeItem(fuel); + } + + public int getFuelAmountAvailable() { + if (iofProvider != null) { + Inventory fuelInv = iofProvider.getFuelInventory(); + if (fuelInv != null) { + ItemMap im = new ItemMap(fuelInv); + return im.getAmount(fuel); + } + } + return new ItemMap(((Furnace) furnace.getState()).getInventory()).getAmount(fuel); + } + + public ItemStack getFuel() { + return fuel; + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/powerManager/IPowerManager.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/powerManager/IPowerManager.java new file mode 100644 index 000000000..569394552 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/powerManager/IPowerManager.java @@ -0,0 +1,49 @@ +package com.github.igotyou.FactoryMod.powerManager; + +/** + * Manager to handle power availability and power consumption for a specific + * factory + * + */ +public interface IPowerManager { + /** + * Consumes one unit of power, what that means is up to the concrete + * implementation + */ + public void consumePower(int fuelCount); + + /** + * @return Whether power for at least one further tick cycle is available + */ + public boolean powerAvailable(int fuelCount); + + /** + * @return How often power should be consumed when running the factory this + * manager is associated with, measure in ticks + */ + public int getPowerConsumptionIntervall(); + + /** + * @return Internal counter to count up until power needs to be consumed + * while a factory is running. Should be in ticks + */ + public int getPowerCounter(); + + /** + * Increases the internal power counter by the given amount + * + * @param amount + * Amount to increase by + */ + public void increasePowerCounter(int amount); + + /** + * Sets the internal power counter to the given value. Use this method with + * great care and try to use increasePowerCounter() instead if possible + * + * @param value + * New power counter value + */ + public void setPowerCounter(int value); + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/AOERepairRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/AOERepairRecipe.java new file mode 100644 index 000000000..5b8d0633b --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/AOERepairRecipe.java @@ -0,0 +1,187 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.repairManager.PercentageHealthRepairManager; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Chest; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class AOERepairRecipe extends InputRecipe { + private ItemStack essence; + private int repairPerEssence; + private int range; + + public AOERepairRecipe(String identifier, String name, int productionTime, ItemStack essence, + int range, int repairPerEssence) { + super(identifier, name, productionTime, new ItemMap(essence)); + this.essence = essence; + this.range = range; + this.repairPerEssence = repairPerEssence; + } + + @Override + public Material getRecipeRepresentationMaterial() { + return essence.getType(); + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + Chest c = (Chest) i.getHolder(); + Location loc = c.getLocation(); + List facs = getNearbyFactoriesSortedByDistance(loc); + int facCounter = 0; + int essenceCount = new ItemMap(i).getAmount(essence); + for (FurnCraftChestFactory fac : facs) { + PercentageHealthRepairManager rm = (PercentageHealthRepairManager) fac + .getRepairManager(); + int diff = 100 - rm.getRawHealth(); + if (diff >= repairPerEssence) { + essenceCount -= Math.min(essenceCount, diff / repairPerEssence); + facCounter++; + } + if (essenceCount <= 0) { + break; + } + } + ItemMap imp = new ItemMap(); + imp.addItemAmount(essence, new ItemMap(i).getAmount(essence) + - essenceCount); + List bla = imp.getItemStackRepresentation(); + for (ItemStack item : bla) { + item.setAmount(new ItemMap(i).getAmount(essence) - essenceCount); + ItemUtils.addLore(item, ChatColor.YELLOW + "Will repair " + + facCounter + " nearby factories total"); + } + return bla; + } + + private List getNearbyFactoriesSortedByDistance( + Location loc) { + LinkedList list = new LinkedList<>(); + Map distances = new HashMap<>(); + for (Factory f : FactoryMod.getInstance().getManager().getNearbyFactories(loc, range)) { + if (f instanceof FurnCraftChestFactory) { + double dist = f.getMultiBlockStructure().getCenter() + .distance(loc); + distances.put((FurnCraftChestFactory) f, dist); + if (list.isEmpty()) { + list.add((FurnCraftChestFactory) f); + } else { + for (int j = 0; j < list.size(); j++) { + if (distances.get(list.get(j)) > dist) { + list.add(j, (FurnCraftChestFactory) f); + break; + } + if (j == list.size() - 1) { + list.add(j, (FurnCraftChestFactory) f); + break; + } + } + } + } + } + return list; + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + Chest c = (Chest) i.getHolder(); + Location loc = c.getLocation(); + List facs = getNearbyFactoriesSortedByDistance(loc); + ItemStack is = new ItemStack(Material.CRAFTING_TABLE); + int essenceCount = new ItemMap(i).getAmount(essence); + for (FurnCraftChestFactory fac : facs) { + PercentageHealthRepairManager rm = (PercentageHealthRepairManager) fac + .getRepairManager(); + int diff = 100 - rm.getRawHealth(); + if (diff >= repairPerEssence) { + ItemUtils.addLore( + is, + ChatColor.LIGHT_PURPLE + + "Will repair " + + fac.getName() + + " to " + + Math.min( + 100, + rm.getRawHealth() + + (repairPerEssence * Math + .min(essenceCount, + diff + / repairPerEssence)))); + essenceCount -= Math.min(essenceCount, diff / repairPerEssence); + } + if (essenceCount <= 0) { + break; + } + } + List bla = new LinkedList<>(); + bla.add(is); + return bla; + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + Location loc = fccf.getChest().getLocation(); + List facs = getNearbyFactoriesSortedByDistance(loc); + int essenceCount = new ItemMap(inputInv).getAmount(essence); + for (FurnCraftChestFactory fac : facs) { + PercentageHealthRepairManager rm = (PercentageHealthRepairManager) fac + .getRepairManager(); + int diff = 100 - rm.getRawHealth(); + fac.getMultiBlockStructure().recheckComplete(); + if (diff >= repairPerEssence + && fac.getMultiBlockStructure().isComplete() + && !fac.isActive() + && fac.getPowerManager().powerAvailable(1)) { + int rem = Math.min(essenceCount, diff / repairPerEssence); + ItemStack remStack = essence.clone(); + remStack.setAmount(rem); + ItemMap remMap = new ItemMap(remStack); + Inventory targetInv = ((InventoryHolder) (fac.getChest() + .getState())).getInventory(); + if (remMap.fitsIn(targetInv)) { + if (remMap.removeSafelyFrom(inputInv)) { + targetInv.addItem(remStack); + for (IRecipe rec : fac.getRecipes()) { + if (rec instanceof RepairRecipe) { + fac.setRecipe(rec); + break; + } + } + fac.attemptToActivate(null, false); + break; + } + } + } + if (essenceCount <= 0) { + break; + } + } + return true; + } + + @Override + public String getTypeIdentifier() { + return "AOEREPAIR"; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("TODO"); + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/CompactingRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/CompactingRecipe.java new file mode 100644 index 000000000..0a3b9eda4 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/CompactingRecipe.java @@ -0,0 +1,207 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; + +import java.util.*; + +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; +import vg.civcraft.mc.civmodcore.inventory.items.MetaUtils; + +/** + * Used to compact items, which means whole or multiple stacks of an item are reduced to a single lored item, which is stackable to the same stacksize + * As the original material. This makes the transportation of + * those items much easier, additionally there can be a cost for each + * compaction. Items that stack to 64 and 16 will be compacted per stack and items that stack to 1 will be compacted with a 8:1 ratio + * + */ +public class CompactingRecipe extends InputRecipe { + private List excludedMaterials; + private String compactedLore; + + public CompactingRecipe(String identifier, ItemMap input, List excludedMaterial, + String name, int productionTime, String compactedLore) { + super(identifier, name, productionTime, input); + this.excludedMaterials = excludedMaterial; + this.compactedLore = compactedLore; + } + + @Override + public boolean enoughMaterialAvailable(Inventory inputInv) { + if (!input.isContainedIn(inputInv)) { + return false; + } + ItemMap im = new ItemMap(inputInv); + for (ItemStack is : inputInv.getContents()) { + if (is != null) { + if (compactable(is, im)) { + return true; + } + } + } + return false; + } + + @Override + public int getProductionTime() { + return productionTime; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + if (input.isContainedIn(inputInv)) { + ItemMap im = new ItemMap(inputInv); + //technically we could just directly work with the ItemMap here to iterate over the items so we dont check identical items multiple times, + //but using the iterator of the inventory preserves the order of the inventory, which is more important here to guarantee one behavior + //to the player + for (ItemStack is : inputInv.getContents()) { + if (is != null) { + if (compactable(is, im)) { + if (input.removeSafelyFrom(inputInv)) { + compact(is, inputInv, outputInv); + } + break; + } + } + } + } + logAfterRecipeRun(combo, fccf); + return true; + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + List result = new LinkedList<>(); + if (i == null) { + result.add(new ItemStack(Material.STONE, 64)); + result.addAll(input.getItemStackRepresentation()); + return result; + } + result = createLoredStacksForInfo(i); + ItemMap im = new ItemMap(i); + for (ItemStack is : i.getContents()) { + if (is != null) { + if (compactable(is, im)) { + ItemStack compactedStack = is.clone(); + result.add(compactedStack); + break; + } + } + } + return result; + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + List result = new LinkedList<>(); + if (i == null) { + ItemStack is = new ItemStack(Material.STONE, 64); + compactStack(is); + result.add(is); + return result; + } + ItemMap im = new ItemMap(i); + for (ItemStack is : i.getContents()) { + if (is != null) { + if (compactable(is, im)) { + ItemStack decompactedStack = is.clone(); + compactStack(decompactedStack); + result.add(decompactedStack); + break; + } + } + } + + return result; + } + + @Override + public Material getRecipeRepresentationMaterial() { + return Material.CHEST; + } + + /** + * Removes the amount required to compact the given ItemStack from the given inventory and adds a comapcted item to the inventory + * + * @param is + */ + private void compact(ItemStack is, Inventory inputInv, Inventory outputInv) { + ItemStack copy = is.clone(); + copy.setAmount(getCompactStackSize(copy.getType())); + ItemMap toRemove = new ItemMap(copy); + if (toRemove.removeSafelyFrom(inputInv)) { + compactStack(copy); + outputInv.addItem(copy); + } + } + + /** + * Applies the lore and set the amount to 1. Dont call this directly if you want to compact items for players + */ + private void compactStack(ItemStack is) { + ItemUtils.addLore(is,compactedLore); + is.setAmount(1); + } + + public static int getCompactStackSize(Material m) { + switch (m.getMaxStackSize()) { + case 64: return 64; + case 16: return 16; + case 1: return 8; + default: + FactoryMod.getInstance().warning("Unknown max stacksize for type " + m.toString()); + } + return 999_999; //prevents compacting in error case, because never enough will fit in a chest + } + /** + * Checks whether enough of a certain item stack is available to compact it + * + * @param is + * ItemStack to check + * @param im + * ItemMap representing the inventory from which is compacted + * @return True if compacting the stack is allowed, false if not + */ + private boolean compactable(ItemStack is, ItemMap im) { + if (is == null || excludedMaterials.contains(is.getType()) || (input.getAmount(is) != 0) || (is.getItemMeta().getLore() != null && + is.getItemMeta().getLore().contains(compactedLore))) { + return false; + } + return im.getAmount(is) >= getCompactStackSize(is.getType()); + } + + @Override + public String getTypeIdentifier() { + return "COMPACT"; + } + + public String getCompactedLore() { + return compactedLore; + } + + public List getExcludedMaterials() { + return excludedMaterials; + } + + @Override + public List getTextualInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("An entire stack of a stackable item", "---OR---", "Eight of a non stackable item"); + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("Input stack compacted into a single item"); + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/DecompactingRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/DecompactingRecipe.java new file mode 100644 index 000000000..10bd1a3d3 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/DecompactingRecipe.java @@ -0,0 +1,190 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +/** + * Used to decompact itemstacks, which means a single item with compacted lore + * is turned into a whole stack without lore. This reverses the functionality of + * the CompactingRecipe + * + */ +public class DecompactingRecipe extends InputRecipe { + private String compactedLore; + + public DecompactingRecipe(String identifier, ItemMap input, String name, int productionTime, + String compactedLore) { + super(identifier, name, productionTime, input); + this.compactedLore = compactedLore; + } + + @Override + public boolean enoughMaterialAvailable(Inventory inputInv) { + if (!input.isContainedIn(inputInv)) { + return false; + } + for (ItemStack is : inputInv.getContents()) { + if (is != null) { + if (isDecompactable(is)) { + return true; + } + } + } + return false; + } + + @Override + public EffectFeasibility evaluateEffectFeasibility(Inventory inputInv, Inventory outputInv) { + boolean isFeasible = Arrays.stream(inputInv.getContents()) + .filter(Objects::nonNull) + .filter(this::isDecompactable) + .map(it -> { + ItemStack removeClone = it.clone(); + removeClone.setAmount(1); + removeCompactLore(removeClone); + ItemMap toAdd = new ItemMap(removeClone); + toAdd.addItemAmount(removeClone, CompactingRecipe.getCompactStackSize(removeClone.getType())); + return toAdd; + }) + .allMatch(it -> it.fitsIn(outputInv)); + return new EffectFeasibility( + isFeasible, + isFeasible ? null : "it ran out of storage space" + ); + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + if (input.isContainedIn(inputInv)) { + for (ItemStack is : inputInv.getContents()) { + if (is != null) { + if (isDecompactable(is)) { + ItemStack removeClone = is.clone(); + removeClone.setAmount(1); + ItemMap toRemove = new ItemMap(removeClone); + ItemMap toAdd = new ItemMap(); + removeCompactLore(removeClone); + toAdd.addItemAmount(removeClone, CompactingRecipe.getCompactStackSize(removeClone.getType())); + if (toAdd.fitsIn(outputInv)) { //fits in chest + if (input.removeSafelyFrom(inputInv)) { //remove extra input + if (toRemove.removeSafelyFrom(inputInv)) { //remove one compacted item + for(ItemStack add : toAdd.getItemStackRepresentation()) { + outputInv.addItem(add); + } + } + } + } else { // does not fit in chest + return false; + } + break; + } + } + } + } + logAfterRecipeRun(combo, fccf); + return true; + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + List result = new LinkedList<>(); + if (i == null) { + ItemStack is = new ItemStack(Material.STONE, 64); + ItemUtils.addLore(is, compactedLore); + is.setAmount(1); + result.add(is); + return result; + } + result = createLoredStacksForInfo(i); + for (ItemStack is : i.getContents()) { + if (is != null) { + if (isDecompactable(is)) { + ItemStack compactedStack = is.clone(); + result.add(compactedStack); + break; + } + } + } + return result; + } + + @Override + public Material getRecipeRepresentationMaterial() { + return Material.TRAPPED_CHEST; + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + List result = new LinkedList<>(); + if (i == null) { + result.add(new ItemStack(Material.STONE, 64)); + return result; + } + for (ItemStack is : i.getContents()) { + if (is != null) { + if (isDecompactable(is)) { + ItemStack copy = is.clone(); + removeCompactLore(copy); + ItemMap output = new ItemMap(); + output.addItemAmount(copy, CompactingRecipe.getCompactStackSize(copy.getType())); + result.addAll(output.getItemStackRepresentation()); + } + } + } + return result; + } + + private boolean isDecompactable(ItemStack is) { + List lore = is.getItemMeta().getLore(); + if (lore != null) { + for(String content : lore) { + if (content.equals(compactedLore)) { + return true; + } + } + } + return false; + } + + private void removeCompactLore(ItemStack is) { + List lore = is.getItemMeta().getLore(); + if (lore != null) { + lore.remove(compactedLore); + } + ItemMeta im = is.getItemMeta(); + im.setLore(lore); + is.setItemMeta(im); + } + + @Override + public String getTypeIdentifier() { + return "DECOMPACT"; + } + + public String getCompactedLore() { + return compactedLore; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("An entire stack of the decompacted item if it's stackable", "---OR---", "Eight of the decompacted item if it's not stackable"); + } + + @Override + public List getTextualInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("A single compacted item"); + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/DeterministicEnchantingRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/DeterministicEnchantingRecipe.java new file mode 100644 index 000000000..5eb4da011 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/DeterministicEnchantingRecipe.java @@ -0,0 +1,134 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class DeterministicEnchantingRecipe extends InputRecipe { + private Enchantment enchant; + private int level; + private ItemMap tool; + + public DeterministicEnchantingRecipe(String identifier, String name, int productionTime, ItemMap input, + ItemMap tool, Enchantment enchant, int level) { + super(identifier, name, productionTime, input); + this.enchant = enchant; + this.tool = tool; + this.level = level; + } + + @Override + public boolean enoughMaterialAvailable(Inventory inputInv) { + if (input.isContainedIn(inputInv)) { + ItemStack toolio = tool.getItemStackRepresentation().get(0); + for (ItemStack is : inputInv.getContents()) { + if (is != null && toolio.getType() == is.getType() + && toolio.getEnchantmentLevel(enchant) == is.getEnchantmentLevel(enchant)) { + return true; + } + } + } + return false; + } + + @Override + public Material getRecipeRepresentationMaterial() { + return tool.getItemStackRepresentation().get(0).getType(); + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + ItemStack is = tool.getItemStackRepresentation().get(0); + ItemMeta im = is.getItemMeta(); + im.removeEnchant(enchant); + im.addEnchant(enchant, level, true); + is.setItemMeta(im); + if (i != null) { + ItemUtils.addLore(is, + ChatColor.GREEN + "Enough materials for " + + String.valueOf( + Math.min(tool.getMultiplesContainedIn(i), input.getMultiplesContainedIn(i))) + + " runs"); + } + List stacks = new LinkedList<>(); + stacks.add(is); + return stacks; + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + if (i == null) { + List bla = input.getItemStackRepresentation(); + bla.add(tool.getItemStackRepresentation().get(0)); + return bla; + } + List returns = createLoredStacksForInfo(i); + ItemStack toSt = tool.getItemStackRepresentation().get(0); + ItemUtils.addLore(toSt, + ChatColor.GREEN + "Enough materials for " + new ItemMap(toSt).getMultiplesContainedIn(i) + " runs"); + returns.add(toSt); + return returns; + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + if (input.removeSafelyFrom(inputInv)) { + ItemStack toolio = tool.getItemStackRepresentation().get(0); + for (ItemStack is : inputInv.getContents()) { + if (is != null && toolio.getType() == is.getType() + && toolio.getEnchantmentLevel(enchant) == is.getEnchantmentLevel(enchant)) { + ItemMeta im = is.getItemMeta(); + im.removeEnchant(enchant); + im.addEnchant(enchant, level, true); + is.setItemMeta(im); + break; + } + } + } + logAfterRecipeRun(combo, fccf); + return true; + } + + @Override + public String getTypeIdentifier() { + return "ENCHANT"; + } + + public int getLevel() { + return level; + } + + public Enchantment getEnchant() { + return enchant; + } + + public ItemMap getTool() { + return tool; + } + + @Override + public List getTextualInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + List res = super.getTextualInputRepresentation(i, fccf); + res.add("1 " + ItemUtils.getItemName(tool.getItemStackRepresentation().get(0))); + return res; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("1 " + ItemUtils.getItemName(tool.getItemStackRepresentation().get(0)) + " with " + + enchant.toString() + " " + level); + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/DummyParsingRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/DummyParsingRecipe.java new file mode 100644 index 000000000..9b11afe5d --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/DummyParsingRecipe.java @@ -0,0 +1,47 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import java.util.List; + +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; + +public class DummyParsingRecipe extends InputRecipe { + + public DummyParsingRecipe(String identifier, String name, int productionTime, ItemMap input) { + super(identifier, name, productionTime, input); + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + return true; + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return null; + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return null; + } + + @Override + public String getTypeIdentifier() { + return "DUMMY"; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return null; + } + + @Override + public Material getRecipeRepresentationMaterial() { + return null; + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/EffectFeasibility.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/EffectFeasibility.java new file mode 100755 index 000000000..6dc42969a --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/EffectFeasibility.java @@ -0,0 +1,10 @@ +package com.github.igotyou.FactoryMod.recipes; + +import javax.annotation.Nullable; + +/** + * Captures the feasibility of applying a recipe effect along with an optional reason string. + * The reason string should be plaintext and formatted such that it can be inserted in a + * player message that reads like "The factory couldn't run because {reasonSnippet}." + */ +public record EffectFeasibility(boolean isFeasible, @Nullable String reasonSnippet) { } diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/FactoryMaterialReturnRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/FactoryMaterialReturnRecipe.java new file mode 100644 index 000000000..4127a31d6 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/FactoryMaterialReturnRecipe.java @@ -0,0 +1,115 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.DoubleChest; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class FactoryMaterialReturnRecipe extends InputRecipe { + + private double factor; + + public FactoryMaterialReturnRecipe(String identifier, String name, int productionTime, + ItemMap input, double factor) { + super(identifier, name, productionTime, input); + this.factor = factor; + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + if (i == null) { + return input.getItemStackRepresentation(); + } + return createLoredStacksForInfo(i); + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + if (i == null) { + ItemStack is = new ItemStack(Material.PAPER); + ItemUtils.setDisplayName(is, "Total setupcost"); + ItemUtils.addLore(is, ChatColor.AQUA + "All the materials invested into setting up and upgrading this factory"); + List stacks = new LinkedList<>(); + stacks.add(is); + return stacks; + } + InventoryHolder ih = i.getHolder(); + Location loc = null; + if (ih instanceof BlockState) { + BlockState bs = (BlockState) ih; + loc = bs.getLocation(); + } else if (ih instanceof DoubleChest) { + DoubleChest dc = (DoubleChest) ih; + loc = dc.getLocation(); + } + FurnCraftChestFactory fcc = (loc != null ? (FurnCraftChestFactory) FactoryMod + .getInstance().getManager().getFactoryAt(loc) : fccf); + return FactoryMod.getInstance().getManager().getTotalSetupCost(fcc) + .getItemStackRepresentation(); + } + + @Override + public Material getRecipeRepresentationMaterial() { + return Material.CRAFTING_TABLE; + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + FactoryMod.getInstance().getManager().removeFactory(fccf); + for (Block b : fccf.getMultiBlockStructure().getRelevantBlocks()) { + b.setType(Material.AIR); + } + Bukkit.getScheduler().runTaskLater(FactoryMod.getInstance(), + new Runnable() { + @Override + public void run() { + Location dropLoc = fccf.getMultiBlockStructure() + .getCenter(); + for (Entry items : FactoryMod.getInstance(). + getManager().getTotalSetupCost(fccf) + .getEntrySet()) { + int returnAmount = (int) (items.getValue() * factor); + ItemMap im = new ItemMap(); + im.addItemAmount(items.getKey(), returnAmount); + for (ItemStack is : im.getItemStackRepresentation()) { + dropLoc.getWorld().dropItemNaturally(dropLoc, + is); + } + } + dropLoc.getWorld().dropItemNaturally(dropLoc, new ItemStack(Material.CRAFTING_TABLE)); + dropLoc.getWorld().dropItemNaturally(dropLoc, new ItemStack(Material.FURNACE)); + dropLoc.getWorld().dropItemNaturally(dropLoc, new ItemStack((fccf.getChest()).getType())); + } + }, 1L); + return true; + } + + public double getFactor() { + return factor; + } + + @Override + public String getTypeIdentifier() { + return "COSTRETURN"; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList(factor * 100 + " % of the factories setup cost"); + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/IRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/IRecipe.java new file mode 100644 index 000000000..7d55f754b --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/IRecipe.java @@ -0,0 +1,63 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import org.bukkit.inventory.Inventory; + +/** + * Encapsulates a specific functionality for a FurnCraftChest factory. Each + * factory of this type can have of many different recipes and what the recipe + * actually does is completly kept inside the recipe's class + */ +public interface IRecipe { + /** + * @return The identifier for this recipe, which is used both internally and + * to display the recipe to a player + */ + public String getName(); + + /** + * @return A unique identifier for this recipe + */ + public String getIdentifier(); + + /** + * @return How long this recipe takes for one run in ticks + */ + public int getProductionTime(); + + /** + * Checks whether enough material is available in the given inventory to run + * this recipe at least once + * + * @param inputInv Inventory to check + * @return true if the recipe could be run at least once, false if not + */ + public boolean enoughMaterialAvailable(Inventory inputInv); + + /** + * Evaluates whether it's currently feasible to apply the recipe effect, given the constraints of the factory, + * input/output inventories, or other custom recipe logic. + * By default, this method returns a result indicating that the effect is always feasible to be applied. + */ + default public EffectFeasibility evaluateEffectFeasibility(Inventory inputInv, Inventory outputInv) { + return new EffectFeasibility(true, null); + } + + /** + * Applies whatever the recipe actually does, it's main functionality + * + * @param inputInv Inventory which contains the materials to work with + * @param outputInv Inventory to add output items to. + * @param fccf Factory which is run + * @return true if the recipe could be run; false otherwise (e.g: not enough storage space) + */ + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf); + + /** + * Each implementation of this class has to specify a unique identifier, + * which is used to identify instances of this recipe in the config + * + * @return Unique identifier for the implementation + */ + public String getTypeIdentifier(); +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/InputRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/InputRecipe.java new file mode 100644 index 000000000..53b068902 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/InputRecipe.java @@ -0,0 +1,241 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.utility.LoggingUtils; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; +import org.apache.commons.lang.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; +import vg.civcraft.mc.civmodcore.utilities.TextUtil; + +/** + * A recipe with any form of item input to run it + * + */ +public abstract class InputRecipe implements IRecipe { + protected String name; + protected int productionTime; + protected ItemMap input; + protected int fuel_consumption_intervall = -1; + protected String identifier; + + public InputRecipe(String identifier, String name, int productionTime, ItemMap input) { + this.name = name; + this.productionTime = productionTime; + this.input = input; + this.identifier = identifier; + } + + /** + * Used to get a representation of a recipes input materials, which is + * displayed in an item gui to illustrate the recipe and to give additional + * information. If null is given instead of an inventory or factory, just + * general information should be returned, which doesnt depend on a specific + * instance + * + * @param i + * Inventory for which the recipe would be run, this is used to + * add lore to the items, which tells how often the recipe could + * be run + * @param fccf + * Factory for which the representation is meant. Needed for + * recipe run scaling + * @return List of itemstacks which represent the input required to run this + * recipe + */ + public abstract List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf); + + /** + * Used to get a representation of a recipes input materials, which is + * displayed in chat or an items lore to illustrate the recipe and to give additional + * information. If null is given instead of an inventory or factory, just + * general information should be returned, which doesnt depend on a specific + * instance + * + * @param i + * Inventory for which the recipe would be run, this is used to + * add a count how often the recipe could be run + * @param fccf + * Factory for which the representation is meant. Needed for + * recipe run scaling + * @return List of Strings each describing one component needed as input for this recipe + */ + public List getTextualInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return formatLore(input); + } + + /** + * Used to get a representation of a recipes output materials, which is + * displayed in an item gui to illustrate the recipe and to give additional + * information. If null is given instead of an inventory or factory, just + * general information should be returned, which doesnt depend on a specific + * instance + * + * @param i + * Inventory for which the recipe would be run, this is used to + * add lore to the items, which tells how often the recipe could + * be run + * @param fccf + * Factory for which the representation is meant. Needed for + * recipe run scaling + * @return List of itemstacks which represent the output returned when + * running this recipe + */ + public abstract List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf); + + /** + * Used to get a representation of a recipes output, which is + * displayed in chat or an items lore to illustrate the recipe and to give additional + * information. If null is given instead of an inventory or factory, just + * general information should be returned, which doesnt depend on a specific + * instance + * + * @param i + * Inventory for which the recipe would be run, this is used to + * add a count how often the recipe could be run + * @param fccf + * Factory for which the representation is meant. Needed for + * recipe run scaling + * @return List of Strings each describing one component produced as output of this recipe + */ + public abstract List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf); + + @Override + public String getName() { + return name; + } + + public int getTotalFuelConsumed() { + if (fuel_consumption_intervall == 0) { + return 0; + } + return productionTime / fuel_consumption_intervall; + } + + public int getFuelConsumptionIntervall() { + return fuel_consumption_intervall; + } + + public void setFuelConsumptionIntervall(int intervall) { + this.fuel_consumption_intervall = intervall; + } + + @Override + public int getProductionTime() { + return productionTime; + } + + public ItemMap getInput() { + return input; + } + + @Override + public boolean enoughMaterialAvailable(Inventory inputInv) { + return input.isContainedIn(inputInv); + } + + @Override + public String getIdentifier() { + return identifier; + } + + /** + * @return A single itemstack which is used to represent this recipe as a + * whole in an item gui + */ + public ItemStack getRecipeRepresentation() { + ItemStack res = new ItemStack(getRecipeRepresentationMaterial()); + ItemMeta im = res.getItemMeta(); + im.setDisplayName(ChatColor.DARK_GREEN + getName()); + List lore = new ArrayList<>(); + lore.add(ChatColor.GOLD + "Input:"); + for(String s : getTextualInputRepresentation(null, null)) { + lore.add(ChatColor.GRAY + " - " + ChatColor.AQUA + s); + } + lore.add(""); + lore.add(ChatColor.GOLD + "Output:"); + for(String s : getTextualOutputRepresentation(null, null)) { + lore.add(ChatColor.GRAY + " - " + ChatColor.AQUA + s); + } + lore.add(""); + lore.add(ChatColor.DARK_AQUA + "Time: " + ChatColor.GRAY + TextUtil + .formatDuration(getProductionTime() * 50, TimeUnit.MILLISECONDS)); + im.setLore(lore); + res.setItemMeta(im); + return res; + } + + public abstract Material getRecipeRepresentationMaterial(); + + /** + * Creates a list of ItemStack for a GUI representation. This list contains + * all the itemstacks contained in the itemstack representation of the input + * map and adds to each of the stacks how many runs could be made with the + * material available in the chest + * + * @param i + * Inventory to calculate the possible runs for + * @return ItemStacks containing the additional information, ready for the + * GUI + */ + protected List createLoredStacksForInfo(Inventory i) { + LinkedList result = new LinkedList<>(); + ItemMap inventoryMap = new ItemMap(i); + ItemMap possibleRuns = new ItemMap(); + for (Entry entry : input.getEntrySet()) { + if (inventoryMap.getAmount(entry.getKey()) != 0) { + possibleRuns.addItemAmount(entry.getKey(), inventoryMap.getAmount(entry.getKey()) / entry.getValue()); + } else { + possibleRuns.addItemAmount(entry.getKey(), 0); + } + } + for (ItemStack is : input.getItemStackRepresentation()) { + ItemUtils.addLore(is, ChatColor.GREEN + "Enough materials for " + String.valueOf(possibleRuns.getAmount(is)) + + " runs"); + result.add(is); + } + return result; + } + + protected void logBeforeRecipeRun(Inventory i, Factory f) { + LoggingUtils.logInventory(i, "Before executing recipe " + name + " for " + f.getLogData()); + } + + protected void logAfterRecipeRun(Inventory i, Factory f) { + LoggingUtils.logInventory(i, "After executing recipe " + name + " for " + f.getLogData()); + } + + @Override + public int hashCode() { + return identifier.hashCode(); + } + + protected List formatLore(ItemMap ingredients) { + List result = new ArrayList<>(); + for(Entry entry : ingredients.getEntrySet()) { + if (entry.getValue() > 0) { + if (!entry.getKey().hasItemMeta()) { + result.add(entry.getValue() + " " + ItemUtils.getItemName(entry.getKey())); + } else { + String lore = String.format("%s %s%s", entry.getValue(), ChatColor.ITALIC, ItemUtils.getItemName(entry.getKey())); + if (entry.getKey().getItemMeta().hasDisplayName()) { + lore += String.format("%s [%s]", ChatColor.DARK_AQUA, StringUtils.abbreviate(entry.getKey().getItemMeta().getDisplayName(), 20)); + } + result.add(lore); + } + } + } + return result; + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/LoreEnchantRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/LoreEnchantRecipe.java new file mode 100644 index 000000000..54fc1f93f --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/LoreEnchantRecipe.java @@ -0,0 +1,172 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class LoreEnchantRecipe extends InputRecipe { + + private List appliedLore; + private List overwritenLore; + private ItemMap tool; + private ItemStack exampleInput; + private ItemStack exampleOutput; + + public LoreEnchantRecipe(String identifier, String name, int productionTime, ItemMap input, ItemMap tool, List appliedLore, + List overwritenLore) { + super(identifier, name, productionTime, input); + this.overwritenLore = overwritenLore; + this.appliedLore = appliedLore; + this.tool = tool; + exampleInput = tool.getItemStackRepresentation().get(0); + for (String s : overwritenLore) { + ItemUtils.addLore(exampleInput, s); + } + exampleOutput = tool.getItemStackRepresentation().get(0); + for (String s : appliedLore) { + ItemUtils.addLore(exampleOutput, s); + } + } + + @Override + public boolean enoughMaterialAvailable(Inventory inputInv) { + if (input.isContainedIn(inputInv)) { + ItemStack toolio = tool.getItemStackRepresentation().get(0); + for (ItemStack is : inputInv.getContents()) { + if (is != null && toolio.getType() == is.getType() && hasStackRequiredLore(is)) { + return true; + } + } + } + return false; + } + + @Override + public Material getRecipeRepresentationMaterial() { + return exampleOutput.getType(); + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + ItemStack is = exampleOutput.clone(); + if (i != null) { + ItemUtils.addLore( + is, + ChatColor.GREEN + + "Enough materials for " + + String.valueOf(Math.min(tool.getMultiplesContainedIn(i), input.getMultiplesContainedIn(i))) + + " runs"); + } + List stacks = new LinkedList<>(); + stacks.add(is); + return stacks; + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + if (i == null) { + return Arrays.asList(exampleInput.clone()); + } + List returns = createLoredStacksForInfo(i); + ItemStack toSt = tool.getItemStackRepresentation().get(0); + for (String s : overwritenLore) { + ItemUtils.addLore(toSt, s); + } + ItemUtils.addLore(toSt, ChatColor.GREEN + "Enough materials for " + new ItemMap(toSt).getMultiplesContainedIn(i) + + " runs"); + returns.add(toSt); + return returns; + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + if (input.removeSafelyFrom(inputInv)) { + ItemStack toolio = tool.getItemStackRepresentation().get(0); + for (ItemStack is : inputInv.getContents()) { + if (is != null && toolio.getType() == is.getType() && hasStackRequiredLore(is)) { + ItemMeta im = is.getItemMeta(); + if (im == null) { + im = Bukkit.getItemFactory().getItemMeta(is.getType()); + } + List currentLore = im.getLore(); + if (!overwritenLore.isEmpty()) { + currentLore.removeAll(overwritenLore); + } + if (currentLore == null) { + currentLore = new LinkedList<>(); + } + currentLore.addAll(appliedLore); + im.setLore(currentLore); + is.setItemMeta(im); + break; + } + } + } + logAfterRecipeRun(combo, fccf); + return true; + } + + private boolean hasStackRequiredLore(ItemStack is) { + if (is == null) { + return false; + } + if (!is.hasItemMeta() && !overwritenLore.isEmpty()) { + return false; + } + ItemMeta im = is.getItemMeta(); + if (!im.hasLore() && !overwritenLore.isEmpty()) { + return false; + } + List lore = im.getLore(); + if (im.hasLore()) { + //check whether lore to apply preexists + if (lore.containsAll(appliedLore)) { + return false; + } + } + if (overwritenLore.isEmpty()) { + return true; + } + return lore.containsAll(overwritenLore); + } + + @Override + public String getTypeIdentifier() { + return "LOREENCHANT"; + } + + public List getAppliedLore() { + return appliedLore; + } + + public List getOverwrittenLore() { + return overwritenLore; + } + + public ItemMap getTool() { + return tool; + } + + @Override + public List getTextualInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("1 " + ItemUtils.getItemName(exampleInput)); + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("1 " + ItemUtils.getItemName(exampleOutput)); + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PlayerHeadRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PlayerHeadRecipe.java new file mode 100644 index 000000000..79b1d7c45 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PlayerHeadRecipe.java @@ -0,0 +1,81 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +/*** + * Outputs a player head belonging to a random player who is connected to the server when the recipe is run + */ +public class PlayerHeadRecipe extends InputRecipe { + public PlayerHeadRecipe(String identifier, String name, int productionTime, ItemMap inputs) { + super(identifier, name, productionTime, inputs); + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + if (i == null) { + return input.getItemStackRepresentation(); + } + return createLoredStacksForInfo(i); + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + ItemStack is = new ItemStack(Material.PLAYER_HEAD, 1); + ItemUtils.addLore(is,"The player head of a randomly chosen online player"); + return Collections.singletonList(is); + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Collections.singletonList("The player head of a randomly chosen online player"); + } + + @Override + public Material getRecipeRepresentationMaterial() { + return Material.PLAYER_HEAD; + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + ItemMap toRemove = input.clone(); + ArrayList players = new ArrayList<>(Bukkit.getOnlinePlayers()); + if (players.isEmpty()) { + return false; + } + if (toRemove.isContainedIn(inputInv)) { + if (toRemove.removeSafelyFrom(inputInv)) { + Random rand = new Random(); + Player player = players.get(rand.nextInt(players.size())); + ItemStack is = new ItemStack(Material.PLAYER_HEAD, 1); + SkullMeta im = (SkullMeta) is.getItemMeta(); + im.setOwningPlayer(Bukkit.getOfflinePlayer(player.getUniqueId())); + im.setDisplayName(player.getDisplayName()); + is.setItemMeta(im); + outputInv.addItem(is); + } + } + logAfterRecipeRun(combo, fccf); + return true; + } + + @Override + public String getTypeIdentifier() { + return "PLAYERHEAD"; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintBookRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintBookRecipe.java new file mode 100644 index 000000000..8145dcc66 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintBookRecipe.java @@ -0,0 +1,169 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import net.minecraft.nbt.CompoundTag; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.inventory.meta.ItemMeta; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +import javax.annotation.Nullable; + +public class PrintBookRecipe extends PrintingPressRecipe { + private ItemMap printingPlate; + private int outputAmount; + + public ItemMap getPrintingPlate() { + return this.printingPlate; + } + + public int getOutputAmount() { + return this.outputAmount; + } + + public PrintBookRecipe( + String identifier, + String name, + int productionTime, + ItemMap input, + ItemMap printingPlate, + int outputAmount + ) { + super(identifier, name, productionTime, input); + this.printingPlate = printingPlate; + this.outputAmount = outputAmount; + } + + @Override + public boolean enoughMaterialAvailable(Inventory inputInv) { + return this.input.isContainedIn(inputInv) && getPrintingPlateItemStack(inputInv, this.printingPlate) != null; + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + + ItemStack printingPlateStack = getPrintingPlateItemStack(inputInv, this.printingPlate); + ItemMap toRemove = this.input.clone(); + + if (printingPlateStack != null + && toRemove.isContainedIn(inputInv) + && toRemove.removeSafelyFrom(inputInv) + ) { + ItemStack book = createBook(printingPlateStack, this.outputAmount); + book.editMeta(BookMeta.class, x -> x.setGeneration(getNextGeneration(x.getGeneration()))); + outputInv.addItem(book); + } + + logAfterRecipeRun(combo, fccf); + return true; + } + + protected ItemStack createBook(ItemStack printingPlateStack, int amount) { + net.minecraft.world.item.ItemStack book = CraftItemStack.asNMSCopy(printingPlateStack); + CompoundTag tag = book.getTag().getCompound("Book"); + + ItemStack bookItem = new ItemStack(Material.WRITTEN_BOOK, amount); + net.minecraft.world.item.ItemStack newBook = CraftItemStack.asNMSCopy(bookItem); + newBook.setTag(tag); + + return CraftItemStack.asBukkitCopy(newBook); + } + + protected BookMeta.Generation getNextGeneration(@Nullable BookMeta.Generation generation) { + if (generation == null) generation = BookMeta.Generation.ORIGINAL; + return switch (generation) { + case ORIGINAL -> BookMeta.Generation.COPY_OF_ORIGINAL; + case COPY_OF_ORIGINAL -> BookMeta.Generation.COPY_OF_COPY; + default -> BookMeta.Generation.TATTERED; + }; + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + List result = new LinkedList<>(); + + if (i == null) { + ItemStack is = getPrintingPlateRepresentation(this.printingPlate, PrintingPlateRecipe.itemName); + + result.addAll(this.input.getItemStackRepresentation()); + result.add(is); + return result; + } + + result = createLoredStacksForInfo(i); + + ItemStack printingPlateStack = getPrintingPlateItemStack(i, this.printingPlate); + + if (printingPlateStack != null) { + result.add(printingPlateStack.clone()); + } + + return result; + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + List stacks = new ArrayList<>(); + stacks.add(new ItemStack(Material.WRITTEN_BOOK, this.outputAmount)); + stacks.add(getPrintingPlateRepresentation(this.printingPlate, PrintingPlateRecipe.itemName)); + + if (i == null) { + return stacks; + } + + int possibleRuns = input.getMultiplesContainedIn(i); + + for (ItemStack is : stacks) { + ItemUtils.addLore(is, ChatColor.GREEN + "Enough materials for " + + String.valueOf(possibleRuns) + " runs"); + } + + return stacks; + } + + @Override + public Material getRecipeRepresentationMaterial() { + return Material.WRITTEN_BOOK; + } + + protected ItemStack getPrintingPlateItemStack(Inventory i, ItemMap printingPlate) { + ItemMap items = new ItemMap(i).getStacksByMaterial(printingPlate.getItemStackRepresentation().get(0).getType()); + + for (ItemStack is : items.getItemStackRepresentation()) { + ItemMeta itemMeta = is.getItemMeta(); + + if (itemMeta.getDisplayName().equals(PrintingPlateRecipe.itemName) + && itemMeta.hasEnchant(Enchantment.DURABILITY) + ) { + return is; + } + } + + return null; + } + + @Override + public String getTypeIdentifier() { + return "PRINTBOOK"; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + ItemStack is = new ItemStack(Material.WRITTEN_BOOK, outputAmount); + return formatLore(new ItemMap(is)); + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintNoteRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintNoteRecipe.java new file mode 100644 index 000000000..38ba3b50f --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintNoteRecipe.java @@ -0,0 +1,166 @@ +/** + * @author Aleksey Terzi + */ + +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.nbt.CompoundTag; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.inventory.meta.ItemMeta; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class PrintNoteRecipe extends PrintBookRecipe { + private static class BookInfo { + public String title; + public List lines; + } + + private static final String pamphletName = "Pamphlet"; + private static final String secureNoteName = "Secure Note"; + + private boolean secureNote; + private String title; + + public boolean isSecurityNote() { + return this.secureNote; + } + + public String getTitle() { + return this.title; + } + + public PrintNoteRecipe( + String identifier, + String name, + int productionTime, + ItemMap input, + ItemMap printingPlate, + int outputAmount, + boolean secureNote, + String title + ) { + super(identifier, name, productionTime, input, printingPlate, outputAmount); + + this.secureNote = secureNote; + + if (title != null && title.length() > 0) { + this.title = title; + } else { + this.title = secureNote ? secureNoteName : pamphletName; + } + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + + ItemStack printingPlateStack = getPrintingPlateItemStack(inputInv, getPrintingPlate()); + ItemMap toRemove = this.input.clone(); + + if (printingPlateStack != null + && toRemove.isContainedIn(inputInv) + && toRemove.removeSafelyFrom(inputInv) + ) { + BookInfo info = getBookInfo(printingPlateStack); + ItemStack paper = new ItemStack(Material.PAPER, getOutputAmount()); + + ItemMeta paperMeta = paper.getItemMeta(); + paperMeta.setDisplayName(ChatColor.RESET + info.title); + paperMeta.setLore(info.lines); + paper.setItemMeta(paperMeta); + + outputInv.addItem(paper); + } + + logAfterRecipeRun(combo, fccf); + return true; + } + + private BookInfo getBookInfo(ItemStack printingPlateStack) { + ItemStack book = createBook(printingPlateStack, 1); + BookMeta bookMeta = (BookMeta) book.getItemMeta(); + int version = getVersion(printingPlateStack); + String text = bookMeta.getPageCount() > 0 ? + version == 0 ? bookMeta.getPage(1) : String.join("", bookMeta.getPages()) + : ""; + String[] lines = text.split("\n"); + List fixedLines = new ArrayList<>(); + + for (String line : lines) { + fixedLines.add(ChatColor.GRAY + line + .replaceAll("(? 0 ? bookTitle : this.title; + + if (this.secureNote) { + net.minecraft.world.item.ItemStack bookItem = CraftItemStack.asNMSCopy(printingPlateStack); + String bookSN = bookItem.getTag().getString("SN"); + info.lines.add(bookSN); + } + + return info; + } + + private int getVersion(ItemStack item) { + var book = CraftItemStack.asNMSCopy(item); + var tag = book.getTag(); + if (tag != null && tag.contains("Version")) return tag.getInt("Version"); + return 0; + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + ItemStack paper = new ItemStack(Material.PAPER, getOutputAmount()); + ItemUtils.setDisplayName(paper, this.title); + + List stacks = new ArrayList<>(); + stacks.add(paper); + stacks.add(getPrintingPlateRepresentation(getPrintingPlate(), PrintingPlateRecipe.itemName)); + + if (i == null) { + return stacks; + } + + int possibleRuns = input.getMultiplesContainedIn(i); + + for (ItemStack is : stacks) { + ItemUtils.addLore(is, ChatColor.GREEN + "Enough materials for " + + String.valueOf(possibleRuns) + " runs"); + } + + return stacks; + } + + @Override + public ItemStack getRecipeRepresentation() { + ItemStack res = new ItemStack(Material.PAPER); + + ItemUtils.setDisplayName(res, getName()); + + return res; + } + + @Override + public String getTypeIdentifier() { + return "PRINTNOTE"; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintingPlateJsonRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintingPlateJsonRecipe.java new file mode 100644 index 000000000..2aab23c39 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintingPlateJsonRecipe.java @@ -0,0 +1,345 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import java.util.Arrays; +import java.util.UUID; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.inventory.meta.ItemMeta; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class PrintingPlateJsonRecipe extends PrintingPlateRecipe { + /* + * WARNING + * + * This inherits from PrintingPlateRecipe + * ^^^^^ + * Not PrintingPressRecipe, as would normally be expected. + * ^^^^^ + * + * This is because this recipe creates a printing plate, like the super class. + */ + + public PrintingPlateJsonRecipe(String identifier, String name, int productionTime, ItemMap input, ItemMap output) { + super(identifier, name, productionTime, input, output); + } + + @Override + public boolean enoughMaterialAvailable(Inventory inputInv) { + ItemStack book = getBook(inputInv); + if (book == null) return false; + String[] pages = String.join("", ((BookMeta) book.getItemMeta()).getPages()).split("<>"); + + for (String page : pages) { + try { + Gson gson = new Gson(); + JsonElement element = gson.fromJson(page, JsonElement.class); + + String result = checkForIllegalSections(element); + if (result != null) { + factioryError(inputInv, "Banned Tag Error", "Error Message: " + result); + + return false; + } + } catch (JsonSyntaxException e) { + factioryError(inputInv, "JSON Syntax Error", e.toString()); + + return false; + } + } + + return this.input.isContainedIn(inputInv) && getBook(inputInv) != null; + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + + ItemStack book = getBook(inputInv); + BookMeta bookMeta = (BookMeta) book.getItemMeta(); + if (!bookMeta.hasGeneration()) { + bookMeta.setGeneration(BookMeta.Generation.ORIGINAL); + } + String serialNumber = UUID.randomUUID().toString(); + + String[] pages = String.join("", bookMeta.getPages()).split("<>"); + ListTag pagesNBT = new ListTag(); + + for (String page : pages) { + pagesNBT.add(StringTag.valueOf(page)); + } + + CompoundTag bookNBT = new CompoundTag(); + bookNBT.putInt("generation", bookMeta.getGeneration().ordinal()); + bookNBT.putString("author", bookMeta.getAuthor()); + bookNBT.putString("title", bookMeta.getTitle()); + bookNBT.put("pages", pagesNBT); + + ItemMap toRemove = input.clone(); + ItemMap toAdd = output.clone(); + + if (toRemove.isContainedIn(inputInv) && toRemove.removeSafelyFrom(inputInv)) { + for (ItemStack is : toAdd.getItemStackRepresentation()) { + is = addTags(serialNumber, is, bookNBT); + + ItemUtils.setDisplayName(is, itemName); + ItemUtils.setLore(is, + serialNumber, + ChatColor.WHITE + bookMeta.getTitle(), + ChatColor.GRAY + "by " + bookMeta.getAuthor(), + ChatColor.GRAY + getGenerationName(bookMeta.getGeneration()), + ChatColor.GRAY + "(JSON)" + ); + is.addUnsafeEnchantment(Enchantment.DURABILITY, 1); + is.getItemMeta().addItemFlags(ItemFlag.HIDE_ENCHANTS); + outputInv.addItem(is); + } + } + + logAfterRecipeRun(combo, fccf); + return true; + } + + @Override + public String getTypeIdentifier() { + return "PRINTINGPLATEJSON"; + } + + /** + * Maximum recursion of raw JSON text allowed before rejecting text. + * + * Very large because recursion is an important feature of JSON text. + */ + private final int MAX_ITER_COUNT = 15; + + /** + * Validates if the text to be put into a book is safe. + * + * Will check if the book text contains a scoreboard value, an entity selector, an NBT path, a clickable URL/File, + * a clickable command (clickable command suggestions are explicitly allowed), clipboard copying (can exploit + * windows visibilities), selectors, or entity hovers. + * + * @param bookText The Raw JSON Text Format to check. + * @return Why the text is banned, or null if it is legal. + * @throws StackOverflowError If the text contains too much recursion. + */ + public String checkForIllegalSections(JsonElement bookText) { + return checkForIllegalSections(bookText, 0); + } + + private String checkForIllegalSections(JsonElement bookText, int iterCount) { + if (iterCount > MAX_ITER_COUNT) { + return "maximum number of nested elements reached, please simplify the json structure: " + iterCount; + } + + if (bookText == null) { + return "Book does not contain any text."; + } + + if (bookText.isJsonObject()) { + return checkForIllegalSections((JsonObject) bookText, iterCount + 1); + } else if (bookText.isJsonArray()) { + JsonArray array = bookText.getAsJsonArray(); + for (JsonElement element : array) { + String result = checkForIllegalSections(element, iterCount + 1); + if (result != null) { + return result; + } + } + } + + // The booktext is a safe type or a array with no bad elements + return null; + } + + private String checkForIllegalSections(JsonObject bookText, int iterCount) { + if (iterCount > MAX_ITER_COUNT) { + return "maximum number of nested elements reached, please simplify the json structure: " + iterCount; + } + + if (bookText.has("extra")) { + JsonElement extra = bookText.get("extra"); + + String result = checkForIllegalSections(extra, iterCount + 1); + if (result != null) { + return result; + } + } + + // List of keys that are not allowed at all. Always can be used to cheat, with no legit uses. + String[] simpleForbiddenKeys = { + "score", // gets the values from the scoreboard. At worst exposes secrets, at best useless. + "selector", // gets lists of all entities in the world that fit a condition + "nbt" // gets any nbt key in the entire world + }; + + for (String forbiddenKey : simpleForbiddenKeys) { + if (bookText.has(forbiddenKey)) { + return "Contains banned key: " + forbiddenKey; + } + } + + if (bookText.has("clickEvent")) { + JsonElement clickEvent = bookText.get("clickEvent"); + + if (clickEvent.isJsonObject()) { + JsonObject clickEventObject = clickEvent.getAsJsonObject(); + + if (clickEventObject.has("action")) { + JsonElement action = clickEventObject.get("action"); + if (action.isJsonPrimitive() && action.getAsJsonPrimitive().isString()) { + String actionString = action.getAsString(); + + String[] forbiddenActions = { + "open_url", + "open_file", + "run_command", + "copy_to_clipboard" + }; + + /* + * Allowed actions: + * + * "suggest_command": Just fills in the chat box, doesn't do anything unless the player presses enter. + * + * "change_page": Changes the currently selected page to the value page. + */ + + for (String forbiddenAction : forbiddenActions) { + if (actionString.equals(forbiddenAction)) { + return "Contains forbidden action for clickEvent: " + forbiddenAction; + } + } + } else { + return "Invalid Tag-Level Syntax: action of clickEvent is not a string"; + } + } else { + return "Invalid Tag-Level Syntax: clickEvent does not have an action."; + } + } else { + return "Invalid Tag-Level Syntax: clickEvent is not an object."; + } + } + + if (bookText.has("hoverEvent")) { + JsonElement hoverEventElement = bookText.get("hoverEvent"); + if (hoverEventElement.isJsonObject()) { + JsonObject hoverEvent = hoverEventElement.getAsJsonObject(); + if (hoverEvent.has("action")) { + JsonElement actionElement = hoverEvent.get("action"); + if (actionElement.isJsonPrimitive() && actionElement.getAsJsonPrimitive().isString()) { + String action = actionElement.getAsString(); + + if (action.equals("show_entity")) { + // Shows the details about an entity in the world, I think. + return "Contains banned action for hoverEvent: show_entity"; + } + + if (action.equals("show_text")) { + if (hoverEvent.has("contents")) { + JsonElement contents = hoverEvent.get("contents"); + + String result = checkForIllegalSections(contents, iterCount + 1); + if (result != null) { + return result; + } + } + + if (hoverEvent.has("value")) { + JsonElement value = hoverEvent.get("value"); + + String result = checkForIllegalSections(value, iterCount + 1); + if (result != null) { + return result; + } + } + } + + // "show_item" is allowed because it can only show a fixed NBT, and can not get any info + // from the game world. + } else { + return "Tag-Level Syntax Error: action of hoverEvent is not a string."; + } + } else { + return "Tag-Level Syntax Error: hoverEvent does not have action."; + } + } else { + return "Tag-Level Syntax Error: hoverEvent is not an object."; + } + } + + if (bookText.has("translate") && bookText.has("with")) { + JsonElement withElement = bookText.get("with"); + if (withElement.isJsonArray()) { + for (JsonElement element : withElement.getAsJsonArray()) { + String result = checkForIllegalSections(element, iterCount + 1); + if (result != null) { + return result; + } + } + } else { + return "Tag-Level Syntax Error: with is not an array"; + } + } + + /* + * List of explicitly allowed keys: + * + * "text": Literally just a string. + * "translate": A string from the user's language files. Has no cheating use, although a screenshot of the + * book will expose the user's set language. However, any screenshot of the interface + * will expose the user's set languate + * "keybind": Shows the user's configured keybind for a specific key. A screensot will expose the user's + * set keybind, but not a big deal. + * "color", "font", "bold", "italic", "underlined", "strikethrough", "obfuscated": + * Already accessible with the standard color codes, except for the new 24-bit color and custom fonts. + */ + + return null; + } + + /** + * Puts an error button into the factory's inventory. + * + * Kind of gross, but there aren't many other ways to give feedback to the player about errors. + * @param inventory The factory chest. + * @param errorTitle The name of the button put into the inventory. + * @param errorDetails The lore of the button. + */ + public void factioryError(Inventory inventory, String errorTitle, String... errorDetails) { + ItemStack invalidSyntaxExplanationButton = new ItemStack(Material.STONE_BUTTON, 1); + + ItemMeta meta; + if (!invalidSyntaxExplanationButton.hasItemMeta()) { + meta = FactoryMod.getInstance().getServer().getItemFactory().getItemMeta(Material.STONE_BUTTON); + } else { + meta = invalidSyntaxExplanationButton.getItemMeta(); + } + + assert meta != null; + meta.setLore(Arrays.asList(errorDetails)); + + meta.setDisplayName(errorTitle); + + invalidSyntaxExplanationButton.setItemMeta(meta); + inventory.addItem(invalidSyntaxExplanationButton); + // Kinda gross, but there aren't many other ways to give feedback to the player about errors. + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintingPlateRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintingPlateRecipe.java new file mode 100644 index 000000000..2830014d8 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintingPlateRecipe.java @@ -0,0 +1,172 @@ +/** + * @author Aleksey Terzi + * + */ + +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; +import net.minecraft.nbt.CompoundTag; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.inventory.meta.BookMeta.Generation; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class PrintingPlateRecipe extends PrintingPressRecipe { + public static final String itemName = "Printing Plate"; + private static final int version = 1; + + protected ItemMap output; + + public ItemMap getOutput() { + return this.output; + } + + public PrintingPlateRecipe(String identifier, String name, int productionTime, ItemMap input, ItemMap output) { + super(identifier, name, productionTime, input); + this.output = output; + } + + @Override + public boolean enoughMaterialAvailable(Inventory inputInv) { + return this.input.isContainedIn(inputInv) && getBook(inputInv) != null; + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + + ItemStack book = getBook(inputInv); + BookMeta bookMeta = (BookMeta)book.getItemMeta(); + if (!bookMeta.hasGeneration()){ + bookMeta.setGeneration(Generation.ORIGINAL); + } + String serialNumber = UUID.randomUUID().toString(); + + ItemMap toRemove = input.clone(); + ItemMap toAdd = output.clone(); + + if (toRemove.isContainedIn(inputInv) && toRemove.removeSafelyFrom(inputInv)) { + for(ItemStack is: toAdd.getItemStackRepresentation()) { + is = addTags(serialNumber, is, CraftItemStack.asNMSCopy(book).getTag()); + + ItemUtils.setDisplayName(is, itemName); + ItemUtils.setLore(is, + serialNumber, + ChatColor.WHITE + bookMeta.getTitle(), + ChatColor.GRAY + "by " + bookMeta.getAuthor(), + ChatColor.GRAY + getGenerationName(bookMeta.getGeneration()) + ); + is.addUnsafeEnchantment(Enchantment.DURABILITY, 1); + is.editMeta(x -> x.addItemFlags(ItemFlag.HIDE_ENCHANTS)); + outputInv.addItem(is); + } + } + + logAfterRecipeRun(combo, fccf); + return true; + } + + public static ItemStack addTags(String serialNumber, ItemStack plate, CompoundTag bookTag) { + net.minecraft.world.item.ItemStack nmsPlate = CraftItemStack.asNMSCopy(plate); + CompoundTag plateTag = nmsPlate.getOrCreateTag(); + + plateTag.putString("SN", serialNumber); + plateTag.put("Book", bookTag); + plateTag.putInt("Version", version); + + nmsPlate.setTag(plateTag); + return CraftItemStack.asBukkitCopy(nmsPlate); + } + + public static String getGenerationName(Generation gen) { + switch(gen) { + case ORIGINAL: return "Original"; + case COPY_OF_ORIGINAL: return "Copy of Original"; + case COPY_OF_COPY: return "Copy of Copy"; + case TATTERED: return "Tattered"; + default: return ""; + } + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + List result = new LinkedList<>(); + + if (i == null) { + result.add(new ItemStack(Material.WRITTEN_BOOK, 1)); + result.addAll(this.input.getItemStackRepresentation()); + return result; + } + + result = createLoredStacksForInfo(i); + + ItemStack book = getBook(i); + + if(book != null) { + result.add(book.clone()); + } + + return result; + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + List stacks = new ArrayList<>(); + stacks.add(getPrintingPlateRepresentation(this.output, itemName)); + stacks.add(new ItemStack(Material.WRITTEN_BOOK)); + + if (i == null) { + return stacks; + } + + int possibleRuns = this.input.getMultiplesContainedIn(i); + + for (ItemStack is : stacks) { + ItemUtils.addLore(is, ChatColor.GREEN + "Enough materials for " + + String.valueOf(possibleRuns) + " runs"); + } + + return stacks; + } + + @Override + public Material getRecipeRepresentationMaterial() { + return getPrintingPlateRepresentation(this.output, getName()).getType(); + } + + public ItemStack getBook(Inventory i) { + for (ItemStack is : i.getContents()) { + if (is != null && + is.getType() == Material.WRITTEN_BOOK && + ((BookMeta) is.getItemMeta()).getGeneration() != Generation.TATTERED) { + return is; + } + } + + return null; + } + + @Override + public String getTypeIdentifier() { + return "PRINTINGPLATE"; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return formatLore(output); + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintingPressRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintingPressRecipe.java new file mode 100644 index 000000000..16fbbda3f --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintingPressRecipe.java @@ -0,0 +1,26 @@ +/** + * @author Aleksey Terzi + * + */ + +package com.github.igotyou.FactoryMod.recipes; + +import java.util.List; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public abstract class PrintingPressRecipe extends InputRecipe { + public PrintingPressRecipe(String identifier, String name, int productionTime, ItemMap input) { + super(identifier, name, productionTime, input); + } + + protected ItemStack getPrintingPlateRepresentation(ItemMap printingPlate, String name) { + List out = printingPlate.getItemStackRepresentation(); + ItemStack res = out.size() == 0 ? new ItemStack(Material.STONE) : out.get(0).clone(); + ItemUtils.setDisplayName(res, name); + + return res; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/ProductionRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/ProductionRecipe.java new file mode 100644 index 000000000..9d8bdab4a --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/ProductionRecipe.java @@ -0,0 +1,176 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.recipes.scaling.ProductionRecipeModifier; +import java.text.DecimalFormat; +import java.util.List; +import java.util.Map.Entry; +import java.util.Random; + +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +/** + * Consumes a set of materials from a container and outputs another set of + * materials to the same container + * + */ +public class ProductionRecipe extends InputRecipe { + + private ItemMap output; + private ItemStack recipeRepresentation; + private ProductionRecipeModifier modifier; + private Random rng; + private DecimalFormat decimalFormatting; + + public ProductionRecipe( + String identifier, + String name, + int productionTime, + ItemMap inputs, + ItemMap output, + ItemStack recipeRepresentation, + ProductionRecipeModifier modifier + ) { + super(identifier, name, productionTime, inputs); + this.output = output; + this.modifier = modifier; + this.rng = new Random(); + this.decimalFormatting = new DecimalFormat("#.#####"); + this.recipeRepresentation = recipeRepresentation != null ? recipeRepresentation : new ItemStack(Material.STONE); + } + + public ItemMap getOutput() { + return output; + } + + public ItemMap getAdjustedOutput(int rank, int runs) { + ItemMap im = output.clone(); + if (modifier != null) { + im.multiplyContent(modifier.getFactor(rank, runs)); + return im; + } + return im; + } + + public ItemMap getGuaranteedOutput(int rank, int runs) { + if (modifier == null) { + return output.clone(); + } + ItemMap adjusted = new ItemMap(); + double factor = modifier.getFactor(rank, runs); + for(Entry entry : output.getEntrySet()) { + adjusted.addItemAmount(entry.getKey(), (int) (Math.floor(entry.getValue() * factor))); + } + return adjusted; + } + + public int getCurrentMultiplier(Inventory i) { + return input.getMultiplesContainedIn(i); + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + if (i == null || fccf == null) { + return output.getItemStackRepresentation(); + } + ItemMap currentOut = getGuaranteedOutput(fccf.getRecipeLevel(this), fccf.getRunCount(this)); + List stacks = currentOut.getItemStackRepresentation(); + double factor = (modifier != null) ? (modifier.getFactor(fccf.getRecipeLevel(this), fccf.getRunCount(this))) : 1.0; + for(Entry entry : output.getEntrySet()) { + double additionalChance = (((double) entry.getValue()) * factor) - currentOut.getAmount(entry.getKey()); + if (Math.abs(additionalChance) > 0.00000001) { + ItemStack is = entry.getKey().clone(); + ItemUtils.addLore(is, ChatColor.GOLD + decimalFormatting.format(additionalChance) + " chance for additional item"); + stacks.add(is); + } + } + int possibleRuns = input.getMultiplesContainedIn(i); + for (ItemStack is : stacks) { + ItemUtils.addLore(is, ChatColor.GREEN + "Enough materials for " + String.valueOf(possibleRuns) + " runs"); + } + return stacks; + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + if (i == null) { + return input.getItemStackRepresentation(); + } + return createLoredStacksForInfo(i); + } + + public List getGuaranteedOutput(Inventory i, FurnCraftChestFactory fccf) { + if (i == null) { + return input.getItemStackRepresentation(); + } + return createLoredStacksForInfo(i); + } + + @Override + public EffectFeasibility evaluateEffectFeasibility(Inventory inputInv, Inventory outputInv) { + boolean isFeasible = input.fitsIn(outputInv); + String reasonSnippet = isFeasible ? null : "it ran out of storage space"; + return new EffectFeasibility( + isFeasible, + reasonSnippet + ); + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + ItemMap toRemove = input.clone(); + ItemMap toAdd; + if (getModifier() == null) { + toAdd = output.clone(); + } + else { + toAdd = getGuaranteedOutput(fccf.getRecipeLevel(this), fccf.getRunCount(this)); + double factor = modifier.getFactor(fccf.getRecipeLevel(this), fccf.getRunCount(this)); + for(Entry entry : output.getEntrySet()) { + double additionalChance = (((double) entry.getValue()) * factor) - toAdd.getAmount(entry.getKey()); + if (rng.nextDouble() <= additionalChance) { + toAdd.addItemAmount(entry.getKey(), 1); + } + } + } + if (toRemove.isContainedIn(inputInv)) { + if (!toAdd.fitsIn(outputInv)) { // does not fit in chest + return false; + } + if (toRemove.removeSafelyFrom(inputInv)) { + for (ItemStack is : toAdd.getItemStackRepresentation()) { + outputInv.addItem(is); + } + } + } + logAfterRecipeRun(combo, fccf); + return true; + } + + @Override + public Material getRecipeRepresentationMaterial() { + return this.recipeRepresentation.getType(); + } + + public ProductionRecipeModifier getModifier() { + return modifier; + } + + @Override + public String getTypeIdentifier() { + return "PRODUCTION"; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return formatLore(output); + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PylonRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PylonRecipe.java new file mode 100644 index 000000000..499dbb78a --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PylonRecipe.java @@ -0,0 +1,152 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class PylonRecipe extends InputRecipe { + + private ItemMap output; + private static int currentGlobalWeight; + private static int globalLimit; + private int weight; + + public PylonRecipe(String identifier, String name, int productionTime, ItemMap input, + ItemMap output, int weight) { + super(identifier, name, productionTime, input); + this.output = output; + this.weight = weight; + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + if (!input.isContainedIn(inputInv)) { + return false; + } + ItemMap actualOutput = getCurrentOutput(); + if (!actualOutput.fitsIn(outputInv)) { + return false; + } + if (input.removeSafelyFrom(inputInv)) { + for (ItemStack is : actualOutput.getItemStackRepresentation()) { + outputInv.addItem(is); + } + } + return true; + } + + public static void setGlobalLimit(int limit) { + globalLimit = limit; + } + + public static int getGlobalLimit() { + return globalLimit; + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + ItemMap currOut = getCurrentOutput(); + List res = new LinkedList(); + for (ItemStack is : currOut.getItemStackRepresentation()) { + ItemUtils.setLore(is, ChatColor.GOLD + "Currently there are " + + FurnCraftChestFactory.getPylonFactories() == null ? "0" + : FurnCraftChestFactory.getPylonFactories().size() + + " pylons on the map", ChatColor.RED + + "Current global weight is " + currentGlobalWeight); + res.add(is); + } + return res; + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + if (i == null) { + return input.getItemStackRepresentation(); + } + return createLoredStacksForInfo(i); + } + + @Override + public Material getRecipeRepresentationMaterial() { + return output.getItemStackRepresentation().get(0).getType(); + } + + @Override + public boolean enoughMaterialAvailable(Inventory inputInv) { + return input.isContainedIn(inputInv) && skyView(); + } + + public int getWeight() { + return weight; + } + + private boolean skyView() { + return true; + // place holder in case we want to do something with it in the future + } + + private ItemMap getCurrentOutput() { + int weight = 0; + Set pylons = FurnCraftChestFactory + .getPylonFactories(); + if (pylons != null) { + // if not a single factory (not limited to pylon) is in the map, + // this will be null + for (FurnCraftChestFactory f : pylons) { + if (f.isActive() && f.getCurrentRecipe() instanceof PylonRecipe) { + weight += ((PylonRecipe) f.getCurrentRecipe()).getWeight(); + } + } + } + currentGlobalWeight = weight; + double overload = Math.max(1.0, (float) currentGlobalWeight + / (float) globalLimit); + double multiplier = 1.0 / overload; + ItemMap actualOutput = new ItemMap(); + for (Entry entry : output.getEntrySet()) { + actualOutput.addItemAmount(entry.getKey(), + (int) (entry.getValue() * multiplier)); + } + return actualOutput; + } + + public static void addWeight(int weight) { + currentGlobalWeight += weight; + } + + public static void removeWeight(int weight) { + currentGlobalWeight -= weight; + } + + @Override + public String getTypeIdentifier() { + return "PYLON"; + } + + public ItemMap getOutput() { + return output; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + List result = new ArrayList<>(); + for(Entry entry : output.getEntrySet()) { + if (entry.getValue() > 0) { + result.add(entry.getValue() + " " + ItemUtils.getItemName(entry.getKey())); + } + } + return result; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RandomEnchantingRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RandomEnchantingRecipe.java new file mode 100644 index 000000000..0e1d65871 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RandomEnchantingRecipe.java @@ -0,0 +1,132 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.EnchantUtils; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class RandomEnchantingRecipe extends InputRecipe { + private List enchants; + private Material tool; + private static Random rng; + + public class RandomEnchant { + private Enchantment enchant; + private int level; + private double chance; + + public RandomEnchant(Enchantment enchant, int level, double chance) { + this.enchant = enchant; + this.level = level; + this.chance = chance; + } + } + + public RandomEnchantingRecipe(String identifier, String name, int productionTime, + ItemMap input, Material tool, List enchants) { + super(identifier, name, productionTime, input); + this.enchants = enchants; + this.tool = tool; + if (rng == null) { + rng = new Random(); + } + } + + @Override + public Material getRecipeRepresentationMaterial() { + return tool; + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + if (i == null) { + List bla = input.getItemStackRepresentation(); + bla.add(new ItemStack(tool)); + return bla; + } + List returns = createLoredStacksForInfo(i); + ItemStack toSt = new ItemStack(tool); + ItemUtils.addLore(toSt, ChatColor.GREEN + "Enough materials for " + + new ItemMap(toSt).getMultiplesContainedIn(i) + " runs"); + returns.add(toSt); + return returns; + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + ItemStack is = new ItemStack(tool); + for (RandomEnchant re : enchants) { + is.addEnchantment(re.enchant, re.level); + } + if (i != null) { + ItemUtils.addLore( + is, + ChatColor.GREEN + + "Enough materials for " + + String.valueOf(Math.max(new ItemMap( + new ItemStack(tool)) + .getMultiplesContainedIn(i), input + .getMultiplesContainedIn(i))) + " runs"); + } + for (RandomEnchant re : enchants) { + ItemUtils.addLore(is, + ChatColor.YELLOW + String.valueOf(re.chance * 100) + + " % chance for " + EnchantUtils.getEnchantNiceName(re.enchant) + + " " + re.level); + } + ItemUtils.addLore(is, ChatColor.LIGHT_PURPLE + + "At least one guaranteed"); + List stacks = new LinkedList<>(); + stacks.add(is); + return stacks; + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + for (ItemStack is : input.getItemStackRepresentation()) { + inputInv.removeItem(is); + } + for (ItemStack is : inputInv.getContents()) { + if (is != null && is.getType() == tool + && !is.getItemMeta().hasEnchants()) { + boolean applied = false; + while (!applied) { + for (RandomEnchant re : enchants) { + if (rng.nextDouble() <= re.chance) { + is.getItemMeta().addEnchant(re.enchant, re.level, + true); + applied = true; + } + } + } + break; + } + } + logAfterRecipeRun(combo, fccf); + return true; + } + + @Override + public String getTypeIdentifier() { + return "RANDOMENCHANT"; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("The tool input with a random enchant applied"); + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RandomOutputRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RandomOutputRecipe.java new file mode 100644 index 000000000..4e07f7a88 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RandomOutputRecipe.java @@ -0,0 +1,133 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Random; + +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class RandomOutputRecipe extends InputRecipe { + private Map outputs; + private static Random rng; + private ItemMap lowestChanceMap; + + public RandomOutputRecipe(String identifier, String name, int productionTime, ItemMap input, + Map outputs, ItemMap displayOutput) { + super(identifier, name, productionTime, input); + this.outputs = outputs; + if (rng == null) { + rng = new Random(); + } + if (displayOutput == null) { + for(Entry entry : outputs.entrySet()) { + if (lowestChanceMap == null) { + lowestChanceMap = entry.getKey(); + continue; + } + if (entry.getValue() < outputs.get(lowestChanceMap)) { + lowestChanceMap = entry.getKey(); + } + } + if (lowestChanceMap == null) { + lowestChanceMap = new ItemMap(new ItemStack(Material.STONE)); + } + } else { + lowestChanceMap = displayOutput; + } + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + ItemMap toRemove = input.clone(); + ItemMap toAdd = null; + int counter = 0; + while(counter < 20) { + toAdd = getRandomOutput(); + if (toAdd != null) { + toAdd = toAdd.clone(); + break; + } + else { + counter++; + } + } + if (toAdd == null) { + FactoryMod.getInstance().warning("Unable to find a random item to output. Recipe execution was cancelled," + fccf.getLogData()); + return false; + } + if (toRemove.isContainedIn(inputInv)) { + if (toRemove.removeSafelyFrom(inputInv)) { + for(ItemStack is: toAdd.getItemStackRepresentation()) { + outputInv.addItem(is); + } + } + } + logAfterRecipeRun(combo, fccf); + return true; + } + + public Map getOutputs() { + return outputs; + } + + public ItemMap getRandomOutput() { + double random = rng.nextDouble(); + double count = 0.0; + for(Entry entry : outputs.entrySet()) { + count += entry.getValue(); + if (count >= random) { + return entry.getKey(); + } + } + return null; + } + + @Override + public Material getRecipeRepresentationMaterial() { + return input.getItemStackRepresentation().get(0).getType(); + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + if (i == null) { + return input.getItemStackRepresentation(); + } + return createLoredStacksForInfo(i); + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + List items = lowestChanceMap.getItemStackRepresentation(); + for (ItemStack is : items) { + ItemUtils.addLore(is, ChatColor.LIGHT_PURPLE + "Randomized output"); + } + return items; + } + + @Override + public String getTypeIdentifier() { + return "RANDOM"; + } + + public ItemMap getDisplayMap() { + return lowestChanceMap; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("A random item"); + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RecipeScalingUpgradeRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RecipeScalingUpgradeRecipe.java new file mode 100644 index 000000000..9dd5afda6 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RecipeScalingUpgradeRecipe.java @@ -0,0 +1,107 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; + +public class RecipeScalingUpgradeRecipe extends InputRecipe { + + private ProductionRecipe toUpgrade; + private int newRank; + private RecipeScalingUpgradeRecipe followUpRecipe; + + public RecipeScalingUpgradeRecipe(String identifier, String name, int productionTime, ItemMap input, + ProductionRecipe toUpgrade, int newRank, RecipeScalingUpgradeRecipe followUpRecipe) { + super(identifier, name, productionTime, input); + this.toUpgrade = toUpgrade; + this.newRank = newRank; + this.followUpRecipe = followUpRecipe; + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + if (toUpgrade == null || !fccf.getRecipes().contains(toUpgrade)) { + return false; + } + ItemMap toRemove = input.clone(); + if (toRemove.isContainedIn(inputInv)) { + if (toRemove.removeSafelyFrom(inputInv)) { + if (newRank == 1) { + fccf.addRecipe(toUpgrade); + } + else { + fccf.setRecipeLevel(toUpgrade, newRank); + } + // no longer needed + fccf.removeRecipe(this); + if (followUpRecipe != null) { + fccf.addRecipe(followUpRecipe); + fccf.setRecipe(toUpgrade); + } + } + } + logAfterRecipeRun(combo, fccf); + return true; + } + + public void setUpgradedRecipe(ProductionRecipe rec) { + this.toUpgrade = rec; + } + + public void setFollowUpRecipe(RecipeScalingUpgradeRecipe rec) { + this.followUpRecipe = rec; + } + + @Override + public String getTypeIdentifier() { + return "RECIPEMODIFIERUPGRADE"; + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + if (i == null) { + return input.getItemStackRepresentation(); + } + return createLoredStacksForInfo(i); + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + ItemStack is = getRecipeRepresentation(); + List result = new LinkedList<>(); + result.add(is); + return result; + } + + @Override + public Material getRecipeRepresentationMaterial() { + return Material.GRINDSTONE; + } + + public IRecipe getToUpgrade() { + return toUpgrade; + } + + public int getNewRank() { + return newRank; + } + + public IRecipe getFollowUpRecipe() { + return followUpRecipe; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("TODO"); + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RepairRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RepairRecipe.java new file mode 100644 index 000000000..a91d94028 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RepairRecipe.java @@ -0,0 +1,85 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.repairManager.PercentageHealthRepairManager; +import com.github.igotyou.FactoryMod.utility.LoggingUtils; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +/** + * Used to repair FurnCraftChest factories. Once one of those factories is in + * disrepair the only recipe that can be run is one of this kind + * + */ +public class RepairRecipe extends InputRecipe { + private int healthPerRun; + + public RepairRecipe(String identifier, String name, int productionTime, ItemMap input, + int healthPerRun) { + super(identifier, name, productionTime, input); + this.healthPerRun = healthPerRun; + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + List result = new LinkedList<>(); + ItemStack furn = new ItemStack(Material.FURNACE); + ItemUtils.setLore(furn, "+" + String.valueOf(healthPerRun) + " health"); + result.add(furn); + return result; + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + if (i == null) { + return input.getItemStackRepresentation(); + } + return createLoredStacksForInfo(i); + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + if (enoughMaterialAvailable(inputInv)) { + if (input.removeSafelyFrom(inputInv)) { + ((PercentageHealthRepairManager) (fccf.getRepairManager())) + .repair(healthPerRun); + LoggingUtils.log(((PercentageHealthRepairManager) (fccf + .getRepairManager())).getHealth() + + " for " + + fccf.getLogData() + " after repairing"); + } + } + logAfterRecipeRun(combo, fccf); + return true; + } + + @Override + public Material getRecipeRepresentationMaterial() { + return Material.FURNACE; + } + + @Override + public String getTypeIdentifier() { + return "REPAIR"; + } + + public int getHealth() { + return healthPerRun; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList(ChatColor.YELLOW + "Repairs the factory by " + healthPerRun + " health"); + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/Upgraderecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/Upgraderecipe.java new file mode 100644 index 000000000..f1b84f23d --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/Upgraderecipe.java @@ -0,0 +1,132 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.eggs.FurnCraftChestEgg; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; + +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class Upgraderecipe extends InputRecipe { + private FurnCraftChestEgg egg; + + public Upgraderecipe(String identifier, String name, int productionTime, ItemMap input, + FurnCraftChestEgg egg) { + super(identifier, name, productionTime, input); + this.egg = egg; + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + logBeforeRecipeRun(combo, fccf); + if (input.isContainedIn(inputInv)) { + if (input.removeSafelyFrom(inputInv)) { + FurnCraftChestEgg e = egg; + fccf.upgrade(e.getName(), + e.getRecipes(), e.getFuel(), + e.getFuelConsumptionIntervall(), e.getUpdateTime(), e.getMaximumHealth(), + e.getDamagePerDamagingPeriod(), e.getBreakGracePeriod(), e.getCitadelBreakReduction()); + } + } + logAfterRecipeRun(combo, fccf); + return true; + } + + @Override + public ItemStack getRecipeRepresentation() { + ItemStack res = ((InputRecipe)egg.getRecipes().get(0)).getOutputRepresentation(null, null).get(0); + res.setAmount(1); + ItemMeta im = res.getItemMeta(); + im.addEnchant(Enchantment.DAMAGE_ALL, 1, true); + im.addItemFlags(ItemFlag.HIDE_ENCHANTS); + res.setItemMeta(im); + ItemUtils.setDisplayName(res, name); + return res; + } + + @Override + public Material getRecipeRepresentationMaterial() { + return ((InputRecipe)egg.getRecipes().get(0)).getOutputRepresentation(null, null).get(0).getType(); + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + if (i == null) { + return input.getItemStackRepresentation(); + } + LinkedList result = new LinkedList<>(); + ItemMap inventoryMap = new ItemMap(i); + ItemMap possibleRuns = new ItemMap(); + for (Entry entry : input.getEntrySet()) { + if (inventoryMap.getAmount(entry.getKey()) != 0) { + possibleRuns.addItemAmount( + entry.getKey(), + inventoryMap.getAmount(entry.getKey()) + / entry.getValue()); + } else { + possibleRuns.addItemAmount(entry.getKey(), 0); + } + } + for (ItemStack is : input.getItemStackRepresentation()) { + if (possibleRuns.getAmount(is) != 0) { + ItemUtils.addLore(is, ChatColor.GREEN + + "Enough of this material available to upgrade"); + } else { + ItemUtils.addLore(is, ChatColor.RED + + "Not enough of this materials available to upgrade"); + } + result.add(is); + } + return result; + } + + @Override + public List getOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + List res = new LinkedList<>(); + ItemStack cr = new ItemStack(Material.CRAFTING_TABLE); + ItemUtils.setDisplayName(cr, egg.getName()); + ItemUtils.setLore(cr, ChatColor.LIGHT_PURPLE + + "Upgrade to get new and better recipes"); + res.add(cr); + ItemStack fur = new ItemStack(Material.FURNACE); + ItemUtils.setDisplayName(fur, egg.getName()); + ItemUtils.setLore(fur, ChatColor.LIGHT_PURPLE + "Recipes:"); + for (IRecipe rec : egg.getRecipes()) { + ItemUtils.addLore(fur, ChatColor.YELLOW + rec.getName()); + } + res.add(fur); + ItemStack che = new ItemStack(Material.CHEST); + ItemUtils.setLore(che, ChatColor.LIGHT_PURPLE + "Careful, you can not", + ChatColor.LIGHT_PURPLE + "revert upgrades!"); + ItemUtils.setDisplayName(che, egg.getName()); + res.add(che); + return res; + } + + public FurnCraftChestEgg getEgg() { + return egg; + } + + @Override + public String getTypeIdentifier() { + return "UPGRADE"; + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("Upgrades the factory to " + egg.getName()); + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/WordBankRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/WordBankRecipe.java new file mode 100644 index 000000000..701c3c5da --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/WordBankRecipe.java @@ -0,0 +1,203 @@ +package com.github.igotyou.FactoryMod.recipes; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.utility.MultiInventoryWrapper; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map.Entry; +import java.util.Random; + +public class WordBankRecipe extends InputRecipe { + + private String key; + private MessageDigest digest; + private List validWords; + + private List colors; + private int words; + private SecureRandom preview; + + public WordBankRecipe(String identifier, String name, int productionTime, String key, List words, + List colors, int wordCount) { + super(identifier, name, productionTime, new ItemMap()); + try { + this.digest = MessageDigest.getInstance("SHA-512"); + } catch (NoSuchAlgorithmException e) { + FactoryMod.getInstance().getLogger().severe("Failed to instanciate SHA-512:" + e.getMessage()); + } + this.key = key; + this.words = wordCount; + this.validWords = words; + this.colors = colors; + this.preview = new SecureRandom(); + } + + @Override + public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChestFactory fccf) { + MultiInventoryWrapper combo = new MultiInventoryWrapper(inputInv, outputInv); + ItemStack toApply = inputInv.getItem(0); + if (!ItemUtils.isValidItem(toApply)) { + return false; + } + if (!ItemUtils.getDisplayName(toApply).isEmpty()) { + return false; + } + ItemMap input = new ItemMap(); + for (int i = 1; i < inputInv.getSize(); i++) { + ItemStack is = inputInv.getItem(i); + if (!ItemUtils.isValidItem(is)) { + continue; + } + input.addItemStack(is); + inputInv.setItem(i, null); + } + //tell player what the recipe consumed + StringBuilder sb = new StringBuilder(); + sb.append(ChatColor.GOLD); + sb.append("Wordbank recipe complete and turned "); + for (Entry entry : input.getEntrySet()) { + sb.append(entry.getValue()); + sb.append(" "); + sb.append(ItemUtils.getItemName(entry.getKey())); + sb.append(", "); + } + String result = sb.substring(0, sb.length() - 2); + String name = getHash(input); + ItemUtils.setDisplayName(toApply, name); + if (fccf.getActivator() != null) { + Player player = Bukkit.getPlayer(fccf.getActivator()); + if (player != null) { + player.sendMessage(result + " into " + name); + } + } + //always return false to turn the factory off + return false; + } + + @Override + public String getTypeIdentifier() { + return "WORDBANK"; + } + + @Override + public List getInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + ItemStack is = new ItemStack(Material.RED_WOOL); + ItemUtils.addLore(is, ChatColor.GOLD + "A tool or piece of armor and any other random amount of items"); + return Collections.singletonList(is); + } + + @Override + public List getOutputRepresentation(Inventory inv, FurnCraftChestFactory fccf) { + ItemStack is = new ItemStack(Material.DIAMOND_SWORD); + StringBuilder output = new StringBuilder(); + output.append(colors.get(preview.nextInt(colors.size()))); + for (int i = 0; i < words; i++) { + String word = validWords.get(preview.nextInt(validWords.size())); + if (i > 0) { + output.append(" "); + } + output.append(word); + } + ItemUtils.setDisplayName(is, output.toString()); + return Collections.singletonList(is); + } + + @Override + public Material getRecipeRepresentationMaterial() { + return Material.WRITABLE_BOOK; + } + + @Override + public boolean enoughMaterialAvailable(Inventory inputInv) { + ItemStack toApply = inputInv.getItem(0); + if (!ItemUtils.isValidItem(toApply)) { + return false; + } + if (!ItemUtils.getDisplayName(toApply).isEmpty()) { + return false; + } + for (int i = 1; i < inputInv.getSize(); i++) { + ItemStack is = inputInv.getItem(i); + if (!ItemUtils.isValidItem(is)) { + continue; + } + return true; + } + return false; + } + + private synchronized String getHash(ItemMap items) { + digest.update(key.getBytes()); + List> entries = new ArrayList<>(items.getEntrySet()); + //sort because hashmaps dont guarantee iteration order + Collections.sort(entries, + (a, b) -> a.getKey().getType().getKey().getKey().compareTo(b.getKey().getType().getKey().getKey())); + for (Entry entry : entries) { + digest.update(entry.getKey().getType().getKey().getKey().getBytes()); + digest.update(toBuffer(entry.getValue())); + if (entry.getKey().getItemMeta().hasLore()) { + ItemStack item = entry.getKey(); + for (String s : item.getItemMeta().getLore()) { + digest.update(s.getBytes()); + } + } + } + byte[] result = digest.digest(); + ByteBuffer buffer = ByteBuffer.allocate(result.length); + buffer.put(result, 0, result.length); + StringBuilder output = new StringBuilder(); + output.append(colors.get(pickIndex(buffer.getInt(0), colors.size()))); + int currentLength = new Random(buffer.getLong(1)).nextInt(words) + 1; + for (int i = 1; i <= currentLength; i++) { + //offset by 4 to avoid overlap with first two longs + int intKey = buffer.getInt(i + 4); + String word = validWords.get(pickIndex(intKey, validWords.size())); + if (i > 1) { + output.append(" "); + } + output.append(word); + } + return output.toString(); + } + + private static int pickIndex(int hash, int length) { + int index = hash % length; + if (index < 0) { + index += length; + } + return index; + } + + private static byte[] toBuffer(int hash) { + ByteBuffer b = ByteBuffer.allocate(4); + b.putInt(hash); + return b.array(); + } + + @Override + public List getTextualInputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("An item to be renamed and an item to be consumed as key"); + } + + @Override + public List getTextualOutputRepresentation(Inventory i, FurnCraftChestFactory fccf) { + return Arrays.asList("The item input with a random colored name applied"); + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/scaling/ProductionRecipeModifier.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/scaling/ProductionRecipeModifier.java new file mode 100644 index 000000000..885b385ed --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/scaling/ProductionRecipeModifier.java @@ -0,0 +1,92 @@ +package com.github.igotyou.FactoryMod.recipes.scaling; + +import java.util.TreeMap; + +public class ProductionRecipeModifier { + + private TreeMap configs; + + public ProductionRecipeModifier() { + this.configs = new TreeMap(); + } + + public double getFactor(int rank, int runAmount) { + ProductionRecipeModifierConfig config = configs.get(rank); + if (config == null) { + //no entry exists, so don't change + return 1.0; + } + return config.getModifier(runAmount); + } + + public void addConfig(int minimumRuns, int maximumRuns, double baseIncrease, double maximumIncrease, int rank) { + configs.put(rank, new ProductionRecipeModifierConfig(minimumRuns, maximumRuns, baseIncrease, maximumIncrease, rank)); + } + + + public ProductionRecipeModifier clone() { + ProductionRecipeModifier modi = new ProductionRecipeModifier(); + for(ProductionRecipeModifierConfig config : this.configs.values()) { + modi.addConfig(config.getMinimumRuns(), config.getMaximumRuns(), config.getBaseIncrease(), config.getMaximumIncrease(), config.getRank()); + } + return modi; + } + + public double getMaximumMultiplierForRank(int rank) { + ProductionRecipeModifierConfig config = configs.get(rank); + if (config == null) { + //no entry exists, so don't change + return 1.0; + } + return config.getMaximumIncrease(); + } + + private class ProductionRecipeModifierConfig { + + private int minimumRuns; + private int maximumRuns; + private double baseIncrease; + private double maximumIncrease; + private int rank; + + public ProductionRecipeModifierConfig(int minimumRuns, int maximumRuns, double baseIncrease, + double maximumIncrease, int rank) { + this.minimumRuns = minimumRuns; + this.maximumIncrease = maximumIncrease; + this.maximumRuns = maximumRuns; + this.baseIncrease = baseIncrease; + this.rank = rank; + } + + public int getMinimumRuns() { + return minimumRuns; + } + + public int getMaximumRuns() { + return maximumRuns; + } + + public double getBaseIncrease() { + return baseIncrease; + } + + public double getMaximumIncrease() { + return maximumIncrease; + } + + public int getRank() { + return rank; + } + + public double getModifier(int runs) { + if (runs > maximumRuns) { + return maximumIncrease; + } + if (runs < minimumRuns) { + return baseIncrease; + } + return (((double) (runs - minimumRuns)) / ((double) (maximumRuns - minimumRuns)) * (maximumIncrease - baseIncrease)) + + baseIncrease; + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/repairManager/IRepairManager.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/repairManager/IRepairManager.java new file mode 100644 index 000000000..9d6d80d92 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/repairManager/IRepairManager.java @@ -0,0 +1,28 @@ +package com.github.igotyou.FactoryMod.repairManager; + +/** + * Used to manager the health of a factory and to handle repairs for it. + * + */ +public interface IRepairManager { + /** + * Sets the health to 0 and makes the factory unusable + */ + public void breakIt(); + + /** + * @return How much health the factory represented currently has as a nice string for output messages + */ + public String getHealth(); + + /** + * @return Whether the factory represented is currently at full health + */ + public boolean atFullHealth(); + + /** + * @return Whether the factory is in disrepair currently + */ + public boolean inDisrepair(); + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/repairManager/NoRepairDestroyOnBreakManager.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/repairManager/NoRepairDestroyOnBreakManager.java new file mode 100644 index 000000000..771e67b39 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/repairManager/NoRepairDestroyOnBreakManager.java @@ -0,0 +1,59 @@ +package com.github.igotyou.FactoryMod.repairManager; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.utility.LoggingUtils; + +public class NoRepairDestroyOnBreakManager implements IRepairManager { + private Factory factory; + + public NoRepairDestroyOnBreakManager(Factory factory) { + this.factory = factory; + } + + public NoRepairDestroyOnBreakManager() { + // we have to offer this explicitly as a possible constructor if we also + // want the one which directly defines the factory + } + + public void setFactory(Factory factory) { + this.factory = factory; + } + + public void breakIt() { + FactoryMod + .getInstance() + .getServer() + .getScheduler() + .scheduleSyncDelayedTask(FactoryMod.getInstance(), + new Runnable() { + + @Override + public void run() { + if (factory.getMultiBlockStructure() + .relevantBlocksDestroyed()) { + LoggingUtils.log(factory.getLogData() + + " removed because blocks were destroyed"); + FactoryMod.getInstance().getManager().removeFactory( + factory); + PercentageHealthRepairManager + .returnStuff(factory); + } + + } + }); + } + + public boolean atFullHealth() { + return true; + } + + public boolean inDisrepair() { + return false; + } + + public String getHealth() { + return "full"; + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/repairManager/PercentageHealthRepairManager.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/repairManager/PercentageHealthRepairManager.java new file mode 100644 index 000000000..13032a03f --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/repairManager/PercentageHealthRepairManager.java @@ -0,0 +1,105 @@ +package com.github.igotyou.FactoryMod.repairManager; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.utility.LoggingUtils; +import java.util.Map.Entry; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; + +public class PercentageHealthRepairManager implements IRepairManager { + private int health; + private Factory factory; + private long breakTime; + private int maximumHealth; + private int damageAmountPerDecayIntervall; + private long gracePeriod; + + public PercentageHealthRepairManager(int initialHealth, int maximumHealth, long breakTime, int damageAmountPerDecayIntervall, long gracePeriod) { + this.health = initialHealth; + this.maximumHealth = maximumHealth; + this.breakTime = breakTime; + this.damageAmountPerDecayIntervall = damageAmountPerDecayIntervall; + this.gracePeriod = gracePeriod; + } + + public boolean atFullHealth() { + return health >= maximumHealth; + } + + public int getMaximumHealth() { + return maximumHealth; + } + + public int getDamageAmountPerDecayIntervall() { + return damageAmountPerDecayIntervall; + } + + public long getGracePeriod() { + return gracePeriod; + } + + public boolean inDisrepair() { + return health <= 0; + } + + public void setFactory(Factory factory) { + this.factory = factory; + } + + public String getHealth() { + return String.valueOf(health / (maximumHealth / 100)) + "." + (health % (maximumHealth / 100) + " %"); + } + + public void repair(int amount) { + health = Math.min(health + amount, maximumHealth); + breakTime = 0; + } + + public void breakIt() { + health = 0; + if (breakTime == 0) { + breakTime = System.currentTimeMillis(); + } + FactoryMod.getInstance().getServer().getScheduler() + .scheduleSyncDelayedTask(FactoryMod.getInstance(), ()->{ + if (factory.getMultiBlockStructure().relevantBlocksDestroyed()) { + LoggingUtils.log(factory.getLogData() + " removed because blocks were destroyed"); + FactoryMod.getInstance().getManager().removeFactory(factory); + returnStuff(factory); + } + }); + } + + public int getRawHealth() { + return health; + } + + public void setHealth(int health) { + this.health = health; + } + + public static void returnStuff(Factory factory) { + double rate = FactoryMod.getInstance().getManager().getEgg(factory.getName()).getReturnRate(); + if (rate == 0.0) { + return; + } + for (Entry items : FactoryMod.getInstance().getManager().getTotalSetupCost(factory).getEntrySet()) { + int returnAmount = (int) (items.getValue() * rate); + ItemMap im = new ItemMap(); + im.addItemAmount(items.getKey(), returnAmount); + for (ItemStack is : im.getItemStackRepresentation()) { + factory.getMultiBlockStructure().getCenter().getWorld() + .dropItemNaturally(factory.getMultiBlockStructure().getCenter(), is); + } + } + } + + public long getBreakTime() { + return breakTime; + } + + public void setBreakTime(long breakTime) { + this.breakTime = breakTime; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/BlockFurnaceStructure.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/BlockFurnaceStructure.java new file mode 100644 index 000000000..52d82c5d2 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/BlockFurnaceStructure.java @@ -0,0 +1,67 @@ +package com.github.igotyou.FactoryMod.structures; + +import java.util.LinkedList; +import java.util.List; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; + +public class BlockFurnaceStructure extends MultiBlockStructure { + + private Location center; + private Location furnace; + private boolean complete = false; + + public BlockFurnaceStructure(Block center) { + if (center.getType() == Material.DROPPER) { + this.center = center.getLocation(); + for (Block b : searchForBlockOnAllSides(center, Material.FURNACE)) { + furnace = b.getLocation(); + complete = true; + break; + } + } + } + + public BlockFurnaceStructure(List blocks) { + this.center = blocks.get(0); + this.furnace = blocks.get(1); + } + + public boolean relevantBlocksDestroyed() { + return center.getBlock().getType() != Material.DROPPER + && furnace.getBlock().getType() != Material.FURNACE; + } + + public Location getCenter() { + return center; + } + + public Block getFurnace() { + return furnace.getBlock(); + } + + public List getAllBlocks() { + List blocks = new LinkedList(); + blocks.add(center); + blocks.add(furnace); + return blocks; + } + + public boolean isComplete() { + return complete; + } + + public void recheckComplete() { + complete = (center.getBlock().getType() == Material.DROPPER && (furnace + .getBlock().getType() == Material.FURNACE)); + } + + public List getRelevantBlocks() { + List blocks = new LinkedList(); + blocks.add(center.getBlock()); + blocks.add(furnace.getBlock()); + return blocks; + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/FurnCraftChestStructure.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/FurnCraftChestStructure.java new file mode 100644 index 000000000..17832767c --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/FurnCraftChestStructure.java @@ -0,0 +1,110 @@ +package com.github.igotyou.FactoryMod.structures; + +import java.util.LinkedList; +import java.util.List; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; + +/** + * Physical representation of a factory consisting of a chest, a crafting table + * and a furnace. The crafting table has to be inbetween the furnace and chest. + * The chest may be a double chest, but the part of the double chest not + * adjacent to the crafting table is ignored when doing any checks + * + */ +public class FurnCraftChestStructure extends MultiBlockStructure { + private Location craftingTable; + private Location furnace; + private Location chest; + private boolean complete; + + public FurnCraftChestStructure(Block center) { + if (center.getType() == Material.CRAFTING_TABLE) { + craftingTable = center.getLocation(); + LinkedList chestBlocks = new LinkedList<>(); + chestBlocks.addAll(searchForBlockOnAllSides(center, Material.CHEST)); + chestBlocks.addAll(searchForBlockOnAllSides(center, Material.TRAPPED_CHEST)); + for (Block b : chestBlocks) { + BlockFace chestFace = center.getFace(b); + if (chestFace == null) continue; // fricc off nullcheck + BlockFace furnaceFace = chestFace.getOppositeFace(); + Block furnaceBlock = center.getRelative(furnaceFace); + if (furnaceBlock.getType() == Material.FURNACE) { + chest = b.getLocation(); + furnace = furnaceBlock.getLocation(); + break; + } + } + } + complete = chest != null && furnace != null; + } + + public FurnCraftChestStructure(List blocks) { + craftingTable = blocks.get(0); + furnace = blocks.get(1); + chest = blocks.get(2); + } + + public void recheckComplete() { + complete = craftingTable != null + && craftingTable.getBlock().getType() == Material.CRAFTING_TABLE + && furnace != null + && furnace.getBlock().getType() == Material.FURNACE + && chest != null + && (chest.getBlock().getType() == Material.CHEST + || chest.getBlock().getType() == Material.TRAPPED_CHEST); + } + + public boolean isComplete() { + return complete; + } + + public Block getCraftingTable() { + return craftingTable.getBlock(); + } + + public Block getFurnace() { + return furnace.getBlock(); + } + + public Block getChest() { + // sometimes a double chest will go across chunk borders and the other + // half of the chest might be unloaded. To load the other half and the + // full inventory this is needed to load the chunk + MultiBlockStructure.searchForBlockOnAllSides(chest.getBlock(), + Material.CHEST); + MultiBlockStructure.searchForBlockOnAllSides(chest.getBlock(), + Material.TRAPPED_CHEST); + return chest.getBlock(); + } + + public boolean relevantBlocksDestroyed() { + return craftingTable.getBlock().getType() != Material.CRAFTING_TABLE + && furnace.getBlock().getType() != Material.FURNACE + && chest.getBlock().getType() != Material.CHEST + && chest.getBlock().getType() != Material.TRAPPED_CHEST; + } + + public List getRelevantBlocks() { + LinkedList result = new LinkedList<>(); + result.add(getCraftingTable()); + result.add(getFurnace()); + result.add(getChest()); + return result; + } + + public List getAllBlocks() { + LinkedList result = new LinkedList<>(); + result.add(craftingTable); + result.add(furnace); + result.add(chest); + return result; + } + + public Location getCenter() { + return craftingTable; + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/MultiBlockStructure.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/MultiBlockStructure.java new file mode 100644 index 000000000..b8b31c343 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/MultiBlockStructure.java @@ -0,0 +1,152 @@ +package com.github.igotyou.FactoryMod.structures; + +import com.github.igotyou.FactoryMod.FactoryMod; +import java.util.LinkedList; +import java.util.List; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import vg.civcraft.mc.citadel.ReinforcementLogic; +import vg.civcraft.mc.citadel.model.Reinforcement; +import vg.civcraft.mc.civmodcore.world.WorldUtils; + +/** + * Physical representation of a factory. This may be any shape as long as the + * required methods can be applied on the shape. + * + */ +public abstract class MultiBlockStructure { + + /** + * Checks all sides of the given block for blocks with the given material + * and returns all the blocks which fulfill that criteria + * + * @param b + * Block to check around + * @param m + * Material which the adjacent block should be + * @return All the blocks adjacent to the given block + * and of the given material type + */ + public static List searchForBlockOnAllSides(Block b, Material m) { + LinkedList result = new LinkedList<>(); + for (BlockFace face : WorldUtils.ALL_SIDES) { + Block side = b.getRelative(face); + if (side.getType() == m) { + result.add(side); + } + } + return result; + } + + /** + * Checks north, south, east and west of the given block for blocks with the + * given material and returns all blocks which fulfill that criteria + * + * @param b Block to search around + * @param m Material to search for + * @return All the blocks adjacent (not above or below) to the given block + * and of the given material type + */ + public static List searchForBlockOnSides(Block b, Material m) { + LinkedList result = new LinkedList<>(); + for (BlockFace face : WorldUtils.PLANAR_SIDES) { + Block side = b.getRelative(face); + if (side.getType() == m) { + result.add(side); + } + } + return result; + } + + /** + * @return Whether all parts of this factory are where they should be and no + * block is broken + */ + public abstract boolean isComplete(); + + /** + * Gets all parts of this factory. It is very important to let this have the + * same order as a constructor to create a factory based on the given list + * of blocks, so this method can be used to dump block locations into the + * db, while the constructor can recreate the same factory at a later point + * + * @return All blocks which are part of this factory + */ + public abstract List getAllBlocks(); + + /** + * Rechecks whether all blocks of this factory exists and sets the variable + * used for isComplete(), if needed + */ + public abstract void recheckComplete(); + + /** + * @return center block of the factory which it was created from + */ + public abstract Location getCenter(); + + /** + * @return All interaction blocks and blocks that are not allowed to be used + * in two factories at once + */ + public abstract List getRelevantBlocks(); + + public static List getAdjacentBlocks(Block b) { + List blocks = new LinkedList<>(); + for (BlockFace face : WorldUtils.ALL_SIDES) { + blocks.add(b.getRelative(face)); + } + return blocks; + } + + /** + * Only deals with directly powered redstone interactions, not indirect + * power. If all blocks powering this block are on the same group or if the + * block is insecure or if the block is unreinforced, true will be returned + * + * @param here + * The block to check around. + * @return Whether all power sources around the given block are on the same + * group + */ + public static boolean citadelRedstoneChecks(Block here) { + if (!FactoryMod.getInstance().getManager().isCitadelEnabled()) { + return true; + } + Reinforcement rein = ReinforcementLogic.getReinforcementProtecting(here); + if (rein == null || rein.isInsecure()) { + return true; + } + int prGID = rein.getGroup().getGroupId(); + for (BlockFace face : WorldUtils.ALL_SIDES) { + Block rel = here.getRelative(face); + if (here.isBlockFacePowered(face)) { + Reinforcement relRein = ReinforcementLogic.getReinforcementProtecting(rel); + if (relRein == null || relRein.getGroup().getGroupId() != prGID) { + return false; + } + } + } + return true; + } + + public boolean blockedByExistingFactory() { + for (Block b : getRelevantBlocks()) { + if (FactoryMod.getInstance().getManager().factoryExistsAt(b.getLocation())) { + return true; + } + } + return false; + } + + /** + * Checks whether all relevant/interaction blocks of this factory were + * completly destroyed/replaced by other blocks + * + * @return True if the structure is completly destroyed, false if not + */ + public abstract boolean relevantBlocksDestroyed(); + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/PipeStructure.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/PipeStructure.java new file mode 100644 index 000000000..51409547e --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/PipeStructure.java @@ -0,0 +1,162 @@ +package com.github.igotyou.FactoryMod.structures; + +import java.util.LinkedList; +import java.util.List; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.data.type.Dispenser; +import org.bukkit.inventory.InventoryHolder; + +/** + * Represents a pipe with a dispenser at each end, which are directly connected + * through blocks of one specific type which make up the actual pipe + * + */ +public class PipeStructure extends MultiBlockStructure { + private Location start; + private Location furnace; + private Location end; + private int length; + private List glassPipe; + private Material pipeMaterial; + private boolean complete; + + @SuppressWarnings("deprecation") + public PipeStructure(Block startBlock) { + if (startBlock.getType() != Material.DISPENSER) { + return; + } + this.start = startBlock.getLocation(); + for (Block b : MultiBlockStructure.searchForBlockOnAllSides(startBlock, + Material.FURNACE)) { + furnace = b.getLocation(); + break; + } + if (furnace == null) { + return; + } + glassPipe = new LinkedList<>(); + Dispenser disp = (Dispenser) startBlock.getBlockData(); + Block currentBlock = startBlock.getRelative(disp.getFacing()); + pipeMaterial = currentBlock.getType(); + + Block previousBlock = null; + + glassPipe.add(currentBlock.getLocation()); + int length = 1; + while (length <= 512) { + List blocks = MultiBlockStructure + .getAdjacentBlocks(currentBlock); + boolean foundEnd = false; + boolean foundPipeBlock = false; + for (Block b : blocks) { + if (b.getState() instanceof InventoryHolder && !b.getLocation().equals(start)) { + end = b.getLocation(); + this.length = length; + complete = true; + foundEnd = true; + break; + } else if (b.getType() == pipeMaterial + && !b.equals(previousBlock)) { + glassPipe.add(b.getLocation()); + previousBlock = currentBlock; + currentBlock = b; + length++; + foundPipeBlock = true; + break; + } + } + if (foundEnd || !foundPipeBlock) { + break; + } + } + } + + public PipeStructure(List blocks) { + this.start = blocks.get(0); + this.furnace = blocks.get(1); + this.end = blocks.get(blocks.size() - 1); + List glass = new LinkedList<>(); + for (int i = 2; i < blocks.size()-1;i++) { + glass.add(blocks.get(i)); + } + this.glassPipe = glass; + length = glassPipe.size(); + recheckComplete(); + } + + public Location getCenter() { + return start; + } + + public boolean relevantBlocksDestroyed() { + return start.getBlock().getType() != Material.DISPENSER + && furnace.getBlock().getType() != Material.FURNACE; + } + + public List getAllBlocks() { + List res = new LinkedList<>(); + res.add(start); + res.add(furnace); + res.addAll(glassPipe); + res.add(end); + return res; + } + + public List getRelevantBlocks() { + List res = new LinkedList<>(); + res.add(start.getBlock()); + res.add(furnace.getBlock()); + return res; + } + + @SuppressWarnings("deprecation") + public void recheckComplete() { + if (start == null + || furnace == null + || end == null + || start.getBlock().getType() != Material.DISPENSER + || furnace.getBlock().getType() != Material.FURNACE + || !(end.getBlock().getState() instanceof InventoryHolder)) { + complete = false; + return; + } + for (Location loc : glassPipe) { + Block b = loc.getBlock(); + if (b.getType() != pipeMaterial) { + complete = false; + return; + } + } + complete = true; + } + + public boolean isComplete() { + return complete; + } + + public int getLength() { + return length; + } + + public Block getStart() { + return start.getBlock(); + } + + public Block getEnd() { + return end.getBlock(); + } + + public Block getFurnace() { + return furnace.getBlock(); + } + + public Material getPipeType() { + return pipeMaterial; + } + + public void setPipeType(Material pipeType) { + pipeMaterial = pipeType; + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/Direction.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/Direction.java new file mode 100644 index 000000000..2d3e33edd --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/Direction.java @@ -0,0 +1,86 @@ +package com.github.igotyou.FactoryMod.utility; + +import org.bukkit.block.BlockFace; + +import java.util.function.Function; + +/** + * @author caucow + */ +public enum Direction { + TOP(bf -> BlockFace.UP), // 1 + BOTTOM(bf -> BlockFace.DOWN), // 2 + LEFT(bf -> { + switch (bf) { + case NORTH: + return BlockFace.EAST; + case EAST: + return BlockFace.SOUTH; + case SOUTH: + return BlockFace.WEST; + case WEST: + return BlockFace.NORTH; + default: + return bf; + } + }), // 4 + RIGHT(bf -> { + switch (bf) { + case NORTH: + return BlockFace.WEST; + case EAST: + return BlockFace.NORTH; + case SOUTH: + return BlockFace.EAST; + case WEST: + return BlockFace.SOUTH; + default: + return bf; + } + }), // 8 + FRONT(bf -> bf), // 16 + BACK(BlockFace::getOppositeFace); // 32 + + private final Function facingModifier; + + private Direction(Function facingModifier) { + this.facingModifier = facingModifier; + } + + /** + * @param front direction a block (such as a furnace) is facing. + * @return BlockFace in this direction, relative to the player's perspective when the player and block are + * facing each other. + */ + public BlockFace getBlockFacing(BlockFace front) { + return facingModifier.apply(front); + } + + public static Direction getDirection(BlockFace front, BlockFace axis) { + for (Direction dir : Direction.values()) { + // if dir's transformation of front == axis + if (dir.getBlockFacing(front) == axis) { + return dir; + } + } + throw new IllegalArgumentException("Direction can only be gotten from an axis-aligned BlockFace."); + } + + private static BlockFace getBlockFaceFromDirection(int dirx, int dirz) { + switch (dirz) { + case 1: + return BlockFace.SOUTH; + case -1: + return BlockFace.NORTH; + case 0: { + switch (dirx) { + case 1: + return BlockFace.EAST; + case -1: + return BlockFace.WEST; + } + } + } + throw new IllegalArgumentException("Not a horizontal ordinal: (" + dirx + "," + dirz + ")"); + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/FactoryGarbageCollector.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/FactoryGarbageCollector.java new file mode 100644 index 000000000..872375eb4 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/FactoryGarbageCollector.java @@ -0,0 +1,33 @@ +package com.github.igotyou.FactoryMod.utility; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.FactoryModManager; +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.repairManager.PercentageHealthRepairManager; + +public class FactoryGarbageCollector implements Runnable { + + public void run() { + FactoryModManager manager = FactoryMod.getInstance().getManager(); + for(Factory f: manager.getAllFactories()) { + if (f.getRepairManager() instanceof PercentageHealthRepairManager) { + PercentageHealthRepairManager rm = (PercentageHealthRepairManager) f.getRepairManager(); + long graceTime = rm.getGracePeriod(); + long broke = rm.getBreakTime(); + if (broke != 0) { + if (System.currentTimeMillis() - broke > graceTime) { + //grace period is over + LoggingUtils.log(f.getLogData() + " has been at no health for too long and is being removed"); + manager.removeFactory(f); + } + } + else { + rm.setHealth(rm.getRawHealth() - rm.getDamageAmountPerDecayIntervall()); + if (rm.getRawHealth() <= 0) { + rm.breakIt(); + } + } + } + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/FactoryModGUI.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/FactoryModGUI.java new file mode 100644 index 000000000..0eb4a27d3 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/FactoryModGUI.java @@ -0,0 +1,377 @@ +package com.github.igotyou.FactoryMod.utility; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.FactoryModManager; +import com.github.igotyou.FactoryMod.eggs.FurnCraftChestEgg; +import com.github.igotyou.FactoryMod.eggs.IFactoryEgg; +import com.github.igotyou.FactoryMod.recipes.IRecipe; +import com.github.igotyou.FactoryMod.recipes.InputRecipe; +import com.github.igotyou.FactoryMod.recipes.Upgraderecipe; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.gui.DecorationStack; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.LClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.components.ComponableInventory; +import vg.civcraft.mc.civmodcore.inventory.gui.components.ComponableSection; +import vg.civcraft.mc.civmodcore.inventory.gui.components.ContentAligners; +import vg.civcraft.mc.civmodcore.inventory.gui.components.InventoryComponent; +import vg.civcraft.mc.civmodcore.inventory.gui.components.Scrollbar; +import vg.civcraft.mc.civmodcore.inventory.gui.components.SlotPredicates; +import vg.civcraft.mc.civmodcore.inventory.gui.components.StaticDisplaySection; +import vg.civcraft.mc.civmodcore.inventory.gui.history.HistoryItem; +import vg.civcraft.mc.civmodcore.inventory.gui.history.HistoryTracker; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class FactoryModGUI { + + // target/result egg is key, parent is value + private static Map upgradeMapping; + + public static void initUpgradeMapping(FactoryModManager manager) { + upgradeMapping = new HashMap<>(); + for (IFactoryEgg egg : manager.getAllFactoryEggs()) { + if (!(egg instanceof FurnCraftChestEgg)) { + continue; + } + FurnCraftChestEgg fccEgg = (FurnCraftChestEgg) egg; + for (IRecipe rec : fccEgg.getRecipes()) { + if (rec instanceof Upgraderecipe) { + Upgraderecipe uRec = (Upgraderecipe) rec; + upgradeMapping.put(uRec.getEgg(), fccEgg); + } + } + } + } + + private Player player; + private InventoryComponent topHalfComponent; + private ComponableInventory inventory; + private FurnCraftChestEgg currentFactory; + private HistoryTracker history; + + public FactoryModGUI(Player player) { + this.player = player; + this.history = new HistoryTracker<>(); + } + + public void showFactoryOverview(boolean addToHistory) { + if (inventory == null) { + inventory = new ComponableInventory(ChatColor.DARK_GREEN + "" + ChatColor.BOLD + "Factories", 6, player); + } else { + inventory.clear(); + topHalfComponent = null; + } + if (addToHistory) { + history.add(new FMCHistoryItem() { + + @Override + public void setStateTo() { + showFactoryOverview(false); + } + + @Override + String toText() { + return "Factory overview"; + } + }); + } + List clicks = new ArrayList<>(); + List eggList = new ArrayList<>(FactoryMod.getInstance().getManager().getAllFactoryEggs()); + Collections.sort(eggList, (o1,o2) -> o1.getName().compareTo(o2.getName())); + for (IFactoryEgg egg : eggList) { + if (!(egg instanceof FurnCraftChestEgg)) { + continue; + } + FurnCraftChestEgg fccEgg = (FurnCraftChestEgg) egg; + InputRecipe firstRec = (InputRecipe) fccEgg.getRecipes().get(0); + ItemStack is = new ItemStack(firstRec.getRecipeRepresentationMaterial()); + ItemUtils.setDisplayName(is, ChatColor.DARK_GREEN + fccEgg.getName()); + List lore = new ArrayList<>(); + lore.add(ChatColor.DARK_AQUA + "Fuel: " + ChatColor.GRAY + ItemUtils.getItemName(fccEgg.getFuel().getType())); + lore.add(""); + lore.add(ChatColor.GOLD + String.valueOf(fccEgg.getRecipes().size() + " recipes:")); + for (IRecipe rec : fccEgg.getRecipes()) { + if (rec instanceof Upgraderecipe) { + lore.add(ChatColor.GRAY + " - " + ChatColor.GREEN + rec.getName()); + } else { + lore.add(ChatColor.GRAY + " - " + ChatColor.AQUA + rec.getName()); + } + } + ItemUtils.addLore(is, lore); + clicks.add(new LClickable(is, p -> { + showForFactory(fccEgg); + })); + } + Scrollbar middleBar = new Scrollbar(clicks, 45, 5, ContentAligners.getCenteredInOrder(clicks.size(), 45, 9)); + inventory.addComponent(middleBar, SlotPredicates.rows(5)); + StaticDisplaySection bottomLine = new StaticDisplaySection(9); + if (history.hasPrevious()) { + bottomLine.set(getBackClick(), 4); + } + inventory.addComponent(bottomLine, SlotPredicates.rows(1)); + inventory.show(); + } + + public void showForFactory(FurnCraftChestEgg factory) { + showRecipeFor(factory, null, true); + } + + public void showForFactory(FurnCraftChestEgg factory, InputRecipe recipe) { + if (this.currentFactory == null) { + this.currentFactory = factory; + } + showRecipeFor(factory, recipe, true); + } + + private IClickable produceRecipeClickable(InputRecipe rec) { + ItemStack is = rec.getRecipeRepresentation(); + return new LClickable(is, p -> showRecipeFor(currentFactory, rec, true)); + } + + private IClickable getSetupClick(FurnCraftChestEgg factory) { + ItemStack is = new ItemStack(Material.CRAFTING_TABLE); + ItemUtils.setDisplayName(is, ChatColor.GOLD + "Show creation cost"); + if (factory.getSetupCost() != null) { + ItemUtils.addLore(is, ChatColor.GREEN + factory.getName() + " can be created directly"); + List lore = new ArrayList<>(); + lore.add(ChatColor.GOLD + "Required materials:"); + for (Entry entry : factory.getSetupCost().getEntrySet()) { + lore.add(ChatColor.GRAY + " - " + ChatColor.AQUA + entry.getValue() + " " + + ItemUtils.getItemName(entry.getKey())); + } + ItemUtils.addLore(is, lore); + } else { + FurnCraftChestEgg parent = getParent(factory); + if (parent != null) { + ItemUtils.addLore(is, ChatColor.GREEN + factory.getName() + " is an upgrade of " + parent.getName()); + Upgraderecipe upRec = getUpgradeRecipe(factory, parent); + if (upRec != null) { + List lore = new ArrayList<>(); + lore.add(ChatColor.GOLD + "Required materials for the upgrade:"); + for (Entry entry : upRec.getInput().getEntrySet()) { + lore.add(ChatColor.GRAY + " - " + ChatColor.AQUA + entry.getValue() + " " + + ItemUtils.getItemName(entry.getKey())); + } + ItemUtils.addLore(is, lore); + return new LClickable(is, p -> showRecipeFor(factory, upRec, true)); + } + } + } + return new LClickable(is, p -> showFactoryCreation(factory, true)); + } + + private DecorationStack getFuelClick(FurnCraftChestEgg factory, InputRecipe recipe) { + if (factory == null || recipe == null) { + ItemStack is = new ItemStack(factory.getFuel().clone()); + ItemUtils.setDisplayName(is, ChatColor.GOLD + "Fuel setup cost for this factory"); + ItemUtils.addLore(is, ChatColor.AQUA + "No fuel setup cost"); + return new DecorationStack(is); + } + ItemStack is = factory.getFuel().clone(); + ItemUtils.setDisplayName(is, ChatColor.GOLD + "Fuel cost for recipe"); + ItemUtils.addLore(is, ChatColor.AQUA + "- " + recipe.getTotalFuelConsumed() + " " + ItemUtils.getItemName(is.getType())); + return new DecorationStack(is); + } + + private IClickable getBackClick() { + if (!history.hasPrevious()) { + return null; + } + FMCHistoryItem previous = history.peekPrevious(); + LClickable click = new LClickable(Material.SPECTRAL_ARROW, ChatColor.GOLD + "Show previous page", p -> { + FMCHistoryItem actualPrevious = history.goBack(); + actualPrevious.setStateTo(); + }); + ItemUtils.addLore(click.getItemStack(), ChatColor.GREEN + previous.toText()); + return click; + } + + private Upgraderecipe getUpgradeRecipe(FurnCraftChestEgg child, FurnCraftChestEgg parent) { + if (child == null || parent == null) { + return null; + } + for (IRecipe rec : parent.getRecipes()) { + if (rec instanceof Upgraderecipe) { + Upgraderecipe uRec = (Upgraderecipe) rec; + if (uRec.getEgg() == child) { + return uRec; + } + } + } + return null; + } + + private void showFactoryCreation(FurnCraftChestEgg factory, boolean addToHistory) { + showRecipeFor(factory, null, addToHistory); + } + + private void showRecipeFor(FurnCraftChestEgg factory, InputRecipe recipe, boolean addToHistory) { + if (inventory == null) { + inventory = new ComponableInventory(ChatColor.DARK_GREEN + "Factories", 6, player); + } + if (addToHistory) { + if (recipe == null) { + history.add(new FMCHistoryItem() { + + @Override + public void setStateTo() { + showFactoryCreation(factory, false); + } + + @Override + String toText() { + return ChatColor.GREEN + "Setup for " + factory.getName(); + } + }); + } else { + history.add(new FMCHistoryItem() { + + @Override + public void setStateTo() { + showRecipeFor(factory, recipe, false); + } + + @Override + String toText() { + return ChatColor.GREEN + "Recipe " + recipe.getName(); + } + }); + } + } + InventoryComponent updatedTopHalf; + int rows; + if (recipe == null) { + updatedTopHalf = constructFactoryCreationComponent(factory); + rows = 6; + } else { + updatedTopHalf = constructDetailedRecipeComponent(recipe); + rows = 5; + } + this.currentFactory = factory; + if (this.topHalfComponent != null && this.topHalfComponent.getSize() == updatedTopHalf.getSize()) { + // recipe before already, so keep recipe selector at bottom in current state + inventory.removeComponent(this.topHalfComponent); + } else { + inventory.clear(); + if (recipe != null) { + Scrollbar recipeScroll = constructRecipeScrollbar(factory); + inventory.addComponent(recipeScroll, SlotPredicates.offsetRectangle(1, 9, 5, 0)); + } + } + this.topHalfComponent = updatedTopHalf; + inventory.addComponent(updatedTopHalf, SlotPredicates.rows(rows)); + inventory.update(); + inventory.updatePlayerView(); + } + + private InventoryComponent constructFactoryCreationComponent(FurnCraftChestEgg factory) { + ComponableSection section = new ComponableSection(54); + + ItemMap costs = null; + if (factory.getSetupCost() != null) { + costs = factory.getSetupCost(); + } else { + FurnCraftChestEgg parent = getParent(factory); + if (parent != null) { + InputRecipe upgradeRecipe = getUpgradeRecipe(factory, parent); + if (upgradeRecipe != null) { + costs = upgradeRecipe.getInput(); + } + } + } + List setupClicks = null; + if (costs != null) { + setupClicks = costs.getItemStackRepresentation().stream().map(DecorationStack::new) + .collect(Collectors.toList()); + } + int size = setupClicks == null ? 0 : setupClicks.size(); + Scrollbar inputSection = new Scrollbar(setupClicks, 24, 8, ContentAligners.getCenteredInOrder(size, 24, 4)); + // top right corner + inputSection.setBackwardsClickSlot(3); + section.addComponent(inputSection, SlotPredicates.rectangle(6, 4)); + + IClickable fuelClick = getFuelClick(factory, getUpgradeRecipe(factory, getParent(factory))); + IClickable setupClick = getSetupClick(factory); + IClickable backClick = getBackClick(); + IClickable mainMenuClick = getMainMenuClick(); + StaticDisplaySection middleLine = new StaticDisplaySection(null, fuelClick, setupClick, mainMenuClick, backClick); + section.addComponent(middleLine, SlotPredicates.offsetRectangle(6, 1, 0, 4)); + + List recipeClicks = factory.getRecipes().stream().map(i -> (InputRecipe) i) + .map(this::produceRecipeClickable).collect(Collectors.toList()); + Scrollbar outputSection = new Scrollbar(recipeClicks, 24, 8, + ContentAligners.getCenteredInOrder(recipeClicks.size(), 24, 4)); + outputSection.setBackwardsClickSlot(3); + section.addComponent(outputSection, SlotPredicates.offsetRectangle(6, 4, 0, 5)); + + return section; + } + + private IClickable getMainMenuClick() { + return new LClickable(Material.BOOK, ChatColor.GREEN + "Return to factory overview", p -> { + showFactoryOverview(true); + }); + } + + private InventoryComponent constructDetailedRecipeComponent(InputRecipe recipe) { + ComponableSection section = new ComponableSection(45); + + List inputClicks = recipe.getInputRepresentation(null, null).stream().map(DecorationStack::new) + .collect(Collectors.toList()); + Scrollbar inputSection = new Scrollbar(inputClicks, 20, 8, + ContentAligners.getCenteredInOrder(inputClicks.size(), 20, 4)); + // top right corner + inputSection.setBackwardsClickSlot(3); + section.addComponent(inputSection, SlotPredicates.rectangle(5, 4)); + IClickable fuelClick = getFuelClick(this.currentFactory, recipe); + IClickable setupClick = getSetupClick(this.currentFactory); + IClickable backClick = getBackClick(); + IClickable recSumSup = new DecorationStack(recipe.getRecipeRepresentation()); + IClickable mainMenuClick = getMainMenuClick(); + StaticDisplaySection middleLine = new StaticDisplaySection(setupClick, fuelClick, recSumSup, mainMenuClick, + backClick); + section.addComponent(middleLine, SlotPredicates.offsetRectangle(5, 1, 0, 4)); + + List outputClicks; + if (recipe instanceof Upgraderecipe) { + outputClicks = recipe.getOutputRepresentation(null, null).stream().map(i -> new LClickable(i, (p) -> { + showFactoryCreation(((Upgraderecipe) recipe).getEgg(), true); + })).collect(Collectors.toList()); + } else { + outputClicks = recipe.getOutputRepresentation(null, null).stream().map(DecorationStack::new) + .collect(Collectors.toList()); + } + Scrollbar outputSection = new Scrollbar(outputClicks, 20, 8, + ContentAligners.getCenteredInOrder(outputClicks.size(), 20, 4)); + outputSection.setBackwardsClickSlot(3); + section.addComponent(outputSection, SlotPredicates.offsetRectangle(5, 4, 0, 5)); + + return section; + } + + private Scrollbar constructRecipeScrollbar(FurnCraftChestEgg factory) { + List recipeClicks = factory.getRecipes().stream().map(i -> (InputRecipe) i) + .map(this::produceRecipeClickable).collect(Collectors.toList()); + return new Scrollbar(recipeClicks, 9); + } + + private FurnCraftChestEgg getParent(FurnCraftChestEgg factory) { + return upgradeMapping.get(factory); + } + + private abstract class FMCHistoryItem implements HistoryItem { + abstract String toText(); + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/FactoryModPermissionManager.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/FactoryModPermissionManager.java new file mode 100644 index 000000000..a77abb24f --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/FactoryModPermissionManager.java @@ -0,0 +1,30 @@ +package com.github.igotyou.FactoryMod.utility; + +import java.util.Arrays; +import java.util.List; +import vg.civcraft.mc.namelayer.GroupManager.PlayerType; +import vg.civcraft.mc.namelayer.permission.PermissionType; + +public class FactoryModPermissionManager { + + private PermissionType useFactory; + private PermissionType upgradeFactory; + + public FactoryModPermissionManager() { + List memberAndAbove = Arrays.asList(PlayerType.MEMBERS, PlayerType.MODS, PlayerType.ADMINS, + PlayerType.OWNER); + List modAndAbove = Arrays.asList(PlayerType.MODS, PlayerType.ADMINS, + PlayerType.OWNER); + useFactory = PermissionType.registerPermission("USE_FACTORY", memberAndAbove, "Allows a player to use factories reinforced under this group."); + upgradeFactory = PermissionType.registerPermission("UPGRADE_FACTORY", modAndAbove, "Allows a player to upgrade/make changes to a factory."); + } + + public PermissionType getUseFactory() { + return useFactory; + } + + public PermissionType getUpgradeFactory() { + return upgradeFactory; + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/FileHandler.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/FileHandler.java new file mode 100644 index 000000000..658678155 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/FileHandler.java @@ -0,0 +1,399 @@ +package com.github.igotyou.FactoryMod.utility; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.FactoryModManager; +import com.github.igotyou.FactoryMod.eggs.FurnCraftChestEgg; +import com.github.igotyou.FactoryMod.eggs.IFactoryEgg; +import com.github.igotyou.FactoryMod.eggs.PipeEgg; +import com.github.igotyou.FactoryMod.eggs.SorterEgg; +import com.github.igotyou.FactoryMod.factories.Factory; +import com.github.igotyou.FactoryMod.factories.FurnCraftChestFactory; +import com.github.igotyou.FactoryMod.factories.Pipe; +import com.github.igotyou.FactoryMod.factories.Sorter; +import com.github.igotyou.FactoryMod.recipes.IRecipe; +import com.github.igotyou.FactoryMod.repairManager.PercentageHealthRepairManager; +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.BlockFace; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.world.WorldUtils; + +public class FileHandler { + private FactoryMod plugin; + private FactoryModManager manager; + private File saveFile; + private File backup; + + private Map factoryRenames; + + private static int saveFileVersion = 2; + + public FileHandler(FactoryModManager manager, Map factoryRenames) { + plugin = FactoryMod.getInstance(); + this.factoryRenames = factoryRenames; + this.manager = manager; + saveFile = new File(plugin.getDataFolder().getAbsolutePath() + + File.separator + "factoryData.yml"); + backup = new File(plugin.getDataFolder().getAbsolutePath() + + File.separator + "factoryDataPreviousSave.yml"); + } + + public void save(Collection factories) { + if (saveFile.exists()) { + // old save file exists, so it is our new backup now + if (backup.exists()) { + backup.delete(); + } + saveFile.renameTo(backup); + } + try { + saveFile.createNewFile(); + YamlConfiguration config = YamlConfiguration + .loadConfiguration(saveFile); + config.set("version", saveFileVersion); + for (Factory f : factories) { + String current = serializeLocation(f.getMultiBlockStructure() + .getCenter()); + config.set(current + ".name", f.getName()); + ConfigurationSection blockSection = config.getConfigurationSection(current).createSection("blocks"); + configureLocation(blockSection, f.getMultiBlockStructure().getAllBlocks()); + if (f instanceof FurnCraftChestFactory) { + FurnCraftChestFactory fccf = (FurnCraftChestFactory) f; + config.set(current + ".type", "FCC"); + config.set(current + ".health", + ((PercentageHealthRepairManager) fccf + .getRepairManager()).getRawHealth()); + config.set(current + ".breakTime", + ((PercentageHealthRepairManager) fccf + .getRepairManager()).getBreakTime()); + config.set(current + ".runtime", fccf.getRunningTime()); + config.set(current + ".selectedRecipe", fccf + .getCurrentRecipe().getName()); + config.set(current + ".autoSelect", fccf.isAutoSelect()); + List recipeList = new LinkedList(); + for(IRecipe rec : fccf.getRecipes()) { + recipeList.add(rec.getIdentifier()); + } + config.set(current + ".recipes", recipeList); + if (fccf.getActivator() == null) { + config.set(current + ".activator", "null"); + } + else { + config.set(current + ".activator", fccf.getActivator().toString()); + } + for(IRecipe i : ((FurnCraftChestFactory) f).getRecipes()) { + config.set(current + ".runcounts." + i.getName(), fccf.getRunCount(i)); + config.set(current + ".recipeLevels." + i.getName(), fccf.getRecipeLevel(i)); + } + config.set(current + ".furnace-io", fccf.getFurnaceIOSelector().toConfigSection()); + config.set(current + ".table-io", fccf.getTableIOSelector().toConfigSection()); + config.set(current + ".ui-menu-mode", fccf.getUiMenuMode().name()); + } else if (f instanceof Pipe) { + Pipe p = (Pipe) f; + config.set(current + ".type", "PIPE"); + config.set(current + ".runtime", p.getRunTime()); + List mats = new LinkedList<>(); + List materials = p.getAllowedMaterials(); + if (materials != null) { + for (Material m : materials) { + mats.add(m.toString()); + } + } + config.set(current + ".materials", mats); + } else if (f instanceof Sorter) { + Sorter s = (Sorter) f; + config.set(current + ".runtime", s.getRunTime()); + config.set(current + ".type", "SORTER"); + for (BlockFace face : WorldUtils.ALL_SIDES) { + config.set(current + ".faces." + face.toString(), s + .getItemsForSide(face) + .getItemStackRepresentation().toArray()); + } + } + } + config.save(saveFile); + plugin.info("Successfully saved factory data"); + } catch (Exception e) { + // In case anything goes wrong while saving we always keep the + // latest valid backup + plugin.severe("Fatal error while trying to save factory data"); + e.printStackTrace(); + saveFile.delete(); + } + } + + private void configureLocation(ConfigurationSection config, List locations) { + int count = 0; + for(Location loc : locations) { + String identifier = "a" + count++ + serializeLocation(loc); + config.set(identifier + ".world", loc.getWorld().getName()); + config.set(identifier + ".x", loc.getBlockX()); + config.set(identifier + ".y", loc.getBlockY()); + config.set(identifier + ".z", loc.getBlockZ()); + } + } + + private String serializeLocation(Location loc) { + return loc.getWorld().getName() + "#" + loc.getBlockX() + "#" + + loc.getBlockY() + "#" + loc.getBlockZ(); + } + + public void load(Map eggs) { + if (saveFile.exists()) { + loadFromFile(saveFile, eggs); + } else { + plugin.warning("No default save file found"); + if (backup.exists()) { + plugin.info("Backup file found, loading backup"); + loadFromFile(backup, eggs); + } else { + plugin.warning("No backup save file found. If you are not starting this plugin for the first time you should be worried now"); + } + } + } + + private void loadFromFile(File f, Map eggs) { + int counter = 0; + YamlConfiguration config = YamlConfiguration + .loadConfiguration(saveFile); + int loadedVersion = config.getInt("version", 1); + for (String key : config.getKeys(false)) { + ConfigurationSection current = config.getConfigurationSection(key); + if (current == null) { + continue; + } + String type = current.getString("type"); + String name = current.getString("name"); + int runtime = current.getInt("runtime"); + List blocks = new LinkedList<>(); + Set blockKeys = current.getConfigurationSection("blocks").getKeys(false); + Collections.sort(new LinkedList (blockKeys)); + for (String blockKey : blockKeys) { + ConfigurationSection currSec = current.getConfigurationSection( + "blocks").getConfigurationSection(blockKey); + String worldName = currSec.getString("world"); + int x = currSec.getInt("x"); + int y = currSec.getInt("y"); + int z = currSec.getInt("z"); + World w = Bukkit.getWorld(worldName); + blocks.add(new Location(w, x, y, z)); + } + switch (type) { + case "FCC": + if (loadedVersion == 1) { + //need to sort the locations properly, because they werent previously + List sortedList = new LinkedList<>(); + int totalX = 0; + int totalY = 0; + int totalZ = 0; + for(Location loc : blocks) { + totalX += loc.getBlockX(); + totalY += loc.getBlockY(); + totalZ += loc.getBlockZ(); + } + Location center = new Location(blocks.get(0).getWorld(), totalX / 3, totalY / 3, totalZ / 3); + if (!blocks.contains(center)) { + plugin.warning("Failed to convert location for factory at " + blocks.get(0).toString() + "; calculated center: " + center.toString()); + } + else { + blocks.remove(center); + sortedList.add(center); + //we cant guarantee that this will work, it might very well fail for partially broken factories, but it's the best thing I got + if (blocks.get(0).getBlock().getType() == Material.CHEST + || blocks.get(0).getBlock().getType() == Material.TRAPPED_CHEST) { + sortedList.add(blocks.get(1)); + sortedList.add(blocks.get(0)); + } + else { + sortedList.add(blocks.get(0)); + sortedList.add(blocks.get(1)); + } + blocks = sortedList; + } + + + } + FurnCraftChestEgg egg = (FurnCraftChestEgg) eggs.get(name.toLowerCase()); + if (egg == null) { + String replaceName = factoryRenames.get(name); + if (replaceName != null) { + egg = (FurnCraftChestEgg) eggs.get(replaceName); + } + if (egg == null) { + plugin.warning("Save file contained factory named " + + name + + " , but no factory with this name was found in the config"); + continue; + } + else { + name = replaceName; + } + } + int health = current.getInt("health"); + long breakTime = current.getLong("breakTime", 0); + String selectedRecipe = current.getString("selectedRecipe"); + List recipes = current.getStringList("recipes"); + + // Now check for recipes marked as force include that should be on this list. + for (IRecipe irecipe : egg.getRecipes()) { + if (manager.isForceInclude(irecipe.getIdentifier())) { + if (!recipes.contains(irecipe.getIdentifier())) { // it's not there, add it. + plugin.info("Augmenting prior " + name + " factory at " + + blocks.get(0).toString() + " with force include recipe " + + irecipe.getName()); + recipes.add(irecipe.getIdentifier()); + } + } + } + + boolean autoSelect = current.getBoolean("autoSelect", false); + FurnCraftChestFactory fac = (FurnCraftChestFactory) egg.revive(blocks, health, selectedRecipe, + runtime, breakTime, recipes); + String activator = current.getString("activator", "null"); + UUID acti; + if (activator.equals("null")) { + acti = null; + } + else { + acti = UUID.fromString(activator); + } + fac.setActivator(acti); + ConfigurationSection runCounts = current.getConfigurationSection("runcounts"); + if(runCounts != null) { + for(String countKey : runCounts.getKeys(false)) { + int runs = runCounts.getInt(countKey); + for(IRecipe r : fac.getRecipes()) { + if (r.getName().equals(countKey)) { + fac.setRunCount(r, runs); + break; + } + } + } + } + ConfigurationSection recipeLevels = current.getConfigurationSection("recipeLevels"); + if(recipeLevels != null) { + for(String countKey : recipeLevels.getKeys(false)) { + int runs = recipeLevels.getInt(countKey); + for(IRecipe r : fac.getRecipes()) { + if (r.getName().equals(countKey)) { + fac.setRecipeLevel(r, runs); + break; + } + } + } + } + fac.setAutoSelect(autoSelect); + { + ConfigurationSection iosec = current.getConfigurationSection("furnace-io"); + if (iosec != null) { + IOSelector furnaceIoSelector = IOSelector.fromConfigSection(iosec); + fac.setFurnaceIOSelector(furnaceIoSelector); + } else { + // Nothing I guess, the furnace has no default state. + } + iosec = current.getConfigurationSection("table-io"); + if (iosec != null) { + IOSelector tableIoSelector = IOSelector.fromConfigSection(iosec); + fac.setTableIOSelector(tableIoSelector); + } else { + // Default table-side IO moved to FCCF.getTableIoSelector() lazy init + } + } + String menuModeRaw = current.getString("ui-menu-mode"); + FurnCraftChestFactory.UiMenuMode menuMode; + if (menuModeRaw == null) { + menuMode = FurnCraftChestFactory.UiMenuMode.SIMPLE; + } else { + try { + menuMode = FurnCraftChestFactory.UiMenuMode.valueOf(menuModeRaw); + } catch (IllegalArgumentException iae) { + menuMode = FurnCraftChestFactory.UiMenuMode.SIMPLE; + } + } + fac.setUiMenuMode(menuMode); + manager.addFactory(fac); + counter++; + break; + case "PIPE": + PipeEgg pipeEgg = (PipeEgg) eggs.get(name); + if (pipeEgg == null) { + String replaceName = factoryRenames.get(name); + if (replaceName != null) { + pipeEgg = (PipeEgg) eggs.get(replaceName); + } + if (pipeEgg == null) { + plugin.warning("Save file contained factory named " + + name + + " , but no factory with this name was found in the config"); + continue; + } + else { + name = replaceName; + } + } + List mats = new LinkedList<>(); + if (current.isSet("materials")) { + for (String mat : current.getStringList("materials")) { + mats.add(Material.valueOf(mat)); + } + } else { + mats = null; + } + if (mats.isEmpty()) { + mats = null; + } + Factory p = pipeEgg.revive(blocks, mats, runtime); + manager.addFactory(p); + counter++; + break; + case "SORTER": + Map assignments = new HashMap<>(); + SorterEgg sorterEgg = (SorterEgg) eggs.get(name); + if (sorterEgg == null) { + String replaceName = factoryRenames.get(name); + if (replaceName != null) { + sorterEgg = (SorterEgg) eggs.get(replaceName); + } + if (sorterEgg == null) { + plugin.warning("Save file contained factory named " + + name + + " , but no factory with this name was found in the config"); + continue; + } + else { + name = replaceName; + } + } + for (String face : current.getConfigurationSection("faces") + .getKeys(false)) { + + @SuppressWarnings("unchecked") + List stacks = (List) current.getConfigurationSection("faces").get(face); + + // it works, okay? + ItemMap map = new ItemMap(stacks); + assignments.put(BlockFace.valueOf(face), map); + } + Factory s = sorterEgg.revive(blocks, assignments, runtime); + manager.addFactory(s); + counter++; + break; + } + } + plugin.info("Loaded " + counter + " factory from save file"); + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/IIOFInventoryProvider.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/IIOFInventoryProvider.java new file mode 100644 index 000000000..362b7609b --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/IIOFInventoryProvider.java @@ -0,0 +1,22 @@ +package com.github.igotyou.FactoryMod.utility; + +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Nullable; + +/** + * Utility interface for classes that provide separate inventories for inputs, outputs, and fuel. + */ +public interface IIOFInventoryProvider { + + Inventory getInputInventory(); + Inventory getOutputInventory(); + @Nullable Inventory getFuelInventory(); + + int getInputCount(); + int getOutputCount(); + int getFuelCount(); + default int getTotalIOFCount() { + return getInputCount() + getOutputCount() + getFuelCount(); + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/IOConfigSection.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/IOConfigSection.java new file mode 100644 index 000000000..ee36bbf5c --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/IOConfigSection.java @@ -0,0 +1,208 @@ +package com.github.igotyou.FactoryMod.utility; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.FactoryModManager; +import com.github.igotyou.FactoryMod.FactoryModPlayerSettings; +import java.util.UUID; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitTask; +import vg.civcraft.mc.civmodcore.inventory.gui.Clickable; +import vg.civcraft.mc.civmodcore.inventory.gui.ClickableInventory; +import vg.civcraft.mc.civmodcore.inventory.gui.LClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.components.StaticDisplaySection; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +/** + * @author caucow + */ +public class IOConfigSection extends StaticDisplaySection { + + private final UUID viewerId; + private final IOSelector ioSelector; + private final Material centerDisplay; + private final BlockFace front; + private final Block centerBlock; + private final IIOFInventoryProvider iofProvider; + private FactoryModPlayerSettings.IoConfigDirectionMode ioDirectionMode; + + public IOConfigSection(Player viewer, IOSelector ioSelector, Material centerDisplay, Block centerBlock, + BlockFace front, IIOFInventoryProvider iofProvider) { + super(9); + this.viewerId = viewer.getUniqueId(); + this.ioSelector = ioSelector; + this.centerDisplay = centerDisplay; + this.centerBlock = centerBlock; + this.front = front; + this.iofProvider = iofProvider; + rebuild(); + } + + private Clickable getIoButton(BlockFace dir) { + Block relativeBlock = centerBlock.getRelative(dir); + Material type = relativeBlock.getType(); + Direction relativeDir = Direction.getDirection(front, dir); + return getIoClickable(type, relativeDir, dir.name()); + } + + private Clickable getIoButton(Direction dir) { + BlockFace relativeDir = dir.getBlockFacing(front); + Block relativeBlock = centerBlock.getRelative(relativeDir); + Material type = relativeBlock.getType(); + return getIoClickable(type, dir, dir.name()); + } + + private Clickable getIoClickable(Material adjacentType, Direction dir, String dirLabel) { + IOSelector.IOState dirState = ioSelector.getState(dir); + boolean chestMissing = adjacentType != Material.CHEST && adjacentType != Material.TRAPPED_CHEST; + ItemStack display; + if (chestMissing) { + display = new ItemStack(Material.BARRIER); + ItemUtils.addComponentLore(display, Component + .text("") + .style(Style.style(TextDecoration.BOLD)) + .color(TextColor.color(255, 0, 0))); + } else { + display = dirState.getUIVisual(); + } + if (ioDirectionMode != null) { + for (String descLine : ioDirectionMode.fullDescription) { + ItemUtils.addComponentLore(display, + Component.text(descLine).color(TextColor.color(255, 255, 192))); + } + } + ItemUtils.setComponentDisplayName(display, + Component.text("\u00a7r") + .append(Component.text(dirLabel).color(TextColor.color(192, 192, 192))) + .append(Component.text(": ")) + .append(Component.text(dirState.displayName).color(TextColor.color(dirState.color))) + .asComponent()); + ItemUtils.addComponentLore(display, + Component.text("L/M/R click to toggle I/F/O") + .style(Style.style(TextDecoration.BOLD)) + .color(TextColor.color(255, 255, 255))); + FactoryModManager fmMgr = FactoryMod.getInstance().getManager(); + return new Clickable(display) { + private ClickableInventory inventory; + private int slot; + private BukkitTask reBarrierTask; + + @Override + protected void clicked(Player player) { + if (!ioSelector.isInput(dir)) { + if (iofProvider.getInputCount() >= fmMgr.getMaxInputChests()) { + player.sendMessage(ChatColor.RED + "This factory is at the maximum number of inputs."); + return; + } + if (iofProvider.getTotalIOFCount() >= fmMgr.getMaxTotalIOFChests()) { + player.sendMessage(ChatColor.RED + "This factory is at the maximum number of total I/O/Fs."); + return; + } + } + ioSelector.toggleInput(dir); + updateItem(); + } + + @Override + protected void onRightClick(Player player) { + if (!ioSelector.isOutput(dir)) { + if (iofProvider.getOutputCount() >= fmMgr.getMaxOutputChests()) { + player.sendMessage(ChatColor.RED + "This factory is at the maximum number of outputs."); + return; + } + if (iofProvider.getTotalIOFCount() >= fmMgr.getMaxTotalIOFChests()) { + player.sendMessage(ChatColor.RED + "This factory is at the maximum number of total I/O/Fs."); + return; + } + } + ioSelector.toggleOutput(dir); + updateItem(); + } + + @Override + protected void onMiddleClick(Player player) { + if (!ioSelector.isFuel(dir)) { + if (iofProvider.getFuelCount() >= fmMgr.getMaxFuelChests()) { + player.sendMessage(ChatColor.RED + "This factory is at the maximum number of fuel inputs."); + return; + } + if (iofProvider.getTotalIOFCount() >= fmMgr.getMaxTotalIOFChests()) { + player.sendMessage(ChatColor.RED + "This factory is at the maximum number of total I/O/Fs."); + return; + } + } + ioSelector.toggleFuel(dir); + updateItem(); + } + + @Override + protected void onDoubleClick(Player p) { } // nop + + @Override + public void addedToInventory(ClickableInventory inv, int slot) { + this.inventory = inv; + this.slot = slot; + } + + private void updateItem() { + IOSelector.IOState newState = ioSelector.getState(dir); + ItemStack curStack = getItemStack(); + curStack.setType(newState.getUIVisual().getType()); + ItemUtils.setComponentDisplayName(curStack, + Component.text("\u00a7r") + .append(Component.text(dirLabel).color(TextColor.color(192, 192, 192))) + .append(Component.text(": ")) + .append(Component.text(newState.displayName).color(TextColor.color(newState.color))) + .asComponent()); + if (inventory != null && inventory.getSlot(slot) == this) { + inventory.setSlot(this, slot); + } + if (chestMissing) { + if (reBarrierTask != null) { + reBarrierTask.cancel(); + } + reBarrierTask = Bukkit.getScheduler().runTaskLater(FactoryMod.getInstance(), () -> { + getItemStack().setType(Material.BARRIER); + inventory.setSlot(this, slot); + reBarrierTask = null; + }, 20); + } + } + }; + } + + @Override + protected void rebuild() { + ioDirectionMode = FactoryMod.getInstance().getManager().getPlayerSettings().getIoDirectionMode(viewerId); + switch (ioDirectionMode) { + case VISUAL_RELATIVE: { + set(getIoButton(Direction.TOP), 1); + set(getIoButton(Direction.FRONT), 2); + set(getIoButton(Direction.LEFT), 3); + set(getIoButton(Direction.RIGHT), 5); + set(getIoButton(Direction.BOTTOM), 7); + set(getIoButton(Direction.BACK), 8); + break; + } + case CARDINAL: { + set(getIoButton(BlockFace.NORTH), 1); + set(getIoButton(BlockFace.UP), 2); + set(getIoButton(BlockFace.WEST), 3); + set(getIoButton(BlockFace.EAST), 5); + set(getIoButton(BlockFace.SOUTH), 7); + set(getIoButton(BlockFace.DOWN), 8); + break; + } + } + set(new LClickable(centerDisplay, p -> {}), 4); + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/IOSelector.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/IOSelector.java new file mode 100644 index 000000000..c8148b245 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/IOSelector.java @@ -0,0 +1,233 @@ +package com.github.igotyou.FactoryMod.utility; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author caucow + */ +public class IOSelector { + + private final EnumSet inputs; + private final EnumSet outputs; + private final EnumSet fuel; + + public IOSelector(Collection inMask, Collection outMask, Collection fuel) { + this(); + this.inputs.addAll(inMask); + this.outputs.addAll(outMask); + this.fuel.addAll(fuel); + } + + public IOSelector() { + this.inputs = EnumSet.noneOf(Direction.class); + this.outputs = EnumSet.noneOf(Direction.class); + this.fuel = EnumSet.noneOf(Direction.class); + } + + public IOState getState(Direction direction) { + return IOState.fromIO(inputs.contains(direction), outputs.contains(direction), fuel.contains(direction)); + } + + public void setState(Direction direction, IOState state) { + if (state.isIn()) { + inputs.add(direction); + } else { + inputs.remove(direction); + } + if (state.isOut()) { + outputs.add(direction); + } else { + outputs.remove(direction); + } + if (state.isFuel()) { + fuel.add(direction); + } else { + fuel.remove(direction); + } + } + + public int getInputCount() { + return inputs.size(); + } + + public int getOutputCount() { + return outputs.size(); + } + + public int getFuelCount() { + return fuel.size(); + } + + public int getTotalIOFCount() { + return getInputCount() + getOutputCount() + getFuelCount(); + } + + public boolean toggleInput(Direction direction) { + boolean added = inputs.add(direction); + if (!added) { + inputs.remove(direction); + } + return added; + } + + public boolean isInput(Direction direction) { + return inputs.contains(direction); + } + + public boolean toggleOutput(Direction direction) { + boolean added = outputs.add(direction); + if (!added) { + outputs.remove(direction); + } + return added; + } + + public boolean isOutput(Direction direction) { + return outputs.contains(direction); + } + + public boolean toggleFuel(Direction direction) { + boolean added = fuel.add(direction); + if (!added) { + fuel.remove(direction); + } + return added; + } + + public boolean isFuel(Direction direction) { + return fuel.contains(direction); + } + + public boolean hasInputs() { + return !inputs.isEmpty(); + } + + public List getInputs(BlockFace front) { + Direction[] values = Direction.values(); + List bfList = new ArrayList<>(values.length); + for (Direction dir : values) { + if (inputs.contains(dir)) { + bfList.add(dir.getBlockFacing(front)); + } + } + return bfList; + } + + public boolean hasOutputs() { + return !outputs.isEmpty(); + } + + public List getOutputs(BlockFace front) { + Direction[] values = Direction.values(); + List bfList = new ArrayList<>(values.length); + for (Direction dir : values) { + if (outputs.contains(dir)) { + bfList.add(dir.getBlockFacing(front)); + } + } + return bfList; + } + + public boolean hasFuel() { + return !fuel.isEmpty(); + } + + public List getFuel(BlockFace front) { + Direction[] values = Direction.values(); + List bfList = new ArrayList<>(values.length); + for (Direction dir : values) { + if (fuel.contains(dir)) { + bfList.add(dir.getBlockFacing(front)); + } + } + return bfList; + } + + public IOState cycleDirection(Direction direction, boolean backwards) { + IOState cur = getState(direction); + if (backwards) { + cur = cur.last(); + } else { + cur = cur.next(); + } + setState(direction, cur); + return cur; + } + + public ConfigurationSection toConfigSection() { + ConfigurationSection sec = new YamlConfiguration(); + sec.set("in", inputs.stream().map(Enum::name).collect(Collectors.toList())); + sec.set("out", outputs.stream().map(Enum::name).collect(Collectors.toList())); + sec.set("fuel", fuel.stream().map(Enum::name).collect(Collectors.toList())); + return sec; + } + + public static IOSelector fromConfigSection(ConfigurationSection conf) { + List inList = conf.getStringList("in").stream().map(Direction::valueOf).collect(Collectors.toList()); + List outList = conf.getStringList("out").stream().map(Direction::valueOf).collect(Collectors.toList()); + List fuelList = conf.getStringList("fuel").stream().map(Direction::valueOf).collect(Collectors.toList()); + return new IOSelector(inList, outList, fuelList); + } + + public enum IOState { + IGNORED("Ignored", new ItemStack(Material.GRAY_WOOL), 0x808080), + INPUT("Input", new ItemStack(Material.BLUE_WOOL), 0x4040FF), + OUTPUT("Output", new ItemStack(Material.RED_WOOL), 0xFF4040), + BOTH("Input+Output", new ItemStack(Material.PURPLE_WOOL), 0xFF40FF), + FUEL("Fuel", new ItemStack(Material.LIGHT_GRAY_WOOL), 0xA0A0A0), + INPUT_FUEL("Input+Fuel", new ItemStack(Material.CYAN_WOOL), 0x8060FF), + OUTPUT_FUEL("Output+Fuel", new ItemStack(Material.PINK_WOOL), 0xFF6080), + BOTH_FUEL("Input+Output+Fuel", new ItemStack(Material.MAGENTA_WOOL), 0xFF60FF); + + public final String displayName; + private final ItemStack uiVisual; + public final int color; + + IOState(String displayName, ItemStack uiVisual, int color) { + this.displayName = displayName; + this.uiVisual = uiVisual; + this.color = color; + } + + public ItemStack getUIVisual() { + return uiVisual.clone(); + } + + public boolean isIn() { + return (ordinal() & 1) != 0; + } + + public boolean isOut() { + return (ordinal() & 2) != 0; + } + + public boolean isFuel() { + return (ordinal() & 4) != 0; + } + + public IOState next() { + IOState[] values = values(); + return values[(ordinal() + values.length + 1) % values.length]; + } + + public IOState last() { + IOState[] values = values(); + return values[(ordinal() + values.length + values.length - 1) % values.length]; + } + + public static IOState fromIO(boolean in, boolean out, boolean fuel) { + return IOState.values()[(in ? 1 : 0) | (out ? 2 : 0) | (fuel ? 4 : 0)]; + } + } + +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/ItemUseGUI.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/ItemUseGUI.java new file mode 100644 index 000000000..05c6f2902 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/ItemUseGUI.java @@ -0,0 +1,181 @@ +package com.github.igotyou.FactoryMod.utility; + +import com.github.igotyou.FactoryMod.FactoryMod; +import com.github.igotyou.FactoryMod.eggs.FurnCraftChestEgg; +import com.github.igotyou.FactoryMod.eggs.IFactoryEgg; +import com.github.igotyou.FactoryMod.recipes.CompactingRecipe; +import com.github.igotyou.FactoryMod.recipes.IRecipe; +import com.github.igotyou.FactoryMod.recipes.InputRecipe; +import com.github.igotyou.FactoryMod.recipes.ProductionRecipe; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.LClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.components.ComponableInventory; +import vg.civcraft.mc.civmodcore.inventory.gui.components.ContentAligners; +import vg.civcraft.mc.civmodcore.inventory.gui.components.Scrollbar; +import vg.civcraft.mc.civmodcore.inventory.gui.components.SlotPredicates; +import vg.civcraft.mc.civmodcore.inventory.gui.components.StaticDisplaySection; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +public class ItemUseGUI { + + private Player player; + private ComponableInventory inventory; + + public ItemUseGUI(Player player) { + this.player = player; + } + + /** + * Shows an interactive menu containing all factory recipes which take or produce a specified item + * @param item + */ + public void showItemOverview(ItemStack item) { + if (inventory == null) { + inventory = new ComponableInventory(ChatColor.DARK_GRAY + + "As Input As Output", 6, player); + } + FactoryModGUI gui = new FactoryModGUI(player); + List itemAsInput = new ArrayList<>(); + List itemAsOutput = new ArrayList<>(); + List eggList = new ArrayList<>(FactoryMod.getInstance().getManager().getAllFactoryEggs()); + for (IFactoryEgg egg : eggList) { + if (!(egg instanceof FurnCraftChestEgg)) { + continue; + } + FurnCraftChestEgg fccEgg = (FurnCraftChestEgg) egg; + + ItemStack getItemAsSetup = getItemAsSetup(fccEgg, item); + if (getItemAsSetup != null) { + itemAsInput.add(new LClickable(getItemAsSetup, p -> { + gui.showForFactory(fccEgg); + })); + } + + for (IRecipe recipe : fccEgg.getRecipes()) { + if (!(recipe instanceof InputRecipe)) { + continue; + } + InputRecipe inputRecipe = (InputRecipe) recipe; + + ItemStack getItemAsInput = getItemAsInput(fccEgg, inputRecipe, item); + if (getItemAsInput != null) { + itemAsInput.add(new LClickable(getItemAsInput, p -> { + gui.showForFactory(fccEgg, inputRecipe); + })); + } + ItemStack getItemAsOutput = getItemAsOutput(fccEgg, inputRecipe, item); + if (getItemAsOutput != null) { + itemAsOutput.add(new LClickable(getItemAsOutput, p -> { + gui.showForFactory(fccEgg, inputRecipe); + })); + } + } + } + if (itemAsInput.isEmpty()) { + ItemStack noItems = new ItemStack(Material.BARRIER); + ItemUtils.setDisplayName(noItems, String.format("%sNo recipes take input %s%s", ChatColor.RED, ChatColor.BOLD, + ItemUtils.getItemName(item))); + itemAsInput.add(new LClickable(noItems, p -> { })); + } + if (itemAsOutput.isEmpty()) { + ItemStack noItems = new ItemStack(Material.BARRIER); + ItemUtils.setDisplayName(noItems, String.format("%sNo recipes output %s%s", ChatColor.RED, ChatColor.BOLD, + ItemUtils.getItemName(item))); + itemAsOutput.add(new LClickable(noItems, p -> { })); + } + Scrollbar itemAsInputBar = new Scrollbar(itemAsInput, 24, 8, ContentAligners + .getCenteredInOrder(itemAsInput.size(), 24, 4)); + itemAsInputBar.setBackwardsClickSlot(3); + inventory.addComponent(itemAsInputBar, SlotPredicates.rectangle(6, 4)); + + IClickable dividerClick = getDividerClick(); + StaticDisplaySection + middleLine = new StaticDisplaySection(dividerClick, dividerClick, dividerClick, dividerClick, dividerClick , dividerClick); + inventory.addComponent(middleLine, SlotPredicates.offsetRectangle(6, 1, 0, 4)); + + Scrollbar itemAsOutputBar = new Scrollbar(itemAsOutput, 24, 5, ContentAligners.getCenteredInOrder(itemAsOutput.size(), 24, 4)); + inventory.addComponent(itemAsOutputBar, SlotPredicates.offsetRectangle(6, 4, 0, 5)); + inventory.show(); + } + + private IClickable getDividerClick() { + ItemStack is = new ItemStack(Material.WHITE_STAINED_GLASS_PANE); + ItemUtils.setDisplayName(is, "Divider"); + return new LClickable(is, p -> { }); + } + + private ItemStack getItemAsSetup(FurnCraftChestEgg fccEgg, ItemStack item) { + if (fccEgg.getSetupCost() != null && fccEgg.getSetupCost().getAmount(item) != 0) { + return getItemSetupStack(fccEgg, item); + } + return null; + } + + private ItemStack getItemAsInput(FurnCraftChestEgg fccEgg, InputRecipe recipe, ItemStack item) { + if (recipe.getInput().getAmount(item) != 0) { + return getItemRecipeStack(fccEgg, recipe, item); + } + return null; + } + + private ItemStack getItemAsOutput(FurnCraftChestEgg fccEgg, InputRecipe recipe, ItemStack item) { + if (recipe instanceof ProductionRecipe) { + ProductionRecipe output = (ProductionRecipe) recipe; + if (output.getOutput().getAmount(item) != 0) { + return getItemRecipeStack(fccEgg, recipe, item); + } + } + if (recipe instanceof CompactingRecipe) { + CompactingRecipe output = (CompactingRecipe) recipe; + if (String.join("", ItemUtils.getLore(item)).equals(output.getCompactedLore())) { + return getItemRecipeStack(fccEgg, recipe, item); + } + } + return null; + } + + private ItemStack getItemRecipeStack(FurnCraftChestEgg fccEgg, InputRecipe recipe, ItemStack item) { + ItemStack is = new ItemStack(recipe.getRecipeRepresentationMaterial()); + ItemUtils.setDisplayName(is, ChatColor.DARK_GREEN + fccEgg.getName()); + List lore = new ArrayList<>(); + lore.add(ChatColor.DARK_AQUA + recipe.getName()); + lore.add(ChatColor.GOLD + "input:"); + for (String input : recipe.getTextualInputRepresentation(null, null)) { + lore.add(formatIngredient(input, item)); + } + lore.add(ChatColor.GOLD + "output:"); + for (String output : recipe.getTextualOutputRepresentation(null, null)) { + lore.add(formatIngredient(output, item)); + } + ItemUtils.addLore(is, lore); + return is; + } + + private ItemStack getItemSetupStack(FurnCraftChestEgg fccEgg, ItemStack item) { + ItemStack is = new ItemStack(Material.CRAFTING_TABLE); + ItemUtils.setDisplayName(is, ChatColor.DARK_GREEN + fccEgg.getName()); + List lore = new ArrayList<>(); + lore.add(ChatColor.GOLD + "Setup cost:"); + for (Map.Entry entry : fccEgg.getSetupCost().getEntrySet()) { + String recipeRepresentation = entry.getValue() + " " + ItemUtils.getItemName(entry.getKey()); + lore.add(formatIngredient(recipeRepresentation, item)); + } + ItemUtils.addLore(is, lore); + return is; + } + + private String formatIngredient(String recipeRepresentation, ItemStack is) { + if (recipeRepresentation.matches("\\d+ " + ItemUtils.getItemName(is))) { + return String.format("%s - %s%s%s", ChatColor.GRAY, ChatColor.AQUA, ChatColor.BOLD, recipeRepresentation); + } else { + return String.format("%s - %s%s", ChatColor.GRAY, ChatColor.AQUA, recipeRepresentation); + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/LoggingUtils.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/LoggingUtils.java new file mode 100644 index 000000000..a08e0be3d --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/LoggingUtils.java @@ -0,0 +1,45 @@ +package com.github.igotyou.FactoryMod.utility; + +import com.github.igotyou.FactoryMod.FactoryMod; +import java.util.logging.Level; +import org.bukkit.block.Block; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; + +public class LoggingUtils { + + private LoggingUtils() { + } + + public static void log(String msg) { + FactoryMod.getInstance().getLogger().log(Level.INFO, msg); + } + + public static void debug(String msg) { + FactoryMod.getInstance().debug(msg); + } + + private static String serializeInventory(Inventory i) { + return new ItemMap(i).toString(); + } + + public static void logInventory(Block b) { + if (FactoryMod.getInstance().getManager().logInventories() + && b.getState() instanceof InventoryHolder) { + log("Contents of " + + b.getType().toString() + + " at " + + b.getLocation().toString() + + " contains: " + + serializeInventory(((InventoryHolder) b.getState()) + .getInventory())); + } + } + + public static void logInventory(Inventory i, String msg) { + if (FactoryMod.getInstance().getManager().logInventories()) { + log(msg + serializeInventory(i)); + } + } +} diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/MultiInventoryWrapper.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/MultiInventoryWrapper.java new file mode 100644 index 000000000..3c6262bf7 --- /dev/null +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/MultiInventoryWrapper.java @@ -0,0 +1,387 @@ +package com.github.igotyou.FactoryMod.utility; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.DoubleChest; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +/** + * @author caucow + */ +public class MultiInventoryWrapper implements Inventory { + + private final Inventory[] wrapped; + private final int size; + private final int[] slotOffsets; + + public MultiInventoryWrapper(Inventory... wrapped) { + this(new ArrayList<>(Arrays.asList(wrapped))); + } + + public MultiInventoryWrapper(List wrapped) { + Inventory[] wrappedArr = uniquify(wrapped); + this.wrapped = wrappedArr; + int lsize = 0; + int[] lslotOffsets = new int[wrappedArr.length]; + for (int i = 0; i < wrappedArr.length; i++) { + Inventory inv = wrappedArr[i]; + lslotOffsets[i] = lsize; + lsize += inv.getSize(); + if (inv.getMaxStackSize() != 64) { + throw new IllegalArgumentException("Inventory max stack size must be 64"); + } + } + this.size = lsize; + this.slotOffsets = lslotOffsets; + } + + private int getInventoryIndex(int combinedSlot) { + for (int i = 0; i < wrapped.length - 1; i++) { + if (combinedSlot < slotOffsets[i + 1]) { + return i; + } + } + if (combinedSlot < size) { + return wrapped.length - 1; + } + throw new IndexOutOfBoundsException("Slot index " + combinedSlot + + " is outside the bounds of the multi-inventory"); + } + + @Override + public int close() { + List viewers = getViewers(); + int num = viewers.size(); + viewers.forEach(HumanEntity::closeInventory); + return num; + } + + @Override + public int getSize() { + return size; + } + + @Override + public int getMaxStackSize() { + return 64; + } + + @Override + public void setMaxStackSize(int i) { + throw new UnsupportedOperationException(); + } + + @Override + public @Nullable ItemStack getItem(int i) { + int invi = getInventoryIndex(i); + return wrapped[invi].getItem(i - slotOffsets[invi]); + } + + @Override + public void setItem(int i, @Nullable ItemStack itemStack) { + int invi = getInventoryIndex(i); + wrapped[invi].setItem(i - slotOffsets[invi], itemStack); + } + + @Override + public @NotNull HashMap addItem(@NotNull ItemStack... itemStacks) throws IllegalArgumentException { + HashMap result = null; + for (int invi = 0; invi < wrapped.length; invi++) { + result = wrapped[invi].addItem(itemStacks); + itemStacks = new ItemStack[result.size()]; + int itemi = 0; + Iterator> eit = result.entrySet().iterator(); + for (; eit.hasNext() && itemi < itemStacks.length; itemi++) { + Map.Entry entry = eit.next(); + itemStacks[itemi] = entry.getValue(); + } + } + return result == null ? new HashMap<>() : result; + } + + @Override + public @NotNull HashMap removeItem(@NotNull ItemStack... itemStacks) throws IllegalArgumentException { + HashMap result = null; + for (int invi = 0; invi < wrapped.length; invi++) { + result = wrapped[invi].removeItem(itemStacks); + if (result.isEmpty()) { + break; + } + itemStacks = new ItemStack[result.size()]; + int itemi = 0; + Iterator> eit = result.entrySet().iterator(); + for (; eit.hasNext() && itemi < itemStacks.length; itemi++) { + Map.Entry entry = eit.next(); + itemStacks[itemi] = entry.getValue(); + } + } + return result == null ? new HashMap<>() : result; + } + + @Override + public @NotNull HashMap removeItemAnySlot(@NotNull ItemStack... itemStacks) throws IllegalArgumentException { + HashMap result = null; + for (int i = 0; i < wrapped.length; i++) { + result = wrapped[i].removeItemAnySlot(itemStacks); + itemStacks = new ItemStack[result.size()]; + int index = 0; + Iterator> eit = result.entrySet().iterator(); + for (; eit.hasNext() && index < itemStacks.length; index++) { + Map.Entry entry = eit.next(); + itemStacks[i] = entry.getValue(); + } + } + return result == null ? new HashMap<>() : result; + } + + @Override + public @org.checkerframework.checker.nullness.qual.Nullable ItemStack @NonNull [] getContents() { + ItemStack[] combinedContents = new ItemStack[size]; + for (int inv = 0, slot = 0; inv < wrapped.length; inv++) { + ItemStack[] sub = wrapped[inv].getContents(); + System.arraycopy(sub, 0, combinedContents, slot, sub.length); + slot += sub.length; + } + return combinedContents; + } + + @Override + public void setContents(@NotNull ItemStack[] combinedContents) throws IllegalArgumentException { + for (int inv = 0, slot = 0; inv < wrapped.length && slotOffsets[inv] < combinedContents.length; inv++) { + ItemStack[] sub = new ItemStack[wrapped[inv].getSize()]; + System.arraycopy(combinedContents, slotOffsets[inv], sub, 0, Math.min(sub.length, slot - slotOffsets[inv])); + wrapped[inv].setContents(sub); + slot += sub.length; + } + } + + @Override + public @NotNull ItemStack[] getStorageContents() { + return getContents(); + } + + @Override + public void setStorageContents(@NotNull ItemStack[] combinedContents) throws IllegalArgumentException { + setContents(combinedContents); + } + + @Override + public boolean contains(@NotNull Material material) throws IllegalArgumentException { + for (Inventory inv : wrapped) { + if (inv.contains(material)) { + return true; + } + } + return false; + } + + @Override + public boolean contains(@Nullable ItemStack itemStack) { + for (Inventory inv : wrapped) { + if (inv.contains(itemStack)) { + return true; + } + } + return false; + } + + @Override + public boolean contains(@NotNull Material material, int i) throws IllegalArgumentException { + int invi = getInventoryIndex(i); + return wrapped[invi].contains(material, i - slotOffsets[invi]); + } + + @Override + public boolean contains(@Nullable ItemStack itemStack, int i) { + int invi = getInventoryIndex(i); + return wrapped[invi].contains(itemStack, i - slotOffsets[invi]); + } + + @Override + public boolean containsAtLeast(@Nullable ItemStack itemStack, int i) { + int invi = getInventoryIndex(i); + return wrapped[invi].containsAtLeast(itemStack, i - slotOffsets[invi]); + } + + @Override + public @NotNull HashMap all(@NotNull Material material) throws IllegalArgumentException { + HashMap result = new HashMap<>(); + for (int i = 0; i < wrapped.length; i++) { + HashMap partial = wrapped[i].all(material); + for (Map.Entry entry : partial.entrySet()) { + result.put(entry.getKey() + slotOffsets[i], entry.getValue()); + } + } + return result; + } + + @Override + public @NotNull HashMap all(@Nullable ItemStack itemStack) { + HashMap result = new HashMap<>(); + for (int i = 0; i < wrapped.length; i++) { + HashMap partial = wrapped[i].all(itemStack); + for (Map.Entry entry : partial.entrySet()) { + result.put(entry.getKey() + slotOffsets[i], entry.getValue()); + } + } + return result; + } + + @Override + public int first(@NotNull Material material) throws IllegalArgumentException { + for (int i = 0; i < wrapped.length; i++) { + int result = wrapped[i].first(material); + if (result != -1) { + return result + slotOffsets[i]; + } + } + return -1; + } + + @Override + public int first(@NotNull ItemStack itemStack) { + for (int i = 0; i < wrapped.length; i++) { + int result = wrapped[i].first(itemStack); + if (result != -1) { + return result + slotOffsets[i]; + } + } + return -1; + } + + @Override + public int firstEmpty() { + for (int i = 0; i < wrapped.length; i++) { + int result = wrapped[i].firstEmpty(); + if (result != -1) { + return result + slotOffsets[i]; + } + } + return -1; + } + + @Override + public boolean isEmpty() { + for (Inventory inv : wrapped) { + if (!inv.isEmpty()) { + return false; + } + } + return true; + } + + @Override + public void remove(@NotNull Material material) throws IllegalArgumentException { + for (Inventory inv : wrapped) { + inv.remove(material); + } + } + + @Override + public void remove(@NotNull ItemStack itemStack) { + for (Inventory inv : wrapped) { + inv.remove(itemStack); + } + } + + @Override + public void clear(int i) { + int invi = getInventoryIndex(i); + wrapped[invi].clear(i - slotOffsets[invi]); + } + + @Override + public void clear() { + for (Inventory inv : wrapped) { + inv.clear(); + } + } + + @Override + public @NotNull List getViewers() { + ArrayList list = new ArrayList<>(); + for (Inventory inv : wrapped) { + list.addAll(inv.getViewers()); + } + return list; + } + + @Override + public @NotNull InventoryType getType() { + return InventoryType.CHEST; + } + + @Override + public @Nullable InventoryHolder getHolder() { + return null; + } + + @Override + public @Nullable InventoryHolder getHolder(boolean b) { + return null; + } + + @Override + public @NotNull ListIterator iterator() { + throw new UnsupportedOperationException("More effort to implement than can be bothered atm. Feel free to CIY"); + } + + @Override + public @NotNull ListIterator iterator(int i) { + throw new UnsupportedOperationException("More effort to implement than can be bothered atm. Feel free to CIY"); + } + + @Override + public @Nullable Location getLocation() { + return null; + } + + private static Inventory[] uniquify(List wrapped) { + ListIterator inverator = wrapped.listIterator(); + while (inverator.hasNext()) { + Inventory next = inverator.next(); + if (next instanceof MultiInventoryWrapper) { + inverator.remove(); + for (Inventory inv : ((MultiInventoryWrapper) next).wrapped) { + inverator.add(inv); + inverator.previous(); + } + } + } + List invHolders = new ArrayList<>(wrapped.size()); + List uniqueInvs = new ArrayList<>(wrapped.size()); + for (Inventory inv : wrapped) { + + InventoryHolder holder = inv.getHolder(); + if (holder instanceof DoubleChest) { + DoubleChest dch = (DoubleChest) holder; + InventoryHolder hleft = dch.getLeftSide(); + InventoryHolder hright = dch.getRightSide(); + if (invHolders.contains(dch) || invHolders.contains(hleft) || invHolders.contains(hright)) { + continue; + } + invHolders.add(hleft); + invHolders.add(hright); + } + invHolders.add(holder); + uniqueInvs.add(inv); + } + Inventory[] wrappedArr = uniqueInvs.toArray(new Inventory[uniqueInvs.size()]); + return wrappedArr; + } +} diff --git a/plugins/factorymod-paper/src/main/resources/config.yml b/plugins/factorymod-paper/src/main/resources/config.yml new file mode 100644 index 000000000..8162c0e99 --- /dev/null +++ b/plugins/factorymod-paper/src/main/resources/config.yml @@ -0,0 +1,768 @@ +#FactoryMod configuration file + +#FactoryMod is a Spigot plugin, which allows players to setup predefined factories for an item cost. There are various +#factories with different purposes and pretty much everything about them is configurable. Their configuration is +#handled within this file. Take great care to ensure that your configurations follow exactly the documentation provided +#here, otherwise unwanted effects/crashes and run time exceptions may occur. + +#---------------------------------------------------------------------------------------------------------------------- + +#Specifying items: + +#Very often in this config you will have to specify items for a configuration. All item specifications are lists of +#configurations, which contain information about the actual item, like this: + +#inputmaterials: +# inputmat1: +# material: STONE +# inputmat345FF: +# material: DIRT + +#Ensure that the identifer (here inputmat1 or inputmat345FF) all have the same intendation and never use duplicate +#identifers on the same level. Duplicate identifers which belong to a different configuration section are a bad habit, +#but should still work, while duplicate identifers on the same level will definitely not lead to the result you desire. +#This applies for every configuration option and not only for items. When you are required to specify a list of items +#you can also just put the option for it there, but not actually any identifers below it to list items and it will +#result in no cost/output. + +#An example item config utilizing all possiblities could look like this: + +#inputmat1: +# material: STONE +# durability: 3 +# amount: 456 +# name: SuperSpecialStone +# lore: +# - First lore line +# - Even more lore +# enchants: +# enchantidentifier1: +# enchant: SILK_TOUCH +# level: 1 +# enchantgharbbl: +# enchant: DAMAGE_ALL +# level: 5 + + +#material requires you to specify the type of the item. Use Spigot's official item identifers here, a full list of those +#can be found here: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html + +#** AS OF 1.18 USE MOJANG NAMES FOR ENCHANTMENTS, E.G DIG_SPEED is now EFFICIENCY + +#durability allows you to specify the durability of the item. Note that this is not only limited to the durability of tools +#or armour, but instead the durability is also used to create different variations of the same material. For example orange +#wool is actually just white wool with a durability of 1. If this option is not specified it will default to 0, which is also +#minecraft's default durability for items. If you don't want to limit an item to a specific durability, but for example want to +#require any type of wool as an input, use -1 as durability. Make sure to only use this for inputs though, if you let a factory +#output an itemstack for players with a durability of -1, the item will be glitched. For a full list of all items and their +#durabilities use this site: http://minecraft-ids.grahamedgecombe.com/ + +#amount allows you to specify the amount of the item, this isn't limited to 64 or whatever the maximum stack size of that +#item is, it can be any number (assuming it fits into an int). If not specfied this option will default to 1 + +#name allows you to define a custom name for this item, if this option is not set the item will have its vanilla name + +#lore allows you to list lore which is added to the item. There is no limit to the lines of lore here, but after too many the +#minecraft client will stop display them properly. Defining lore is completly optional. + +#Finally enchants allows you to list all the enchants wanted on this item, each enchant requires it's own config identifer. + +#The two options requires per enchant are relatively straight forward, first of all you need to specify the enchantment type +#with it's spigot identifer. https://hub.spigotmc.org/javadocs/spigot/org/bukkit/enchantments/Enchantment.html provides a +#full list of those. Second you will need to specify the level you want the desired enchantment to be, this may exceed the +#possibilites of vanilla without causing problems. + + +#You can also create items with specific meta data, currently supported are anything storing potion data, dyed leather armour +#and custom nbt tags + +#For potion data (so for potions, splash potions, linering potions and tipped arrows) you can specify an additional section +#like this: + +#examplePot: +# material: POTION +# potion_effects: +# type: LUCK +# upgraded: true +# extended: true +# custom_effects: +# exampleEffect1: +# type: SPEED +# duration: 1m +# amplifier: 1 + +#Each potion always has one default effect and as many additional custom effects as desired + +#type specifies the look of the potion and the base type of effect that will be applied by the potion. Not all possible status +#effects can be used here, but only the ones that have PotionData, as listed here: +#https://hub.spigotmc.org/javadocs/spigot/org/bukkit/potion/PotionType.html +#If not specified the type will default to AWKWARD + +#upgraded specifies whether the default potion effect on the item is upgraded or not, per default this is false + +#extended specifies whether the default potion effect on this item is extended or not, per default this is false + +#custom_effects allows you to list other potion effects that are tied to this item, but don't affect it's item model, which is +#determined by the primary potion effect + +#To make dyed leather armour, two different formats are supported. The first one looks like this: + +#exampleItem: +# material: LEATHER_HELMET +# color: +# red: 255 +# blue: 0 +# green: 255 + +#The additional color section here has an option for each part of an RGB color, which is then applied on the item. All values +#must be within 0 and 255 + +#The other way to specify color of a leather item is directly through a hexadecimal number representing the RGB color like this: + +#exampleItem2: +# material: LEATHER_BOOTS +# color: FF00FF + +#Instead of using color as config identifier, you can as well just use it as option to directly specify the color in hex + +#---------------------------------------------------------------------------------------------------------------------- +#General + +#This section allows you to define global default values and some general stuff. + +#Additionally you can specify default options here, which are applied to all factories which use this option, unless +#you chose to overwrite the default for a specific factory, in that case the option specified in the factories config +#will apply to it. + +#The first option here is default_update_time. The update time describes how often a factory is ticked, ergo how often +#it checks whether it still has enough fuel, enough other resources to run and so on. This is basically the smallest +#time unit within your factory can possibly react and all other time values your factory works with should be multiples +#of this value. If they are not a multiple of this value, their de-facto value will be the next higher multiple of the +#update time, simply because whatever effect they have can only be applied if the factory is actually ticked. Note that +#this value highly influences the performance of this plugin, if FactoryMod is consuming more server power than you want +#it to, the first step should be to make this value higher. Recommended value is 1 second + +default_update_time: 1s + + +#With the option default_fuel you can specify the default fuel for all factories which are consuming fuel in a furnace +#while running. This doesn't have to be a vanilla furnace fuel, but can be any item instead. A factory may chose to over +#write this option + +default_fuel: + charcoal: + material: COAL + durability: 1 + + +#default_fuel_consumption_intervall specifies a default for how often factories consume fuel. Basically every time a +#factory which consumes fuel has run for the time specified here, one fuel unit will be consumed. This value should +#always be a multiple of the updatetime and a factory may chose to specify it's own value and not use the default value + +default_fuel_consumption_intervall: 2s + +#default_health specifies a default for how much health a factory has. Factories will continuously lose health over time and have +#to be repaired before they can be used again, once they are at 0 health +default_health: 10000 + +#default_menu_factory allows you to specify the menu for which factory will be opened when a player runs /fm without +#specifying a factory. If this is not specified and the player runs "/fm" one will be picked randomly. +default_menu_factory: + +#As a debug mode or to prevent additional information when tracking down bugs this plugin can log all of its inventory +#interactions and the full inventories at that point, if desired. If this option is not specified it will default to true + +log_inventories: true + + +#Additionally this plugin can disable vanilla nether portals. This isn't really part of the plugin's core functionality, +#but it was picked up from the first implementation of FactoryMod and stayed here after it's rewrite. Deal with it. +disable_nether: false + + +#When FCCs are in disrepair they will be removed after a set amount of time, which can be specified here +default_break_grace_period: 7d + + +#How often factories are passively decayed +decay_intervall: 1h + +#How much health is decayed from factories by default each damaging run +default_decay_amount: 21 + +#Whether the yaml identifier or the factory name should be used when assigning recipes to factories, default to false +use_recipe_yamlidentifiers: false + +#How often factory data is saved automatically. Recommended value and default is 15 minutes. Set to -1 to disable +saving_intervall: 15m + +# Maximum number of input, output, fuel, and total IOF chests. If a chest is used for multiple settings, it is counted +# multiple times. +max_input_chests: 10 +max_output_chests: 10 +max_fuel_chests: 10 +max_iof_chests: 15 + + +#---------------------------------------------------------------------------------------------------------------------- +#Factories + +#The main part of this plugin's functionality will be defined by this section as it specifies most of the configuration +#for your factories. Under the option factories you will be required to specify each factories configuration, each factory +#needs it's own identifier here, an example could look like this: + +#factories: +# IAmAnIdentifer: +# type: FCC +# name: Stone Smelter +# SoAmI: +# type: PIPE +# name: Pipinator + +#All factories have in common that they require you to define a type and a name. The type must be one of the predefined +#options explained further below to specify the physical appearance and the broad functionality of the factory. +#The name can be any string, which will be used to identify this factory at a later point. There may be many different +#factories of the same type with different names with completly different configurations, but ingame factories with +#the same name are guaranteed to have the same functionality. Because of that NEVER EVER duplicate factory names, not +#even if their type is different. + +#Currently there are three different types of factories: + +#1. FCC + +#FCC is an acronym for FurnaceCraftingTableChest, which is the "classic" factory and what factories used to be in the first +#version of FactoryMod before its rewrite. As you might guess it consist of a crafting table, a furnace and a chest. The +#crafting table has to be in the middle between the furnace and the chest either vertical or horizontal. Those factories +#use an item which is burned in furnace as fuel to execute recipes, which usually use up some sort of item in the chest +#to produce another. A factory can have as many recipes as you want, the details for those are defined further down in the +#config. The identifier used for this type of factory in the config is FCC. An example config to explain every possible +#option for this factory: + +#smelteridentiferbla: +# type: FCC +# name: Smelter +# updatetime: 2s +# health: 20000 +# grace_period: 14d +# decay_amount: 40 +# fuel: +# fuelidentifier: +# material: COAL +# durability: 1 +# fuel_consumption_intervall: 10s +# setupcost: +# dirt: +# material: DIRT +# amount: 64 +# stone: +# material: STONE +# amount: 64 +# recipes: +# - Smelt iron ore +# - Smelt diamond ore +# - Smelt stone +# - Upgrade smelter + +#type and name work as already described above + +#updatetime decides how often this factory is ticked to check whether it still has all the materials required, fuel etc. +#If this is not specified the default option which was specified above will be used. For more details on this option read +#the explaination next to the default updatetime above + +#health determines how much health the factory has and how long it takes for it go in disrepair. If this option is not set, +#the default_health specified at the beginning of the config will be used + +#grace_period determines how long the factory will stay alive while at 0 health. If the factory remains at 0 health for this +#time period, it will be permanently removed. Repairing the factory once will reset this counter and if this value is not set +#for a factory, default_grace_period as specified at the start of the config will be used + +#decay_amount is the amount of health the factory loses every time factories are damaged by time based decay. The intervall for +#this damaging is the same for all factories and specified as decay_intervall at the top of the config. If no decay_amount is +#specified for a factory, default_decay_amount as specified at the top of the config will be used + +#fuel specifies which item is used in the furnace to power this factory. You will still need to give the fuel it's own +#sub identifer here, because it's techincally part of a list of items. If this option is not set, the default fuel specified +#above will be used. + +#fuel_consumption_intervall describes how often fuel is consumed if the factory is running with any recipe. If it is not set, +#the default fuel consumption intervall specified above will be used. + +#setupcost are the materials the player will need to create this factory. Two factories may not have the exact same setupcost, +#otherwise there is no way to determine which factory a player actually intended to setup. + +#recipes is the option which defines what this FCC actually does, but you are only supposed to list the names of the +#recipes this factory is meant to run here. The exact functionality of the recipes is defined further below, ensure that the +#names given to the recipes are identical with the ones used here, even the capitalization has to be the same. The same recipe +#may be used by multiple factories or exist in the config without being used by any factory. + + +#Additionally it is also possible to upgrade factories, which turns the factory into a completly different one after a specific +#recipe. To do so, the upgraded version must be added to the list of factories with FCCUPGRADE as the type identifier. Example: + +#upgradedsmelter: +# type: FCCUPGRADE +# name: Upgraded Smelter +# updatetime: 2s +# fuel: +# fuelidentifier: +# material: COAL +# durability: 1 +# fuel_consumption_intervall: 10s +# recipes: +# - Smelt emerald ore + +#As you can see this is pretty much identical to a normal factory aside from the type and the fact that an upgraded factory +#does not specify a setup cost. The actual upgrade cost will be contained in the recipe which is used to upgrade the factory + + +#2. Pipe + +#Pipes allow players to transport items over longer distances. This is meant to replace hopper chains, which can have heavy +#impact on the server performance. The pipes themselves consist of a dropper, which represents the start point of the pipe +#and it's pumper, a furnace to consume fuel while transporting, the actual pipe consisting of stained glass blocks and a target +#block with an inventory. + +#An example config for a pipe: + +#thisIsAPipeIdentifer123: +# type: PIPE +# name: Example Pipe +# updatetime: 2s +# setupcost: +# redstone: +# material: REDSTONE +# amount: 64 +# fuel: +# normalcoal: +# material: COAL +# fuel_consumption_intervall: 5s +# transfer_time_multiplier: 1s +# transferamount: 16 +# pipe_block: ORANGE_STAINED_GLASS +# maximum_length: 32 + +#type for all pipes must be PIPE and name a unique identifer, standard stuff + +#update time specifies how often this factory is ticked, see default_update_time for a detailed description of updatetimes +#functionality + +#setupcost are the items required in the dropper when creating this factory + +#fuel specifies the fuel the pipe uses up while transferring, standard as described above + +#fuel_consumption_intervall specifies how often fuel is consumed, again this works exactly as described above + +#transfer_time_multiplier defines how long the pipe takes to transfer one load of items. The total transportation time scales +#directly with the length of the pipe and the value specified here multiplied with the length of the pipe (only counting +#glass blocks) results in the total transportation time per batch. For example if you set this value to one second, a pipe +#which is 10 blocks long will take 10 seconds to transport a batch of items. + +#transferamount is the total amount of items transferred per batch. + +#pipe_block is the block you use to define a pipe. + +#maximum_length defines how long this type of pipe may be at maximum. Length only counts the glass part here and this value +#may not be bigger than 512. Be aware that allowing many very long pipes might cause performance problems + + +#3. Sorter + +#Sorters allow players to sort item from a single dispenser into different other containers. What gets sorted where is +#completly up to the player, the factory itself only consists of a dispenser and a furnace. The dispenser is the main block of +#the factory which contains the items which will be sorted and the blocks in which will be sorted have to be adjacent to it. +#The furnace simply consumes fuel to power the factory, like it's done for other factories as well. An example config: + +#fkIsGonnaLikeThis: +# type: SORTER +# name: The first sorter +# updatetime: 2s +# fuel: +# normalcoal: +# material: COAL +# fuel_consumption_intervall: 5s +# setupcost: +# redblocks: +# material: REDSTONE_BLOCK +# amount: 64 +# sort_time: 2s +# sort_amount: 32 +# maximum_materials_per_side: 20 + + +# type, name, updatetime, fuel and fuel_consumption_intervall work all exactly as described above + +#setupcost are the material requires when setting up this factory + +#sort_time is the amount of time it takes for the sorter to sort one batch of items, where one batch is the sort amount specified +#in the config. + +#sort_amount is the amount of items that gets sorted per run + +#maximum_materials_per_side is an amount that limits how many items you can assign to a specific side (so the sorter sorts them +#in this direction) + +factories: + +#----------------------------------------------------------------------------------------------------------------------------- + +#FCC recipes + +#FCCs have a wide variety of different recipes with different functionality they can run. Those will be listed under the config +#option recipes, like this: + +#recipes: +# smeltstone: +# name: well +# ... +# anotheridentifer: +# name: weg +# ... + +#Every recipe has two configurations values, that always have to be specified, those are name and production_time. + +#name can be any String, but it's not allowed to contain '#', because this is used internally. The name you give your recipe +#here has to be the same you used above to list a factorys recipes, even capitalization has to be the same. + +#production_time is simply how long this recipe takes to complete one run + + +#Now on to the actual recipes: + +#1. Productionrecipe + +#This is the standard recipe; you put in materials, run the factory and get others as an output. + +#An example: + +#identiferABC: +# type: PRODUCTION +# name: Productionrecipeexample +# production_time: 10s +# forceInclude: false +# input: +# item1: +# material: STONE +# output: +# outputstuff: +# material: DIAMOND +# nbt: +# displayName: UberSword + +#The type for recipes works exactly like the one for factories, each recipe has it's own unique identifer that has to be used. +#The one for production recipes is PRODUCTION. + +#input is a list of items that gets consumed when the recipe completes, those items are required to even activate a factory +#with this recipe + +#output is a list of items that will get returned after the the input materials were consumed. +# FOR MINECRAFT 1.9: +# For a particular item you can now specify custom NBT data to be attached. This should only be used for output (currently) as it is not checked when comparing item stacks on input. + +#forceInclude allows you to indicate that whereever it is used, to force a _new_ recipe to be added to existing factories. +# this is recipe wide, and can't be changed factory to factory. +# This might be replaced by something else later on. + +#2. Compactionrecipe + +#Compaction allows players to store large amounts of items easily. Whole stacks of items get compacted into single items marked +#with lore and those items are stackable. This means a player can keep up to 4096 items in a single inventory slot! The recipe +#to add the lore and reduce the stack to a single one is this one, the decompation recipe is used to reverse this process. + +#An example: + +#compactIdentifier: +# type: COMPACT +# name: Compact item +# production_time 5s +# input: +# crate: +# material: CHEST +# lore: +# - Crate +# compact_lore: Compacted Item +# excluded_materials: +# - BOOK + + +#type always has to be COMPACT for this type of recipe + +#input specifies the compaction cost, ergo items that are consumed addtionally to the stack which is turned into a single item + +#compact_lore is the lore that will be added to the item to signalized that it's compacted + +#excluded_materials allows you to list materials, which will not be compactable. Per default anything that has a stack size of 1, +#has lore or enchants is not compactable, but if you also want to prevent other items from being compactable, you can do that here. + + +#3. Decompactionrecipe + +#Decompaction allows you to decompact items that were previously compacted. For a description of compaction see the compaction +#recipe. + +#Example: + +#decompactedIdentifier: +# type:DECOMPACT +# name: Decompaction recipe +# production_time: 5s +# input: +# compact_lore: Compact Item + + +#type always has to be DECOMPACT here. + +#input is a list of items that get consumed as decompaction cost + +#compact_lore is the lore that is required on the compacted item. Ensure that the lore here and the one in the compaction recipe +#are the same. + + +#4. Repairrecipe + +#Repair recipes allow repairing their factory. Once a factory is at 0 health, the only recipe that can be run is the repair +#recipe. + +#Example config: + +#hurryItsAboutToBreak: +# type: REPAIR +# name: Repair factory +# production_time: 20s +# input: +# diamonds: +# material: DIAMOND +# amount: 576 +# health_gained: 50 + + +#type always has to be REPAIR for this. + +#Input is as usual a list of items, which will be consumed when the recipe completes. + +#health_gained is the amount of health that will be restored by a successfull run. 10000 means fully health here + + +#5. Upgraderecipe + +#Upgrades recipes allow you to upgrade one factory into another to completly change it's recipes and properties + +#Example: + +#upgrading: +# type: UPGRADE +# production_time: 1d +# name: Upgrade to super duper factory +# input: +# diamond: +# material: DIAMOND +# amount: 64 +# factory: upgradedsmelter + +#type always has to be UPGRADE + +#input is again the list of items which are consumed + +#factory is the name of the factory it's supposed to be upgraded to. This factory must have been specified as FCCUPGRADE +#above and the name here and above has to be exactly the same. + + +#6. Enchantingrecipe + +#Enchanting recipes allow factories to apply enchants to items. Instead of just consuming a blank tool to produce an enchanted +#one the way it would be possible with enchanted tools, this actually applies the enchant. This can also be used to increase the +#level of an already existing enchant + +#Example: + +#enchanting: +# type: ENCHANT +# name: Enchant with Effiency 5 +# production_time: 10s +# input: +# diamond: +# material: DIAMOND +# amount: 50 +# enchant: DIG_SPEED +# level: 5 +# enchant_item: +# tool: +# material: DIAMOND_PICKAXE + +#type has to be ENCHANT for this recipe + +#name is the name of the recipe displayed ingame + +#input are additional items which are consumed as a cost to enchant the item + +#enchant is the enchant which is applied to the item, use official spigot identifers for this: +#https://hub.spigotmc.org/javadocs/spigot/org/bukkit/enchantments/Enchantment.html + +#level is the level of the enchant, if not specified this will default to 1 + +#enchant_item is the item to which the enchant is applied. This can just be a blank tool, if you want this enchant to be appliable +#to any tool of this material or you can give this item an enchant with the same type as this recipe, but of a different level to use +#this recipe as an upgrade to the enchant + + +#7. Randomized output recipe + +#Random recipes allow you to specify multiple different outputs for a recipe and to assign a certain chance to each. The sum of all the +#chances should always be 1.0 + +#Optionally use "display:" to pick which output to show in the GUI; otherwise the least-likely option +# will be shown. + +#Example: + +#random: +# type: RANDOM +# production_time: 2s +# name: Stone or Dirt +# input: +# diamond: +# material: DIAMOND +# outputs: +# display: second +# first: +# chance: 0.5 +# dirt: +# material: DIRT +# second: +# chance: 0.5 +# dirt: +# material: STONE + +#type has to be RANDOM here + +#input are the materials consumed + +#output is a list of item configurations with a chance for it each. It's important to note here that the items themselves still need be a level lower +#yaml structure wise than the chance + + +#8. Cost return recipe + +#Cost return recipes allow you to return a percentage of the total setup cost of a factory, which is different from the return rate on break. This can for +#example be very useful when making factories obsolete to reimburse players. When the recipe completes, the factory will be destroyed. + +#Example: + +#return: +# type: COSTRETURN +# input: +# dirt: +# material: DIRT +# factor: 1.0 + +#type has to be COSTRETURN + +#input allows you to specify items which are consumed when breaking the factory, it may be left empty if desired + +#factor specifies a multiplier for how much of the total setup cost is dropped, where 1.0 means full setupcost. + + +#9. Lore enchanting recipes + +#This type of recipe allows you to apply a set of lore to an item in exchange for consuming another set of lore, basically custom enchants + +#Example: + +#loreEnchant: +# type: LOREENCHANT +# input: +# emeralds: +# material: EMERALD +# amount: 9 +# loredItem: +# pick: +# material: DIAMOND_PICKAXE +# overwrittenLore: +# - TNTBreaker I +# appliedLore: +# - TNTBreaker II + +#This recipe will increment the count after the lore line 'TNTBreaker' + +#type has to be LOREENCHANT + +#input is whatever input is consumed additionally + +#loredItem specifies the tool to which this enchant is applied. Only it's material is respected here, specific ItemMeta is ignored + +#overwrittenLore is the lore which is required on the item which is being enchanted. This list may be empty or have multiple lines, both will work + +#appliedLore is the lore which is applied as replacement for the removed lore. This list may not be empty, but it may contain multiple entries + +#GENERAL NOTE: + +# Every recipe type can include at the same level as "name: " the following, although only FCC's will respect it currently: + +#forceInclude allows you to indicate that whereever it is used, to force a _new_ recipe to be added to existing factories. +# this is recipe wide, and can't be changed factory to factory. +# This might be replaced by something else later on. + +recipes: + + + +#Sometimes you will want to rename existing factories. Just changing the name in the config and restarting the server will delete all existing +#factories of this type though, so instead you can here specify factories, which you on startup want to rename/convert into a different one + +#Example format: + +#renames: +# example1: +# oldName: Stone Smelter With Bad Name +# newName: Stone Smelter +# example2: +# oldName: tempName +# newName: Emerald Extractor + +#oldName is the previous name and newName is the one the factory will be converted to. The configuration for the factory with the oldName has to be +#removed from the config already, otherwise the name won't be changed and the old configuration for the factory will be loaded. The renaming feature +#will work for all types of factories, not only FCCs + +renames: + + +#CivMenu configuration + +#This plugin uses CivMenu (https://github.com/civcraft/CivMenu) to display additional information to players. You can specify +#the messages sent to the players here. Note that for all those events a short message to sum it up will already be displayed, +#this is only meant to provide messages to teach the game mechanic as a whole. + +CivMenu: + events: + #Called when a FCC factory is successfully created + FactoryCreation: + + #Called when a player attempt to create a FCC factory with invalid materials + WrongFactoryCreationItems: + + #Called when a player attempts to create a FCC factory with a wrong block setup + WrongFactoryBlockSetup: + + #Called when a pipe is successfully created + PipeCreation: + + #Called when a player attempt to create a pipe with invalid materials + WrongPipeCreationItems: + + #Called when a player attempts to create a pipe with a wrong block setup + WrongPipeBlockSetup: + + #Called when a sorter is successfully created + SorterCreation: + + #Called when a player attempt to create a sorter with invalid materials + WrongSorterCreationItems: + + #Called when a player attempts to create a sorter with a wrong block setup + WrongSorterBlockSetup: + + #Called when someone attempts to interact with any type of factory, but doesnt have the correct permissions + FactoryNoPermission: + + + + diff --git a/plugins/factorymod-paper/src/main/resources/plugin.yml b/plugins/factorymod-paper/src/main/resources/plugin.yml new file mode 100644 index 000000000..00eb717d7 --- /dev/null +++ b/plugins/factorymod-paper/src/main/resources/plugin.yml @@ -0,0 +1,12 @@ +name: FactoryMod +main: com.github.igotyou.FactoryMod.FactoryMod +author: Maxopoly, igotyou +version: ${version} +depend: [CivModCore] +softdepend: [CivMenu, NameLayer, Citadel] +permissions: + fm.public: + default: true + fm.op: + default: op +api-version: 1.17 diff --git a/settings.gradle.kts b/settings.gradle.kts index 743ba60ff..20f631b5b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -21,4 +21,5 @@ include(":plugins:namelayer-paper") include(":plugins:randomspawn-paper") include(":plugins:realisticbiomes-paper") include(":plugins:simpleadminhacks-paper") +include(":plugins:factorymod-paper") include(":plugins:finale-paper")