1717
1818package com.lambda.module.modules.combat
1919
20+ import com.lambda.config.groups.RotationSettings
2021import com.lambda.config.groups.Targeting
2122import com.lambda.context.SafeContext
2223import com.lambda.event.events.EntityEvent
@@ -30,6 +31,7 @@ import com.lambda.graphics.renderer.gui.font.FontRenderer
3031import com.lambda.graphics.renderer.gui.font.LambdaEmoji
3132import com.lambda.graphics.renderer.gui.font.LambdaFont
3233import com.lambda.interaction.RotationManager.rotate
34+ import com.lambda.interaction.rotation.Rotation.Companion.rotationTo
3335import com.lambda.interaction.rotation.RotationRequest
3436import com.lambda.module.Module
3537import 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