Skip to content

Commit d72809c

Browse files
committed
Rotation & refactor
1 parent fd89186 commit d72809c

File tree

2 files changed

+181
-106
lines changed

2 files changed

+181
-106
lines changed

common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt

Lines changed: 126 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package com.lambda.module.modules.combat
1919

20+
import com.lambda.config.groups.RotationSettings
2021
import com.lambda.config.groups.Targeting
2122
import com.lambda.context.SafeContext
2223
import com.lambda.event.events.EntityEvent
@@ -30,6 +31,7 @@ import com.lambda.graphics.renderer.gui.font.FontRenderer
3031
import com.lambda.graphics.renderer.gui.font.LambdaEmoji
3132
import com.lambda.graphics.renderer.gui.font.LambdaFont
3233
import com.lambda.interaction.RotationManager.rotate
34+
import com.lambda.interaction.rotation.Rotation.Companion.rotationTo
3335
import com.lambda.interaction.rotation.RotationRequest
3436
import com.lambda.module.Module
3537
import com.lambda.module.tag.ModuleTag
@@ -68,6 +70,7 @@ object CrystalAura : Module(
6870
private val explodeDelay by setting("Explode Delay", 10L, 0L..1000L, 5L, "Delay between explosion attempts", " ms") { page == Page.General }
6971
private val updateMode by setting("Update Mode", UpdateMode.Async) { page == Page.General }
7072
private val updateDelaySetting by setting("Update Delay", 25L, 5L..200L, 5L) { page == Page.General && updateMode == UpdateMode.Async }
73+
private val maxUpdatesPerFrame by setting("Max Updates Per Frame", 5, 1..20, 1) { page == Page.General && updateMode == UpdateMode.Async }
7174
private val updateDelay get() = if (updateMode == UpdateMode.Async) updateDelaySetting else 0L
7275
private val debug by setting("Debug", false) { page == Page.General }
7376

@@ -88,18 +91,19 @@ object CrystalAura : Module(
8891
private val targeting = Targeting.Combat(this, 10.0) { page == Page.Targeting }
8992

9093
/* Rotation */
91-
//private val rotateToPlace by setting("Rotate To Place", true) { page == Page.Rotation }
92-
//private val rotateToExplode by setting("Rotate To Explode", true) { page == Page.Rotation }
93-
//private val rotation = RotationSettings(this) { page == Page.Rotation }
94-
private var rotationTarget: RotationRequest? = null
94+
private val rotation = RotationSettings(this) { page == Page.Rotation }
9595

9696
private val blueprint = mutableMapOf<BlockPos, Opportunity>()
97+
private var activeOpportunity: Opportunity? = null
98+
private var currentTarget: LivingEntity? = null
9799

98100
private val damage = mutableListOf<Opportunity>()
99101
private val actionMap = mutableMapOf<ActionType, MutableList<Opportunity>>()
100102
private var actionType = ActionType.Normal
101103

102104
private val updateTimer = SimpleTimer()
105+
private var updatesThisFrame = 0
106+
103107
private val placeTimer = SimpleTimer()
104108
private val explodeTimer = SimpleTimer()
105109

@@ -124,12 +128,19 @@ object CrystalAura : Module(
124128
name = "Crystal Aura Thread",
125129
daemon = true,
126130
initialDelay = 0L,
127-
period = 3L
131+
period = 1L
128132
) {
129133
if (CrystalAura.isDisabled || updateMode != UpdateMode.Async) return@fixedRateTimer
130134

131-
runSafeGameScheduled {
132-
tick()
135+
runSafe {
136+
// timer may spam faster than main thread computes(game freezes completely at the beginning of the frame)
137+
if (updatesThisFrame > maxUpdatesPerFrame) return@runSafe
138+
updatesThisFrame++
139+
140+
// run this safely again to ensure that the context will stay safe at the next frame
141+
runSafeGameScheduled {
142+
tick()
143+
}
133144
}
134145
}
135146

@@ -138,6 +149,10 @@ object CrystalAura : Module(
138149
if (updateMode == UpdateMode.Ticked) tick()
139150
}
140151

152+
listen<TickEvent.Render.Post> {
153+
updatesThisFrame = 0
154+
}
155+
141156
listen<RenderEvent.World> {
142157
if (!debug) return@listen
143158

@@ -154,19 +169,21 @@ object CrystalAura : Module(
154169
}
155170
}
156171

172+
// Prediction
157173
listen<EntityEvent.EntitySpawn> { event ->
174+
// Update last received entity spawn
158175
lastEntityId = event.entity.id
159176
predictionTimer.reset()
160177

161178
val crystal = event.entity as? EndCrystalEntity ?: return@listen
162179
val pos = crystal.baseBlockPos
163180

181+
// Update crystal
164182
val opportunity = blueprint[pos] ?: return@listen
165183
opportunity.crystal = crystal
166184

167-
if (!prediction.onPacket) return@listen
168-
if (getBestOpportunity() != opportunity) return@listen
169-
185+
// Run packet prediction
186+
if (!prediction.onPacket || activeOpportunity != opportunity) return@listen
170187
repeat(predictionPackets) {
171188
val offset = if (prediction.postPlace) 0 else it
172189
explodeInternal(lastEntityId + offset)
@@ -183,38 +200,88 @@ object CrystalAura : Module(
183200
val crystal = event.entity as? EndCrystalEntity ?: return@listen
184201
val pos = crystal.baseBlockPos
185202

203+
// Invalidate crystal entity
186204
blueprint[pos]?.crystal = null
187205
}
188206

189207
onEnable {
190-
lastEntityId = -1
208+
currentTarget = null
209+
resetBlueprint()
191210
}
192211

193212
rotate {
194-
request { rotationTarget }
213+
request {
214+
activeOpportunity?.let {
215+
RotationRequest(
216+
player.eyePos.rotationTo(
217+
it.blockPos.crystalPosition
218+
), rotation
219+
)
220+
}
221+
}
195222
}
196223
}
197224

198225
private fun SafeContext.tick() {
199-
val target = targeting.target() ?: run {
200-
blueprint.clear()
201-
return
202-
}
226+
// Update the target
227+
currentTarget = targeting.target()
203228

204229
// Update the blueprint
205-
buildBlueprint(target)
230+
currentTarget?.let {
231+
updateBlueprint(it)
232+
} ?: resetBlueprint()
206233

207234
// Choosing and running the best opportunity
208-
val best = getBestOpportunity() ?: return
235+
activeOpportunity?.let {
236+
tickInteraction(it)
237+
}
238+
}
209239

210-
tickInteraction(best)
240+
private fun tickInteraction(best: Opportunity) {
241+
if (!best.blocked) {
242+
best.explode()
243+
best.place()
244+
return
245+
}
246+
247+
val mutableBlockPos = BlockPos.Mutable()
248+
249+
// Break crystals nearby if the best crystal placement is blocked by other crystals
250+
collidingOffsets.mapNotNull {
251+
mutableBlockPos.set(
252+
best.blockPos.x + it.x,
253+
best.blockPos.y + it.y,
254+
best.blockPos.z + it.z
255+
)
256+
257+
blueprint[mutableBlockPos]
258+
}.filter { it.hasCrystal }.maxByOrNull { it.priority }?.explode()
259+
260+
best.place()
211261
}
212262

213-
private fun SafeContext.buildBlueprint(target: LivingEntity) = updateTimer.runIfPassed(updateDelay) {
214-
blueprint.clear()
215-
damage.clear()
216-
actionMap.clear()
217-
actionType = ActionType.Normal
263+
private fun SafeContext.placeInternal(blockPos: BlockPos, hand: Hand) {
264+
connection.sendPacket(
265+
PlayerInteractBlockC2SPacket(
266+
hand, BlockHitResult(blockPos.crystalPosition, Direction.UP, blockPos, false), 0
267+
)
268+
)
269+
270+
player.swingHand(hand)
271+
}
272+
273+
private fun SafeContext.explodeInternal(id: Int) {
274+
connection.sendPacket(
275+
PlayerInteractEntityC2SPacket(
276+
id, player.isSneaking, PlayerInteractEntityC2SPacket.ATTACK
277+
)
278+
)
279+
280+
player.swingHand(Hand.MAIN_HAND)
281+
}
282+
283+
private fun SafeContext.updateBlueprint(target: LivingEntity) = updateTimer.runIfPassed(updateDelay) {
284+
resetBlueprint()
218285

219286
// Build damage info
220287
fun info(
@@ -244,7 +311,7 @@ object CrystalAura : Module(
244311
)
245312
}
246313

247-
// Extra checks for placement, because you may explode in special cases(crystal in the air) but not place
314+
// Extra checks for placement, because you may explode but not place in special cases(crystal in the air)
248315
@Suppress("ConvertArgumentToSet")
249316
fun placeInfo(
250317
pos: BlockPos,
@@ -276,7 +343,7 @@ object CrystalAura : Module(
276343
it.baseBlockPos == pos
277344
}
278345

279-
val crystalPlaceBox = pos.crystalPlaceHitbox
346+
val crystalPlaceBox = pos.crystalPlaceHitBox
280347
val blocked = baseCrystal == null && crystals.any {
281348
it.boundingBox.intersects(crystalPlaceBox)
282349
}
@@ -320,54 +387,18 @@ object CrystalAura : Module(
320387
actionType = opportunity.actionType
321388
}
322389
}
323-
}
324-
325-
private fun tickInteraction(best: Opportunity) {
326-
if (!best.blocked) {
327-
best.explode()
328-
best.place()
329-
return
330-
}
331-
332-
val mutableBlockPos = BlockPos.Mutable()
333-
334-
// Break crystals nearby if the best crystal placement is blocked by other crystals
335-
collidingOffsets.mapNotNull {
336-
mutableBlockPos.set(
337-
best.blockPos.x + it.x,
338-
best.blockPos.y + it.y,
339-
best.blockPos.z + it.z
340-
)
341-
342-
blueprint[mutableBlockPos]
343-
}.filter { it.hasCrystal }.maxByOrNull { it.priority }?.explode()
344390

345-
best.place()
346-
}
347-
348-
private fun getBestOpportunity() =
349-
actionMap[actionType]?.maxByOrNull {
391+
// Select best action
392+
activeOpportunity = actionMap[actionType]?.maxByOrNull {
350393
it.priority
351394
}
352-
353-
private fun SafeContext.placeInternal(blockPos: BlockPos, hand: Hand) {
354-
connection.sendPacket(
355-
PlayerInteractBlockC2SPacket(
356-
hand, BlockHitResult(blockPos.crystalPosition, Direction.UP, blockPos, false), 0
357-
)
358-
)
359-
360-
player.swingHand(hand)
361395
}
362396

363-
private fun SafeContext.explodeInternal(id: Int) {
364-
connection.sendPacket(
365-
PlayerInteractEntityC2SPacket(
366-
id, player.isSneaking, PlayerInteractEntityC2SPacket.ATTACK
367-
)
368-
)
369-
370-
player.swingHand(Hand.MAIN_HAND)
397+
private fun resetBlueprint() {
398+
blueprint.clear()
399+
damage.clear()
400+
actionMap.clear()
401+
activeOpportunity = null
371402
}
372403

373404
/**
@@ -395,53 +426,39 @@ object CrystalAura : Module(
395426
* Places the crystal on [blockPos]
396427
* @return Whether the delay passed, null if the interaction failed
397428
*/
398-
fun place(): Boolean? {
399-
val timePassed = placeTimer.timePassed(placeDelay)
400-
if (!timePassed) return false
401-
402-
runSafe {
403-
placeInternal(blockPos, Hand.MAIN_HAND)
429+
fun place() = placeTimer.runSafeIfPassed(placeDelay) {
430+
placeInternal(blockPos, Hand.MAIN_HAND)
431+
if (prediction.onPlace) predictionPlace()
404432

405-
run {
406-
if (!prediction.onPlace || predictionTimer.timePassed(packetLifetime)) return@run
407-
408-
val last = lastEntityId
433+
placeTimer.reset()
434+
}
409435

410-
repeat(predictionPackets) {
411-
if (it != 0 && prediction.postPlace) {
412-
placeInternal(blockPos, Hand.MAIN_HAND)
413-
}
414-
explodeInternal(++lastEntityId)
415-
}
436+
private fun SafeContext.predictionPlace() = predictionTimer.runIfNotPassed(packetLifetime) {
437+
val last = lastEntityId
416438

417-
if (prediction == PredictionMode.StepDeferred) {
418-
lastEntityId = last + 1
419-
crystal = null
420-
}
439+
repeat(predictionPackets) {
440+
if (it != 0 && prediction.postPlace) {
441+
placeInternal(blockPos, Hand.MAIN_HAND)
421442
}
422443

423-
placeTimer.reset()
424-
} ?: return null
444+
explodeInternal(++lastEntityId)
445+
}
425446

426-
return true
447+
if (prediction == PredictionMode.StepDeferred) {
448+
lastEntityId = last + 1
449+
crystal = null
450+
}
427451
}
428452

429453
/**
430454
* Explodes a crystal that is on [blockPos]
431455
* @return Whether the delay passed, null if the interaction failed or no crystal found
432456
*/
433-
fun explode(): Boolean? {
434-
val timePassed = explodeTimer.timePassed(explodeDelay)
435-
if (!timePassed) return false
436-
437-
runSafe {
438-
crystal?.let { crystal ->
439-
explodeInternal(crystal.id)
440-
explodeTimer.reset()
441-
}
442-
} ?: return null
443-
444-
return true
457+
fun explode() = explodeTimer.runSafeIfPassed(explodeDelay) {
458+
crystal?.let { crystal ->
459+
explodeInternal(crystal.id)
460+
explodeTimer.reset()
461+
}
445462
}
446463

447464
fun buildDebug() {
@@ -472,7 +489,7 @@ object CrystalAura : Module(
472489
private val BlockPos.crystalPosition get() =
473490
this.getHitVec(Direction.UP)
474491

475-
private val BlockPos.crystalPlaceHitbox get() =
492+
private val BlockPos.crystalPlaceHitBox get() =
476493
crystalPosition.let { base ->
477494
Box(
478495
base - Vec3d(1.0, 0.0, 1.0),
@@ -492,14 +509,16 @@ object CrystalAura : Module(
492509
General,
493510
Placement,
494511
Prediction,
495-
Targeting
512+
Targeting,
513+
Rotation
496514
}
497515

498516
private enum class UpdateMode {
499517
Async,
500518
Ticked
501519
}
502520

521+
@Suppress("Unused")
503522
private enum class PredictionMode(val onPacket: Boolean, val onPlace: Boolean, val postPlace: Boolean) {
504523
// Prediction disable
505524
None(false, false, false),
@@ -526,6 +545,7 @@ object CrystalAura : Module(
526545
}
527546

528547
// ToDo: implement actions
548+
@Suppress("Unused")
529549
private enum class ActionType(val priority: Int) {
530550
Normal(0),
531551
ForcePlace(1),

0 commit comments

Comments
 (0)