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
// The writes: everything a module does *to* the game rather than reads from it.
//
// Kept apart from `Mc` on purpose. Reading is safe from anywhere; writing is
// what gets you noticed, so it all lives in one place where the rules are
// visible — every setter here is something the vanilla client itself does, just
// at a time or a rate a person would not.
//
// Signatures checked against the decompiled 26.2 classes.
#pragma once
#include <string>
#include <vector>
#include "jni.hpp"
#include "mc.hpp"
#include "state.hpp"
namespace lodestone::mc {
class Actions {
public:
bool resolve(const Env& j, const Mc& core, std::vector<std::string>& missing) {
jclass entity = core.entity_class();
jclass living = core.living_class();
jclass player = j.find_class("net/minecraft/world/entity/player/Player");
c_abilities_ = j.find_class("net/minecraft/world/entity/player/Abilities");
c_vec3_ = j.find_class("net/minecraft/world/phys/Vec3");
// Motion. setDeltaMovement has a (double,double,double) overload, which
// saves building a Vec3 just to throw it away.
m_get_delta_ = j.method(entity, "getDeltaMovement", "()Lnet/minecraft/world/phys/Vec3;");
m_set_delta_ = j.method(entity, "setDeltaMovement", "(DDD)V");
m_set_pos_ = j.method(entity, "setPos", "(DDD)V");
if (c_vec3_) {
f_vx_ = j.field(c_vec3_, "x", "D");
f_vy_ = j.field(c_vec3_, "y", "D");
f_vz_ = j.field(c_vec3_, "z", "D");
}
// Rotation. Setting both halves is what keeps the body and the head
// from disagreeing.
m_set_yrot_ = j.method(entity, "setYRot", "(F)V");
m_set_xrot_ = j.method(entity, "setXRot", "(F)V");
m_is_sprinting_ = j.method(entity, "isSprinting", "()Z");
m_set_sprinting_ = j.method(entity, "setSprinting", "(Z)V");
// Flight is a permission the server grants; the client just believes
// its own copy, which is why creative flight works and why a server
// that disagrees will pull you back.
m_get_abilities_ =
j.method(player, "getAbilities", "()Lnet/minecraft/world/entity/player/Abilities;");
if (c_abilities_) {
f_flying_ = j.field(c_abilities_, "flying", "Z");
f_may_fly_ = j.field(c_abilities_, "mayfly", "Z");
m_set_fly_speed_ = j.method(c_abilities_, "setFlyingSpeed", "(F)V");
m_get_fly_speed_ = j.method(c_abilities_, "getFlyingSpeed", "()F");
}
m_line_of_sight_ =
j.method(living, "hasLineOfSight", "(Lnet/minecraft/world/entity/Entity;)Z");
// Pushing a position/look packet ourselves. The server takes your aim
// from the movement stream — an attack packet carries no rotation — so
// this is what makes a silent aim actually land, and what lets a
// leaving-the-ground position register before the tick's own send.
// `send` is declared on the common superclass, not on
// ClientPacketListener itself.
c_posrot_ =
j.find_class("net/minecraft/network/protocol/game/ServerboundMovePlayerPacket$PosRot");
m_posrot_init_ = j.method(c_posrot_, "<init>", "(DDDFFZZ)V");
jclass common = j.find_class("net/minecraft/client/multiplayer/ClientCommonPacketListenerImpl");
m_send_ = j.method(common, "send", "(Lnet/minecraft/network/protocol/Packet;)V");
f_connection_ = j.field(j.find_class("net/minecraft/client/player/LocalPlayer"), "connection",
"Lnet/minecraft/client/multiplayer/ClientPacketListener;");
if (!m_posrot_init_ || !m_send_ || !f_connection_) {
missing.emplace_back("position flush (silent aim, no-fall)");
}
if (!m_set_delta_ || !m_set_yrot_) missing.emplace_back("Entity motion/rotation setters");
if (!m_get_abilities_ || !f_flying_) missing.emplace_back("Player abilities");
return true;
}
// ---- motion ----------------------------------------------------------
bool delta_movement(const Env& j, jobject entity, state::Vec3* out) const {
jobject v = j.call_obj(entity, m_get_delta_);
if (!v) return false;
out->x = j.double_field(v, f_vx_);
out->y = j.double_field(v, f_vy_);
out->z = j.double_field(v, f_vz_);
j.delete_local(v);
return true;
}
void set_delta_movement(const Env& j, jobject entity, double x, double y, double z) const {
j.call_void(entity, m_set_delta_, {jv(x), jv(y), jv(z)});
}
void set_pos(const Env& j, jobject entity, double x, double y, double z) const {
j.call_void(entity, m_set_pos_, {jv(x), jv(y), jv(z)});
}
// ---- rotation --------------------------------------------------------
void set_rotation(const Env& j, jobject entity, float yaw, float pitch) const {
j.call_void(entity, m_set_yrot_, {jv(yaw)});
j.call_void(entity, m_set_xrot_, {jv(pitch)});
}
// ---- sprint ----------------------------------------------------------
bool sprinting(const Env& j, jobject entity) const {
return j.call_bool(entity, m_is_sprinting_);
}
void set_sprinting(const Env& j, jobject entity, bool on) const {
j.call_void(entity, m_set_sprinting_, {jv(on)});
}
// ---- flight ----------------------------------------------------------
/// The abilities object is a live reference into the player, so writes to
/// it take effect immediately.
jobject abilities(const Env& j, jobject player) const {
return j.call_obj(player, m_get_abilities_);
}
bool flying(const Env& j, jobject abilities) const {
return j.bool_field(abilities, f_flying_);
}
void set_flying(const Env& j, jobject abilities, bool on) const {
j.set_bool(abilities, f_flying_, on);
}
void set_may_fly(const Env& j, jobject abilities, bool on) const {
j.set_bool(abilities, f_may_fly_, on);
}
float fly_speed(const Env& j, jobject abilities) const {
return j.call_float(abilities, m_get_fly_speed_);
}
void set_fly_speed(const Env& j, jobject abilities, float speed) const {
j.call_void(abilities, m_set_fly_speed_, {jv(speed)});
}
// ---- sight -----------------------------------------------------------
/// The game already knows how to answer this, so ask it rather than casting
/// our own ray.
bool has_line_of_sight(const Env& j, jobject from, jobject to) const {
if (!m_line_of_sight_) return true; // cannot tell: do not block the shot
return j.call_bool(from, m_line_of_sight_, {jv(to)});
}
// ---- telling the server ----------------------------------------------
/// Send one position/look packet now, without waiting for the tick's own.
///
/// The trailing pair is (onGround, horizontalCollision) — the first is what
/// No Fall lies about, and the rotation is what a silent aim needs the
/// server to believe.
bool flush_position(const Env& j, jobject player, const state::Vec3& pos, float yaw,
float pitch, bool on_ground) const {
if (!m_posrot_init_ || !m_send_ || !f_connection_) return false;
jobject connection = j.obj_field(player, f_connection_);
if (!connection) return false;
jobject packet = j.new_object(
c_posrot_, m_posrot_init_,
{jv(pos.x), jv(pos.y), jv(pos.z), jv(yaw), jv(pitch), jv(on_ground), jv(false)});
if (!packet) return false;
j.call_void(connection, m_send_, {jv(packet)});
j.delete_local(packet);
return true;
}
private:
jclass c_abilities_ = nullptr, c_vec3_ = nullptr;
jmethodID m_get_delta_ = nullptr, m_set_delta_ = nullptr, m_set_pos_ = nullptr;
jmethodID m_set_yrot_ = nullptr, m_set_xrot_ = nullptr;
jmethodID m_is_sprinting_ = nullptr, m_set_sprinting_ = nullptr;
jmethodID m_get_abilities_ = nullptr, m_set_fly_speed_ = nullptr, m_get_fly_speed_ = nullptr;
jmethodID m_line_of_sight_ = nullptr;
jclass c_posrot_ = nullptr;
jmethodID m_posrot_init_ = nullptr, m_send_ = nullptr;
jfieldID f_vx_ = nullptr, f_vy_ = nullptr, f_vz_ = nullptr;
jfieldID f_flying_ = nullptr, f_may_fly_ = nullptr, f_connection_ = nullptr;
};
} // namespace lodestone::mc