From fa28e37c313bf83f330c611162709216609aae14 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Tue, 23 Jun 2026 22:02:35 -0400 Subject: [PATCH 1/8] Implement falling block --- .../casting/actions/spells/OpFallingBlock.kt | 128 ++++++++++++++++++ .../hexcasting/common/lib/hex/HexActions.java | 2 + 2 files changed, 130 insertions(+) create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpFallingBlock.kt diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpFallingBlock.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpFallingBlock.kt new file mode 100644 index 000000000..72ec9d71d --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpFallingBlock.kt @@ -0,0 +1,128 @@ +package at.petrak.hexcasting.common.casting.actions.spells + +import at.petrak.hexcasting.api.mod.HexConfig +import at.petrak.hexcasting.api.casting.* +import at.petrak.hexcasting.api.casting.castables.SpellAction +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.misc.MediaConstants +import at.petrak.hexcasting.xplat.IXplatAbstractions +import net.minecraft.core.BlockPos +import net.minecraft.core.particles.BlockParticleOption +import net.minecraft.core.particles.ParticleTypes +import net.minecraft.core.registries.Registries +import net.minecraft.server.level.ServerLevel +import net.minecraft.server.level.ServerPlayer +import net.minecraft.tags.BlockTags +import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.item.FallingBlockEntity +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.minecraft.world.item.Tier +import net.minecraft.world.item.Tiers +import net.minecraft.world.item.enchantment.Enchantments +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.FallingBlock +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.phys.Vec3 +import kotlin.math.roundToLong + +// https://github.com/VazkiiMods/Botania/blob/1.21.1-porting/Xplat/src/main/java/vazkii/botania/common/item/lens/WeightLens.java +object OpFallingBlock : SpellAction { + override val argc = 1 + + override fun execute(args: List, env: CastingEnvironment): SpellAction.Result { + val pos = args.getVec3(0, argc) + env.assertVecInRange(pos) + + val centered = Vec3.atCenterOf(BlockPos.containing(pos)) + return SpellAction.Result( + Spell(pos), + (1.5 * MediaConstants.DUST_UNIT).roundToLong(), + listOf(ParticleSpray.burst(centered, 1.0)) + ) + } + + private data class Spell(val v: Vec3) : RenderedSpell { + override fun cast(env: CastingEnvironment) { + val pos = BlockPos.containing(v) + + val blockstate = env.world.getBlockState(pos) + if (!env.canEditBlockAt(pos) || !IXplatAbstractions.INSTANCE.isBreakingAllowed(env.world, pos, blockstate, env.caster)) + return + + val tier = HexConfig.server().opBreakHarvestLevel() + + val stateBelow = env.world.getBlockState(pos.below()) + + if (( + FallingBlock.isFree(stateBelow) + || !stateBelow.canOcclude() + || stateBelow.`is`(BlockTags.SLABS) + ) + && !blockstate.isAir + && blockstate.getDestroySpeed(env.world, pos) >= 0f // fix being able to break bedrock &c + && env.world.getBlockEntity(pos) == null + && IXplatAbstractions.INSTANCE.isCorrectTierForDrops(tier, blockstate) + && canSilkTouch(env.world, pos, blockstate, tier, env.castingEntity as? ServerPlayer) + ) { + val falling: FallingBlockEntity = FallingBlockEntity.fall(env.world, pos, blockstate) + falling.time = 1 + env.world.sendParticles( + BlockParticleOption(ParticleTypes.FALLING_DUST, blockstate), + pos.x + 0.5, + pos.y + 0.5, + pos.z + 0.5, + 10, + 0.45, + 0.45, + 0.45, + 5.0 + ) + } + } + + fun canSilkTouch(level: ServerLevel, pos: BlockPos, state: BlockState, harvestTier: Tier, owner: Entity?): Boolean { + val harvestToolStack: ItemStack = getHarvestToolStack(harvestTier, state) + if (harvestToolStack.isEmpty) { + return false + } + harvestToolStack.enchant(level.holderLookup(Registries.ENCHANTMENT).getOrThrow(Enchantments.SILK_TOUCH), 1) + val drops: List = Block.getDrops(state, level, pos, null, owner, harvestToolStack) + val blockItem: Item = state.block.asItem() + return drops.any { s -> s.item === blockItem } + } + + companion object { + fun getHarvestToolStack(harvestTier: Tier, state: BlockState): ItemStack { + return getTool(harvestTier, state).copy() + } + + private fun getTool(harvestTier: Tier, state: BlockState): ItemStack { + if (harvestTier !in HARVEST_TOOLS_BY_LEVEL.keys) return ItemStack.EMPTY + if (!state.requiresCorrectToolForDrops()) { + return HARVEST_TOOLS_BY_LEVEL[harvestTier]!![0] + } + for (tool in HARVEST_TOOLS_BY_LEVEL[harvestTier]!!) { + if (tool.isCorrectToolForDrops(state)) { + return tool + } + } + return ItemStack.EMPTY + } + + private val HARVEST_TOOLS_BY_LEVEL: Map> = mapOf( + Tiers.WOOD to stacks(Items.WOODEN_PICKAXE, Items.WOODEN_AXE, Items.WOODEN_HOE, Items.WOODEN_SHOVEL), + Tiers.STONE to stacks(Items.STONE_PICKAXE, Items.STONE_AXE, Items.STONE_HOE, Items.STONE_SHOVEL), + Tiers.IRON to stacks(Items.IRON_PICKAXE, Items.IRON_AXE, Items.IRON_HOE, Items.IRON_SHOVEL), + Tiers.DIAMOND to stacks(Items.DIAMOND_PICKAXE, Items.DIAMOND_AXE, Items.DIAMOND_HOE, Items.DIAMOND_SHOVEL), + Tiers.NETHERITE to stacks(Items.NETHERITE_PICKAXE, Items.NETHERITE_AXE, Items.NETHERITE_HOE, Items.NETHERITE_SHOVEL) + ) + + private fun stacks(vararg items: Item): List { + return items.map { item -> ItemStack(item) } + } + } + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java index eb049a8e7..6e65a78f0 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java @@ -275,6 +275,8 @@ public class HexActions { new ActionRegistryEntry(HexPattern.fromAngles("qdqawwaww", HexDir.EAST), OpErase.INSTANCE)); public static final ActionRegistryEntry EDIFY = make("edify", new ActionRegistryEntry(HexPattern.fromAngles("wqaqwd", HexDir.NORTH_EAST), OpEdifySapling.INSTANCE)); + public static final ActionRegistryEntry FALLING_BLOCK = make("falling_block", + new ActionRegistryEntry(HexPattern.fromAngles("wqwawqwqwqwqwqw", HexDir.EAST), OpFallingBlock.INSTANCE)); public static final ActionRegistryEntry BEEP = make("beep", new ActionRegistryEntry(HexPattern.fromAngles("adaa", HexDir.WEST), OpBeep.INSTANCE)); From d400c0012af60049b76c45706ece6b12036d1506 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Tue, 23 Jun 2026 22:02:43 -0400 Subject: [PATCH 2/8] Implement freeze --- .../recipes/freeze/freeze/blue_ice.json | 32 +++++++ .../recipes/freeze/freeze/packed_ice.json | 32 +++++++ .../freeze/freeze/powder_snow_cauldron.json | 32 +++++++ .../recipe/freeze/freeze/blue_ice.json | 10 +++ .../recipe/freeze/freeze/packed_ice.json | 10 +++ .../freeze/freeze/powder_snow_cauldron.json | 13 +++ .../common/casting/actions/spells/OpFreeze.kt | 64 ++++++++++++++ .../actions/spells/great/OpBrainsweep.kt | 3 +- .../hexcasting/common/lib/hex/HexActions.java | 11 ++- .../common/recipe/BrainsweepRecipe.java | 13 --- .../common/recipe/CopyProperties.java | 19 +++++ .../common/recipe/FreezeRecipe.java | 85 +++++++++++++++++++ .../common/recipe/HexRecipeStuffRegistry.java | 3 + .../datagen/recipe/HexplatRecipes.java | 11 +++ .../recipe/builders/FreezeRecipeBuilder.java | 58 +++++++++++++ .../recipes/freeze/freeze/blue_ice.json | 32 +++++++ .../recipes/freeze/freeze/packed_ice.json | 32 +++++++ .../freeze/freeze/powder_snow_cauldron.json | 32 +++++++ .../recipe/freeze/freeze/blue_ice.json | 10 +++ .../recipe/freeze/freeze/packed_ice.json | 10 +++ .../freeze/freeze/powder_snow_cauldron.json | 13 +++ .../recipes/freeze/freeze/blue_ice.json | 32 +++++++ .../recipes/freeze/freeze/packed_ice.json | 32 +++++++ .../freeze/freeze/powder_snow_cauldron.json | 32 +++++++ .../recipe/freeze/freeze/blue_ice.json | 10 +++ .../recipe/freeze/freeze/packed_ice.json | 10 +++ .../freeze/freeze/powder_snow_cauldron.json | 13 +++ 27 files changed, 636 insertions(+), 18 deletions(-) create mode 100644 Common/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/blue_ice.json create mode 100644 Common/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/packed_ice.json create mode 100644 Common/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/powder_snow_cauldron.json create mode 100644 Common/src/generated/resources/data/hexcasting/recipe/freeze/freeze/blue_ice.json create mode 100644 Common/src/generated/resources/data/hexcasting/recipe/freeze/freeze/packed_ice.json create mode 100644 Common/src/generated/resources/data/hexcasting/recipe/freeze/freeze/powder_snow_cauldron.json create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpFreeze.kt create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/recipe/CopyProperties.java create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/recipe/FreezeRecipe.java create mode 100644 Common/src/main/java/at/petrak/hexcasting/datagen/recipe/builders/FreezeRecipeBuilder.java create mode 100644 Fabric/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/blue_ice.json create mode 100644 Fabric/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/packed_ice.json create mode 100644 Fabric/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/powder_snow_cauldron.json create mode 100644 Fabric/src/generated/resources/data/hexcasting/recipe/freeze/freeze/blue_ice.json create mode 100644 Fabric/src/generated/resources/data/hexcasting/recipe/freeze/freeze/packed_ice.json create mode 100644 Fabric/src/generated/resources/data/hexcasting/recipe/freeze/freeze/powder_snow_cauldron.json create mode 100644 Neoforge/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/blue_ice.json create mode 100644 Neoforge/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/packed_ice.json create mode 100644 Neoforge/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/powder_snow_cauldron.json create mode 100644 Neoforge/src/generated/resources/data/hexcasting/recipe/freeze/freeze/blue_ice.json create mode 100644 Neoforge/src/generated/resources/data/hexcasting/recipe/freeze/freeze/packed_ice.json create mode 100644 Neoforge/src/generated/resources/data/hexcasting/recipe/freeze/freeze/powder_snow_cauldron.json diff --git a/Common/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/blue_ice.json b/Common/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/blue_ice.json new file mode 100644 index 000000000..db092ef28 --- /dev/null +++ b/Common/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/blue_ice.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_item": { + "conditions": { + "items": [ + { + "items": "#hexcasting:staves" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "hexcasting:freeze/blue_ice" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_item" + ] + ], + "rewards": { + "recipes": [ + "hexcasting:freeze/blue_ice" + ] + } +} \ No newline at end of file diff --git a/Common/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/packed_ice.json b/Common/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/packed_ice.json new file mode 100644 index 000000000..ccded8855 --- /dev/null +++ b/Common/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/packed_ice.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_item": { + "conditions": { + "items": [ + { + "items": "#hexcasting:staves" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "hexcasting:freeze/packed_ice" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_item" + ] + ], + "rewards": { + "recipes": [ + "hexcasting:freeze/packed_ice" + ] + } +} \ No newline at end of file diff --git a/Common/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/powder_snow_cauldron.json b/Common/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/powder_snow_cauldron.json new file mode 100644 index 000000000..1e49ebee8 --- /dev/null +++ b/Common/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/powder_snow_cauldron.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_item": { + "conditions": { + "items": [ + { + "items": "#hexcasting:staves" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "hexcasting:freeze/powder_snow_cauldron" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_item" + ] + ], + "rewards": { + "recipes": [ + "hexcasting:freeze/powder_snow_cauldron" + ] + } +} \ No newline at end of file diff --git a/Common/src/generated/resources/data/hexcasting/recipe/freeze/freeze/blue_ice.json b/Common/src/generated/resources/data/hexcasting/recipe/freeze/freeze/blue_ice.json new file mode 100644 index 000000000..8106c7878 --- /dev/null +++ b/Common/src/generated/resources/data/hexcasting/recipe/freeze/freeze/blue_ice.json @@ -0,0 +1,10 @@ +{ + "type": "hexcasting:freeze", + "blockIn": { + "type": "hexcasting:block", + "block": "minecraft:packed_ice" + }, + "result": { + "Name": "minecraft:blue_ice" + } +} \ No newline at end of file diff --git a/Common/src/generated/resources/data/hexcasting/recipe/freeze/freeze/packed_ice.json b/Common/src/generated/resources/data/hexcasting/recipe/freeze/freeze/packed_ice.json new file mode 100644 index 000000000..cc60d0637 --- /dev/null +++ b/Common/src/generated/resources/data/hexcasting/recipe/freeze/freeze/packed_ice.json @@ -0,0 +1,10 @@ +{ + "type": "hexcasting:freeze", + "blockIn": { + "type": "hexcasting:block", + "block": "minecraft:ice" + }, + "result": { + "Name": "minecraft:packed_ice" + } +} \ No newline at end of file diff --git a/Common/src/generated/resources/data/hexcasting/recipe/freeze/freeze/powder_snow_cauldron.json b/Common/src/generated/resources/data/hexcasting/recipe/freeze/freeze/powder_snow_cauldron.json new file mode 100644 index 000000000..da6f3a7d3 --- /dev/null +++ b/Common/src/generated/resources/data/hexcasting/recipe/freeze/freeze/powder_snow_cauldron.json @@ -0,0 +1,13 @@ +{ + "type": "hexcasting:freeze", + "blockIn": { + "type": "hexcasting:block", + "block": "minecraft:water_cauldron" + }, + "result": { + "Name": "minecraft:powder_snow_cauldron", + "Properties": { + "level": "1" + } + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpFreeze.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpFreeze.kt new file mode 100644 index 000000000..1f8e7c24a --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpFreeze.kt @@ -0,0 +1,64 @@ +package at.petrak.hexcasting.common.casting.actions.spells + +import at.petrak.hexcasting.api.casting.* +import at.petrak.hexcasting.api.casting.castables.SpellAction +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.misc.MediaConstants +import at.petrak.hexcasting.common.recipe.CopyProperties +import at.petrak.hexcasting.common.recipe.HexRecipeStuffRegistry +import at.petrak.hexcasting.xplat.IXplatAbstractions +import net.minecraft.core.BlockPos +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.level.block.Blocks +import net.minecraft.world.level.block.LiquidBlock +import net.minecraft.world.level.material.Fluids +import net.minecraft.world.phys.Vec3 + +object OpFreeze : SpellAction { + override val argc = 1 + + override fun execute(args: List, env: CastingEnvironment): SpellAction.Result { + val toFreeze = Vec3.atCenterOf(BlockPos.containing(args.getVec3(0, argc))) + + env.assertVecInRange(toFreeze) + + return SpellAction.Result( + Spell(toFreeze), + MediaConstants.DUST_UNIT, + listOf(ParticleSpray.burst(toFreeze, 1.0)) + ) + } + + private data class Spell(val vec: Vec3) : RenderedSpell { + override fun cast(env: CastingEnvironment) { + val pos = BlockPos.containing(vec) + val blockState = env.world.getBlockState(pos) + val fluidState = env.world.getFluidState(pos) + + if (!env.canEditBlockAt(pos) || !IXplatAbstractions.INSTANCE.isBreakingAllowed(env.world, pos, blockState, env.castingEntity as? ServerPlayer)) + return + + if (fluidState.type == Fluids.WATER && blockState.block is LiquidBlock) { + env.world.setBlockAndUpdate(pos, Blocks.ICE.defaultBlockState()) + return + } + if (fluidState.type == Fluids.LAVA && blockState.block is LiquidBlock) { + env.world.setBlockAndUpdate(pos, Blocks.OBSIDIAN.defaultBlockState()) + return + } + if (fluidState.type == Fluids.FLOWING_LAVA && blockState.block is LiquidBlock) { + env.world.setBlockAndUpdate(pos, Blocks.COBBLESTONE.defaultBlockState()) + return + } + + val recman = env.world.recipeManager + val recipes = recman.getAllRecipesFor(HexRecipeStuffRegistry.FREEZE_TYPE).map{ holder -> holder.value } + + val recipe = recipes.find{ it.matches(blockState) } + + if (recipe != null) + env.world.setBlockAndUpdate(pos, CopyProperties.copyProperties(blockState, recipe.result)) + } + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/great/OpBrainsweep.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/great/OpBrainsweep.kt index 29730acca..2e0f9ade7 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/great/OpBrainsweep.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/great/OpBrainsweep.kt @@ -14,6 +14,7 @@ import at.petrak.hexcasting.api.casting.mishaps.MishapBadLocation import at.petrak.hexcasting.api.mod.HexConfig import at.petrak.hexcasting.api.mod.HexTags import at.petrak.hexcasting.common.recipe.BrainsweepRecipe +import at.petrak.hexcasting.common.recipe.CopyProperties import at.petrak.hexcasting.common.recipe.HexRecipeStuffRegistry import at.petrak.hexcasting.ktxt.tellWitnessesThatIWasMurdered import at.petrak.hexcasting.mixin.accessor.AccessorLivingEntity @@ -75,7 +76,7 @@ object OpBrainsweep : SpellAction { val recipe: BrainsweepRecipe ) : RenderedSpell { override fun cast(env: CastingEnvironment) { - env.world.setBlockAndUpdate(pos, BrainsweepRecipe.copyProperties(state, recipe.result)) + env.world.setBlockAndUpdate(pos, CopyProperties.copyProperties(state, recipe.result)) HexAPI.instance().brainsweep(sacrifice) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java index 6e65a78f0..33158c6fc 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java @@ -258,10 +258,6 @@ public class HexActions { Fluids.WATER))); public static final ActionRegistryEntry DESTROY_WATER = make("destroy_water", new ActionRegistryEntry(HexPattern.fromAngles("dedwedade", HexDir.SOUTH_WEST), OpDestroyFluid.INSTANCE)); - public static final ActionRegistryEntry IGNITE = make("ignite", - new ActionRegistryEntry(HexPattern.fromAngles("aaqawawa", HexDir.SOUTH_EAST), OpIgnite.INSTANCE)); - public static final ActionRegistryEntry EXTINGUISH = make("extinguish", - new ActionRegistryEntry(HexPattern.fromAngles("ddedwdwd", HexDir.SOUTH_WEST), OpExtinguish.INSTANCE)); public static final ActionRegistryEntry CONJURE_BLOCK = make("conjure_block", new ActionRegistryEntry(HexPattern.fromAngles("qqa", HexDir.NORTH_EAST), new OpConjureBlock(false))); public static final ActionRegistryEntry CONJURE_LIGHT = make("conjure_light", @@ -278,6 +274,13 @@ public class HexActions { public static final ActionRegistryEntry FALLING_BLOCK = make("falling_block", new ActionRegistryEntry(HexPattern.fromAngles("wqwawqwqwqwqwqw", HexDir.EAST), OpFallingBlock.INSTANCE)); + public static final ActionRegistryEntry IGNITE = make("ignite", + new ActionRegistryEntry(HexPattern.fromAngles("aaqawawa", HexDir.SOUTH_EAST), OpIgnite.INSTANCE)); + public static final ActionRegistryEntry EXTINGUISH = make("extinguish", + new ActionRegistryEntry(HexPattern.fromAngles("ddedwdwd", HexDir.SOUTH_WEST), OpExtinguish.INSTANCE)); + public static final ActionRegistryEntry FREEZE = make("freeze", + new ActionRegistryEntry(HexPattern.fromAngles("weeeweedada", HexDir.WEST), OpFreeze.INSTANCE)); + public static final ActionRegistryEntry BEEP = make("beep", new ActionRegistryEntry(HexPattern.fromAngles("adaa", HexDir.WEST), OpBeep.INSTANCE)); diff --git a/Common/src/main/java/at/petrak/hexcasting/common/recipe/BrainsweepRecipe.java b/Common/src/main/java/at/petrak/hexcasting/common/recipe/BrainsweepRecipe.java index a9f8ea338..844fccf2d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/recipe/BrainsweepRecipe.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/recipe/BrainsweepRecipe.java @@ -67,19 +67,6 @@ public ItemStack getResultItem(HolderLookup.Provider registries) { return ItemStack.EMPTY.copy(); } - // Because kotlin doesn't like doing raw, unchecked types - // Can't blame it, but that's what we need to do - @SuppressWarnings({"rawtypes", "unchecked"}) - public static BlockState copyProperties(BlockState original, BlockState copyTo) { - for (Property prop : original.getProperties()) { - if (copyTo.hasProperty(prop)) { - copyTo = copyTo.setValue(prop, original.getValue(prop)); - } - } - - return copyTo; - } - public static class Serializer extends RecipeSerializerBase { public static MapCodec CODEC = RecordCodecBuilder.mapCodec(inst -> inst.group( diff --git a/Common/src/main/java/at/petrak/hexcasting/common/recipe/CopyProperties.java b/Common/src/main/java/at/petrak/hexcasting/common/recipe/CopyProperties.java new file mode 100644 index 000000000..0cf3fca47 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/recipe/CopyProperties.java @@ -0,0 +1,19 @@ +package at.petrak.hexcasting.common.recipe; + +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; + +public class CopyProperties { + // Because kotlin doesn't like doing raw, unchecked types + // Can't blame it, but that's what we need to do + @SuppressWarnings({"rawtypes", "unchecked"}) + public static BlockState copyProperties(BlockState original, BlockState copyTo) { + for (Property prop : original.getProperties()) { + if (copyTo.hasProperty(prop)) { + copyTo = copyTo.setValue(prop, original.getValue(prop)); + } + } + + return copyTo; + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/common/recipe/FreezeRecipe.java b/Common/src/main/java/at/petrak/hexcasting/common/recipe/FreezeRecipe.java new file mode 100644 index 000000000..2979527f3 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/recipe/FreezeRecipe.java @@ -0,0 +1,85 @@ +package at.petrak.hexcasting.common.recipe; + +import at.petrak.hexcasting.common.lib.HexStateIngredients; +import at.petrak.hexcasting.common.recipe.ingredient.state.StateIngredient; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.HolderLookup; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeInput; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.NotNull; + +public record FreezeRecipe( + StateIngredient blockIn, + BlockState result +) implements Recipe { + public boolean matches(BlockState blockIn) { + return this.blockIn.test(blockIn); + } + + @Override + public RecipeType getType() { + return HexRecipeStuffRegistry.FREEZE_TYPE; + } + + @Override + public RecipeSerializer getSerializer() { + return HexRecipeStuffRegistry.FREEZE; + } + + // in order to get this to be a "Recipe" we need to do a lot of bending-over-backwards + // to get the implementation to be satisfied even though we never use it + @Override + public boolean matches(RecipeInput input, Level level) { + return false; + } + + @Override + public ItemStack assemble(RecipeInput input, HolderLookup.Provider registries) { + return ItemStack.EMPTY; + } + + @Override + public boolean canCraftInDimensions(int pWidth, int pHeight) { + return false; + } + + @Override + public ItemStack getResultItem(HolderLookup.Provider registries) { + return ItemStack.EMPTY.copy(); + } + + public static class Serializer extends RecipeSerializerBase { + public static MapCodec CODEC = RecordCodecBuilder.mapCodec(inst -> + inst.group( + HexStateIngredients.TYPED_CODEC.fieldOf("blockIn").forGetter(FreezeRecipe::blockIn), + BlockState.CODEC.fieldOf("result").forGetter(FreezeRecipe::result) + ).apply(inst, FreezeRecipe::new) + ); + public static StreamCodec STREAM_CODEC = StreamCodec.composite( + HexStateIngredients.TYPED_STREAM_CODEC, FreezeRecipe::blockIn, + ByteBufCodecs.VAR_INT, (recipe) -> Block.getId(recipe.result), + (state, stateId) -> + new FreezeRecipe(state, Block.stateById(stateId)) + ); + + @Override + public @NotNull MapCodec codec() { + return CODEC; + } + + @Override + public @NotNull StreamCodec streamCodec() { + return STREAM_CODEC; + } + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/common/recipe/HexRecipeStuffRegistry.java b/Common/src/main/java/at/petrak/hexcasting/common/recipe/HexRecipeStuffRegistry.java index cc2ffbba0..2dfc941d3 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/recipe/HexRecipeStuffRegistry.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/recipe/HexRecipeStuffRegistry.java @@ -30,12 +30,15 @@ public static void registerTypes(BiConsumer, ResourceLocation> r) public static final RecipeSerializer BRAINSWEEP = registerSerializer("brainsweep", new BrainsweepRecipe.Serializer()); + public static final RecipeSerializer FREEZE = registerSerializer("freeze", + new FreezeRecipe.Serializer()); public static final RecipeSerializer SEAL_FOCUS = registerSerializer( "seal_focus", SealThingsRecipe.FOCUS_SERIALIZER); public static final RecipeSerializer SEAL_SPELLBOOK = registerSerializer( "seal_spellbook", SealThingsRecipe.SPELLBOOK_SERIALIZER); public static RecipeType BRAINSWEEP_TYPE = registerType("brainsweep"); + public static RecipeType FREEZE_TYPE = registerType("freeze"); private static > RecipeSerializer registerSerializer(String name, RecipeSerializer rs) { var old = SERIALIZERS.put(modLoc(name), rs); diff --git a/Common/src/main/java/at/petrak/hexcasting/datagen/recipe/HexplatRecipes.java b/Common/src/main/java/at/petrak/hexcasting/datagen/recipe/HexplatRecipes.java index 35e4ab202..02bd956bb 100644 --- a/Common/src/main/java/at/petrak/hexcasting/datagen/recipe/HexplatRecipes.java +++ b/Common/src/main/java/at/petrak/hexcasting/datagen/recipe/HexplatRecipes.java @@ -19,6 +19,7 @@ import at.petrak.hexcasting.datagen.recipe.builders.BrainsweepRecipeBuilder; import at.petrak.hexcasting.datagen.recipe.builders.CreateCrushingRecipeBuilder; import at.petrak.hexcasting.datagen.recipe.builders.FarmersDelightCuttingRecipeBuilder; +import at.petrak.hexcasting.datagen.recipe.builders.FreezeRecipeBuilder; import at.petrak.paucal.api.PaucalAPI; import at.petrak.paucal.api.datagen.PaucalAdvancementSubProvider; import net.minecraft.advancements.CriteriaTriggers; @@ -469,6 +470,16 @@ public void buildRecipes(RecipeOutput recipes) { .requires(HexBlocks.AMETHYST_PILLAR) .unlockedBy("has_item", has(HexBlocks.SLATE)).save(recipes); + new FreezeRecipeBuilder(HexStateIngredients.of(Blocks.ICE), Blocks.PACKED_ICE.defaultBlockState()) + .unlockedBy("has_item", hasItem(HexTags.Items.STAVES)) + .save(recipes, modLoc("freeze/packed_ice")); + new FreezeRecipeBuilder(HexStateIngredients.of(Blocks.PACKED_ICE), Blocks.BLUE_ICE.defaultBlockState()) + .unlockedBy("has_item", hasItem(HexTags.Items.STAVES)) + .save(recipes, modLoc("freeze/blue_ice")); + new FreezeRecipeBuilder(HexStateIngredients.of(Blocks.WATER_CAULDRON), Blocks.POWDER_SNOW_CAULDRON.defaultBlockState()) + .unlockedBy("has_item", hasItem(HexTags.Items.STAVES)) + .save(recipes, modLoc("freeze/powder_snow_cauldron")); + new BrainsweepRecipeBuilder(HexStateIngredients.of(Blocks.AMETHYST_BLOCK), new VillagerIngredient(null, null, 3), Blocks.BUDDING_AMETHYST.defaultBlockState(), MediaConstants.CRYSTAL_UNIT * 10) diff --git a/Common/src/main/java/at/petrak/hexcasting/datagen/recipe/builders/FreezeRecipeBuilder.java b/Common/src/main/java/at/petrak/hexcasting/datagen/recipe/builders/FreezeRecipeBuilder.java new file mode 100644 index 000000000..4193f007b --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/datagen/recipe/builders/FreezeRecipeBuilder.java @@ -0,0 +1,58 @@ +package at.petrak.hexcasting.datagen.recipe.builders; + +import at.petrak.hexcasting.common.recipe.FreezeRecipe; +import at.petrak.hexcasting.common.recipe.ingredient.state.StateIngredient; +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementRequirements; +import net.minecraft.advancements.AdvancementRewards; +import net.minecraft.advancements.Criterion; +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger; +import net.minecraft.data.recipes.RecipeBuilder; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class FreezeRecipeBuilder implements RecipeBuilder { + private final StateIngredient blockIn; + private final BlockState result; + + private final Map> criteria = new LinkedHashMap<>(); + + public FreezeRecipeBuilder(StateIngredient blockIn, BlockState result) { + this.blockIn = blockIn; + this.result = result; + } + + @Override + public RecipeBuilder unlockedBy(String pCriterionName, Criterion pCriterionTrigger) { + criteria.put(pCriterionName, pCriterionTrigger); + return this; + } + + @Override + public RecipeBuilder group(@Nullable String pGroupName) { + return this; + } + + @Override + public Item getResult() { + return this.result.getBlock().asItem(); + } + + @Override + public void save(RecipeOutput recipeOutput, ResourceLocation id) { + Advancement.Builder advancement = recipeOutput.advancement() + .addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(id)) + .rewards(AdvancementRewards.Builder.recipe(id)) + .requirements(AdvancementRequirements.Strategy.OR); + this.criteria.forEach(advancement::addCriterion); + + var recipe = new FreezeRecipe(blockIn, result); + recipeOutput.accept(id.withPrefix("freeze/"), recipe, advancement.build(id.withPrefix("recipes/freeze/"))); + } +} diff --git a/Fabric/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/blue_ice.json b/Fabric/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/blue_ice.json new file mode 100644 index 000000000..db092ef28 --- /dev/null +++ b/Fabric/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/blue_ice.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_item": { + "conditions": { + "items": [ + { + "items": "#hexcasting:staves" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "hexcasting:freeze/blue_ice" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_item" + ] + ], + "rewards": { + "recipes": [ + "hexcasting:freeze/blue_ice" + ] + } +} \ No newline at end of file diff --git a/Fabric/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/packed_ice.json b/Fabric/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/packed_ice.json new file mode 100644 index 000000000..ccded8855 --- /dev/null +++ b/Fabric/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/packed_ice.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_item": { + "conditions": { + "items": [ + { + "items": "#hexcasting:staves" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "hexcasting:freeze/packed_ice" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_item" + ] + ], + "rewards": { + "recipes": [ + "hexcasting:freeze/packed_ice" + ] + } +} \ No newline at end of file diff --git a/Fabric/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/powder_snow_cauldron.json b/Fabric/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/powder_snow_cauldron.json new file mode 100644 index 000000000..1e49ebee8 --- /dev/null +++ b/Fabric/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/powder_snow_cauldron.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_item": { + "conditions": { + "items": [ + { + "items": "#hexcasting:staves" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "hexcasting:freeze/powder_snow_cauldron" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_item" + ] + ], + "rewards": { + "recipes": [ + "hexcasting:freeze/powder_snow_cauldron" + ] + } +} \ No newline at end of file diff --git a/Fabric/src/generated/resources/data/hexcasting/recipe/freeze/freeze/blue_ice.json b/Fabric/src/generated/resources/data/hexcasting/recipe/freeze/freeze/blue_ice.json new file mode 100644 index 000000000..8106c7878 --- /dev/null +++ b/Fabric/src/generated/resources/data/hexcasting/recipe/freeze/freeze/blue_ice.json @@ -0,0 +1,10 @@ +{ + "type": "hexcasting:freeze", + "blockIn": { + "type": "hexcasting:block", + "block": "minecraft:packed_ice" + }, + "result": { + "Name": "minecraft:blue_ice" + } +} \ No newline at end of file diff --git a/Fabric/src/generated/resources/data/hexcasting/recipe/freeze/freeze/packed_ice.json b/Fabric/src/generated/resources/data/hexcasting/recipe/freeze/freeze/packed_ice.json new file mode 100644 index 000000000..cc60d0637 --- /dev/null +++ b/Fabric/src/generated/resources/data/hexcasting/recipe/freeze/freeze/packed_ice.json @@ -0,0 +1,10 @@ +{ + "type": "hexcasting:freeze", + "blockIn": { + "type": "hexcasting:block", + "block": "minecraft:ice" + }, + "result": { + "Name": "minecraft:packed_ice" + } +} \ No newline at end of file diff --git a/Fabric/src/generated/resources/data/hexcasting/recipe/freeze/freeze/powder_snow_cauldron.json b/Fabric/src/generated/resources/data/hexcasting/recipe/freeze/freeze/powder_snow_cauldron.json new file mode 100644 index 000000000..da6f3a7d3 --- /dev/null +++ b/Fabric/src/generated/resources/data/hexcasting/recipe/freeze/freeze/powder_snow_cauldron.json @@ -0,0 +1,13 @@ +{ + "type": "hexcasting:freeze", + "blockIn": { + "type": "hexcasting:block", + "block": "minecraft:water_cauldron" + }, + "result": { + "Name": "minecraft:powder_snow_cauldron", + "Properties": { + "level": "1" + } + } +} \ No newline at end of file diff --git a/Neoforge/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/blue_ice.json b/Neoforge/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/blue_ice.json new file mode 100644 index 000000000..db092ef28 --- /dev/null +++ b/Neoforge/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/blue_ice.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_item": { + "conditions": { + "items": [ + { + "items": "#hexcasting:staves" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "hexcasting:freeze/blue_ice" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_item" + ] + ], + "rewards": { + "recipes": [ + "hexcasting:freeze/blue_ice" + ] + } +} \ No newline at end of file diff --git a/Neoforge/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/packed_ice.json b/Neoforge/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/packed_ice.json new file mode 100644 index 000000000..ccded8855 --- /dev/null +++ b/Neoforge/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/packed_ice.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_item": { + "conditions": { + "items": [ + { + "items": "#hexcasting:staves" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "hexcasting:freeze/packed_ice" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_item" + ] + ], + "rewards": { + "recipes": [ + "hexcasting:freeze/packed_ice" + ] + } +} \ No newline at end of file diff --git a/Neoforge/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/powder_snow_cauldron.json b/Neoforge/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/powder_snow_cauldron.json new file mode 100644 index 000000000..1e49ebee8 --- /dev/null +++ b/Neoforge/src/generated/resources/data/hexcasting/advancement/recipes/freeze/freeze/powder_snow_cauldron.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_item": { + "conditions": { + "items": [ + { + "items": "#hexcasting:staves" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "hexcasting:freeze/powder_snow_cauldron" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_item" + ] + ], + "rewards": { + "recipes": [ + "hexcasting:freeze/powder_snow_cauldron" + ] + } +} \ No newline at end of file diff --git a/Neoforge/src/generated/resources/data/hexcasting/recipe/freeze/freeze/blue_ice.json b/Neoforge/src/generated/resources/data/hexcasting/recipe/freeze/freeze/blue_ice.json new file mode 100644 index 000000000..8106c7878 --- /dev/null +++ b/Neoforge/src/generated/resources/data/hexcasting/recipe/freeze/freeze/blue_ice.json @@ -0,0 +1,10 @@ +{ + "type": "hexcasting:freeze", + "blockIn": { + "type": "hexcasting:block", + "block": "minecraft:packed_ice" + }, + "result": { + "Name": "minecraft:blue_ice" + } +} \ No newline at end of file diff --git a/Neoforge/src/generated/resources/data/hexcasting/recipe/freeze/freeze/packed_ice.json b/Neoforge/src/generated/resources/data/hexcasting/recipe/freeze/freeze/packed_ice.json new file mode 100644 index 000000000..cc60d0637 --- /dev/null +++ b/Neoforge/src/generated/resources/data/hexcasting/recipe/freeze/freeze/packed_ice.json @@ -0,0 +1,10 @@ +{ + "type": "hexcasting:freeze", + "blockIn": { + "type": "hexcasting:block", + "block": "minecraft:ice" + }, + "result": { + "Name": "minecraft:packed_ice" + } +} \ No newline at end of file diff --git a/Neoforge/src/generated/resources/data/hexcasting/recipe/freeze/freeze/powder_snow_cauldron.json b/Neoforge/src/generated/resources/data/hexcasting/recipe/freeze/freeze/powder_snow_cauldron.json new file mode 100644 index 000000000..da6f3a7d3 --- /dev/null +++ b/Neoforge/src/generated/resources/data/hexcasting/recipe/freeze/freeze/powder_snow_cauldron.json @@ -0,0 +1,13 @@ +{ + "type": "hexcasting:freeze", + "blockIn": { + "type": "hexcasting:block", + "block": "minecraft:water_cauldron" + }, + "result": { + "Name": "minecraft:powder_snow_cauldron", + "Properties": { + "level": "1" + } + } +} \ No newline at end of file From 346999b0db65544183b55a8348feb5a7bf008837 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Tue, 23 Jun 2026 22:17:53 -0400 Subject: [PATCH 3/8] Implement smelt --- .../common/casting/actions/spells/OpSmelt.kt | 104 ++++++++++++++++++ .../hexcasting/common/lib/hex/HexActions.java | 2 + 2 files changed, 106 insertions(+) create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpSmelt.kt diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpSmelt.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpSmelt.kt new file mode 100644 index 000000000..41b406a49 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpSmelt.kt @@ -0,0 +1,104 @@ +package at.petrak.hexcasting.common.casting.actions.spells + +import at.petrak.hexcasting.api.casting.* +import at.petrak.hexcasting.api.casting.castables.SpellAction +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.iota.EntityIota +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.iota.Vec3Iota +import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidIota +import at.petrak.hexcasting.api.misc.MediaConstants +import at.petrak.hexcasting.xplat.IXplatAbstractions +import net.minecraft.core.BlockPos +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.item.ItemEntity +import net.minecraft.world.item.BlockItem +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.RecipeHolder +import net.minecraft.world.item.crafting.RecipeType +import net.minecraft.world.item.crafting.SingleRecipeInput +import net.minecraft.world.item.crafting.SmeltingRecipe +import net.minecraft.world.phys.Vec3 +import java.util.* +import kotlin.math.roundToLong + +object OpSmelt : SpellAction { + override val argc = 1 + + override fun execute(args: List, env: CastingEnvironment): SpellAction.Result { + when (val target = args[0]) { + is EntityIota -> { + val itemEntity = args.getItemEntity(env.world, 0, argc) + env.assertEntityInRange(itemEntity) + return SpellAction.Result( + ItemSpell(itemEntity), + (itemEntity.item.count * 0.75 * MediaConstants.DUST_UNIT).roundToLong(), + listOf(ParticleSpray.burst(itemEntity.position(), 1.0)) + ) + } + is Vec3Iota -> { + val pos = args.getBlockPos(0, argc) + env.assertPosInRangeForEditing(pos) + return SpellAction.Result( + BlockSpell(pos), + (0.75 * MediaConstants.DUST_UNIT).roundToLong(), + listOf(ParticleSpray.burst(Vec3.atCenterOf(pos), 1.0)) + ) + } + else -> throw MishapInvalidIota.ofType(target, 0, "entity_or_vector") + } + } + + fun smeltResult(item: Item, env: CastingEnvironment): ItemStack? { + val optional: Optional> = env.world.recipeManager.getRecipeFor( + RecipeType.SMELTING, SingleRecipeInput(ItemStack(item, 1)), + env.world + ) + + if (!optional.isPresent) return null + + val result = optional.get().value.getResultItem(env.world.registryAccess()).copy() + + if (result.isEmpty) return null + + return result + } + + private data class ItemSpell(val itemEntity: ItemEntity) : RenderedSpell { + override fun cast(env: CastingEnvironment) { + val result = smeltResult(itemEntity.item.item, env) ?: return // cursed .item.item to map from ItemEntity to ItemLike to ItemStack + + result.count *= itemEntity.item.count + + env.world.addFreshEntity(ItemEntity(env.world, itemEntity.x, itemEntity.y, itemEntity.z, result.copy())) + itemEntity.remove(Entity.RemovalReason.DISCARDED) + } + } + + private data class BlockSpell(val pos: BlockPos) : RenderedSpell { + override fun cast(env: CastingEnvironment) { + if (!env.canEditBlockAt(pos)) return + val blockState = env.world.getBlockState(pos) + if (!IXplatAbstractions.INSTANCE.isBreakingAllowed(env.world, pos, blockState, env.castingEntity as? ServerPlayer)) return + + val itemStack = smeltResult(blockState.block.asItem(), env) ?: return + + if (itemStack.item is BlockItem) { + env.world.setBlockAndUpdate(pos, (itemStack.item as BlockItem).block.defaultBlockState()) + + if (itemStack.count > 1) { + itemStack.count -= 1 + env.world.addFreshEntity(ItemEntity(env.world, pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble(), itemStack.copy())) + } + } else { + env.world.destroyBlock(pos, false, env.caster) + env.world.addFreshEntity(ItemEntity(env.world, pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble(), itemStack.copy())) + // Send a block update, also copied from Ars Nouveau (this is all copied from Ars Nouveau) + if (!env.world.isOutsideBuildHeight(pos)) + env.world.sendBlockUpdated(pos, env.world.getBlockState(pos), env.world.getBlockState(pos), 3) // don't know how this works + } + } + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java index 33158c6fc..108df01ca 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java @@ -278,6 +278,8 @@ public class HexActions { new ActionRegistryEntry(HexPattern.fromAngles("aaqawawa", HexDir.SOUTH_EAST), OpIgnite.INSTANCE)); public static final ActionRegistryEntry EXTINGUISH = make("extinguish", new ActionRegistryEntry(HexPattern.fromAngles("ddedwdwd", HexDir.SOUTH_WEST), OpExtinguish.INSTANCE)); + public static final ActionRegistryEntry SMELT = make("smelt", + new ActionRegistryEntry(HexPattern.fromAngles("wqqqwqqadad", HexDir.EAST), OpSmelt.INSTANCE)); public static final ActionRegistryEntry FREEZE = make("freeze", new ActionRegistryEntry(HexPattern.fromAngles("weeeweedada", HexDir.WEST), OpFreeze.INSTANCE)); From 4414300215a66e703d08a6da034faf70a1eb89f0 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:10:10 -0400 Subject: [PATCH 4/8] Implement particles --- .../hexcasting/api/casting/ActionUtils.kt | 19 ++++++ .../petrak/hexcasting/api/utils/HexUtils.kt | 13 ++++ .../casting/actions/spells/OpParticles.kt | 62 +++++++++++++++++++ .../hexcasting/common/lib/hex/HexActions.java | 2 + .../common/msgs/MsgParticleLinesS2C.java | 56 +++++++++++++++++ .../common/msgs/MsgSingleParticleS2C.java | 54 ++++++++++++++++ .../fabric/network/FabricPacketHandler.java | 2 + .../forge/network/ForgePacketHandler.java | 4 ++ 8 files changed, 212 insertions(+) create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpParticles.kt create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgParticleLinesS2C.java create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgSingleParticleS2C.java diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt index d67e0693b..9331605dc 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt @@ -271,6 +271,25 @@ fun List.getNumOrVec(idx: Int, argc: Int = 0): Either { } } +fun List.getVecOrVecList(idx: Int, argc: Int = 0): Either> { + val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } + return when (x) { + is Vec3Iota -> Either.left(x.vec3) + is ListIota -> { + val out = mutableListOf() + for (v in x.list) { + if (v is Vec3Iota) { + out.add(v.vec3) + } else { + throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "veclist") + } + } + Either.right(out) + } + else -> throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "veclist") + } +} + fun List.getLongOrList(idx: Int, argc: Int = 0): Either { val datum = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) } if (datum is DoubleIota) { diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt b/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt index aa4999873..776390a8e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/HexUtils.kt @@ -8,6 +8,7 @@ import at.petrak.hexcasting.api.casting.iota.ListIota import at.petrak.hexcasting.api.casting.iota.NullIota import at.petrak.hexcasting.api.casting.math.HexCoord import at.petrak.hexcasting.api.casting.validateSubIotas +import at.petrak.hexcasting.api.pigment.FrozenPigment import net.minecraft.ChatFormatting import net.minecraft.core.HolderLookup import net.minecraft.core.Registry @@ -19,6 +20,7 @@ import net.minecraft.resources.ResourceKey import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerLevel import net.minecraft.tags.TagKey +import net.minecraft.util.RandomSource import net.minecraft.world.InteractionHand import net.minecraft.world.item.ItemStack import net.minecraft.world.phys.Vec2 @@ -64,6 +66,17 @@ fun vec2FromNBT(tag: LongArray): Vec2 = if (tag.size != 2) Vec2.ZERO else Double.fromBits(tag[1]).toFloat(), ) +fun FrozenPigment.nextColor(random: RandomSource): Int { + return colorProvider.getColor( + random.nextFloat() * 16384, + Vec3( + random.nextFloat().toDouble(), + random.nextFloat().toDouble(), + random.nextFloat().toDouble() + ).scale((random.nextFloat() * 3).toDouble()) + ) +} + fun otherHand(hand: InteractionHand) = if (hand == InteractionHand.MAIN_HAND) InteractionHand.OFF_HAND else InteractionHand.MAIN_HAND diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpParticles.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpParticles.kt new file mode 100644 index 000000000..9c79de3a3 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpParticles.kt @@ -0,0 +1,62 @@ +package at.petrak.hexcasting.common.casting.actions.spells + +import at.petrak.hexcasting.api.casting.RenderedSpell +import at.petrak.hexcasting.api.casting.castables.SpellAction +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.getVecOrVecList +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.mishaps.MishapBadLocation +import at.petrak.hexcasting.api.misc.MediaConstants +import at.petrak.hexcasting.common.msgs.MsgParticleLinesS2C +import at.petrak.hexcasting.common.msgs.MsgSingleParticleS2C +import at.petrak.hexcasting.xplat.IXplatAbstractions +import com.mojang.datafixers.util.Either +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.phys.Vec3 +import kotlin.math.roundToLong + +object OpParticles : SpellAction { + override val argc = 1 + + override fun execute(args: List, env: CastingEnvironment): SpellAction.Result { + val loc = args.getVecOrVecList(0, argc) + + // assert all locs in ambit. + loc.map({ env.assertVecInRange(it) }, { assertVecListInRange(env, it, 32.0) }) + + return SpellAction.Result( + Spell(loc), + loc.map({ 0.002 * MediaConstants.DUST_UNIT }, { it.size * 0.002 * MediaConstants.DUST_UNIT }).roundToLong(), + listOf() + ) + } + + fun assertVecListInRange(env: CastingEnvironment, list: List, intraRange: Double) { + for (vec in list) { + env.assertVecInRange(vec) + } + + val sqrRange = intraRange * intraRange + + for (i in list.indices) { + for (j in i until list.size) { + if (list[i].distanceToSqr(list[j]) > sqrRange) throw MishapBadLocation(list[j]) + } + } + } + + data class Spell(val loc: Either>) : RenderedSpell { + override fun cast(env: CastingEnvironment) { + val colouriser = IXplatAbstractions.INSTANCE.getPigment(env.castingEntity as? ServerPlayer) + + loc.map({ + IXplatAbstractions.INSTANCE.sendPacketNear(it, 128.0, env.world, MsgSingleParticleS2C(it, colouriser)) + }, { + if (it.isNotEmpty()) { + val first = it[0] + IXplatAbstractions.INSTANCE.sendPacketNear(first, 128.0, env.world, MsgParticleLinesS2C(it, colouriser)) + } + }) + } + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java index 108df01ca..e7460a172 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java @@ -285,6 +285,8 @@ public class HexActions { public static final ActionRegistryEntry BEEP = make("beep", new ActionRegistryEntry(HexPattern.fromAngles("adaa", HexDir.WEST), OpBeep.INSTANCE)); + public static final ActionRegistryEntry PARTICLES = make("particles", + new ActionRegistryEntry(HexPattern.fromAngles("eqqqqa", HexDir.NORTH_EAST), OpParticles.INSTANCE)); public static final ActionRegistryEntry CRAFT$CYPHER = make("craft/cypher", new ActionRegistryEntry( HexPattern.fromAngles("waqqqqq", HexDir.EAST), diff --git a/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgParticleLinesS2C.java b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgParticleLinesS2C.java new file mode 100644 index 000000000..0cc1e19ea --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgParticleLinesS2C.java @@ -0,0 +1,56 @@ +package at.petrak.hexcasting.common.msgs; + +import at.petrak.hexcasting.api.HexAPI; +import at.petrak.hexcasting.api.pigment.FrozenPigment; +import at.petrak.hexcasting.api.utils.HexUtils; +import at.petrak.hexcasting.common.particles.ConjureParticleOptions; +import at.petrak.paucal.api.PaucalCodecs; +import net.minecraft.client.Minecraft; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.world.phys.Vec3; + +import java.util.List; + +public record MsgParticleLinesS2C(List locs, FrozenPigment colorizer) implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(HexAPI.modLoc("prtcl_l")); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + PaucalCodecs.VEC3.apply(ByteBufCodecs.list()), MsgParticleLinesS2C::locs, + FrozenPigment.STREAM_CODEC, MsgParticleLinesS2C::colorizer, + MsgParticleLinesS2C::new + ); + + @Override + public Type type() { + return TYPE; + } + + public void handle() { + MsgParticleLinesS2C.Handler.handle(this); + } + + public static final class Handler { + + public static void handle(MsgParticleLinesS2C msg) { + Minecraft.getInstance().execute(() -> { + var level = Minecraft.getInstance().level; + if (level == null) return; + + for (int i = 0; i < msg.locs.size()-1; ++i) { + Vec3 start = msg.locs.get(i); + Vec3 end = msg.locs.get(i+1); + int steps = (int) (end.subtract(start).length() * 10); + for (int j = 0; j <= steps; ++j) { + Vec3 pos = start.add(end.subtract(start).scale((double) j / steps)); + var color = HexUtils.nextColor(msg.colorizer, level.random); + level.addParticle(new ConjureParticleOptions(color), + pos.x, pos.y, pos.z, 0.0, 0.0, 0.0); + } + } + }); + } + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgSingleParticleS2C.java b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgSingleParticleS2C.java new file mode 100644 index 000000000..0c03aa92b --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgSingleParticleS2C.java @@ -0,0 +1,54 @@ +package at.petrak.hexcasting.common.msgs; + +import at.petrak.hexcasting.api.HexAPI; +import at.petrak.hexcasting.api.utils.HexUtils; +import at.petrak.hexcasting.api.pigment.FrozenPigment; +import at.petrak.hexcasting.common.particles.ConjureParticleOptions; +import at.petrak.paucal.api.PaucalCodecs; +import net.minecraft.client.Minecraft; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.world.phys.Vec3; + +import java.util.Random; + +public record MsgSingleParticleS2C(Vec3 pos, FrozenPigment colorizer) implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(HexAPI.modLoc("prtcl_s")); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + PaucalCodecs.VEC3, MsgSingleParticleS2C::pos, + FrozenPigment.STREAM_CODEC, MsgSingleParticleS2C::colorizer, + MsgSingleParticleS2C::new + ); + + @Override + public Type type() { + return TYPE; + } + + public void handle() { + MsgSingleParticleS2C.Handler.handle(this); + } + + public static final class Handler { + + public static void handle(MsgSingleParticleS2C msg) { + Minecraft.getInstance().execute(() -> { + var level = Minecraft.getInstance().level; + if (level == null) return; + var color = HexUtils.nextColor(msg.colorizer, level.random); + level.addParticle(new ConjureParticleOptions(color), + msg.pos.x, msg.pos.y, msg.pos.z, 0.0, 0.0, 0.0); + for (int i = 0; i <= 10; i++) { + color = HexUtils.nextColor(msg.colorizer, level.random); + double offsetX = level.random.nextFloat() * 0.1 - 0.05; + double offsetY = level.random.nextFloat() * 0.1 - 0.05; + double offsetZ = level.random.nextFloat() * 0.1 - 0.05; + level.addParticle(new ConjureParticleOptions(color), + msg.pos.x + offsetX, msg.pos.y + offsetY, msg.pos.z + offsetZ, 0.0, 0.0, 0.0); + } + }); + } + } +} diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/network/FabricPacketHandler.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/network/FabricPacketHandler.java index c7e2346e9..d6d866f34 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/network/FabricPacketHandler.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/network/FabricPacketHandler.java @@ -24,6 +24,8 @@ public static void initPackets() { PayloadTypeRegistry.playS2C().register(MsgNewWallScrollS2C.TYPE, MsgNewWallScrollS2C.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(MsgRecalcWallScrollDisplayS2C.TYPE, MsgRecalcWallScrollDisplayS2C.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(MsgNewSpiralPatternsS2C.TYPE, MsgNewSpiralPatternsS2C.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(MsgSingleParticleS2C.TYPE, MsgSingleParticleS2C.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(MsgParticleLinesS2C.TYPE, MsgParticleLinesS2C.STREAM_CODEC); } public static void init() { diff --git a/Neoforge/src/main/java/at/petrak/hexcasting/forge/network/ForgePacketHandler.java b/Neoforge/src/main/java/at/petrak/hexcasting/forge/network/ForgePacketHandler.java index c53a796a4..66e9b58b6 100644 --- a/Neoforge/src/main/java/at/petrak/hexcasting/forge/network/ForgePacketHandler.java +++ b/Neoforge/src/main/java/at/petrak/hexcasting/forge/network/ForgePacketHandler.java @@ -49,6 +49,10 @@ public static void init(IEventBus modBus) { makeClientBoundHandler(MsgNewSpiralPatternsS2C::handle)); registar.playToClient(MsgClearSpiralPatternsS2C.TYPE, MsgClearSpiralPatternsS2C.STREAM_CODEC, makeClientBoundHandler(MsgClearSpiralPatternsS2C::handle)); + registar.playToClient(MsgSingleParticleS2C.TYPE, MsgSingleParticleS2C.STREAM_CODEC, + makeClientBoundHandler(MsgSingleParticleS2C::handle)); + registar.playToClient(MsgParticleLinesS2C.TYPE, MsgParticleLinesS2C.STREAM_CODEC, + makeClientBoundHandler(MsgParticleLinesS2C::handle)); }); } From b9399f1aed9e2f1c30a23e3ece98f05beab30e8c Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Wed, 24 Jun 2026 01:01:50 -0400 Subject: [PATCH 5/8] Implement factorial --- .../api/casting/arithmetic/Arithmetic.java | 1 + .../casting/arithmetic/DoubleArithmetic.kt | 5 +- .../casting/arithmetic/Vec3Arithmetic.java | 6 +- .../arithmetic/operator/OperatorFactorial.kt | 60 +++++++++++++++++++ .../hexcasting/common/lib/hex/HexActions.java | 2 + 5 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/operator/OperatorFactorial.kt diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/Arithmetic.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/Arithmetic.java index 10f9f9cd6..cecd0cc8f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/Arithmetic.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/Arithmetic.java @@ -41,6 +41,7 @@ public interface Arithmetic { HexPattern ARCTAN2 = HexPattern.fromAngles("deadeeeeewd", HexDir.WEST); HexPattern LOG = HexPattern.fromAngles("eqaqe", HexDir.NORTH_WEST); HexPattern MOD = HexPattern.fromAngles("addwaad", HexDir.NORTH_EAST); + HexPattern FACT = HexPattern.fromAngles("wawdedwaw", HexDir.SOUTH_EAST); // Vecs diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/DoubleArithmetic.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/DoubleArithmetic.kt index 075a17a76..9aadf9295 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/DoubleArithmetic.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/DoubleArithmetic.kt @@ -12,6 +12,7 @@ import at.petrak.hexcasting.api.casting.iota.DoubleIota import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.math.HexPattern import at.petrak.hexcasting.api.casting.mishaps.MishapDivideByZero +import at.petrak.hexcasting.common.casting.arithmetic.operator.OperatorFactorial import at.petrak.hexcasting.common.casting.arithmetic.operator.OperatorLog import at.petrak.hexcasting.common.casting.arithmetic.operator.asDoubleBetween import at.petrak.hexcasting.common.lib.hex.HexIotaTypes @@ -38,7 +39,8 @@ object DoubleArithmetic : Arithmetic { ARCTAN, ARCTAN2, LOG, - MOD + MOD, + FACT ) /** @@ -70,6 +72,7 @@ object DoubleArithmetic : Arithmetic { ARCTAN2 -> make2 { a, b -> atan2(a, b) } LOG -> OperatorLog MOD -> make2 { a, b -> if (b == 0.0) throw MishapDivideByZero.of(a, b) else a % b } + FACT -> OperatorFactorial else -> throw InvalidOperatorException("$pattern is not a valid operator in Arithmetic $this.") } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/Vec3Arithmetic.java b/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/Vec3Arithmetic.java index a1d0af1ce..ab0ca37f1 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/Vec3Arithmetic.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/Vec3Arithmetic.java @@ -9,6 +9,7 @@ import at.petrak.hexcasting.api.casting.iota.DoubleIota; import at.petrak.hexcasting.api.casting.iota.Vec3Iota; import at.petrak.hexcasting.api.casting.math.HexPattern; +import at.petrak.hexcasting.common.casting.arithmetic.operator.OperatorFactorial; import at.petrak.hexcasting.common.casting.arithmetic.operator.vec.OperatorPack; import at.petrak.hexcasting.common.casting.arithmetic.operator.vec.OperatorUnpack; import at.petrak.hexcasting.common.casting.arithmetic.operator.vec.OperatorVec3Delegating; @@ -36,7 +37,8 @@ public enum Vec3Arithmetic implements Arithmetic { POW, FLOOR, CEIL, - MOD + MOD, + FACT ); public static final IotaMultiPredicate ACCEPTS = IotaMultiPredicate.any(IotaPredicate.ofType(VEC3), IotaPredicate.ofType(DOUBLE)); @@ -75,6 +77,8 @@ public Operator getOperator(HexPattern pattern) { return make1(v -> new Vec3(Math.ceil(v.x), Math.ceil(v.y), Math.ceil(v.z))); } else if (pattern.equals(MOD)) { return make2Fallback(pattern); + } else if (pattern.equals(FACT)) { + return OperatorFactorial.INSTANCE; } throw new InvalidOperatorException(pattern + " is not a valid operator in Arithmetic " + this + "."); } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/operator/OperatorFactorial.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/operator/OperatorFactorial.kt new file mode 100644 index 000000000..5c336e725 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/operator/OperatorFactorial.kt @@ -0,0 +1,60 @@ +package at.petrak.hexcasting.common.casting.arithmetic.operator + +import at.petrak.hexcasting.api.casting.arithmetic.operator.OperatorBasic +import at.petrak.hexcasting.api.casting.arithmetic.predicates.IotaMultiPredicate +import at.petrak.hexcasting.api.casting.arithmetic.predicates.IotaPredicate +import at.petrak.hexcasting.api.casting.asActionResult +import at.petrak.hexcasting.api.casting.eval.CastingEnvironment +import at.petrak.hexcasting.api.casting.iota.DoubleIota +import at.petrak.hexcasting.api.casting.iota.Iota +import at.petrak.hexcasting.api.casting.iota.Vec3Iota +import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidIota +import at.petrak.hexcasting.common.lib.hex.HexIotaTypes.DOUBLE +import at.petrak.hexcasting.common.lib.hex.HexIotaTypes.VEC3 +import net.minecraft.world.phys.Vec3 +import kotlin.math.PI +import kotlin.math.exp +import kotlin.math.ln +import kotlin.math.roundToInt +import kotlin.math.sqrt + +object OperatorFactorial : OperatorBasic(1, IotaMultiPredicate.all(IotaPredicate.or( + IotaPredicate.ofType(DOUBLE), IotaPredicate.ofType(VEC3) +))) { + override fun apply(iotas: Iterable, env : CastingEnvironment): Iterable { + val iota = iotas.first() + return when (iota) { + is DoubleIota -> compute(iota.double).asActionResult + is Vec3Iota -> Vec3(compute(iota.vec3.x), compute(iota.vec3.y), compute(iota.vec3.z)).asActionResult + else -> throw MishapInvalidIota.of(iota, 0, "numvec") + } + } + + // Take the standard factorial if it's an integer, otherwise use the gamma function. + private fun compute(arg: Double): Double { + val argInt = arg.roundToInt() + if (arg >= 0 && DoubleIota.tolerates(arg, argInt.toDouble())) { + return factorial(argInt) + } + return exp(logGamma(arg + 1)) + } + + private fun factorial(number: Int): Double { + var result: Long = 1 + + for (factor in 2..number) { + result *= factor + } + + return result.toDouble() + } + + // https://introcs.cs.princeton.edu/java/91float/Gamma.java.html + private fun logGamma(x: Double): Double { + val ser = ( 1.0 + 76.18009173 / (x + 0) - 86.50532033 / (x + 1) + + 24.01409822 / (x + 2) - 1.231739516 / (x + 3) + + 0.00120858003 / (x + 4) - 0.00000536382 / (x + 5) ) + + return (x - 0.5) * ln(x + 4.5) - (x + 4.5) + ln(ser * sqrt(2*PI)) + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java index e7460a172..7b796bad0 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java @@ -223,6 +223,8 @@ public class HexActions { new OperationAction(HexPattern.fromAngles("eqaqe", HexDir.NORTH_WEST))); public static final ActionRegistryEntry MODULO = make("modulo", new OperationAction(HexPattern.fromAngles("addwaad", HexDir.NORTH_EAST))); + public static final ActionRegistryEntry FACTORIAL = make("factorial", + new OperationAction(HexPattern.fromAngles("wawdedwaw", HexDir.SOUTH_EAST))); // == Sets == From cb7be7374328f458719b04546b02b1e248fcc848 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Mon, 29 Jun 2026 15:16:30 -0400 Subject: [PATCH 6/8] Better factorial implementation --- .../casting/arithmetic/DoubleArithmetic.kt | 4 +- .../casting/arithmetic/Vec3Arithmetic.java | 6 +- .../arithmetic/operator/OperatorFactorial.kt | 60 ------------------- .../arithmetic/operator/OperatorUtils.kt | 29 ++++++++- 4 files changed, 34 insertions(+), 65 deletions(-) delete mode 100644 Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/operator/OperatorFactorial.kt diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/DoubleArithmetic.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/DoubleArithmetic.kt index 9aadf9295..37f893871 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/DoubleArithmetic.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/DoubleArithmetic.kt @@ -12,9 +12,9 @@ import at.petrak.hexcasting.api.casting.iota.DoubleIota import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.math.HexPattern import at.petrak.hexcasting.api.casting.mishaps.MishapDivideByZero -import at.petrak.hexcasting.common.casting.arithmetic.operator.OperatorFactorial import at.petrak.hexcasting.common.casting.arithmetic.operator.OperatorLog import at.petrak.hexcasting.common.casting.arithmetic.operator.asDoubleBetween +import at.petrak.hexcasting.common.casting.arithmetic.operator.generalFactorial import at.petrak.hexcasting.common.lib.hex.HexIotaTypes import java.util.function.DoubleBinaryOperator import java.util.function.DoubleUnaryOperator @@ -72,7 +72,7 @@ object DoubleArithmetic : Arithmetic { ARCTAN2 -> make2 { a, b -> atan2(a, b) } LOG -> OperatorLog MOD -> make2 { a, b -> if (b == 0.0) throw MishapDivideByZero.of(a, b) else a % b } - FACT -> OperatorFactorial + FACT -> make1 { a -> generalFactorial(a) } else -> throw InvalidOperatorException("$pattern is not a valid operator in Arithmetic $this.") } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/Vec3Arithmetic.java b/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/Vec3Arithmetic.java index ab0ca37f1..e06ddce67 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/Vec3Arithmetic.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/Vec3Arithmetic.java @@ -9,7 +9,7 @@ import at.petrak.hexcasting.api.casting.iota.DoubleIota; import at.petrak.hexcasting.api.casting.iota.Vec3Iota; import at.petrak.hexcasting.api.casting.math.HexPattern; -import at.petrak.hexcasting.common.casting.arithmetic.operator.OperatorFactorial; +import at.petrak.hexcasting.common.casting.arithmetic.operator.OperatorUtilsKt; import at.petrak.hexcasting.common.casting.arithmetic.operator.vec.OperatorPack; import at.petrak.hexcasting.common.casting.arithmetic.operator.vec.OperatorUnpack; import at.petrak.hexcasting.common.casting.arithmetic.operator.vec.OperatorVec3Delegating; @@ -78,7 +78,9 @@ public Operator getOperator(HexPattern pattern) { } else if (pattern.equals(MOD)) { return make2Fallback(pattern); } else if (pattern.equals(FACT)) { - return OperatorFactorial.INSTANCE; + return make1(v -> new Vec3(OperatorUtilsKt.generalFactorial(v.x), + OperatorUtilsKt.generalFactorial(v.y), + OperatorUtilsKt.generalFactorial(v.z))); } throw new InvalidOperatorException(pattern + " is not a valid operator in Arithmetic " + this + "."); } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/operator/OperatorFactorial.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/operator/OperatorFactorial.kt deleted file mode 100644 index 5c336e725..000000000 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/operator/OperatorFactorial.kt +++ /dev/null @@ -1,60 +0,0 @@ -package at.petrak.hexcasting.common.casting.arithmetic.operator - -import at.petrak.hexcasting.api.casting.arithmetic.operator.OperatorBasic -import at.petrak.hexcasting.api.casting.arithmetic.predicates.IotaMultiPredicate -import at.petrak.hexcasting.api.casting.arithmetic.predicates.IotaPredicate -import at.petrak.hexcasting.api.casting.asActionResult -import at.petrak.hexcasting.api.casting.eval.CastingEnvironment -import at.petrak.hexcasting.api.casting.iota.DoubleIota -import at.petrak.hexcasting.api.casting.iota.Iota -import at.petrak.hexcasting.api.casting.iota.Vec3Iota -import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidIota -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes.DOUBLE -import at.petrak.hexcasting.common.lib.hex.HexIotaTypes.VEC3 -import net.minecraft.world.phys.Vec3 -import kotlin.math.PI -import kotlin.math.exp -import kotlin.math.ln -import kotlin.math.roundToInt -import kotlin.math.sqrt - -object OperatorFactorial : OperatorBasic(1, IotaMultiPredicate.all(IotaPredicate.or( - IotaPredicate.ofType(DOUBLE), IotaPredicate.ofType(VEC3) -))) { - override fun apply(iotas: Iterable, env : CastingEnvironment): Iterable { - val iota = iotas.first() - return when (iota) { - is DoubleIota -> compute(iota.double).asActionResult - is Vec3Iota -> Vec3(compute(iota.vec3.x), compute(iota.vec3.y), compute(iota.vec3.z)).asActionResult - else -> throw MishapInvalidIota.of(iota, 0, "numvec") - } - } - - // Take the standard factorial if it's an integer, otherwise use the gamma function. - private fun compute(arg: Double): Double { - val argInt = arg.roundToInt() - if (arg >= 0 && DoubleIota.tolerates(arg, argInt.toDouble())) { - return factorial(argInt) - } - return exp(logGamma(arg + 1)) - } - - private fun factorial(number: Int): Double { - var result: Long = 1 - - for (factor in 2..number) { - result *= factor - } - - return result.toDouble() - } - - // https://introcs.cs.princeton.edu/java/91float/Gamma.java.html - private fun logGamma(x: Double): Double { - val ser = ( 1.0 + 76.18009173 / (x + 0) - 86.50532033 / (x + 1) - + 24.01409822 / (x + 2) - 1.231739516 / (x + 3) - + 0.00120858003 / (x + 4) - 0.00000536382 / (x + 5) ) - - return (x - 0.5) * ln(x + 4.5) - (x + 4.5) + ln(ser * sqrt(2*PI)) - } -} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/operator/OperatorUtils.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/operator/OperatorUtils.kt index 24247286c..cde87842e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/operator/OperatorUtils.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/arithmetic/operator/OperatorUtils.kt @@ -5,8 +5,12 @@ import at.petrak.hexcasting.api.casting.iota.DoubleIota import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.ListIota import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidIota +import kotlin.math.PI import kotlin.math.abs +import kotlin.math.exp +import kotlin.math.ln import kotlin.math.roundToInt +import kotlin.math.sqrt fun Iterator>.nextList(argc: Int = 0): SpellList { val (idx, x) = this.next() @@ -64,4 +68,27 @@ fun Iterator>.nextPositiveIntUnderInclusive(max: Int, argc: I * the double's index from the top of the stack (i.e. top iota has [idx]=0, second from the top has [idx]=1, etc.). */ fun Double.asDoubleBetween(min: Double, max: Double, idx: Int) = if (this in min .. max) this - else throw MishapInvalidIota.of(DoubleIota(this), idx, "double.between", min, max) \ No newline at end of file + else throw MishapInvalidIota.of(DoubleIota(this), idx, "double.between", min, max) + +/** + * Takes the standard factorial if `arg` is a positive integer, otherwise uses the gamma function. + */ +fun generalFactorial(arg: Double): Double { + val argInt = arg.roundToInt() + if (arg >= 0 && DoubleIota.tolerates(arg, argInt.toDouble())) { + // normal integer factorial + var result: Long = 1 + for (factor in 2..argInt) { + result *= factor + } + return result.toDouble() + } + // gamma function of arg+1 for non-integer factorial + // https://introcs.cs.princeton.edu/java/91float/Gamma.java.html + val x = arg + 1 + val ser = ( 1.0 + 76.18009173 / (x + 0) - 86.50532033 / (x + 1) + + 24.01409822 / (x + 2) - 1.231739516 / (x + 3) + + 0.00120858003 / (x + 4) - 0.00000536382 / (x + 5) ) + val logGamma = (x - 0.5) * ln(x + 4.5) - (x + 4.5) + ln(ser * sqrt(2*PI)) + return exp(logGamma) +} \ No newline at end of file From 81760576af8a2df687249a732548f7e9a62599f3 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:28:18 -0400 Subject: [PATCH 7/8] Action/mishap lang, tweak Freeze pattern --- .../java/at/petrak/hexcasting/api/casting/ActionUtils.kt | 4 ++-- .../at/petrak/hexcasting/common/lib/hex/HexActions.java | 2 +- .../resources/assets/hexcasting/lang/en_us.flatten.json5 | 8 +++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt index 9331605dc..e1da55fed 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt @@ -281,12 +281,12 @@ fun List.getVecOrVecList(idx: Int, argc: Int = 0): Either if (v is Vec3Iota) { out.add(v.vec3) } else { - throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "veclist") + throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "vec_or_veclist") } } Either.right(out) } - else -> throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "veclist") + else -> throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "vec_or_veclist") } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java index 7b796bad0..4f267c5c8 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/hex/HexActions.java @@ -283,7 +283,7 @@ public class HexActions { public static final ActionRegistryEntry SMELT = make("smelt", new ActionRegistryEntry(HexPattern.fromAngles("wqqqwqqadad", HexDir.EAST), OpSmelt.INSTANCE)); public static final ActionRegistryEntry FREEZE = make("freeze", - new ActionRegistryEntry(HexPattern.fromAngles("weeeweedada", HexDir.WEST), OpFreeze.INSTANCE)); + new ActionRegistryEntry(HexPattern.fromAngles("weeeweedada", HexDir.EAST), OpFreeze.INSTANCE)); public static final ActionRegistryEntry BEEP = make("beep", new ActionRegistryEntry(HexPattern.fromAngles("adaa", HexDir.WEST), OpBeep.INSTANCE)); diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index 528f82646..18aeb3339 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -837,6 +837,7 @@ random: "Entropy Reflection", logarithm: "Logarithmic Distillation", coerce_axial: "Axial Purification", + factorial: "Factorial Purification", read: "Scribe's Reflection", "read/entity": "Chronicler's Purification", @@ -855,6 +856,7 @@ print: "Reveal", beep: "Make Note", + particles: "Particles", explode: "Explosion", "explode/fire": "Fireball", add_motion: "Impulse", @@ -873,10 +875,13 @@ destroy_water: "Destroy Liquid", ignite: "Ignite", extinguish: "Extinguish Area", + smelt: "Smelt", + freeze: "Freeze", conjure_block: "Conjure Block", conjure_light: "Conjure Light", bonemeal: "Overgrow", edify: "Edify Sapling", + falling_block: "Gravitate Block", colorize: "Internalize Pigment", cycle_variant: "Caster's Glamour", @@ -1106,7 +1111,7 @@ living: "a living entity", }, - entity_or_vector: "an entity or a vector", + entity_or_vector: "an entity or vector", unknown: "(unknown, uh-oh, this is a bug)", }, @@ -1114,6 +1119,7 @@ numvec: "a number or vector", numlist: "an integer or list", "list.pattern": "a list of patterns", + vec_or_veclist: "a vector or list of vectors", double: { positive: { From 82e3d6d47b8f5e78c9601f03f09ad4addf845af3 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Tue, 30 Jun 2026 18:02:39 -0400 Subject: [PATCH 8/8] Book entries for new patterns --- .../hexcasting/api/casting/ActionUtils.kt | 4 +- .../hexcasting/lang/en_us.flatten.json5 | 10 +++++ .../en_us/entries/patterns/math.json | 8 ++++ .../en_us/entries/patterns/spells/basic.json | 8 ++++ .../entries/patterns/spells/blockworks.json | 14 ++----- .../entries/patterns/spells/heatworks.json | 42 +++++++++++++++++++ 6 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/heatworks.json diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt index e1da55fed..266e5f791 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/ActionUtils.kt @@ -281,12 +281,12 @@ fun List.getVecOrVecList(idx: Int, argc: Int = 0): Either if (v is Vec3Iota) { out.add(v.vec3) } else { - throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "vec_or_veclist") + throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "vec_or_veclist") } } Either.right(out) } - else -> throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "vec_or_veclist") + else -> throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "vec_or_veclist") } } diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index 18aeb3339..3f72cc846 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -1324,6 +1324,7 @@ itempicking: "Working with Items", basic_spell: "Basic Spells", blockworks: "Block Manipulation", + heatworks: "Heat Manipulation", nadirs: "Nadirs", hexcasting_spell: "Crafting Casting Items", sentinels: "Sentinels", @@ -1825,6 +1826,7 @@ construct_vec: "Combine three numbers at the top of the stack into a vector's X, Y, and Z components (bottom to top).", deconstruct_vec: "Split a vector into its X, Y, and Z components (bottom to top).", modulo: "Takes the modulus of two numbers. This is the amount $(italics)remaining/$ after division - for example, 5 %% 2 is 1, and 5 %% 3 is 2. When applied on vectors, performs the above operation elementwise.", + factorial: "Takes the factorial of a number. This is the product of all integers from 1 to the number. For non-integers, uses the $(l:https://en.wikipedia.org/wiki/Gamma_function)gamma function/$. For vectors, performs the above operation elementwise.", coerce_axial: "For a vector, coerce it to its nearest axial direction, a unit vector. For a number, return the sign of the number; 1 if positive, -1 if negative. In both cases, zero is unaffected.", random: "Creates a random number between 0 and 1.", }, @@ -2053,6 +2055,8 @@ "beep.1": "Remove a vector and two numbers from the stack. Plays an $(thing)instrument/$ defined by the first number at the given location, with a $(thing)note/$ defined by the second number. Costs a negligible amount of _media.", "beep.2": "There appear to be 16 different $(thing)instruments/$ and 25 different $(thing)notes/$. Both are indexed by zero.$(br2)These seem to be the same instruments I can produce with a $(item)Note Block/$, though the reason for each instrument's number being what it is eludes me.$(br2)Either way, I can find the numbers I need to use by inspecting a $(item)Note Block/$ through a $(l:items/lens)$(item)Scrying Lens/$.", + + particles: "Accepts a location or a list of locations, and either creates a particle at the location, or creates lines of particles between the locations in the list. Costs two thousandths of an $(l:items/amethyst)$(item)Amethyst Dust/$ per location.", }, blockworks: { @@ -2064,8 +2068,14 @@ conjure_light: "Conjure a magical light that softly glows with my pigment at the given position. Costs about one $(l:items/amethyst)$(item)Amethyst Dust/$.", bonemeal: "Encourage a plant or sapling at the target position to grow, as if $(item)Bonemeal/$ was applied. Costs a bit more than one $(l:items/amethyst)$(item)Amethyst Dust/$.", edify: "Forcibly infuse _media into the sapling at the target position, causing it to grow into an $(l:items/edified)$(thing)Edified Tree/$. Costs about one $(l:items/amethyst)$(item)Charged Amethyst/$.", + falling_block: "Force the block at the target position to fall as if it were sand or gravel. Certain blocks seem mysteriously immune to this effect. Costs one and a half $(l:items/amethyst)$(item)Amethyst Dust/$.", + }, + + heatworks: { ignite: "Start a fire on top of the given location, as if a $(item)Fire Charge/$ was applied, or set fire to an entity. Costs about one $(l:items/amethyst)$(item)Amethyst Dust/$.", extinguish: "Extinguish blocks in a large area. Costs about six $(l:items/amethyst)$(item)Amethyst Dust/$.", + smelt: "Smelts either the block at the given location or all the items in the given item entity. Costs three quarters of an $(l:items/amethyst)$(item)Amethyst Dust/$ per block/item smelted.", + freeze: "Freezes the block at the given location. For example, turns water into ice, and ice into packed ice. Costs one $(l:items/amethyst)$(item)Amethyst Dust/$.", }, nadirs: { diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/math.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/math.json index 668875a54..86a6b1573 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/math.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/math.json @@ -125,6 +125,14 @@ "output": "num|vec", "text": "hexcasting.page.math.modulo" }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:factorial", + "anchor": "hexcasting:factorial", + "input": "num|vec", + "output": "num|vec", + "text": "hexcasting.page.math.factorial" + }, { "type": "hexcasting:pattern", "op_id": "hexcasting:coerce_axial", diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/basic.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/basic.json index 27b2c90e8..64937ec1f 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/basic.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/basic.json @@ -57,6 +57,14 @@ { "type": "patchouli:text", "text": "hexcasting.page.basic_spell.beep.2" + }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:particles", + "anchor": "hexcasting:particles", + "input": "vector | [vector]", + "output": "", + "text": "hexcasting.page.basic_spell.particles" } ] } diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/blockworks.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/blockworks.json index f39e871d4..eb2c5c27e 100644 --- a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/blockworks.json +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/blockworks.json @@ -72,19 +72,11 @@ }, { "type": "hexcasting:pattern", - "op_id": "hexcasting:ignite", - "anchor": "hexcasting:ignite", - "input": "entity | vector", - "output": "", - "text": "hexcasting.page.blockworks.ignite" - }, - { - "type": "hexcasting:pattern", - "op_id": "hexcasting:extinguish", - "anchor": "hexcasting:extinguish", + "op_id": "hexcasting:falling_block", + "anchor": "hexcasting:falling_block", "input": "vector", "output": "", - "text": "hexcasting.page.blockworks.extinguish" + "text": "hexcasting.page.blockworks.falling_block" } ] } \ No newline at end of file diff --git a/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/heatworks.json b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/heatworks.json new file mode 100644 index 000000000..b02adda11 --- /dev/null +++ b/Common/src/main/resources/assets/hexcasting/patchouli_books/thehexbook/en_us/entries/patterns/spells/heatworks.json @@ -0,0 +1,42 @@ +{ + "name": "hexcasting.entry.heatworks", + "category": "hexcasting:patterns/spells", + "icon": "minecraft:magma_block", + "sortnum": 1, + "advancement": "hexcasting:root", + "read_by_default": true, + "pages": [ + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:ignite", + "anchor": "hexcasting:ignite", + "input": "vector | entity", + "output": "", + "text": "hexcasting.page.heatworks.ignite" + }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:extinguish", + "anchor": "hexcasting:extinguish", + "input": "vector", + "output": "", + "text": "hexcasting.page.heatworks.extinguish" + }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:smelt", + "anchor": "hexcasting:smelt", + "input": "vector | entity", + "output": "", + "text": "hexcasting.page.heatworks.smelt" + }, + { + "type": "hexcasting:pattern", + "op_id": "hexcasting:freeze", + "anchor": "hexcasting:freeze", + "input": "vector", + "output": "", + "text": "hexcasting.page.heatworks.freeze" + } + ] +} \ No newline at end of file