console.info("[SOCIETY] artisanHopper.js loaded"); const artisanMachineCanHaveAdditionalOutput = [ "society:loom", "society:crystalarium", "society:seed_maker", "society:aging_cask", "society:mayonnaise_machine", "society:wine_keg" ]; /** * @param {Internal.Stages|SocietyStages} stages */ global.handleAdditionalArtisanMachineOutputs = ( level, block, artisanMachine, recipes, recipeId, upgraded, stages ) => { switch (artisanMachine.id) { case "society:loom": { if (upgraded && rnd25()) { global.insertBelow( level, block, Ingredient.of("#society:loot_furniture").itemIds[ Math.floor( Math.random() * Ingredient.of("#society:loot_furniture").itemIds.length ) ] ); } break; } case "society:crystalarium": { if (upgraded && rnd10()) { let recipe = recipes.get(recipeId); if (!recipe) { let legacyType = artisanMachine.getEntityData().data.type; if (legacyType > 0) { let legacyKey = Array.from(recipes.keys())[Number(legacyType) - 1]; recipe = recipes.get(legacyKey); } } if (recipe && recipe.output) { recipe.output.forEach((item) => { const pristinePath = String(Item.of(item).id).split(":")[1]; if (pristinePath) { global.insertBelow(level, block, `society:pristine_${pristinePath}`); } }); } } break; } case "society:seed_maker": { if (upgraded && rnd5()) { global.insertBelow(level, block, "society:ancient_fruit_seed"); } break; } case "society:aging_cask": { if (stages.has("aged_prize") && rnd5()) { global.insertBelow(level, block, "society:prize_ticket"); } break; } case "society:wine_keg": { if (upgraded && rnd5()) { global.insertBelow(level, block, "society:relic_trove"); } break; } case "society:mayonnaise_machine": { if (upgraded && rnd5()) { global.insertBelow(level, block, "society:supreme_mayonnaise"); } break; } } }; // TODO: make artisan hopper set tappers /** * @param {Internal.Stages|SocietyStages} stages */ global.getArtisanMachineData = (player, block, upgraded, stages) => { let machineData = { recipes: [], stageCount: 0, multipleInputs: false, hasTag: false, outputMult: 1, soundType: "minecraft:ui.toast.in", }; let rancherOutputCount; if (stages.has("rancher") && Math.random() <= 0.2) { rancherOutputCount = 2; } let machineNbt = block.getEntityData(); switch (block.id) { case "society:loom": machineData = { recipes: global.loomRecipes, stageCount: 5, multipleInputs: true, hasTag: true, outputMult: rancherOutputCount, soundType: "minecraft:block.wool.fall", }; break; case "society:mayonnaise_machine": machineData = { recipes: global.mayonnaiseMachineRecipes, stageCount: 3, outputMult: rancherOutputCount, soundType: "minecraft:block.sniffer_egg.plop", }; break; case "society:preserves_jar": machineData = { recipes: global.preservesJarRecipes, stageCount: upgraded ? 3 : 5, multipleInputs: true, soundType: "minecraft:block.wood.place", }; break; case "society:crystalarium": machineData = { recipes: global.crystalariumCrystals, stageCount: 5, soundType: "minecraft:block.amethyst_block.step", }; break; case "society:wine_keg": machineData = { recipes: global.wineKegRecipes, stageCount: 3, multipleInputs: true, soundType: "minecraft:block.wood.place", }; break; case "society:aging_cask": machineData = { recipes: global.agingCaskRecipes, stageCount: 10, soundType: "minecraft:block.wood.place", }; break; case "society:cheese_press": machineData = { recipes: global.cheesePressRecipes, stageCount: 2, outputMult: rancherOutputCount, soundType: "species:block.frozen_meat.place", }; break; case "society:ancient_cask": if (stages.has("ancient_aging")) { if (upgraded) { machineData = { recipes: global.ancientCaskRecipes, stageCount: 4, multipleInputs: true, hasTag: false, outputMult: 4, soundType: "", }; } else machineData = { recipes: global.ancientCaskRecipes, stageCount: 20, soundType: "minecraft:block.wood.place", }; } else machineData = undefined; break; case "society:dehydrator": machineData = { recipes: global.dehydratorRecipes, stageCount: 8, multipleInputs: true, soundType: "species:block.alphacene_foliage.place", }; break; case "society:deluxe_worm_farm": machineData = { recipes: global.deluxeWormFarmRecipes, stageCount: 4, multipleInputs: true, soundType: "aquaculture:fish_flop", }; break; case "society:seed_maker": machineData = { recipes: global.seedMakerRecipes, stageCount: 3, multipleInputs: true, soundType: "unusualfishmod:crab_scuttling", }; break; case "society:fish_smoker": machineData = { recipes: global.fishSmokerRecipes, stageCount: 5, outputMult: upgraded ? 2 : 1, soundType: "farmersdelight:block.skillet.add_food", }; break; case "society:espresso_machine": machineData = { recipes: global.espressoMachineRecipes, stageCount: 4, multipleInputs: true, soundType: "doapi:brewstation_whistle", }; break; case "society:bait_maker": machineData = { recipes: global.baitMakerRecipes, stageCount: 1, soundType: "aquaculture:fish_death", }; break; case "society:recycling_machine": machineData = { recipes: global.recyclingMachineRecipes, stageCount: 1, soundType: "twigs:block.basalt_bricks.fall", outputMult: upgraded ? 2 : 1, }; break; case "society:oil_maker": machineData = { recipes: global.oilMakerRecipes, stageCount: 1, soundType: "supplementaries:block.jar.place", }; break; case "society:tapper": machineData = { recipes: global.tapperRecipes, stageCount: 1, soundType: "vinery:cabinet_close", outputMult: stages.has("canadian_and_famous") ? 2 : 1, }; break; case "society:mushroom_log": machineData = { recipes: global.mushroomLogRecipes, stageCount: 1, soundType: "species:block.alphacene_moss.place", outputMult: machineNbt && machineNbt.data && machineNbt.data.baseCount ? machineNbt.data.baseCount : 1, }; break; case "society:charging_rod": machineData = { recipes: null, stageCount: 5, soundType: "" }; break; default: machineData = undefined; } return machineData; }; /** * @param {Internal.BlockEntityJS} artisanHopper * @param {Internal.Player|null} player */ global.runArtisanHopper = (artisanHopper, artisanMachinePos, player, delay) => { const { level, block, inventory } = artisanHopper; const server = level.server; server.scheduleInTicks(delay, () => { const artisanMachine = level.getBlock(artisanMachinePos); const { x, y, z } = artisanMachine; const nbt = artisanMachine.getEntityData(); if (!nbt || !nbt.data) return; const upgraded = artisanMachine.properties.get("upgraded") == "true"; const stages = global.getBlockEntityStages(artisanHopper); const loadedData = global.getArtisanMachineData( player, artisanMachine, upgraded, stages, ); const season = global.getSeasonFromLevel(level); const chargingRodOutput = Item.of( `${upgraded && season === "winter" ? 3 : 1}x society:battery` ); if (loadedData && artisanMachine) { let { recipes, stageCount, multipleInputs, hasTag, outputMult, soundType, } = loadedData; if (recipes) { global.convertFromLegacy(recipes, level, artisanMachine); } let refreshedNbt = artisanMachine.getEntityData(); let { stage, recipe } = refreshedNbt.data; let currentStage = stage || 0; let resolvedRecipeId = recipe; if (recipes && !recipes.has(resolvedRecipeId)) { let legacyType = refreshedNbt.data.type; if (legacyType > 0) { let legacyKey = Array.from(recipes.keys())[Number(legacyType) - 1]; if (legacyKey) resolvedRecipeId = legacyKey; } } let hasInfinityWorm = artisanMachine.id === "society:deluxe_worm_farm" && upgraded; let machineOutputs = []; let newProperties = artisanMachine.getProperties(); let recycleSparkstone; if ( newProperties.get("mature").toLowerCase() === "true" && (artisanMachine.id === "society:charging_rod" ? global.inventoryBelowHasRoom(level, block, chargingRodOutput) : recipes.has(resolvedRecipeId) && global.inventoryBelowHasRoomForAll( level, block, recipes.get(resolvedRecipeId).output )) && global.hasInventoryItems(inventory, "society:sparkstone", 1) ) { server.runCommandSilent( `playsound stardew_fishing:dwop block @a ${x} ${y} ${z}` ); if (artisanMachine.id === "society:charging_rod") { machineOutputs.push(chargingRodOutput); artisanMachine.set(artisanMachine.id, { working: false, mature: false, upgraded: upgraded, stage: "0", }); } else if (hasInfinityWorm) { machineOutputs.push( Item.of("4x crabbersdelight:deluxe_crab_trap_bait") ); artisanMachine.set(artisanMachine.id, { facing: artisanMachine.properties.get("facing"), type: "1", working: true, mature: false, upgraded: upgraded, stage: "0", }); } else { machineOutputs = global.artisanHarvest( artisanMachine, recipes, stageCount, outputMult, artisanMachine.id === "society:cheese_press", true ); } if (machineOutputs && machineOutputs.length > 0) { recycleSparkstone = global.checkSparkstoneRecyclers(level, block); if ( artisanMachine.id === "society:dehydrator" && upgraded && global.dehydratableMushroomOutputs.includes(machineOutputs[0].id) ) { machineOutputs.forEach((output) => { output.count = 2; }); } if ( artisanMachineCanHaveAdditionalOutput.includes(artisanMachine.id) ) { global.handleAdditionalArtisanMachineOutputs( level, block, artisanMachine, recipes, resolvedRecipeId, upgraded, stages ); } let sparkstoneSaveChance = 0; if (stages.has("slouching_towards_artistry")) { sparkstoneSaveChance = Number(currentStage) * 0.05; } if (!recycleSparkstone && Math.random() > sparkstoneSaveChance) { global.useInventoryItems(inventory, "society:sparkstone", 1); } else { level.spawnParticles( "species:youth_potion", true, x, y + 0.5, z, 0.1 * rnd(1, 4), 0.1 * rnd(1, 4), 0.1 * rnd(1, 4), 5, 0.01 ); } machineOutputs.forEach((output) => { global.insertBelow(level, block, output); }); level.spawnParticles( "species:ascending_dust", true, x, y + 1, z, 0.2 * rnd(1, 1.5), 0.2 * rnd(1, 1.5), 0.2 * rnd(1, 1.5), 3, 0.01 ); } } let abovePos = block.getPos().above(); let aboveBlock = level.getBlock(abovePos.x, abovePos.y, abovePos.z); if ( recipes && newProperties.get("working").toLowerCase() === "false" && global.hasInventoryItems(inventory, "society:sparkstone", 1) && aboveBlock.inventory && !aboveBlock.inventory.isEmpty() ) { let aboveBlockData = aboveBlock.getEntityData(); if (aboveBlockData && aboveBlockData.toString().includes("filter_upgrade")) { if (player) { player.tell(Text.translatable("block.society.artisan_hopper.filter").red()); } return; } let slots = aboveBlock.inventory.getSlots(); let slotStack; let outputCount; for (let i = 0; i < slots; i++) { slotStack = aboveBlock.inventory.getStackInSlot(i); if ( !( multipleInputs && !slotStack.isEmpty() && slotStack.count < stageCount ) ) { outputCount = global.artisanInsert( artisanMachine, slotStack, level, recipes, stageCount, soundType, multipleInputs, hasTag, true, server ); if (outputCount > 0) { recycleSparkstone = global.checkSparkstoneRecyclers(level, block); if (!recycleSparkstone) global.useInventoryItems(inventory, "society:sparkstone", 1); else { level.spawnParticles( "species:youth_potion", true, x, y + 0.5, z, 0.1 * rnd(1, 4), 0.1 * rnd(1, 4), 0.1 * rnd(1, 4), 5, 0.01 ); } level.runCommandSilent( `playsound create:fwoomp block @a ${x} ${y} ${z} 0.8` ); aboveBlock.inventory.extractItem(i, outputCount, false); break; } } } } if ( hasInfinityWorm && newProperties.get("working").toLowerCase() === "false" ) { artisanMachine.set(artisanMachine.id, { facing: artisanMachine.properties.get("facing"), type: "1", working: true, mature: false, upgraded: upgraded, stage: "0", }); } } }); }; /** * @param {Internal.BlockEntityJS} entity */ global.artisanHopperScan = (entity, radius) => { const { block, level } = entity; const { x, y, z } = block; const attachedPlayer = global.cacheOwner(entity, [ "slouching_towards_artistry", "ancient_aging", "rancher", "aged_prize", "canadian_and_famous" ]); let scanBlock; let scannedBlocks = 0; for (let pos of BlockPos.betweenClosed( new BlockPos(x - radius, y - radius, z - radius), [x + radius, y + radius, z + radius] )) { if (!level.isLoaded(pos)) continue; scanBlock = level.getBlock(pos); if (scanBlock.hasTag("society:artisan_machine")) { global.runArtisanHopper( entity, pos.immutable(), attachedPlayer, scannedBlocks * 5 ); scannedBlocks++; } } }; StartupEvents.registry("block", (event) => { event .create("society:artisan_hopper", "cardinal") .tagBlock("minecraft:mineable/pickaxe") .tagBlock("minecraft:needs_stone_tool") .defaultCutout() .item((item) => { item.tooltip( Text.translatable("block.society.artisan_hopper.description").gray() ); item.tooltip( Text.translatable( "society.working_block_entity.apply_player_skill" ).gray() ); item.tooltip(Text.translatable("tooltip.society.area", `7x7x7`).green()); item.tooltip( Text.translatable( "block.society.artisan_hopper.description.fuel" ).lightPurple() ); item.modelJson({ parent: "society:block/kubejs/artisan_hopper", }); }) .soundType("copper") .model("society:block/kubejs/artisan_hopper") .blockEntity((blockInfo) => { blockInfo.inventory(9, 2); blockInfo.initialData({ owner: "-1" }); blockInfo.serverTick(600, 0, (entity) => { global.artisanHopperScan(entity, 3); }); blockInfo.rightClickOpensInventory(); blockInfo.attachCapability( CapabilityBuilder.ITEM.blockEntity() .insertItem((blockEntity, slot, stack, simulate) => blockEntity.inventory.insertItem(slot, stack, simulate) ) .extractItem((blockEntity, slot, stack, simulate) => blockEntity.inventory.extractItem(slot, stack, simulate) ) .getSlotLimit((blockEntity, slot) => blockEntity.inventory.getSlotLimit(slot) ) .getSlots((blockEntity) => blockEntity.inventory.slots) .getStackInSlot((blockEntity, slot) => blockEntity.inventory.getStackInSlot(slot) ) ); }); }); StartupEvents.registry("block", (event) => { event .create("society:mini_artisan_hopper", "cardinal") .tagBlock("minecraft:mineable/pickaxe") .tagBlock("minecraft:needs_stone_tool") .defaultCutout() .item((item) => { item.tooltip( Text.translatable("block.society.artisan_hopper.description").gray() ); item.tooltip( Text.translatable( "society.working_block_entity.apply_player_skill" ).gray() ); item.tooltip(Text.translatable("tooltip.society.area", `3x3x3`).green()); item.tooltip( Text.translatable( "block.society.artisan_hopper.description.fuel" ).lightPurple() ); item.modelJson({ parent: "society:block/kubejs/mini_artisan_hopper", }); }) .soundType("copper") .model("society:block/kubejs/mini_artisan_hopper") .blockEntity((blockInfo) => { blockInfo.inventory(9, 2); blockInfo.initialData({ owner: "-1" }); blockInfo.serverTick(600, 0, (entity) => { global.artisanHopperScan(entity, 1); }); blockInfo.rightClickOpensInventory(); blockInfo.attachCapability( CapabilityBuilder.ITEM.blockEntity() .insertItem((blockEntity, slot, stack, simulate) => blockEntity.inventory.insertItem(slot, stack, simulate) ) .extractItem((blockEntity, slot, stack, simulate) => blockEntity.inventory.extractItem(slot, stack, simulate) ) .getSlotLimit((blockEntity, slot) => blockEntity.inventory.getSlotLimit(slot) ) .getSlots((blockEntity) => blockEntity.inventory.slots) .getStackInSlot((blockEntity, slot) => blockEntity.inventory.getStackInSlot(slot) ) ); }); });