Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to The Spectator #4587

Merged
Merged
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
1 change: 1 addition & 0 deletions Fabric/src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"vazkii.botania.test.item.FlowerPouchTest",
"vazkii.botania.test.item.LifeAggregatorTest",
"vazkii.botania.test.item.GrassSeedDispensingTest",
"vazkii.botania.test.item.SpectatorScanTest",
"vazkii.botania.test.item.lens.BoreLensTest",
"vazkii.botania.test.item.lens.EntropicWarpLensTest",
"vazkii.botania.test.item.lens.PaintslingerLensTest"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
public final class ItemNBTHelper {

private static final int[] EMPTY_INT_ARRAY = new int[0];
private static final long[] EMPTY_LONG_ARRAY = new long[0];

// SETTERS ///////////////////////////////////////////////////////////////////

Expand Down Expand Up @@ -58,6 +59,10 @@ public static void setLong(ItemStack stack, String tag, long l) {
stack.getOrCreateTag().putLong(tag, l);
}

public static void setLongArray(ItemStack stack, String tag, long[] val) {
stack.getOrCreateTag().putLongArray(tag, val);
}

public static void setFloat(ItemStack stack, String tag, float f) {
stack.getOrCreateTag().putFloat(tag, f);
}
Expand Down Expand Up @@ -91,6 +96,10 @@ public static boolean verifyExistance(ItemStack stack, String tag) {
return !stack.isEmpty() && stack.hasTag() && stack.getOrCreateTag().contains(tag);
}

public static boolean verifyType(ItemStack stack, String tag, Class<? extends Tag> tagClass) {
return !stack.isEmpty() && stack.hasTag() && tagClass.isInstance(stack.getOrCreateTag().get(tag));
}

@Nullable
public static Tag get(ItemStack stack, String tag) {
return verifyExistance(stack, tag) ? stack.getOrCreateTag().get(tag) : null;
Expand Down Expand Up @@ -120,6 +129,10 @@ public static long getLong(ItemStack stack, String tag, long defaultExpected) {
return verifyExistance(stack, tag) ? stack.getOrCreateTag().getLong(tag) : defaultExpected;
}

public static long[] getLongArray(ItemStack stack, String tag) {
return verifyExistance(stack, tag) ? stack.getOrCreateTag().getLongArray(tag) : EMPTY_LONG_ARRAY;
}

public static float getFloat(ItemStack stack, String tag, float defaultExpected) {
return verifyExistance(stack, tag) ? stack.getOrCreateTag().getFloat(tag) : defaultExpected;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.mojang.blaze3d.vertex.VertexConsumer;

import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList;

import net.minecraft.client.Minecraft;
import net.minecraft.client.model.HumanoidModel;
Expand All @@ -20,19 +21,21 @@
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.LongTag;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.LongArrayTag;
import net.minecraft.world.Container;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.animal.allay.Allay;
import net.minecraft.world.entity.animal.horse.AbstractChestedHorse;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.AbstractMinecartContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.trading.Merchant;
import net.minecraft.world.item.trading.MerchantOffer;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
import net.minecraft.world.phys.AABB;

import vazkii.botania.api.BotaniaAPI;
Expand All @@ -42,13 +45,19 @@
import vazkii.botania.client.render.AccessoryRenderer;
import vazkii.botania.common.helper.ItemNBTHelper;
import vazkii.botania.common.proxy.Proxy;
import vazkii.botania.mixin.AbstractHorseAccessor;
import vazkii.botania.mixin.RandomizableContainerBlockEntityAccessor;

import java.util.List;

public class SpectatorItem extends BaubleItem {

private static final String TAG_ENTITY_POSITIONS = "highlightPositionsEnt";
private static final String TAG_BLOCK_POSITIONS = "highlightPositionsBlock";
private static final int[] EMPTY_ENTITIES_ARRAY = new int[0];
private static final long[] EMPTY_BLOCKPOS_ARRAY = new long[0];
public static final String TAG_ENTITY_POSITIONS = "highlightPositionsEnt";
public static final String TAG_BLOCK_POSITIONS = "highlightPositionsBlock";
public static final int RANGE_ENTITIES = 24;
public static final int RANGE_BLOCKS = 12;
public static final int SCAN_INTERVAL_TICKS = 4;

public SpectatorItem(Properties props) {
super(props);
Expand All @@ -62,12 +71,18 @@ public void onWornTick(ItemStack stack, LivingEntity living) {
}

if (living.level().isClientSide) {
this.tickClient(stack, player);
} else {
this.tickServer(stack, player);
this.showScanResults(stack, player);
} else if (living.tickCount % SCAN_INTERVAL_TICKS == 0) {
this.scanForItems(stack, player);
}
}

@Override
public void onUnequipped(ItemStack stack, LivingEntity entity) {
ItemNBTHelper.removeEntry(stack, TAG_BLOCK_POSITIONS);
ItemNBTHelper.removeEntry(stack, TAG_ENTITY_POSITIONS);
}

public static class Renderer implements AccessoryRenderer {
@Override
public void doRender(HumanoidModel<?> bipedModel, ItemStack stack, LivingEntity living, PoseStack ms, MultiBufferSource buffers, int light, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch) {
Expand All @@ -83,15 +98,18 @@ public void doRender(HumanoidModel<?> bipedModel, ItemStack stack, LivingEntity
}
}

protected void tickClient(ItemStack stack, Player player) {
protected void showScanResults(ItemStack stack, Player player) {
if (player != Proxy.INSTANCE.getClientPlayer()) {
return;
}

ListTag blocks = ItemNBTHelper.getList(stack, TAG_BLOCK_POSITIONS, Tag.TAG_LONG, false);
// backward compatibility: this was a list tag before
var blockPosLongs = ItemNBTHelper.verifyType(stack, TAG_BLOCK_POSITIONS, LongArrayTag.class)
? ItemNBTHelper.getLongArray(stack, TAG_BLOCK_POSITIONS)
: EMPTY_BLOCKPOS_ARRAY;

for (var block : blocks) {
BlockPos pos = BlockPos.of(((LongTag) block).getAsLong());
for (var blockPosLong : blockPosLongs) {
BlockPos pos = BlockPos.of(blockPosLong);
float m = 0.02F;
WispParticleData data = WispParticleData.wisp(0.15F + 0.05F * (float) Math.random(), (float) Math.random(), (float) Math.random(), (float) Math.random(), false);
player.level().addParticle(data, pos.getX() + (float) Math.random(), pos.getY() + (float) Math.random(), pos.getZ() + (float) Math.random(), m * (float) (Math.random() - 0.5), m * (float) (Math.random() - 0.5), m * (float) (Math.random() - 0.5));
Expand All @@ -100,91 +118,108 @@ protected void tickClient(ItemStack stack, Player player) {
int[] entities = ItemNBTHelper.getIntArray(stack, TAG_ENTITY_POSITIONS);
for (int i : entities) {
Entity e = player.level().getEntity(i);
if (e != null && Math.random() < 0.6) {
if (e != null && e.isAlive() && Math.random() < 0.6) {
WispParticleData data = WispParticleData.wisp(0.15F + 0.05F * (float) Math.random(), (float) Math.random(), (float) Math.random(), (float) Math.random(), Math.random() < 0.6);
player.level().addParticle(data, e.getX() + (float) (Math.random() * 0.5 - 0.25) * 0.45F, e.getY() + e.getBbHeight(), e.getZ() + (float) (Math.random() * 0.5 - 0.25) * 0.45F, 0, 0.05F + 0.03F * (float) Math.random(), 0);
}
}
}

protected void tickServer(ItemStack stack, Player player) {
IntArrayList entPosBuilder = new IntArrayList();
ListTag blockPosBuilder = new ListTag();

scanForStack(player.getMainHandItem(), player, entPosBuilder, blockPosBuilder);
scanForStack(player.getOffhandItem(), player, entPosBuilder, blockPosBuilder);
public void scanForItems(ItemStack stack, Player player) {
ItemStack mainHandStack = player.getMainHandItem();
ItemStack offHandStack = player.getOffhandItem();

int[] currentEnts = entPosBuilder.elements();
int[] entityIds = scanEntities(player, mainHandStack, offHandStack);
ItemNBTHelper.setIntArray(stack, TAG_ENTITY_POSITIONS, entityIds);

ItemNBTHelper.setIntArray(stack, TAG_ENTITY_POSITIONS, currentEnts);
ItemNBTHelper.setList(stack, TAG_BLOCK_POSITIONS, blockPosBuilder);
long[] blockPositionLongs = scanBlockContainers(player, mainHandStack, offHandStack);
ItemNBTHelper.setLongArray(stack, TAG_BLOCK_POSITIONS, blockPositionLongs);
}

private void scanForStack(ItemStack pstack, Player player, IntArrayList entIdBuilder, ListTag blockPosBuilder) {
if (!pstack.isEmpty() || player.isShiftKeyDown()) {
int range = 24;

List<Entity> entities = player.level().getEntitiesOfClass(Entity.class, new AABB(player.getX() - range, player.getY() - range, player.getZ() - range, player.getX() + range, player.getY() + range, player.getZ() + range));
for (Entity e : entities) {
if (e == player) {
continue;
private int[] scanEntities(Player player, ItemStack mainHandStack, ItemStack offHandStack) {
boolean emptyHands = mainHandStack.isEmpty() && offHandStack.isEmpty();
if (emptyHands && !player.isShiftKeyDown()) {
return EMPTY_ENTITIES_ARRAY;
}
var entityIds = new IntArrayList();
List<Entity> entities = player.level().getEntitiesOfClass(Entity.class, new AABB(player.blockPosition()).inflate(RANGE_ENTITIES));
for (Entity e : entities) {
if (e == player) {
continue;
}
if (e instanceof ItemEntity item) {
ItemStack entityStack = item.getItem();
if (player.isShiftKeyDown() || equalStacks(entityStack, mainHandStack, offHandStack)) {
entityIds.add(item.getId());
}
if (e instanceof ItemEntity item) {
ItemStack istack = item.getItem();
if (player.isShiftKeyDown() || ItemStack.isSameItemSameTags(istack, pstack)) {
entIdBuilder.add(item.getId());
}
} else if (e instanceof Player targetPlayer) {
Container binv = BotaniaAPI.instance().getAccessoriesInventory(targetPlayer);
if (scanInventory(targetPlayer.getInventory(), pstack) || scanInventory(binv, pstack)) {
entIdBuilder.add(targetPlayer.getId());
}

} else if (e instanceof Merchant villager) {
for (MerchantOffer offer : villager.getOffers()) {
if (equalStacks(pstack, offer.getBaseCostA())
|| equalStacks(pstack, offer.getCostB())
|| equalStacks(pstack, offer.getResult())) {
entIdBuilder.add(e.getId());
}
}
} else if (e instanceof Container inv) {
if (scanInventory(inv, pstack)) {
entIdBuilder.add(e.getId());
} else if (emptyHands) {
continue;
}
if (e instanceof Player targetPlayer) {
if (scanInventory(targetPlayer.getInventory(), mainHandStack, offHandStack)) {
entityIds.add(targetPlayer.getId());
} else {
Container baubleInventory = BotaniaAPI.instance().getAccessoriesInventory(targetPlayer);
if (scanInventory(baubleInventory, mainHandStack, offHandStack)) {
entityIds.add(targetPlayer.getId());
}
}
}

if (!pstack.isEmpty()) {
range = 12;
BlockPos pos = player.blockPosition();
for (BlockPos pos_ : BlockPos.betweenClosed(pos.offset(-range, -range, -range), pos.offset(range + 1, range + 1, range + 1))) {
BlockEntity tile = player.level().getBlockEntity(pos_);
if (tile != null) {
if (tile instanceof Container inv) {
if (scanInventory(inv, pstack)) {
blockPosBuilder.add(LongTag.valueOf(pos_.asLong()));
}
}
} else if (e instanceof AbstractChestedHorse horse && horse.hasChest()) {
if (scanInventory(((AbstractHorseAccessor) horse).getInventory(), mainHandStack, offHandStack)) {
entityIds.add(horse.getId());
}
} else if (e instanceof Allay allay && allay.hasItemInHand()) {
if (equalStacks(allay.getMainHandItem(), mainHandStack, offHandStack)) {
entityIds.add(allay.getId());
}
} else if (e instanceof Merchant villager) {
for (MerchantOffer offer : villager.getOffers()) {
if (equalStacks(offer.getBaseCostA(), mainHandStack, offHandStack)
|| equalStacks(offer.getCostB(), mainHandStack, offHandStack)
|| equalStacks(offer.getResult(), mainHandStack, offHandStack)) {
entityIds.add(e.getId());
}
}
} else if (e instanceof Container inv && (!(inv instanceof AbstractMinecartContainer minecart)
|| minecart.getLootTable() == null)) {
if (scanInventory(inv, mainHandStack, offHandStack)) {
entityIds.add(e.getId());
}
}
}
entityIds.trim();
return entityIds.elements();
}

private boolean equalStacks(ItemStack stack1, ItemStack stack2) {
return ItemStack.isSameItemSameTags(stack1, stack2);
private long[] scanBlockContainers(Player player, ItemStack mainHandStack, ItemStack offHandStack) {
if (mainHandStack.isEmpty() && offHandStack.isEmpty()) {
return EMPTY_BLOCKPOS_ARRAY;
}
var blockPositions = new LongArrayList();
BlockPos.betweenClosedStream(new AABB(player.blockPosition()).inflate(RANGE_BLOCKS))
.filter(pos -> scanBlock(player, pos, mainHandStack, offHandStack))
.forEach(pos -> blockPositions.add(pos.asLong()));
blockPositions.trim();
return blockPositions.elements();
}

private boolean scanInventory(Container inv, ItemStack pstack) {
if (pstack.isEmpty()) {
return false;
}
private boolean scanBlock(Player player, BlockPos pos, ItemStack mainHandStack, ItemStack offHandStack) {
BlockEntity blockEntity = player.level().getBlockEntity(pos);
return blockEntity instanceof Container inv && (!(blockEntity instanceof RandomizableContainerBlockEntity lootInv)
|| ((RandomizableContainerBlockEntityAccessor) lootInv).getLootTable() == null)
&& scanInventory(inv, mainHandStack, offHandStack);
}

private boolean equalStacks(ItemStack testStack, ItemStack referenceStack1, ItemStack referenceStack2) {
return !testStack.isEmpty() && (ItemStack.isSameItemSameTags(testStack, referenceStack1)
|| ItemStack.isSameItemSameTags(testStack, referenceStack2));
}

private boolean scanInventory(Container inv, ItemStack mainHandStack, ItemStack offHandStack) {
for (int l = 0; l < inv.getContainerSize(); l++) {
ItemStack istack = inv.getItem(l);
ItemStack inventoryStack = inv.getItem(l);
// Some mods still set stuff to null apparently...
if (istack != null && !istack.isEmpty() && equalStacks(istack, pstack)) {
if (inventoryStack != null && equalStacks(inventoryStack, mainHandStack, offHandStack)) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;

@Mixin(AbstractHorse.class)
public interface AbstractHorseAccessor {
@Accessor
SimpleContainer getInventory();

@Invoker("createInventory")
void botania_createInventory();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package vazkii.botania.mixin;

import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

@Mixin(RandomizableContainerBlockEntity.class)
public interface RandomizableContainerBlockEntityAccessor {
@Accessor
ResourceLocation getLootTable();
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ private void spawnItem(GameTestHelper helper, Item item) {
helper.spawnItem(item, pos.getX() + 0.5F, pos.getY() + 0.5F, pos.getZ() + 0.5F);
}

@GameTest(template = TEMPLATE)
@GameTest(template = TEMPLATE, batch = "apothecary")
public void testItemEnterPrevention(GameTestHelper helper) {
helper.startSequence().thenExecute(() -> {
spawnItem(helper, BotaniaItems.whitePetal);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public void testComposterFull(GameTestHelper helper) {
helper.startSequence().thenExecuteAfter(61, () -> {
helper.assertBlockProperty(composterPos, ComposterBlock.LEVEL, 8);
helper.assertItemEntityPresent(Items.PUMPKIN_PIE, SPAWN_ITEM_POS, 0);
}).thenExecute(helper::killAllEntities).thenSucceed();
}).thenSucceed();
}

private void spawnItemAndExpectNoPickUp(GameTestHelper helper, BlockPos composterPos, Item item) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class RannuncarpusTest {
private static final BlockPos SEA_PICKLE_POS = new BlockPos(1, 2, 1);
private static final BlockPos FLOATING_YELLOW_CANDLE_POS = new BlockPos(1, 5, 2);

@GameTest(template = TEMPLATE)
@GameTest(template = TEMPLATE, batch = "rannuncarpus1")
public void testDestinationFilterPositive(GameTestHelper helper) {
helper.startSequence().thenExecute(() -> {
helper.killAllEntities();
Expand All @@ -40,7 +40,7 @@ public void testDestinationFilterPositive(GameTestHelper helper) {
}).thenSucceed();
}

@GameTest(template = TEMPLATE)
@GameTest(template = TEMPLATE, batch = "rannuncarpus2")
public void testDestinationFilterNegative(GameTestHelper helper) {
helper.startSequence().thenExecute(() -> {
helper.killAllEntities();
Expand Down Expand Up @@ -79,7 +79,7 @@ public void testPickupFilterNegative(GameTestHelper helper) {
// new items are supplied twice during the test
+ 2 * DelayHelper.FUNCTIONAL_INHERENT_DELAY;

@GameTest(template = TEMPLATE_CANDLES, timeoutTicks = MULTI_PLACEMENT_TIMEOUT_TICKS)
@GameTest(template = TEMPLATE_CANDLES, timeoutTicks = MULTI_PLACEMENT_TIMEOUT_TICKS, batch = "rannuncarpus3")
public void testMultiplePlacements(GameTestHelper helper) {
final var yellowCandles = helper.spawnItem(Blocks.YELLOW_CANDLE.asItem(), FLOWER_POS.getX(), FLOWER_POS.getY() + 1, FLOWER_POS.getZ());
helper.startSequence().thenExecute(() -> {
Expand Down
Loading
Loading