Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Moved certain lang keys for type iotas from Hexal into MoreIotas, since MoreIotas is what actually implements type iotas.
- Phase Block now ensures that the caster has permission to break the target block.
- Trade now mishaps if the list contains more than 2 motes, and makes it clear that the list should match a single trade offer.
- The mishap when providing something non-placeable to Place Block II now specifies all valid input types.

### Fixed

Expand All @@ -46,6 +47,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Fixed an error when passing null or an empty mote to Use Item On.
- Fixed a variety of broken lang keys in the config menu.
- Fixed Trade allowing trades even when not enough input items were present.
- Fixed Place Block II failing silently if the caster doesn't have any items matching the specified type.

## `0.3.1` - 2025-10-30

Expand Down
2 changes: 1 addition & 1 deletion Common/src/main/java/ram/talia/hexal/api/OperatorUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ fun List<Iota>.getBlockTypeOrBlockItemStackOrBlockMote(idx: Int, argc: Int = 0):
return x.itemStack?.let { if (it.item is BlockItem) Anyone.second(it) else null }
if (x is MoteIota)
return x.selfOrNull()?.let { if (it.item is BlockItem) Anyone.third(it) else null }
throw MishapInvalidIota.ofType(x, if (argc == 0) idx else argc - (idx + 1), "type.block")
throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "placeable")
}

fun List<Iota>.getGate(idx: Int, argc: Int = 0): GateIota {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ram.talia.hexal.api.casting.mishaps

import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.Mishap
import at.petrak.hexcasting.api.pigment.FrozenPigment
import net.minecraft.world.item.DyeColor

class MishapItemNotFound(val wanted: Iota) : Mishap() {
override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenPigment =
dyeColor(DyeColor.BROWN)

override fun execute(env: CastingEnvironment, errorCtx: Context, stack: MutableList<Iota>) {
env.mishapEnvironment.dropHeldItems()
}

override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context) = error("item_not_found", wanted.display())
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.getBlockPos
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapBadBlock
import at.petrak.hexcasting.api.casting.mishaps.MishapBadOffhandItem
import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidIota
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.core.BlockPos
Expand All @@ -26,14 +27,15 @@ import net.minecraft.world.phys.Vec3
import ram.talia.hexal.api.config.HexalConfig
import ram.talia.hexal.api.getBlockTypeOrBlockItemStackOrBlockMote
import ram.talia.hexal.api.casting.iota.MoteIota
import ram.talia.hexal.api.casting.mishaps.MishapItemNotFound
import ram.talia.hexal.api.util.Anyone

object OpPlaceType : SpellAction {
override val argc = 2

override fun execute(args: List<Iota>, env: CastingEnvironment): SpellAction.Result {
val block = args.getBlockTypeOrBlockItemStackOrBlockMote(0, argc) ?:
throw MishapInvalidIota.ofType(args[0], 1, "type.block.able")
throw MishapInvalidIota.of(args[0], 1, "placeable")
val pos = args.getBlockPos(1, argc)

env.assertVecInRange(pos.center)
Expand All @@ -49,14 +51,27 @@ object OpPlaceType : SpellAction {
if (!worldState.canBeReplaced(placeContext))
throw MishapBadBlock.of(pos, "replaceable")

// TODO: queryForMatchingStack() only searches the hotbar. This should be changed either to use a custom
// function once hex 0.11.4 makes getUsableStacks() public, or to specify the full inventory once
// https://github.com/FallingColors/HexMod/issues/1052 gets implemented.
val placeeStack = block.flatMap(
{ block -> env.queryForMatchingStack { it.item is BlockItem && (it.item as BlockItem).block == block }?.copy() },
{ itemStack -> env.queryForMatchingStack { ItemStack.isSameItemSameTags(it, itemStack) }?.copy() },
{ itemIota -> if (itemIota.item is BlockItem) itemIota.record?.toStack()?.takeUnless { it.isEmpty } else null }
)
if (placeeStack == null) {
if (block.isC) throw MishapInvalidIota.of(args[0], 1, "placeable")
else throw MishapItemNotFound(args[0])
}

return SpellAction.Result(
Spell(pos, block),
Spell(pos, placeeStack, block),
HexalConfig.server.placeTypeCost,
listOf(ParticleSpray.cloud(Vec3.atCenterOf(pos), 1.0))
)
}

private data class Spell(val pos: BlockPos, val blockOrMoteIota: Anyone<Block, ItemStack, MoteIota>) : RenderedSpell {
private data class Spell(val pos: BlockPos, val placeeStack: ItemStack, val blockOrMoteIota: Anyone<Block, ItemStack, MoteIota>) : RenderedSpell {
override fun cast(env: CastingEnvironment) {
val caster = env.caster

Expand All @@ -65,11 +80,6 @@ object OpPlaceType : SpellAction {
)

val bstate = env.world.getBlockState(pos)
val placeeStack = blockOrMoteIota.flatMap(
{ block -> env.queryForMatchingStack { it.item is BlockItem && (it.item as BlockItem).block == block }?.copy() },
{ itemStack -> env.queryForMatchingStack { ItemStack.isSameItemSameTags(it, itemStack) }?.copy() },
{ itemIota -> if (itemIota.item is BlockItem) itemIota.record?.toStack()?.takeUnless { it.isEmpty } else null }
) ?: return

if (!IXplatAbstractions.INSTANCE.isPlacingAllowed(env.world, pos, placeeStack, env.caster))
return
Expand Down
2 changes: 2 additions & 0 deletions Common/src/main/resources/assets/hexal/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"hexcasting.mishap.others_wisp": "That wisp is owned by %s, not you.",
"hexcasting.mishap.no_links": "%s has no links.",
"hexcasting.mishap.self_link": "Tried to link %s to itself.",
"hexcasting.mishap.item_not_found": "Couldn't find any item matching %s in the hotbar.",
"hexcasting.mishap.no_bound_storage": "No bound Mote Nexus.",
"hexcasting.mishap.storage_unloaded": "Bound Mote Nexus is unloaded.",
"hexcasting.mishap.excessive_reproduction": "Wisp %s tried to reproduce excessively; only 1 child per tick.",
Expand Down Expand Up @@ -44,6 +45,7 @@
"hexcasting.mishap.invalid_value.cant_combine_motes": "two motes that can be combined",
"hexcasting.mishap.invalid_value.mote_empty": "a non-empty mote",
"hexcasting.mishap.invalid_value.mote_not_size_one": "a mote with no NBT data or of size one",
"hexcasting.mishap.invalid_value.placeable": "a mote, item type, or item stack that can be placed",
"hexcasting.mishap.invalid_value.villager_trade": "a list of motes matching a single trade offer",
"hexcasting.mishap.invalid_value.gate.offset": "an offset of at most %f blocks",

Expand Down
Loading