Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,25 @@ 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.MishapBadLocation
import at.petrak.hexcasting.api.casting.mishaps.MishapLackingHotbarItem
import at.petrak.hexcasting.api.misc.MediaConstants
import at.petrak.hexcasting.mixin.accessor.AccessorBlockItem
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.particles.BlockParticleOption
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.core.registries.Registries
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundSource
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.item.context.UseOnContext
import net.minecraft.world.level.block.state.pattern.BlockInWorld
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.Vec3

Expand All @@ -40,13 +45,11 @@ object OpPlaceBlock : SpellAction {
)
val itemUseCtx = env
.queryForMatchingStack { it.item is BlockItem }
?.let { UseOnContext(env.world, env.castingEntity as? ServerPlayer, env.castingHand, it, blockHit) }
?.let { UseOnContext(env.world, env.castingEntity as? ServerPlayer, env.otherHand, it, blockHit) }
?: throw MishapLackingHotbarItem.of("placeable")
val placeContext = BlockPlaceContext(itemUseCtx)

val worldState = env.world.getBlockState(pos)
if (!worldState.canBeReplaced(placeContext))
throw MishapBadBlock.of(pos, "replaceable")
assertCanPlaceAt(env, pos, placeContext)

return SpellAction.Result(
Spell(pos),
Expand All @@ -55,6 +58,77 @@ object OpPlaceBlock : SpellAction {
)
}

fun assertCanPlaceAt(env: CastingEnvironment, pos: BlockPos, placeContext: BlockPlaceContext) {
// stepping through all the checks that the spell performs
val casterPlayer = env.castingEntity as? Player
val stack = placeContext.itemInHand

// XXX: this might have side effects from other mods, is it safe to call twice/call it here?
if (!IXplatAbstractions.INSTANCE.isPlacingAllowed(env.world, pos, stack, casterPlayer))
throw MishapBadLocation(Vec3.atCenterOf(pos), "forbidden")

val worldState = env.world.getBlockState(pos)
if (!worldState.canBeReplaced(placeContext))
throw MishapBadBlock.of(pos, "replaceable")

if (!env.withdrawItem({ItemStack.isSameItemSameTags(it, stack)}, 1, false)) {
throw MishapLackingHotbarItem.of("placable")
}

// Begin checks from ItemStack.useOn()

if (
casterPlayer != null
&& !casterPlayer.abilities.mayBuild
&& !stack.hasAdventureModePlaceTagForBlock(
env.world.registryAccess().registryOrThrow(Registries.BLOCK),
BlockInWorld(env.world, pos, false)
)
) {
// Adventure mode check, assertPosInRangeForEditing should already catch this but just in case
throw MishapBadLocation(Vec3.atCenterOf(pos), "forbidden")
}

val item = stack.item as BlockItem
// Checks in BlockItem.useOn -> BlockItem.place
if (!item.block.isEnabled(env.world.enabledFeatures())) {
// XXX: ideally we never select this for placement at all
throw MishapLackingHotbarItem.of("placable")
}

if (!placeContext.canPlace()) {
throw MishapBadBlock.of(pos, "replacable")
}

val newPlacementContext = item.updatePlacementContext(placeContext)
?: throw MishapBadLocation(Vec3.atCenterOf(pos), "obstructed")
// in vanilla, this only happens for wall-likes that are obstructed by entities
// or attempting to place op-only blocks. assume the latter doesn't happen, because
// we don't have a way to really differentiate

val hasPlacementState = item.block.getStateForPlacement(newPlacementContext)
?.let { (item as AccessorBlockItem).`hex$canPlace`(newPlacementContext, it) }
?: false
if (!hasPlacementState) {
// likely has an entity blocking placement
throw MishapBadLocation(Vec3.atCenterOf(pos), "obstructed")
}

// Checks in BlockItem.placeBlock -> ServerLevel.setBlock
if (env.world.isOutsideBuildHeight(pos)) {
// probably redundant but better safe than sorry
throw MishapBadLocation(Vec3.atCenterOf(pos), "out_of_world")
}

if (env.world.isDebug) {
// debug world type cannot have blocks edited
throw MishapBadLocation(Vec3.atCenterOf(pos), "forbidden")
}

// Checks in LevelChunk.setBlockState shouldn't trip for OpPlaceBlock
// checks: chunk fully air, blockstate identical to current
}

private data class Spell(val pos: BlockPos) : RenderedSpell {
override fun cast(env: CastingEnvironment) {
val caster = env.castingEntity
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package at.petrak.hexcasting.mixin.accessor;

import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;

@Mixin(BlockItem.class)
public interface AccessorBlockItem {
@Invoker("canPlace")
boolean hex$canPlace(BlockPlaceContext blockPlaceContext, BlockState blockState);
}
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,7 @@
too_close_to_out: "%s is too close to the boundary of the world",
forbidden: "%s is forbidden to you",
bad_dimension: "This dimension forbids that action",
obstructed: "%s is obstructed",
},

bad_item: {
Expand Down
1 change: 1 addition & 0 deletions Common/src/main/resources/hexplat.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"MixinWanderingTrader",
"MixinWitch",
"accessor.AccessorAbstractArrow",
"accessor.AccessorBlockItem",
"accessor.AccessorEntity",
"accessor.AccessorLivingEntity",
"accessor.AccessorLootTable",
Expand Down
Loading