mirror of
https://github.com/Theta-Dev/ConstructionWand.git
synced 2025-08-27 21:21:11 +02:00
395 lines
13 KiB
Java
395 lines
13 KiB
Java
package thetadev.constructionwand.job;
|
|
|
|
import net.minecraft.block.*;
|
|
import net.minecraft.entity.LivingEntity;
|
|
import net.minecraft.entity.player.PlayerEntity;
|
|
import net.minecraft.item.*;
|
|
import net.minecraft.state.Property;
|
|
import net.minecraft.state.properties.BlockStateProperties;
|
|
import net.minecraft.state.properties.SlabType;
|
|
import net.minecraft.stats.Stats;
|
|
import net.minecraft.util.*;
|
|
import net.minecraft.util.math.AxisAlignedBB;
|
|
import net.minecraft.util.math.BlockPos;
|
|
import net.minecraft.util.math.BlockRayTraceResult;
|
|
import net.minecraft.util.math.shapes.VoxelShape;
|
|
import net.minecraft.world.World;
|
|
import net.minecraftforge.common.MinecraftForge;
|
|
import net.minecraftforge.common.util.BlockSnapshot;
|
|
import net.minecraftforge.event.world.BlockEvent;
|
|
import thetadev.constructionwand.ConstructionWand;
|
|
import thetadev.constructionwand.basics.*;
|
|
import thetadev.constructionwand.basics.option.WandOptions;
|
|
import thetadev.constructionwand.containers.ContainerManager;
|
|
import thetadev.constructionwand.items.ItemWand;
|
|
|
|
import javax.annotation.Nullable;
|
|
import java.util.*;
|
|
import java.util.stream.Collectors;
|
|
|
|
public abstract class WandJob
|
|
{
|
|
protected PlayerEntity player;
|
|
protected World world;
|
|
protected BlockRayTraceResult rayTraceResult;
|
|
protected ItemStack wand;
|
|
protected ItemWand wandItem;
|
|
|
|
// Wand options
|
|
protected WandOptions options;
|
|
protected int maxBlocks;
|
|
protected boolean doRandomize;
|
|
|
|
protected LinkedHashMap<BlockItem, Integer> itemCounts;
|
|
protected HashMap<BlockItem, Integer> itemWeights;
|
|
|
|
protected LinkedList<PlaceSnapshot> placeSnapshots;
|
|
|
|
|
|
public WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand)
|
|
{
|
|
this.player = player;
|
|
this.world = world;
|
|
this.rayTraceResult = rayTraceResult;
|
|
placeSnapshots = new LinkedList<>();
|
|
|
|
// Get wand
|
|
if(wand == null || wand == ItemStack.EMPTY || !(wand.getItem() instanceof ItemWand)) return;
|
|
this.wand = wand;
|
|
|
|
wandItem = (ItemWand) wand.getItem();
|
|
|
|
// Get options
|
|
options = new WandOptions(wand);
|
|
doRandomize = options.random.get();
|
|
|
|
// Get place item
|
|
addBlockItems();
|
|
if(itemCounts.isEmpty()) return;
|
|
|
|
// Get inventory supply
|
|
for(int v : itemCounts.values()) {
|
|
try {
|
|
maxBlocks = Math.addExact(maxBlocks, v);
|
|
}
|
|
catch(ArithmeticException e) {
|
|
maxBlocks = Integer.MAX_VALUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
maxBlocks = Math.min(maxBlocks, wandItem.getLimit(player, wand));
|
|
if(maxBlocks == 0) return;
|
|
|
|
getBlockPositionList();
|
|
}
|
|
|
|
public static WandJob getJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack itemStack) {
|
|
WandOptions options = new WandOptions(itemStack);
|
|
|
|
if(options.mode.get() == WandOptions.MODE.ANGEL) return new TransductionJob(player, world, rayTraceResult, itemStack);
|
|
return new ConstructionJob(player, world, rayTraceResult, itemStack);
|
|
}
|
|
|
|
public Set<BlockPos> getBlockPositions() {
|
|
return placeSnapshots.stream().map(snapshot -> snapshot.pos).collect(Collectors.toSet());
|
|
}
|
|
|
|
public BlockRayTraceResult getRayTraceResult() { return rayTraceResult; }
|
|
|
|
public BlockPos getTargetPos() { return rayTraceResult.getPos(); }
|
|
|
|
public PlayerEntity getPlayer() { return player; }
|
|
|
|
public void setPlayer(PlayerEntity player) { this.player = player; }
|
|
|
|
public World getWorld() { return world; }
|
|
|
|
public void setWorld(World world) { this.world = world; }
|
|
|
|
public ItemStack getWand() { return wand; }
|
|
|
|
private void addBlockItem(BlockItem item) {
|
|
int count = countItem(item);
|
|
if(count > 0) itemCounts.put(item, count);
|
|
}
|
|
|
|
private void addBlockItems() {
|
|
itemCounts = new LinkedHashMap<>();
|
|
itemWeights = new HashMap<>();
|
|
|
|
BlockPos targetPos = rayTraceResult.getPos();
|
|
BlockState targetState = world.getBlockState(targetPos);
|
|
Block targetBlock = targetState.getBlock();
|
|
ItemStack offhandStack = player.getHeldItem(Hand.OFF_HAND);
|
|
|
|
if(doRandomize) {
|
|
for(ItemStack stack : WandUtil.getHotbarWithOffhand(player)) {
|
|
if(stack.getItem() instanceof BlockItem) {
|
|
BlockItem item = (BlockItem) stack.getItem();
|
|
addBlockItem(item);
|
|
itemWeights.compute(item, (k, v) -> (v == null) ? 1 : v+1);
|
|
}
|
|
}
|
|
}
|
|
else if(!offhandStack.isEmpty() && offhandStack.getItem() instanceof BlockItem) {
|
|
// Block in offhand -> override
|
|
addBlockItem((BlockItem) offhandStack.getItem());
|
|
}
|
|
|
|
// Otherwise use target block
|
|
if(itemCounts.isEmpty()) {
|
|
Item item = targetBlock.asItem();
|
|
if(item instanceof BlockItem) {
|
|
addBlockItem((BlockItem) item);
|
|
|
|
// Add replacement items
|
|
if(options.match.get() != WandOptions.MATCH.EXACT) {
|
|
for(Item it : ReplacementRegistry.getMatchingSet(item)) {
|
|
if(it instanceof BlockItem) addBlockItem((BlockItem) it);
|
|
}
|
|
}
|
|
}
|
|
doRandomize = false;
|
|
}
|
|
}
|
|
|
|
private int countItem(Item item) {
|
|
if(player.inventory == null || player.inventory.mainInventory == null) return 0;
|
|
if(player.isCreative()) return Integer.MAX_VALUE;
|
|
|
|
int total = 0;
|
|
ContainerManager containerManager = ConstructionWand.instance.containerManager;
|
|
List<ItemStack> inventory = WandUtil.getFullInv(player);
|
|
|
|
for(ItemStack stack : inventory) {
|
|
if(stack == null) continue;
|
|
|
|
if(WandUtil.stackEquals(stack, item)) {
|
|
total += stack.getCount();
|
|
}
|
|
else {
|
|
int amount = containerManager.countItems(player, new ItemStack(item), stack);
|
|
if(amount == Integer.MAX_VALUE) return Integer.MAX_VALUE;
|
|
total += amount;
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
// Attempts to take specified number of items, returns number of missing items
|
|
private int takeItems(Item item, int count)
|
|
{
|
|
if(player.inventory == null || player.inventory.mainInventory == null) return count;
|
|
if(player.isCreative()) return 0;
|
|
|
|
List<ItemStack> hotbar = WandUtil.getHotbarWithOffhand(player);
|
|
List<ItemStack> mainInv = WandUtil.getMainInv(player);
|
|
|
|
// Take items from main inv, loose items first
|
|
count = takeItemsInvList(count, item, mainInv, false);
|
|
count = takeItemsInvList(count, item, mainInv, true);
|
|
|
|
// Take items from hotbar, containers first
|
|
count = takeItemsInvList(count, item, hotbar, true);
|
|
count = takeItemsInvList(count, item, hotbar, false);
|
|
|
|
return count;
|
|
}
|
|
|
|
private int takeItemsInvList(int count, Item item, List<ItemStack> inv, boolean container) {
|
|
ContainerManager containerManager = ConstructionWand.instance.containerManager;
|
|
|
|
for(ItemStack stack : inv) {
|
|
if(count == 0) break;
|
|
|
|
if(container) {
|
|
int nCount = containerManager.useItems(player, new ItemStack(item), stack, count);
|
|
count = nCount;
|
|
}
|
|
|
|
if(!container && WandUtil.stackEquals(stack, item)) {
|
|
int toTake = Math.min(count, stack.getCount());
|
|
stack.shrink(toTake);
|
|
count -= toTake;
|
|
player.inventory.markDirty();
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
protected abstract void getBlockPositionList();
|
|
|
|
// Get PlaceSnapshot, or null if no block can be placed
|
|
@Nullable
|
|
protected PlaceSnapshot getPlaceSnapshot(BlockPos pos, BlockState supportingBlock) {
|
|
// Is position out of world?
|
|
if(!world.isBlockPresent(pos)) return null;
|
|
|
|
// If replace mode is off, target has to be air
|
|
if(!options.replace.get() && !world.isAirBlock(pos)) return null;
|
|
|
|
ArrayList<BlockItem> items = new ArrayList<>(itemCounts.keySet());
|
|
if(doRandomize) {
|
|
for(BlockItem item : itemWeights.keySet()) {
|
|
int weight = itemWeights.get(item);
|
|
for(int i=0; i<weight-1; i++) items.add(item);
|
|
}
|
|
|
|
Collections.shuffle(items, player.getRNG());
|
|
}
|
|
|
|
for(BlockItem item : items) {
|
|
int count = itemCounts.get(item);
|
|
if(count == 0) continue;
|
|
|
|
// Is block at pos replaceable?
|
|
BlockItemUseContext ctx = new WandItemUseContext(this, pos, item);
|
|
if(!ctx.canPlace()) continue;
|
|
|
|
// Can block be placed?
|
|
BlockState blockState = item.getBlock().getStateForPlacement(ctx);
|
|
if(blockState == null) continue;
|
|
blockState = Block.getValidBlockForPosition(blockState, world, pos);
|
|
if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) continue;
|
|
|
|
// No entities colliding?
|
|
VoxelShape shape = blockState.getCollisionShape(world, pos);
|
|
if(!shape.isEmpty()) {
|
|
AxisAlignedBB blockBB = shape.getBoundingBox().offset(pos);
|
|
if(!world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty()) continue;
|
|
}
|
|
|
|
// Reduce item count
|
|
if(count < Integer.MAX_VALUE) itemCounts.merge(item, -1, Integer::sum);
|
|
return new PlaceSnapshot(pos, supportingBlock, item);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private boolean placeBlock(PlaceSnapshot placeSnapshot) {
|
|
BlockPos blockPos = placeSnapshot.pos;
|
|
|
|
BlockItemUseContext ctx = new WandItemUseContext(this, blockPos, placeSnapshot.item);
|
|
if(!ctx.canPlace()) return false;
|
|
|
|
BlockState placeBlock = Block.getBlockFromItem(placeSnapshot.item).getStateForPlacement(ctx);
|
|
if(placeBlock == null) return false;
|
|
|
|
BlockState supportingBlock = placeSnapshot.supportingBlock;
|
|
|
|
if(options.direction.get() == WandOptions.DIRECTION.TARGET) {
|
|
// Block properties to be copied (alignment/rotation properties)
|
|
for(Property property : new Property[] {
|
|
BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP,
|
|
BlockStateProperties.ROTATION_0_15, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE})
|
|
{
|
|
if(supportingBlock.hasProperty(property) && placeBlock.hasProperty(property)) {
|
|
placeBlock = placeBlock.with(property, supportingBlock.get(property));
|
|
}
|
|
}
|
|
|
|
// Dont dupe double slabs
|
|
if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE) && placeBlock.hasProperty(BlockStateProperties.SLAB_TYPE)) {
|
|
SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE);
|
|
if(slabType != SlabType.DOUBLE) placeBlock = placeBlock.with(BlockStateProperties.SLAB_TYPE, slabType);
|
|
}
|
|
}
|
|
// Place the block
|
|
if(!world.setBlockState(blockPos, placeBlock)) {
|
|
ConstructionWand.LOGGER.info("Block could not be placed");
|
|
return false;
|
|
}
|
|
|
|
// Remove block if placeEvent is canceled
|
|
BlockSnapshot snapshot = BlockSnapshot.create(world, blockPos);
|
|
BlockEvent.EntityPlaceEvent placeEvent = new BlockEvent.EntityPlaceEvent(snapshot, placeBlock, player);
|
|
MinecraftForge.EVENT_BUS.post(placeEvent);
|
|
if(placeEvent.isCanceled()) {
|
|
world.removeBlock(blockPos, false);
|
|
return false;
|
|
}
|
|
|
|
// Update stats
|
|
player.addStat(Stats.ITEM_USED.get(placeSnapshot.item));
|
|
player.addStat(ModStats.USE_WAND);
|
|
|
|
placeSnapshot.block = placeBlock;
|
|
return true;
|
|
}
|
|
|
|
protected boolean matchBlocks(Block b1, Block b2) {
|
|
switch(options.match.get()) {
|
|
case EXACT: return b1 == b2;
|
|
case SIMILAR: return ReplacementRegistry.matchBlocks(b1, b2);
|
|
case ANY: return b1 != Blocks.AIR && b2 != Blocks.AIR;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean doIt() {
|
|
LinkedList<PlaceSnapshot> placed = new LinkedList<>();
|
|
|
|
for(PlaceSnapshot snapshot : placeSnapshots) {
|
|
if(wand.isEmpty() || wandItem.getLimit(player, wand) == 0) continue;
|
|
|
|
BlockPos pos = snapshot.pos;
|
|
BlockItem placeItem = snapshot.item;
|
|
|
|
if(placeBlock(snapshot)) {
|
|
wand.damageItem(1, player, (e) -> e.sendBreakAnimation(player.swingingHand));
|
|
|
|
// If the item cant be taken, undo the placement
|
|
if(takeItems(placeItem, 1) == 0) placed.add(snapshot);
|
|
else {
|
|
ConstructionWand.LOGGER.info("Item could not be taken. Remove block: "+placeItem.toString());
|
|
world.removeBlock(pos, false);
|
|
}
|
|
}
|
|
}
|
|
placeSnapshots = placed;
|
|
|
|
// Play place sound
|
|
if(!placeSnapshots.isEmpty()) {
|
|
SoundType sound = placeSnapshots.getFirst().block.getSoundType();
|
|
world.playSound(null, WandUtil.playerPos(player), sound.getPlaceSound(), SoundCategory.BLOCKS, sound.volume, sound.pitch);
|
|
}
|
|
|
|
// Add to job history for undo
|
|
if(placeSnapshots.size() > 1) ConstructionWand.instance.jobHistory.add(this);
|
|
|
|
return !placeSnapshots.isEmpty();
|
|
}
|
|
|
|
public boolean undo() {
|
|
for(PlaceSnapshot snapshot : placeSnapshots) {
|
|
BlockState currentBlock = world.getBlockState(snapshot.pos);
|
|
|
|
// If placed block is still present and can be broken, break it and return item
|
|
if(world.isBlockModifiable(player, snapshot.pos) &&
|
|
(player.isCreative() ||
|
|
(currentBlock.getBlockHardness(world, snapshot.pos) > -1 && world.getTileEntity(snapshot.pos) == null && ReplacementRegistry.matchBlocks(currentBlock.getBlock(), snapshot.block.getBlock()))))
|
|
{
|
|
BlockEvent.BreakEvent breakEvent = new BlockEvent.BreakEvent(world, snapshot.pos, currentBlock, player);
|
|
MinecraftForge.EVENT_BUS.post(breakEvent);
|
|
if(breakEvent.isCanceled()) continue;
|
|
|
|
world.removeBlock(snapshot.pos, false);
|
|
|
|
if(!player.isCreative()) {
|
|
ItemStack stack = new ItemStack(snapshot.item);
|
|
if(!player.inventory.addItemStackToInventory(stack)) {
|
|
player.dropItem(stack, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
player.inventory.markDirty();
|
|
|
|
// Play teleport sound
|
|
SoundEvent sound = SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT;
|
|
world.playSound(null, WandUtil.playerPos(player), sound, SoundCategory.PLAYERS, 1.0F, 1.0F);
|
|
|
|
return true;
|
|
}
|
|
}
|