1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
// What the modules actually do.
//
// Everything here is client-side only: nothing depends on owning the server, so
// it behaves the same on a real one. Anything that changes game state also
// remembers what it changed, so switching a module off puts the game back
// rather than leaving it altered.
//
// The rule that shapes most of this: the server re-simulates your position and
// takes your aim from the movement stream. So moving is a matter of nudging
// velocity within what it will accept, and aiming silently means telling it a
// rotation the camera never took.
#pragma once
#include <cmath>
#include "actions.hpp"
#include "interact.hpp"
#include "jni.hpp"
#include "mc.hpp"
#include "state.hpp"
#include "world.hpp"
namespace lodestone::cheats {
/// What we changed and must put back.
struct Saved {
bool fly_applied = false;
bool had_may_fly = false;
float had_fly_speed = 0.05f;
unsigned long long last_attack_ms = 0;
unsigned long long last_crystal_ms = 0;
/// Criticals wait for the fall: once we have hopped, hold the swing until
/// the player is actually descending.
bool crit_jumped = false;
bool sprint_reset_pending = false;
/// The slot the mace was swapped in from, so it can go back on landing.
int mace_prev_slot = -1;
/// Aimbot turn rate, carried between frames so the turn accelerates and
/// settles rather than snapping on and off.
float aim_vel_yaw = 0.0f, aim_vel_pitch = 0.0f;
double last_frame_s = 0.0;
unsigned long long last_trigger_ms = 0;
unsigned long long last_totem_ms = 0;
/// Damage is detected as an edge, so the previous frame's values matter.
float prev_health = 20.0f;
int prev_hurt_time = 0;
/// Attribute values as they were before a module overwrote them.
bool reach_saved = false;
double had_entity_reach = 0.0, had_block_reach = 0.0;
bool step_saved = false;
double had_step_height = 0.0;
bool no_physics_applied = false;
/// Visual options as they were found, so switching a module off puts the
/// game back instead of leaving it altered.
bool gamma_saved = false;
double had_gamma = 0.0;
bool tilt_saved = false;
double had_tilt = 0.0;
bool bob_saved = false;
bool had_bob = true;
bool cull_saved = false;
bool had_cull = true;
bool fov_saved = false;
int had_fov = 70;
bool view_saved = false;
int had_view = 12;
/// The last view distance actually asked for: the request is a packet, so
/// it only goes when the number changes.
int view_sent = 0;
bool timer_applied = false;
unsigned long long last_click_ms = 0;
unsigned long long last_respawn_ms = 0;
/// Block Reach writes the same attribute as combat Reach, so it keeps its
/// own remembered value rather than sharing one.
bool build_reach_saved = false;
double had_build_reach = 0.0;
};
namespace {
double now_seconds() { return static_cast<double>(GetTickCount64()) / 1000.0; }
/// Yaw/pitch that looks from `from` at `to`, in Minecraft's convention.
void look_at(const state::Vec3& from, const state::Vec3& to, float* yaw, float* pitch) {
double dx = to.x - from.x, dy = to.y - from.y, dz = to.z - from.z;
double flat = std::sqrt(dx * dx + dz * dz);
*yaw = static_cast<float>(std::atan2(-dx, dz) * 180.0 / 3.14159265358979323846);
*pitch = static_cast<float>(-std::atan2(dy, flat) * 180.0 / 3.14159265358979323846);
}
float clampf(float v, float lo, float hi) { return v < lo ? lo : (v > hi ? hi : v); }
/// Shortest signed turn from one yaw to another, in degrees. Going the long way
/// round a wrap is the difference between a flick and a spin.
float angle_delta(float from, float to) {
float d = std::fmod(to - from, 360.0f);
if (d > 180.0f) d -= 360.0f;
if (d < -180.0f) d += 360.0f;
return d;
}
/// A cheap -0.5..0.5 wobble that moves over time. Deliberately not a good
/// random source — nothing here needs one, it only needs to not be still.
float pseudo_random() {
unsigned long long t = GetTickCount64() / 50;
t = t * 6364136223846793005ULL + 1442695040888963407ULL;
return static_cast<float>((t >> 33) & 0xFFFF) / 65535.0f - 0.5f;
}
/// Launch pitch (degrees, negative is up) and flight time (ticks) that land a
/// projectile `horizontal` away and `dy` up at launch `speed`.
///
/// An arrow leaves at power*3 blocks/tick and then falls under gravity
/// (0.05/tick) with air drag (0.99/tick). There is no closed form once drag is
/// in, so this simulates a virtual arrow at each candidate angle and keeps the
/// one that lands nearest.
bool solve_pitch(double speed, double horizontal, double dy, double* out_pitch, double* out_time) {
if (speed <= 1e-6 || horizontal <= 1e-6) return false;
double best_angle = 0.0, best_err = 1e18, best_t = 0.0;
// Sweeping upward from -85 takes the low arc first, which is the shot a
// person would take when both arcs reach.
for (double angle = -85.0; angle <= 85.0; angle += 0.5) {
double rad = angle * 3.14159265358979323846 / 180.0;
double vx = std::cos(rad) * speed;
double vy = -std::sin(rad) * speed; // pitch up is negative in Minecraft
double x = 0.0, y = 0.0, t = 0.0;
while (x < horizontal && t < 200.0) {
x += vx;
y += vy;
vy -= 0.05;
vx *= 0.99;
vy *= 0.99;
t += 1.0;
}
double err = std::fabs(y - dy);
if (err < best_err) {
best_err = err;
best_angle = angle;
best_t = t;
}
}
if (best_err >= 1.5) return false; // out of range whatever the angle
*out_pitch = best_angle;
*out_time = best_t;
return true;
}
bool aura_wants(const state::Combat& c, state::TargetKind kind) {
switch (kind) {
case state::TargetKind::Player: return c.aura_players;
case state::TargetKind::Mob: return c.aura_mobs;
case state::TargetKind::Animal: return c.aura_animals;
default: return false;
}
}
// ---------------------------------------------------------------------------
/// Flight, by telling the client it is allowed to. The server grants this
/// normally in creative; elsewhere it will disagree and pull you back, which is
/// why the speed matters more than the permission.
void flight(const jni::Env& j, const mc::Actions& act, jobject player,
const state::Config& cfg, Saved& saved) {
jobject abilities = act.abilities(j, player);
if (!abilities) return;
if (cfg.movement.fly) {
if (!saved.fly_applied) {
saved.had_may_fly = act.flying(j, abilities);
saved.had_fly_speed = act.fly_speed(j, abilities);
saved.fly_applied = true;
}
act.set_may_fly(j, abilities, true);
act.set_flying(j, abilities, true);
act.set_fly_speed(j, abilities, cfg.movement.fly_speed);
} else if (saved.fly_applied) {
// Put back exactly what was there, so turning it off is invisible.
act.set_flying(j, abilities, saved.had_may_fly);
act.set_fly_speed(j, abilities, saved.had_fly_speed);
saved.fly_applied = false;
}
j.delete_local(abilities);
}
/// Horizontal speed, applied to the velocity the server will re-simulate.
void speed(const jni::Env& j, const mc::Actions& act, jobject player, const state::Config& cfg,
const state::GameState& game) {
if (!cfg.movement.speed || game.screen_open) return;
state::Vec3 v;
if (!act.delta_movement(j, player, &v)) return;
double flat = std::sqrt(v.x * v.x + v.z * v.z);
if (flat < 0.02) return; // standing still: nothing to scale
// Keeping inside what the server re-simulates without snapping you back is
// the whole difference between "fast" and "rubber-banded".
double want = cfg.movement.speed_limit ? 0.42 : 2.0;
double target = flat + static_cast<double>(cfg.movement.speed_value) * 0.2;
if (target > want) target = want;
double scale = target / flat;
act.set_delta_movement(j, player, v.x * scale, v.y, v.z * scale);
}
/// Sprint, held on rather than tapped.
void sprint(const jni::Env& j, const mc::Actions& act, jobject player, const state::Config& cfg,
const state::GameState& game) {
if (!cfg.movement.sprint || game.screen_open || !game.in_world) return;
state::Vec3 v;
if (!act.delta_movement(j, player, &v)) return;
if (std::sqrt(v.x * v.x + v.z * v.z) > 0.05 && !act.sprinting(j, player)) {
act.set_sprinting(j, player, true);
}
}
/// No Fall: the server decides fall damage from the on-ground flag you send, so
/// keep saying you are on the ground while descending.
void no_fall(const jni::Env& j, const mc::Actions& act, jobject player, const state::Config& cfg,
const state::GameState& game) {
if (!cfg.movement.no_fall || game.on_ground || !game.in_world) return;
state::Vec3 v;
if (!act.delta_movement(j, player, &v) || v.y > -0.5) return; // only once actually falling
act.flush_position(j, player, game.pos, game.yaw, game.pitch, true);
}
/// Bhop: jump the instant you land, so momentum is never lost to a step.
void bhop(const jni::Env& j, const mc::Actions& act, jobject player, const state::Config& cfg,
const state::GameState& game) {
if (!cfg.movement.bhop || !game.on_ground || game.screen_open) return;
state::Vec3 v;
if (!act.delta_movement(j, player, &v)) return;
if (std::sqrt(v.x * v.x + v.z * v.z) < 0.05) return; // only while actually moving
act.set_delta_movement(j, player, v.x, 0.42, v.z); // a vanilla jump
}
/// High Jump: amplify the jump itself, not free flight — so it still reads as a
/// jump to anything watching the position stream.
void high_jump(const jni::Env& j, const mc::Actions& act, jobject player,
const state::Config& cfg, const state::GameState& game) {
if (!cfg.movement.jump_power || game.screen_open || game.on_ground) return;
state::Vec3 v;
if (!act.delta_movement(j, player, &v)) return;
// Only the frame you leave the ground: a vanilla jump is 0.42.
if (v.y > 0.40 && v.y < 0.45) {
act.set_delta_movement(j, player, v.x, v.y * cfg.movement.jump_multiplier, v.z);
}
}
/// Jetpack: rise while the jump key is held.
void jetpack(const jni::Env& j, const mc::Actions& act, jobject player, const state::Config& cfg,
const state::GameState& game) {
if (!cfg.movement.jetpack || game.screen_open) return;
if ((GetAsyncKeyState(VK_SPACE) & 0x8000) == 0) return;
state::Vec3 v;
if (!act.delta_movement(j, player, &v)) return;
act.set_delta_movement(j, player, v.x, cfg.movement.jetpack_power, v.z);
}
/// Auto Mace: get the mace into your hand on the way down, and give the slot
/// back on landing. The swing is left to the aura or to you.
void auto_mace(const jni::Env& j, const mc::Interact& it, const mc::Actions& act, jobject player,
const state::Config& cfg, const state::GameState& game, Saved& saved) {
if (!cfg.combat.auto_mace || game.screen_open) return;
state::Vec3 v;
bool descending = !game.on_ground && act.delta_movement(j, player, &v) && v.y < -0.25;
if (descending) {
if (!it.holding_mace(j, player)) {
int slot = it.find_mace(j, player);
if (slot >= 0) {
if (saved.mace_prev_slot < 0) saved.mace_prev_slot = it.selected_slot(j, player);
it.select_slot(j, player, slot);
}
}
} else if (saved.mace_prev_slot >= 0) {
it.select_slot(j, player, saved.mace_prev_slot);
saved.mace_prev_slot = -1;
}
}
/// Auto Crystal: detonate the end crystals near you. A crystal is an entity, so
/// breaking one is just an attack; placing needs a face to build on beside the
/// target and is a separate problem.
void auto_crystal(const jni::Env& j, const mc::Mc& core, const mc::World& world,
const mc::Interact& it, jobject instance, jobject player,
const state::Config& cfg, const state::GameState& game, Saved& saved) {
const state::Combat& c = cfg.combat;
if (!c.auto_crystal || game.screen_open || !game.in_world) return;
double delay = c.crystal_delay > 0.02f ? static_cast<double>(c.crystal_delay) : 0.02;
if (now_seconds() - static_cast<double>(saved.last_crystal_ms) / 1000.0 < delay) return;
jobject level = core.level(j, instance);
jobject game_mode = core.game_mode(j, instance);
if (!level || !game_mode) return;
jobject iterator = world.entity_iterator(j, level);
if (!iterator) return;
// Nearest within reach: the closest crystal is both the one most likely
// aimed at you and the one the server will accept the hit on.
jobject best = nullptr;
double best_distance = static_cast<double>(c.crystal_range);
for (int n = 0; n < 4096; ++n) {
jobject entity = world.iter_next(j, iterator);
if (!entity) break;
bool keep = false;
if (it.is_end_crystal(j, entity)) {
state::Vec3 lo, hi;
if (world.bounding_box(j, entity, &lo, &hi)) {
double cx = (lo.x + hi.x) * 0.5, cy = (lo.y + hi.y) * 0.5, cz = (lo.z + hi.z) * 0.5;
double dx = cx - game.eye.x, dy = cy - game.eye.y, dz = cz - game.eye.z;
double d = std::sqrt(dx * dx + dy * dy + dz * dz);
if (d < best_distance) {
best_distance = d;
if (best) j.delete_local(best);
best = entity;
keep = true;
}
}
}
if (!keep) j.delete_local(entity);
}
j.delete_local(iterator);
if (!best) return;
world.attack(j, game_mode, player, best);
saved.last_crystal_ms = GetTickCount64();
j.delete_local(best);
}
/// Kill Aura. Iterates entities itself rather than reusing the render scan,
/// because attacking needs the live object and the scan keeps only the numbers.
void kill_aura(const jni::Env& j, const mc::Mc& core, const mc::World& world,
const mc::Actions& act, jobject instance, jobject player,
const state::Config& cfg, const state::GameState& game, Saved& saved) {
const state::Combat& c = cfg.combat;
if (!c.kill_aura || game.screen_open || !game.in_world) return;
double interval = 1.0 / (c.aura_cps > 0.5f ? static_cast<double>(c.aura_cps) : 0.5);
double now = now_seconds();
if (now - static_cast<double>(saved.last_attack_ms) / 1000.0 < interval) return;
jobject level = core.level(j, instance);
jobject game_mode = core.game_mode(j, instance);
if (!level || !game_mode) return;
jobject iterator = world.entity_iterator(j, level);
if (!iterator) return;
jobject best = nullptr;
double best_distance = static_cast<double>(c.aura_range);
state::Vec3 best_centre{};
for (int n = 0; n < 4096; ++n) {
jobject entity = world.iter_next(j, iterator);
if (!entity) break;
bool keep = false;
if (!j.same_object(entity, player) && core.is_alive(j, entity)) {
state::TargetKind kind = world.classify(j, core, entity);
if (aura_wants(c, kind)) {
state::Vec3 lo, hi;
if (world.bounding_box(j, entity, &lo, &hi)) {
// Hitbox: consider a box larger than the one being drawn.
// The server still checks the real one, so this widens what
// we will *try* rather than making a miss land.
if (c.hitbox) {
double e = static_cast<double>(c.hitbox_expand);
lo = {lo.x - e, lo.y - e, lo.z - e};
hi = {hi.x + e, hi.y + e, hi.z + e};
}
state::Vec3 centre{(lo.x + hi.x) * 0.5, (lo.y + hi.y) * 0.5,
(lo.z + hi.z) * 0.5};
double dx = centre.x - game.eye.x, dy = centre.y - game.eye.y,
dz = centre.z - game.eye.z;
double d = std::sqrt(dx * dx + dy * dy + dz * dz);
// Walls: the game already knows how to answer this, so ask
// it rather than casting our own ray.
bool visible =
c.aura_through_walls || act.has_line_of_sight(j, player, entity);
if (d < best_distance && visible) {
best_distance = d;
best_centre = centre;
if (best) j.delete_local(best);
best = entity;
keep = true;
}
}
}
}
if (!keep) j.delete_local(entity);
}
j.delete_local(iterator);
if (!best) return;
// Criticals: a hit only crits while you are falling and not sprinting, so
// hop first and hold the swing until the game would agree. A nudge is
// enough — a few centimetres, visible in the position stream, barely
// visible to anyone watching.
bool may_swing = true;
if (c.criticals) {
state::Vec3 v;
bool falling = act.delta_movement(j, player, &v) && v.y < 0.0;
if (saved.crit_jumped && falling) {
saved.crit_jumped = false; // descending: this one counts
} else {
if (act.sprinting(j, player)) act.set_sprinting(j, player, false);
if (game.on_ground) {
state::Vec3 d;
if (act.delta_movement(j, player, &d)) {
act.set_delta_movement(j, player, d.x, static_cast<double>(c.crit_hop), d.z);
}
// Push the leaving-the-ground position now rather than waiting
// for the tick's own send, so the server agrees we are airborne.
state::Vec3 lifted{game.pos.x, game.pos.y + 0.001, game.pos.z};
act.flush_position(j, player, lifted, game.yaw, game.pitch, false);
saved.crit_jumped = true;
}
may_swing = false;
}
}
if (!may_swing) {
j.delete_local(best);
return;
}
// Sprint reset: a hit landing on the tick sprint restarts gets the sprint
// knockback bonus. It fights criticals, which need no sprint, so it only
// runs when criticals is off.
if (c.sprint_reset && !c.criticals && act.sprinting(j, player)) {
act.set_sprinting(j, player, false);
saved.sprint_reset_pending = true;
}
// Aim at it. Silent means the server is told a rotation the camera never
// took, so the look has to go out in the movement stream — the attack
// packet carries none.
if (c.aura_rotate || c.aim_mode == state::AimMode::Silent) {
float yaw = 0, pitch = 0;
look_at(game.eye, best_centre, &yaw, &pitch);
if (c.aim_mode == state::AimMode::Silent && !c.aura_rotate) {
act.flush_position(j, player, game.pos, yaw, pitch, game.on_ground);
} else {
act.set_rotation(j, player, yaw, pitch);
}
}
world.attack(j, game_mode, player, best);
saved.last_attack_ms = GetTickCount64();
if (saved.sprint_reset_pending) {
act.set_sprinting(j, player, true); // restart, completing the reset
saved.sprint_reset_pending = false;
}
j.delete_local(best);
}
/// Aimbot, camera mode: turn the view towards the nearest target.
///
/// Reads the entity scan the frame already did rather than walking the world
/// again — aiming only needs a position, which the scan already has.
void aimbot(const jni::Env& j, const mc::Actions& act, jobject player, const state::Config& cfg,
const state::GameState& game, Saved& saved) {
const state::Combat& c = cfg.combat;
if (!c.aimbot || game.screen_open || !game.in_world) return;
// Silent aim is applied at the swing in kill_aura and put straight back, so
// only Camera mode moves the view itself.
if (c.aim_mode != state::AimMode::Camera) return;
double now = now_seconds();
float dt = saved.last_frame_s > 0.0 ? static_cast<float>(now - saved.last_frame_s) : 1.0f / 60.0f;
saved.last_frame_s = now;
if (dt <= 0.0f || dt > 0.25f) dt = 1.0f / 60.0f; // a stall is not a turn
const state::Target* best = nullptr;
float best_distance = 1e9f;
for (const auto& t : game.targets) {
if (!aura_wants(c, t.kind)) continue;
// Worth aiming at things further off than you can actually hit, so the
// turn is already finished by the time they close.
if (t.distance > c.aura_range * 3.0f) continue;
if (t.distance < best_distance) {
best_distance = t.distance;
best = &t;
}
}
// Let a built-up turn rate decay rather than snapping to a stop.
auto settle = [&] {
saved.aim_vel_yaw *= 0.85f;
saved.aim_vel_pitch *= 0.85f;
};
if (!best) return settle();
state::Vec3 centre{(best->min.x + best->max.x) * 0.5, (best->min.y + best->max.y) * 0.5,
(best->min.z + best->max.z) * 0.5};
float want_yaw = 0.0f, want_pitch = 0.0f;
look_at(game.eye, centre, &want_yaw, &want_pitch);
if (std::fabs(angle_delta(game.yaw, want_yaw)) > c.aim_fov * 0.5f) return settle();
// A lerp towards the target is what makes an aimbot look like one: fastest
// at the instant it acquires and then crawling, which is the opposite of
// how a hand moves. This is a critically damped spring, so the turn
// accelerates, carries and settles without overshooting — and it is capped
// at a maximum rate, so no acquisition is instant however far off it starts.
float stiffness = c.aim_speed * 20.0f;
if (stiffness < 0.8f) stiffness = 0.8f;
float damping = 2.0f * std::sqrt(stiffness);
float max_rate = 45.0f + c.aim_speed * 320.0f;
// A little noise on the aim point: a head held perfectly still on a moving
// target is not something a hand does.
float wobble = pseudo_random() * 0.6f;
float dy = angle_delta(game.yaw, want_yaw + wobble);
float dp = (want_pitch + wobble * 0.4f) - game.pitch;
saved.aim_vel_yaw += (dy * stiffness - saved.aim_vel_yaw * damping) * dt;
saved.aim_vel_pitch += (dp * stiffness - saved.aim_vel_pitch * damping) * dt;
saved.aim_vel_yaw = clampf(saved.aim_vel_yaw, -max_rate, max_rate);
saved.aim_vel_pitch = clampf(saved.aim_vel_pitch, -max_rate, max_rate);
act.set_rotation(j, player, game.yaw + saved.aim_vel_yaw * dt,
clampf(game.pitch + saved.aim_vel_pitch * dt, -90.0f, 90.0f));
}
/// Swing when the crosshair is already on something — no aiming and no target
/// selection, which is why it is the least conspicuous of the three.
void trigger_bot(const jni::Env& j, const mc::Mc& core, const mc::World& world, jobject instance,
jobject player, const state::Config& cfg, const state::GameState& game,
Saved& saved) {
const state::Combat& c = cfg.combat;
if (!c.trigger_bot || game.screen_open || !game.in_world) return;
double delay = c.trigger_delay > 0.05f ? static_cast<double>(c.trigger_delay) : 0.05;
if (now_seconds() - static_cast<double>(saved.last_trigger_ms) / 1000.0 < delay) return;
jobject target = world.crosshair_entity(j, instance);
if (!target) return;
// The server disconnects you outright for attacking a dropped item, an
// experience orb or yourself — handleInteract treats those as a protocol
// violation rather than a miss. The crosshair lands on them all the time,
// so this filter is not optional. Everything it must exclude is excluded by
// the entity simply being a LivingEntity.
if (j.same_object(target, player) || !world.is_living(j, target)) {
j.delete_local(target);
return;
}
if (jobject game_mode = core.game_mode(j, instance)) {
world.attack(j, game_mode, player, target);
saved.last_trigger_ms = GetTickCount64();
}
j.delete_local(target);
}
/// Staying alive: the totem and the shield, both driven by damage landing.
void survival(const jni::Env& j, const mc::Mc& core, const mc::Interact& it, jobject instance,
jobject player, const state::Config& cfg, const state::GameState& game,
Saved& saved) {
const state::Combat& c = cfg.combat;
if (!game.in_world) return;
// No health threshold of its own. A threshold either holds the offhand
// hostage for nothing or arrives after the hit that mattered; what actually
// decides it is damage landing — the instant health drops, or the instant
// the totem you were holding pops and leaves the hand empty.
bool took_damage = game.health < saved.prev_health || game.hurt_time > saved.prev_hurt_time;
saved.prev_health = game.health;
saved.prev_hurt_time = game.hurt_time;
if (c.auto_totem && game.health > 0.0f && took_damage && !it.holding_totem(j, player)) {
double since = now_seconds() - static_cast<double>(saved.last_totem_ms) / 1000.0;
bool ready = c.totem_mode == state::TotemMode::Legit
? (game.health <= 10.0f && since > 0.18)
: since > 0.1;
if (ready) {
int slot = it.find_totem(j, player);
if (slot >= 0) {
if (jobject game_mode = core.game_mode(j, instance)) {
it.swap_to_offhand(j, game_mode, player, slot);
saved.last_totem_ms = GetTickCount64();
}
}
}
}
// Raised on the tick a hit lands and held while the damage flash lasts.
// Nothing here predicts the swing: a shield that comes up before the
// attacker moves is a tell, one that comes up on the hit is just fast.
if (c.auto_shield && game.hurt_time > 0 && !game.screen_open && it.holding_shield(j, player)) {
it.start_use(j, instance);
}
}
/// Reach and step height are attributes, so they are set once and put back when
/// the module goes off rather than rewritten every frame.
void attributes(const jni::Env& j, const mc::World& world, jobject player,
const state::Config& cfg, Saved& saved) {
const state::Combat& c = cfg.combat;
if (c.reach) {
if (!saved.reach_saved) {
saved.reach_saved = world.attribute_base(j, player, world.holder_entity_reach(),
&saved.had_entity_reach);
world.attribute_base(j, player, world.holder_block_reach(), &saved.had_block_reach);
}
double want = static_cast<double>(c.reach_distance);
world.set_attribute_base(j, player, world.holder_entity_reach(), want);
world.set_attribute_base(j, player, world.holder_block_reach(), want);
} else if (saved.reach_saved) {
world.set_attribute_base(j, player, world.holder_entity_reach(), saved.had_entity_reach);
world.set_attribute_base(j, player, world.holder_block_reach(), saved.had_block_reach);
saved.reach_saved = false;
}
const state::Movement& m = cfg.movement;
if (m.step) {
if (!saved.step_saved) {
saved.step_saved =
world.attribute_base(j, player, world.holder_step_height(), &saved.had_step_height);
}
world.set_attribute_base(j, player, world.holder_step_height(),
static_cast<double>(m.step_height));
} else if (saved.step_saved) {
world.set_attribute_base(j, player, world.holder_step_height(), saved.had_step_height);
saved.step_saved = false;
}
// Block Reach is a module of its own and writes the same attribute combat
// Reach does, so it keeps its own remembered value: whichever is switched
// on last wins, and each still restores what it actually found.
if (cfg.building.block_reach) {
if (!saved.build_reach_saved) {
saved.build_reach_saved = world.attribute_base(j, player, world.holder_block_reach(),
&saved.had_build_reach);
}
world.set_attribute_base(j, player, world.holder_block_reach(),
static_cast<double>(cfg.building.block_reach_dist));
} else if (saved.build_reach_saved) {
world.set_attribute_base(j, player, world.holder_block_reach(), saved.had_build_reach);
saved.build_reach_saved = false;
}
}
/// The client-side cooldowns that gate how fast you can place and break.
/// Neither is enforced by the server and both refill on their own, so this just
/// keeps knocking them back to zero — there is nothing to put back.
void building(const jni::Env& j, const mc::Mc& core, jobject instance,
const state::Config& cfg) {
if (!cfg.building.no_cooldown) return;
core.clear_use_cooldowns(j, instance);
if (jobject game_mode = core.game_mode(j, instance)) {
core.set_destroy_delay(j, game_mode, 0);
j.delete_local(game_mode);
}
}
/// Noclip, by clearing the physics flag the game already uses for spectators.
/// The server still re-simulates your position, so this is what lets you move
/// through something — not what convinces the server you did.
void noclip(const jni::Env& j, const mc::World& world, jobject player, const state::Config& cfg,
Saved& saved) {
if (cfg.movement.noclip) {
world.set_no_physics(j, player, true);
saved.no_physics_applied = true;
} else if (saved.no_physics_applied) {
world.set_no_physics(j, player, false);
saved.no_physics_applied = false;
}
}
/// Spider: climb whatever you walk into, by pushing up while a wall is being
/// hit. The game already tracks that collision, so there is nothing to trace.
void spider(const jni::Env& j, const mc::World& world, const mc::Actions& act, jobject player,
const state::Config& cfg, const state::GameState& game) {
if (!cfg.movement.spider || game.screen_open || !game.in_world) return;
if (!world.hitting_wall(j, player)) return;
state::Vec3 v;
if (!act.delta_movement(j, player, &v)) return;
act.set_delta_movement(j, player, v.x, static_cast<double>(cfg.movement.spider_power), v.z);
}
/// Aim a drawn bow at the ballistic solution for a moving target.
void bow_aimbot(const jni::Env& j, const mc::Mc& core, const mc::World& world,
const mc::Actions& act, jobject instance, jobject player,
const state::Config& cfg, const state::GameState& game) {
if (!cfg.combat.bow_aimbot || game.screen_open || !game.in_world) return;
float charge = world.bow_charge(j, player);
if (charge < 0.1f) return; // not drawn enough to be worth aiming
jobject level = core.level(j, instance);
if (!level) return;
jobject target = world.nearest_living(j, core, level, player, game.eye, 80.0);
if (!target) return;
state::Vec3 lo, hi, vel{0, 0, 0};
bool have_box = world.bounding_box(j, target, &lo, &hi);
act.delta_movement(j, target, &vel);
j.delete_local(target);
if (!have_box) return;
double launch_speed = static_cast<double>(charge) * 3.0;
state::Vec3 base{(lo.x + hi.x) * 0.5, lo.y + (hi.y - lo.y) * 0.5, (lo.z + hi.z) * 0.5};
// Converge the flight time and the lead together: where the target will be
// depends on how long the arrow takes, which depends on where it is going.
double flight = 0.0;
state::Vec3 aim = base;
double pitch = 0.0, t = 0.0;
for (int i = 0; i < 4; ++i) {
aim = {base.x + vel.x * flight, base.y + vel.y * flight, base.z + vel.z * flight};
double ax = aim.x - game.eye.x, az = aim.z - game.eye.z;
if (!solve_pitch(launch_speed, std::sqrt(ax * ax + az * az), aim.y - game.eye.y, &pitch,
&t)) {
return;
}
flight = t;
}
double ax = aim.x - game.eye.x, az = aim.z - game.eye.z;
float yaw = static_cast<float>(std::atan2(az, ax) * 180.0 / 3.14159265358979323846 - 90.0);
act.set_rotation(j, player, yaw, static_cast<float>(pitch));
}
/// The visual options. Each remembers what it found before changing anything,
/// so turning a module off puts the setting back rather than leaving it.
void visuals(const jni::Env& j, const mc::Mc& core, const mc::World& world, jobject instance,
const state::Config& cfg, Saved& saved) {
const state::Visuals& v = cfg.visuals;
if (v.no_weather) {
if (jobject level = core.level(j, instance)) {
world.set_weather(j, level, 0.0f);
j.delete_local(level);
}
}
// Occlusion culling: with it off the renderer stops skipping sections it
// believes are hidden, so terrain you are inside of draws instead of
// reading as a black wall. It cannot conjure chunks the server never sent —
// only stop hiding the ones already here.
if (v.no_culling) {
if (!saved.cull_saved) saved.cull_saved = core.smart_cull(j, instance, &saved.had_cull);
core.set_smart_cull(j, instance, false);
} else if (saved.cull_saved) {
core.set_smart_cull(j, instance, saved.had_cull);
saved.cull_saved = false;
}
// The damage tilt is scaled by an accessibility option the game reads while
// it renders, so zeroing that removes the lurch before it is ever drawn.
// Clearing hurtTime afterwards only takes effect for the *next* frame,
// which still leaves one frame of it on every hit.
if (v.no_hurt_cam) {
if (!saved.tilt_saved) saved.tilt_saved = core.damage_tilt(j, instance, &saved.had_tilt);
core.set_damage_tilt(j, instance, 0.0);
} else if (saved.tilt_saved) {
core.set_damage_tilt(j, instance, saved.had_tilt);
saved.tilt_saved = false;
}
if (v.no_bob) {
if (!saved.bob_saved) saved.bob_saved = core.bob_view(j, instance, &saved.had_bob);
core.set_bob_view(j, instance, false);
} else if (saved.bob_saved) {
core.set_bob_view(j, instance, saved.had_bob);
saved.bob_saved = false;
}
if (v.fullbright) {
if (!saved.gamma_saved) saved.gamma_saved = core.gamma(j, instance, &saved.had_gamma);
core.set_gamma(j, instance, 15.0);
} else if (saved.gamma_saved) {
core.set_gamma(j, instance, saved.had_gamma);
saved.gamma_saved = false;
}
if (v.fov) {
if (!saved.fov_saved) saved.fov_saved = core.fov_option(j, instance, &saved.had_fov);
core.set_fov_option(j, instance, static_cast<int>(v.fov_value));
} else if (saved.fov_saved) {
core.set_fov_option(j, instance, saved.had_fov);
saved.fov_saved = false;
}
// Asking for a wider view, and only when the number changes: the request
// travels as a packet, and one per frame would be a flood.
if (v.view_distance) {
int want = static_cast<int>(v.view_distance_chunks);
if (!saved.view_saved) saved.view_saved = core.render_distance(j, instance, &saved.had_view);
if (saved.view_sent != want) {
saved.view_sent = want;
core.set_render_distance(j, instance, want);
}
} else if (saved.view_saved) {
core.set_render_distance(j, instance, saved.had_view);
saved.view_saved = false;
saved.view_sent = 0;
}
if (cfg.movement.timer) {
core.set_timer(j, instance, cfg.movement.timer_speed);
saved.timer_applied = true;
} else if (saved.timer_applied) {
core.set_timer(j, instance, 1.0f); // back to 20 TPS
saved.timer_applied = false;
}
}
/// Respawn as soon as you are dead. The client can do this the instant the
/// death screen would appear, which is well before a person could click.
void misc(const jni::Env& j, const mc::Mc& core, jobject player, const state::Config& cfg,
const state::GameState& game, Saved& saved) {
if (!cfg.misc.auto_respawn || game.health > 0.0f) return;
// A short gap between attempts: the first one has to reach the server and
// come back before a second would mean anything.
if (now_seconds() - static_cast<double>(saved.last_respawn_ms) / 1000.0 < 0.25) return;
saved.last_respawn_ms = GetTickCount64();
core.respawn(j, player);
}
/// Anti-knockback: scale down the velocity a hit just imparted.
void anti_knockback(const jni::Env& j, const mc::Actions& act, jobject player,
const state::Config& cfg, const state::GameState& game, Saved& saved) {
const state::Combat& c = cfg.combat;
// Only on the frame a hit lands. Scaling velocity every frame would fight
// ordinary movement rather than the knockback.
if (!c.anti_knockback || game.hurt_time <= saved.prev_hurt_time) return;
state::Vec3 v;
if (!act.delta_movement(j, player, &v)) return;
act.set_delta_movement(j, player, v.x * static_cast<double>(c.kb_horizontal),
v.y * static_cast<double>(c.kb_vertical),
v.z * static_cast<double>(c.kb_horizontal));
}
/// Auto clicker: swing while the button is held, at a rate whose gap is
/// jittered — a machine-perfect rhythm is the easiest thing there is to spot.
void auto_clicker(const jni::Env& j, const mc::Mc& core, jobject instance,
const state::Config& cfg, const state::GameState& game, Saved& saved) {
const state::Combat& c = cfg.combat;
if (!c.auto_clicker || game.screen_open || !game.in_world) return;
if ((GetAsyncKeyState(VK_LBUTTON) & 0x8000) == 0) return; // only while held
double interval = 1.0 / (c.click_cps > 0.5f ? static_cast<double>(c.click_cps) : 0.5);
double jitter = 1.0 + static_cast<double>(c.click_jitter) * static_cast<double>(pseudo_random());
if (now_seconds() - static_cast<double>(saved.last_click_ms) / 1000.0 < interval * jitter) {
return;
}
saved.last_click_ms = GetTickCount64();
core.start_attack(j, instance);
}
} // namespace
/// Run every enabled module for this frame. `cfg` is a copy taken under the
/// lock, so nothing here blocks the window procedure.
inline void apply(const jni::Env& j, const mc::Mc& core, const mc::World& world,
const mc::Interact& it, const mc::Actions& act, jobject instance,
jobject player, const state::Config& cfg, const state::GameState& game,
Saved& saved) {
if (!player) return;
flight(j, act, player, cfg, saved);
speed(j, act, player, cfg, game);
sprint(j, act, player, cfg, game);
bhop(j, act, player, cfg, game);
high_jump(j, act, player, cfg, game);
jetpack(j, act, player, cfg, game);
no_fall(j, act, player, cfg, game);
noclip(j, world, player, cfg, saved);
spider(j, world, act, player, cfg, game);
attributes(j, world, player, cfg, saved);
visuals(j, core, world, instance, cfg, saved);
building(j, core, instance, cfg);
misc(j, core, player, cfg, game, saved);
aimbot(j, act, player, cfg, game, saved);
bow_aimbot(j, core, world, act, instance, player, cfg, game);
auto_clicker(j, core, instance, cfg, game, saved);
// Before survival: both read the same hurt-time edge, and survival is what
// consumes it by advancing prev_hurt_time.
anti_knockback(j, act, player, cfg, game, saved);
survival(j, core, it, instance, player, cfg, game, saved);
auto_mace(j, it, act, player, cfg, game, saved);
auto_crystal(j, core, world, it, instance, player, cfg, game, saved);
trigger_bot(j, core, world, instance, player, cfg, game, saved);
kill_aura(j, core, world, act, instance, player, cfg, game, saved);
}
} // namespace lodestone::cheats