Sign in Sign up
kretrod/lodestone-cpp Public
Branches
main
190 lines (160 loc) · 8.2 KB Raw
// 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