Skip to content

Commit 7fe9330

Browse files
Spawn Proofer/Light Overlay improvements (#5485)
- Added optional raycasting for stricter servers. - Added blocks per tick option so Spawn Proofer can place faster on servers where allowed. - Added adjustable light levels, this allows for players to be able to place blocks wherever regardless of light level (useful for the nether). - Added adjustable light levels to the light overlay. This will help a lot when players are trying to spawn proof odd mobs that don't spawn in typical light levels. - Fixed Spawn Proofer not placing on soul sand and snow. Light overlay also renders those blocks now.
1 parent c83ada6 commit 7fe9330

3 files changed

Lines changed: 106 additions & 82 deletions

File tree

src/main/java/meteordevelopment/meteorclient/systems/modules/render/LightOverlay.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,12 @@ public class LightOverlay extends Module {
5151
.build()
5252
);
5353

54-
private final Setting<Boolean> newMobSpawnLightLevel = sgGeneral.add(new BoolSetting.Builder()
55-
.name("new-mob-spawn-light-level")
56-
.description("Use the new (1.18+) mob spawn behavior")
57-
.defaultValue(true)
54+
private final Setting<Integer> lightLevel = sgGeneral.add(new IntSetting.Builder()
55+
.name("light-level")
56+
.description("Which light levels to render. Old spawning light: 7.")
57+
.defaultValue(0)
58+
.min(0)
59+
.sliderMax(15)
5860
.build()
5961
);
6062

@@ -86,9 +88,8 @@ private void onTick(TickEvent.Pre event) {
8688
for (Cross cross : crosses) crossPool.free(cross);
8789
crosses.clear();
8890

89-
int spawnLightLevel = newMobSpawnLightLevel.get() ? 0 : 7;
9091
BlockIterator.register(horizontalRange.get(), verticalRange.get(), (blockPos, blockState) -> {
91-
switch (BlockUtils.isValidMobSpawn(blockPos, blockState, spawnLightLevel)) {
92+
switch (BlockUtils.isValidMobSpawn(blockPos, blockState, lightLevel.get())) {
9293
case Potential -> crosses.add(crossPool.get().set(blockPos, true));
9394
case Always -> crosses.add((crossPool.get().set(blockPos, false)));
9495
}

src/main/java/meteordevelopment/meteorclient/systems/modules/world/SpawnProofer.java

Lines changed: 83 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -12,46 +12,71 @@
1212
import meteordevelopment.meteorclient.utils.misc.Pool;
1313
import meteordevelopment.meteorclient.utils.player.FindItemResult;
1414
import meteordevelopment.meteorclient.utils.player.InvUtils;
15+
import meteordevelopment.meteorclient.utils.player.PlayerUtils;
1516
import meteordevelopment.meteorclient.utils.world.BlockIterator;
1617
import meteordevelopment.meteorclient.utils.world.BlockUtils;
1718
import meteordevelopment.orbit.EventHandler;
1819
import net.minecraft.block.*;
1920
import net.minecraft.util.math.BlockPos;
21+
import net.minecraft.util.math.Vec3d;
22+
import net.minecraft.util.hit.BlockHitResult;
23+
import net.minecraft.world.RaycastContext;
2024

2125
import java.util.ArrayList;
2226
import java.util.List;
27+
import java.util.Comparator;
2328

2429
public class SpawnProofer extends Module {
2530
private final SettingGroup sgGeneral = settings.getDefaultGroup();
2631

27-
private final Setting<Integer> range = sgGeneral.add(new IntSetting.Builder()
28-
.name("range")
29-
.description("Range for block placement and rendering")
30-
.defaultValue(3)
32+
private final Setting<Integer> placeDelay = sgGeneral.add(new IntSetting.Builder()
33+
.name("place-delay")
34+
.description("The tick delay between placing blocks.")
35+
.defaultValue(1)
36+
.range(0, 10)
37+
.build()
38+
);
39+
40+
private final Setting<Double> placeRange = sgGeneral.add(new DoubleSetting.Builder()
41+
.name("place-range")
42+
.description("How far away from the player you can place a block.")
43+
.defaultValue(4.5)
3144
.min(0)
45+
.sliderMax(6)
3246
.build()
3347
);
3448

35-
private final Setting<List<Block>> blocks = sgGeneral.add(new BlockListSetting.Builder()
36-
.name("blocks")
37-
.description("Block to use for spawn proofing")
38-
.defaultValue(Blocks.TORCH, Blocks.STONE_BUTTON, Blocks.STONE_SLAB)
39-
.filter(this::filterBlocks)
49+
private final Setting<Double> wallsRange = sgGeneral.add(new DoubleSetting.Builder()
50+
.name("walls-range")
51+
.description("How far away from the player you can place a block behind walls.")
52+
.defaultValue(4.5)
53+
.min(0)
54+
.sliderMax(6)
4055
.build()
4156
);
4257

43-
private final Setting<Integer> delay = sgGeneral.add(new IntSetting.Builder()
44-
.name("delay")
45-
.description("Delay in ticks between placing blocks")
58+
private final Setting<Integer> blocksPerTick = sgGeneral.add(new IntSetting.Builder()
59+
.name("blocks-per-tick")
60+
.description("How many blocks to place in one tick.")
61+
.defaultValue(1)
62+
.min(1)
63+
.build()
64+
);
65+
66+
private final Setting<Integer> lightLevel = sgGeneral.add(new IntSetting.Builder()
67+
.name("light-level")
68+
.description("Light levels to spawn proof. Old spawning light: 7.")
4669
.defaultValue(0)
4770
.min(0)
71+
.sliderMax(15)
4872
.build()
4973
);
5074

51-
private final Setting<Boolean> rotate = sgGeneral.add(new BoolSetting.Builder()
52-
.name("rotate")
53-
.description("Rotates towards the blocks being placed.")
54-
.defaultValue(true)
75+
private final Setting<List<Block>> blocks = sgGeneral.add(new BlockListSetting.Builder()
76+
.name("blocks")
77+
.description("Block to use for spawn proofing.")
78+
.defaultValue(Blocks.TORCH, Blocks.STONE_BUTTON, Blocks.STONE_SLAB)
79+
.filter(this::filterBlocks)
5580
.build()
5681
);
5782

@@ -62,33 +87,29 @@ public class SpawnProofer extends Module {
6287
.build()
6388
);
6489

65-
private final Setting<Boolean> newMobSpawnLightLevel = sgGeneral.add(new BoolSetting.Builder()
66-
.name("new-mob-spawn-light-level")
67-
.description("Use the new (1.18+) mob spawn behavior")
90+
private final Setting<Boolean> rotate = sgGeneral.add(new BoolSetting.Builder()
91+
.name("rotate")
92+
.description("Rotates towards the blocks being placed.")
6893
.defaultValue(true)
6994
.build()
7095
);
7196

72-
7397
private final Pool<BlockPos.Mutable> spawnPool = new Pool<>(BlockPos.Mutable::new);
7498
private final List<BlockPos.Mutable> spawns = new ArrayList<>();
75-
private int ticksWaited;
99+
private int timer;
76100

77101
public SpawnProofer() {
78102
super(Categories.World, "spawn-proofer", "Automatically spawnproofs unlit areas.");
79103
}
80104

81105
@EventHandler
82106
private void onTickPre(TickEvent.Pre event) {
83-
// Delay
84-
if (delay.get() != 0 && ticksWaited < delay.get() - 1) {
85-
return;
86-
}
107+
if (timer < placeDelay.get()) return;
87108

88109
// Find slot
89110
boolean foundBlock = InvUtils.testInHotbar(itemStack -> blocks.get().contains(Block.getBlockFromItem(itemStack.getItem())));
90111
if (!foundBlock) {
91-
error("Found none of the chosen blocks in hotbar");
112+
error("Found none of the chosen blocks in hotbar.");
92113
toggle();
93114
return;
94115
}
@@ -97,13 +118,17 @@ private void onTickPre(TickEvent.Pre event) {
97118
for (BlockPos.Mutable blockPos : spawns) spawnPool.free(blockPos);
98119
spawns.clear();
99120

100-
int lightLevel = newMobSpawnLightLevel.get() ? 0 : 7;
101-
BlockIterator.register(range.get(), range.get(), (blockPos, blockState) -> {
102-
BlockUtils.MobSpawn spawn = BlockUtils.isValidMobSpawn(blockPos, blockState, lightLevel);
121+
BlockIterator.register((int) Math.ceil(placeRange.get()), (int) Math.ceil(placeRange.get()), (blockPos, blockState) -> {
122+
BlockUtils.MobSpawn spawn = BlockUtils.isValidMobSpawn(blockPos, blockState, lightLevel.get());
103123

104124
if ((spawn == BlockUtils.MobSpawn.Always && (mode.get() == Mode.Always || mode.get() == Mode.Both)) ||
105125
spawn == BlockUtils.MobSpawn.Potential && (mode.get() == Mode.Potential || mode.get() == Mode.Both)) {
106126

127+
if (!BlockUtils.canPlace(blockPos)) return;
128+
129+
// Check range and raycast
130+
if (isOutOfRange(blockPos)) return;
131+
107132
spawns.add(spawnPool.get().set(blockPos));
108133
}
109134
});
@@ -112,49 +137,48 @@ private void onTickPre(TickEvent.Pre event) {
112137
@EventHandler
113138
private void onTickPost(TickEvent.Post event) {
114139
// Delay
115-
if (delay.get() != 0 && ticksWaited < delay.get() - 1) {
116-
ticksWaited++;
117-
return;
118-
}
140+
if (timer++ < placeDelay.get()) return;
119141

120142
if (spawns.isEmpty()) return;
121143

122144
// Find slot
123145
FindItemResult block = InvUtils.findInHotbar(itemStack -> blocks.get().contains(Block.getBlockFromItem(itemStack.getItem())));
124146
if (!block.found()) {
125-
error("Found none of the chosen blocks in hotbar");
147+
error("Found none of the chosen blocks in hotbar.");
126148
toggle();
127149
return;
128150
}
129151

130-
// Place blocks
131-
if (delay.get() == 0) {
132-
for (BlockPos blockPos : spawns) BlockUtils.place(blockPos, block, rotate.get(), -50, false);
152+
int placedCount = 0;
153+
154+
// Sort blocks to use the lowest light level spawns first
155+
if (isLightSource(Block.getBlockFromItem(mc.player.getInventory().getStack(block.slot()).getItem()))) {
156+
spawns.sort(Comparator.comparingInt(blockPos -> mc.world.getLightLevel(blockPos)));
157+
placedCount = blocksPerTick.get() - 1; // Force only one light source per tick to stop unnecessary placements
133158
}
134-
else {
135-
// Check if light source
136-
if (isLightSource(Block.getBlockFromItem(mc.player.getInventory().getStack(block.slot()).getItem()))) {
137-
138-
// Find lowest light level
139-
int lowestLightLevel = 16;
140-
BlockPos.Mutable selectedBlockPos = spawns.getFirst();
141-
142-
for (BlockPos blockPos : spawns) {
143-
int lightLevel = mc.world.getLightLevel(blockPos);
144-
if (lightLevel < lowestLightLevel) {
145-
lowestLightLevel = lightLevel;
146-
selectedBlockPos.set(blockPos);
147-
}
148-
}
149-
150-
BlockUtils.place(selectedBlockPos, block, rotate.get(), -50, false);
151-
}
152-
else {
153-
BlockUtils.place(spawns.getFirst(), block, rotate.get(), -50, false);
159+
160+
// Place blocks!
161+
for (BlockPos blockPos : spawns) {
162+
if (placedCount >= blocksPerTick.get()) continue;
163+
164+
if (BlockUtils.place(blockPos, block, rotate.get(), -50, false)) {
165+
placedCount++;
154166
}
155167
}
156168

157-
ticksWaited = 0;
169+
timer = 0;
170+
}
171+
172+
private boolean isOutOfRange(BlockPos blockPos) {
173+
Vec3d pos = blockPos.toCenterPos();
174+
if (!PlayerUtils.isWithin(pos, placeRange.get())) return true;
175+
176+
RaycastContext raycastContext = new RaycastContext(mc.player.getEyePos(), pos, RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, mc.player);
177+
BlockHitResult result = mc.world.raycast(raycastContext);
178+
if (result == null || !result.getBlockPos().equals(blockPos))
179+
return !PlayerUtils.isWithin(pos, wallsRange.get());
180+
181+
return false;
158182
}
159183

160184
private boolean filterBlocks(Block block) {
@@ -180,7 +204,6 @@ private boolean isLightSource(Block block) {
180204
public enum Mode {
181205
Always,
182206
Potential,
183-
Both,
184-
None
207+
Both
185208
}
186209
}

src/main/java/meteordevelopment/meteorclient/utils/world/BlockUtils.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -306,32 +306,32 @@ public static boolean isClickable(Block block) {
306306
|| block instanceof TrapdoorBlock;
307307
}
308308

309-
public static MobSpawn isValidMobSpawn(BlockPos blockPos, boolean newMobSpawnLightLevel) {
310-
return isValidMobSpawn(blockPos, mc.world.getBlockState(blockPos), newMobSpawnLightLevel ? 0 : 7);
311-
}
312309

313310
public static MobSpawn isValidMobSpawn(BlockPos blockPos, BlockState blockState, int spawnLightLimit) {
314-
if (!(blockState.getBlock() instanceof AirBlock)) return MobSpawn.Never;
315-
316-
BlockPos down = blockPos.down();
317-
BlockState downState = mc.world.getBlockState(down);
318-
if (downState.getBlock() == Blocks.BEDROCK) return MobSpawn.Never;
311+
boolean snow = blockState.getBlock() instanceof SnowBlock && blockState.get(SnowBlock.LAYERS) == 1;
312+
if (!blockState.isAir() && !snow) return MobSpawn.Never;
319313

320-
if (!topSurface(downState)) {
321-
if (downState.getCollisionShape(mc.world, down) != VoxelShapes.fullCube())
322-
return MobSpawn.Never;
323-
if (downState.isTransparent()) return MobSpawn.Never;
324-
}
314+
if (!isValidSpawnBlock(mc.world.getBlockState(blockPos.down()))) return MobSpawn.Never;
325315

326316
if (mc.world.getLightLevel(LightType.BLOCK, blockPos) > spawnLightLimit) return MobSpawn.Never;
327317
else if (mc.world.getLightLevel(LightType.SKY, blockPos) > spawnLightLimit) return MobSpawn.Potential;
328318

329319
return MobSpawn.Always;
330320
}
331321

332-
public static boolean topSurface(BlockState blockState) {
333-
if (blockState.getBlock() instanceof SlabBlock && blockState.get(SlabBlock.TYPE) == SlabType.TOP) return true;
334-
else return blockState.getBlock() instanceof StairsBlock && blockState.get(StairsBlock.HALF) == BlockHalf.TOP;
322+
public static boolean isValidSpawnBlock(BlockState blockState) {
323+
Block block = blockState.getBlock();
324+
325+
if (block == Blocks.BEDROCK
326+
|| block == Blocks.BARRIER
327+
|| block instanceof TransparentBlock
328+
|| block instanceof ScaffoldingBlock) return false;
329+
330+
if (block == Blocks.SOUL_SAND || block == Blocks.MUD) return true;
331+
if (block instanceof SlabBlock && blockState.get(SlabBlock.TYPE) == SlabType.TOP) return true;
332+
if (block instanceof StairsBlock && blockState.get(StairsBlock.HALF) == BlockHalf.TOP) return true;
333+
334+
return blockState.isOpaqueFullCube();
335335
}
336336

337337
// Finds the best block direction to get when interacting with the block.

0 commit comments

Comments
 (0)