Skip to content

Commit 8306291

Browse files
Avanatikerbladekt
andauthored
[1.20.x] [All] Feat: Add Scaffold Module (#73)
**Description:** **What does this PR do?** This PR adds a new Scaffold Module, which allows players to place blocks beneath themselves while moving, enabling them to build bridges and stack blocks more easily. **Why is this change needed?** This feature is needed to provide players with a convenient way to build and navigate complex structures, improving the overall building and exploration experience in the game. --------- Co-authored-by: Blade-gl <bladecore.kt@gmail.com>
1 parent a4ef050 commit 8306291

File tree

27 files changed

+962
-112
lines changed

27 files changed

+962
-112
lines changed

common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22

33
import com.lambda.Lambda;
44
import com.lambda.event.EventFlow;
5-
import com.lambda.event.events.ClientEvent;
6-
import com.lambda.event.events.ScreenEvent;
7-
import com.lambda.event.events.ScreenHandlerEvent;
8-
import com.lambda.event.events.TickEvent;
9-
import com.lambda.interaction.RotationManager;
5+
import com.lambda.event.events.*;
106
import com.lambda.module.modules.player.Interact;
117
import net.minecraft.client.MinecraftClient;
128
import net.minecraft.client.gui.screen.Screen;
@@ -27,7 +23,6 @@ public class MinecraftClientMixin {
2723
@Inject(method = "tick", at = @At("HEAD"))
2824
void onTickPre(CallbackInfo ci) {
2925
EventFlow.post(new TickEvent.Pre());
30-
RotationManager.update();
3126
}
3227

3328
@Inject(method = "tick", at = @At("RETURN"))

common/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.spongepowered.asm.mixin.injection.Inject;
1919
import org.spongepowered.asm.mixin.injection.Redirect;
2020
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
21+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
2122

2223
import java.util.Objects;
2324

@@ -67,11 +68,22 @@ boolean isSprinting(ClientPlayerEntity entity) {
6768
return EventFlow.post(new MovementEvent.Sprint(entity.isSprinting())).getSprint();
6869
}
6970

71+
@Inject(method = "isSneaking", at = @At(value = "HEAD"), cancellable = true)
72+
void redirectSneaking(CallbackInfoReturnable<Boolean> cir) {
73+
ClientPlayerEntity self = (ClientPlayerEntity) (Object) this;
74+
if (self != Lambda.getMc().player) return;
75+
76+
if (self.input == null) return;
77+
cir.setReturnValue(EventFlow.post(new MovementEvent.Sneak(self.input.sneaking)).getSneak());
78+
}
79+
7080
@Inject(method = "sendMovementPackets", at = @At(value = "HEAD"), cancellable = true)
7181
void sendBegin(CallbackInfo ci) {
7282
ci.cancel();
7383
PlayerPacketManager.sendPlayerPackets();
7484
autoJumpEnabled = Lambda.getMc().options.getAutoJump().getValue();
85+
86+
RotationManager.update();
7587
}
7688

7789
@Inject(method = "tick", at = @At(value = "HEAD"))

common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class InteractionSettings(
88
vis: () -> Boolean = { true },
99
) : InteractionConfig {
1010
override val reach by c.setting("Reach", defaultReach, 0.1..10.0, 0.1, "Players reach / range", " blocks", vis)
11-
override val useRayCast by c.setting("Raycast", false, "Verify hit vector with ray casting (for very strict ACs)", vis)
12-
override val resolution by c.setting("Resolution", 10, 1..30, 1, "How many raycast checks per surface (will be squared)") { vis() && useRayCast }
11+
override val useRayCast by c.setting("Raycast", true, "Verify hit vector with ray casting (for very strict ACs)", vis)
12+
override val resolution by c.setting("Resolution", 20, 1..40, 1, "How many raycast checks per surface (will be squared)") { vis() && useRayCast }
1313
override val swingHand by c.setting("Swing Hand", true, "Swing hand on interactions", vis)
1414
}

common/src/main/kotlin/com/lambda/config/groups/Targeting.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.lambda.util.math.VecUtils.distSq
1010
import com.lambda.util.world.entitySearch
1111
import net.minecraft.client.network.ClientPlayerEntity
1212
import net.minecraft.entity.LivingEntity
13+
import net.minecraft.entity.decoration.ArmorStandEntity
1314
import net.minecraft.entity.mob.MobEntity
1415
import net.minecraft.entity.passive.PassiveEntity
1516

@@ -89,6 +90,7 @@ abstract class Targeting(
8990
!players && entity.isPlayer -> false
9091
!animals && entity is PassiveEntity -> false
9192
!hostiles && entity is MobEntity -> false
93+
entity is ArmorStandEntity -> false
9294

9395
!invisible && entity.isInvisibleTo(player) -> false
9496
!dead && entity.isDead -> false

common/src/main/kotlin/com/lambda/event/events/MovementEvent.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ abstract class MovementEvent : Event {
2121
) : MovementEvent()
2222

2323
class Sprint(var sprint: Boolean) : MovementEvent()
24+
class Sneak(var sneak: Boolean) : MovementEvent()
2425

2526
class ClipAtLedge(
2627
var clip: Boolean,

common/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ abstract class PlayerPacketEvent : Event {
1616
var isSneaking: Boolean,
1717
) : PlayerPacketEvent(), ICancellable by Cancellable()
1818

19-
class Post(
19+
class Post : PlayerPacketEvent()
20+
21+
class Send(
2022
val packet: PlayerMoveC2SPacket,
2123
) : PlayerPacketEvent(), ICancellable by Cancellable()
2224
}

common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.lambda.core.Loadable
55
import com.lambda.event.EventFlow.post
66
import com.lambda.event.EventFlow.postChecked
77
import com.lambda.event.events.PlayerPacketEvent
8+
import com.lambda.interaction.rotation.Rotation
89
import com.lambda.threading.runSafe
910
import com.lambda.util.collections.LimitedOrderedSet
1011
import com.lambda.util.math.VecUtils.approximate
@@ -15,9 +16,17 @@ import com.lambda.util.extension.component2
1516
import com.lambda.util.extension.component3
1617
import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket
1718
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.*
19+
import net.minecraft.util.math.Vec3d
1820

1921
object PlayerPacketManager : Loadable {
2022
val configurations = LimitedOrderedSet<PlayerPacketEvent.Pre>(100)
23+
24+
var lastPosition = Vec3d.ZERO
25+
var lastRotation = Rotation.ZERO
26+
var lastSprint = false
27+
var lastSneak = false
28+
var lastOnGround = false
29+
2130
private var sendTicks = 0
2231

2332
@JvmStatic
@@ -36,11 +45,10 @@ object PlayerPacketManager : Loadable {
3645
}
3746

3847
private fun SafeContext.updatePlayerPackets(new: PlayerPacketEvent.Pre) {
39-
val previous = configurations.lastOrNull() ?: new
4048
configurations.add(new)
4149

42-
reportSprint(previous, new)
43-
reportSneak(previous, new)
50+
reportSprint(lastSprint, new.isSprinting)
51+
reportSneak(lastSneak, new.isSneaking)
4452

4553
if (mc.cameraEntity != player) return
4654

@@ -63,9 +71,9 @@ object PlayerPacketManager : Loadable {
6371
return
6472
}
6573

66-
val updatePosition = position.approximate(previous.position, 2.0E-4) || ++sendTicks >= 20
74+
val updatePosition = position.approximate(lastPosition, 2.0E-4) || ++sendTicks >= 20
6775
// has to be different in float precision
68-
val updateRotation = !rotation.equalFloat(previous.rotation)
76+
val updateRotation = !rotation.equalFloat(lastRotation)
6977

7078
val (x, y, z) = position
7179

@@ -82,46 +90,57 @@ object PlayerPacketManager : Loadable {
8290
LookAndOnGround(yaw, pitch, onGround)
8391
}
8492

85-
previous.onGround != onGround -> {
93+
lastOnGround != onGround -> {
8694
OnGroundOnly(onGround)
8795
}
8896

8997
else -> null
9098
}
9199

92-
if (updatePosition) {
93-
sendTicks = 0
94-
}
95-
96100
packet?.let {
97-
PlayerPacketEvent.Post(it).postChecked {
101+
PlayerPacketEvent.Send(it).postChecked {
98102
connection.sendPacket(this.packet)
103+
104+
if (updatePosition) {
105+
sendTicks = 0
106+
lastPosition = position
107+
}
108+
109+
if (updateRotation) {
110+
lastRotation = rotation
111+
}
112+
113+
lastOnGround = onGround
99114
}
100115
}
116+
117+
PlayerPacketEvent.Post().post()
101118
}
102119

103-
private fun SafeContext.reportSprint(previous: PlayerPacketEvent.Pre, new: PlayerPacketEvent.Pre) {
104-
if (previous.isSprinting == new.isSprinting) return
120+
fun SafeContext.reportSprint(previous: Boolean, new: Boolean) {
121+
if (previous == new) return
105122

106-
val state = if (new.isSprinting) {
123+
val state = if (new) {
107124
ClientCommandC2SPacket.Mode.START_SPRINTING
108125
} else {
109126
ClientCommandC2SPacket.Mode.STOP_SPRINTING
110127
}
111128

112129
connection.sendPacket(ClientCommandC2SPacket(player, state))
130+
lastSprint = new
113131
}
114132

115-
private fun SafeContext.reportSneak(previous: PlayerPacketEvent.Pre, new: PlayerPacketEvent.Pre) {
116-
if (previous.isSneaking == new.isSneaking) return
133+
fun SafeContext.reportSneak(previous: Boolean, new: Boolean) {
134+
if (previous == new) return
117135

118-
val state = if (new.isSneaking) {
136+
val state = if (new) {
119137
ClientCommandC2SPacket.Mode.PRESS_SHIFT_KEY
120138
} else {
121139
ClientCommandC2SPacket.Mode.RELEASE_SHIFT_KEY
122140
}
123141

124142
connection.sendPacket(ClientCommandC2SPacket(player, state))
143+
lastSneak = new
125144
}
126145
}
127146

common/src/main/kotlin/com/lambda/interaction/RotationManager.kt

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ object RotationManager : Loadable {
3838
fun Any.requestRotation(
3939
priority: Int = 0,
4040
alwaysListen: Boolean = false,
41-
onUpdate: SafeContext.() -> RotationContext?,
42-
onReceive: SafeContext.() -> Unit
41+
onUpdate: SafeContext.(lastContext: RotationContext?) -> RotationContext?,
42+
onReceive: SafeContext.() -> Unit = {}
4343
) {
4444
var lastCtx: RotationContext? = null
4545

4646
this.listener<RotationEvent.Update>(priority, alwaysListen) { event ->
47-
val rotationContext = onUpdate()
47+
val rotationContext = onUpdate(event.context)
4848

4949
rotationContext?.let {
5050
event.context = it
@@ -162,23 +162,20 @@ object RotationManager : Loadable {
162162
@JvmStatic
163163
val movementYaw: Float?
164164
get() {
165-
val config = currentContext?.config ?: return null
166-
if (config.rotationMode == RotationMode.SILENT) return null
165+
if (currentContext?.config?.rotationMode == RotationMode.SILENT) return null
167166
return currentRotation.yaw.toFloat()
168167
}
169168

170169
@JvmStatic
171170
val movementPitch: Float?
172171
get() {
173-
val config = currentContext?.config ?: return null
174-
if (config.rotationMode == RotationMode.SILENT) return null
172+
if (currentContext?.config?.rotationMode == RotationMode.SILENT) return null
175173
return currentRotation.pitch.toFloat()
176174
}
177175

178176
@JvmStatic
179177
fun getRotationForVector(deltaTime: Double): Vec2d? {
180-
val config = currentContext?.config ?: return null
181-
if (config.rotationMode == RotationMode.SILENT) return null
178+
if (currentContext?.config?.rotationMode == RotationMode.SILENT) return null
182179

183180
val rot = lerp(deltaTime, prevRotation, currentRotation)
184181
return Vec2d(rot.yaw, rot.pitch)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.lambda.interaction.blockplace
2+
3+
import com.lambda.context.SafeContext
4+
import com.lambda.interaction.blockplace.PlaceInteraction.canPlaceAt
5+
import com.lambda.interaction.blockplace.PlaceInteraction.isClickable
6+
import com.lambda.interaction.visibilty.VisibilityChecker.getVisibleSurfaces
7+
import com.lambda.util.BlockUtils.blockState
8+
import com.lambda.util.math.VecUtils.distSq
9+
import com.lambda.util.math.VecUtils.getHitVec
10+
import net.minecraft.util.math.BlockPos
11+
import net.minecraft.util.math.Box
12+
import net.minecraft.util.math.Direction
13+
import net.minecraft.util.math.Vec3d
14+
import java.util.*
15+
import kotlin.collections.Collection
16+
17+
class PlaceFinder(
18+
private val basePos: BlockPos,
19+
private val maxAttempts: Int,
20+
range: Double,
21+
private val eyes: Vec3d,
22+
private val visibleCheck: Boolean,
23+
private val sides: Set<Direction>
24+
) {
25+
private val rangeSq = range * range
26+
27+
private val Collection<PlaceInfo>.selectClosest get() = minByOrNull { it.eyeDistanceSq }
28+
private val Collection<PlaceInfo>.selectBest get() = this.let { infos ->
29+
if (infos.isEmpty()) return@let null
30+
val lowestStepAmount = infos.minOf { it.placeSteps }
31+
infos.filter { it.placeSteps == lowestStepAmount }.selectClosest
32+
}
33+
34+
companion object {
35+
fun SafeContext.buildPlaceInfo(
36+
basePos: BlockPos,
37+
maxAttempts: Int = 4,
38+
range: Double = 3.25,
39+
eyes: Vec3d = player.eyePos,
40+
visibleCheck: Boolean = true,
41+
sides: Set<Direction> = EnumSet.allOf(Direction::class.java)
42+
) = PlaceFinder(basePos, maxAttempts, range, eyes, visibleCheck, sides).build(this)
43+
}
44+
45+
private fun build(
46+
ctx: SafeContext,
47+
pos: BlockPos = basePos,
48+
attempts: Int = 0
49+
): PlaceInfo? = with(ctx) {
50+
if (sides.isEmpty()) return null
51+
if (!canPlaceAt(pos)) return null
52+
53+
sides.mapNotNull { checkSide(pos, it, attempts) }.selectClosest?.let {
54+
return it
55+
}
56+
57+
if (attempts > maxAttempts) return null
58+
59+
return sides.mapNotNull { side ->
60+
build(this, pos.offset(side), attempts + 1)
61+
}.selectBest
62+
}
63+
64+
private fun SafeContext.checkSide(pos: BlockPos, side: Direction, attempts: Int): PlaceInfo? {
65+
val clickPos = pos.offset(side)
66+
val clickSide = side.opposite
67+
68+
val hitVec = clickPos.getHitVec(clickSide)
69+
val distSq = eyes distSq hitVec
70+
71+
if (distSq > rangeSq) return null
72+
if (clickPos.blockState(world).isClickable) return null
73+
74+
if (visibleCheck) {
75+
val box = Box(clickPos)
76+
val visible = box.getVisibleSurfaces(eyes)
77+
if (clickSide !in visible) return null
78+
}
79+
80+
return PlaceInfo(clickPos, clickSide, pos, hitVec, distSq, attempts)
81+
}
82+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.lambda.interaction.blockplace
2+
3+
import net.minecraft.util.math.BlockPos
4+
import net.minecraft.util.math.Direction
5+
import net.minecraft.util.math.Vec3d
6+
7+
data class PlaceInfo(
8+
val clickPos: BlockPos,
9+
val clickSide: Direction,
10+
val placedPos: BlockPos,
11+
val hitVec: Vec3d,
12+
13+
val eyeDistanceSq: Double,
14+
val placeSteps: Int
15+
)

0 commit comments

Comments
 (0)