Client-side autojump. Remove Android-only stepheight autojump (#7228)
authorBen Deutsch <ben@bendeutsch.de>
Thu, 22 Nov 2018 21:47:15 +0000 (22:47 +0100)
committerParamat <paramat@users.noreply.github.com>
Thu, 22 Nov 2018 21:47:15 +0000 (21:47 +0000)
Works by detecting a collision while moving forward and then
simulating a jump. If the simulated jump is more successful,
an artificial jump key press is injected in the client.

Includes setting and key change GUI element for enabling and
disabling this feature.

12 files changed:
builtin/settingtypes.txt
minetest.conf.example
src/collision.cpp
src/collision.h
src/defaultsettings.cpp
src/game.cpp
src/gui/guiKeyChangeMenu.cpp
src/localplayer.cpp
src/localplayer.h
src/player.cpp
src/player.h
src/settings_translation_file.cpp

index 7566be594ae6d6602abfdf193c0a6eab9d3df67e..950de5d1cd34fdb7b4a4254c2ff1ef04155e4f03 100644 (file)
@@ -111,6 +111,10 @@ always_fly_fast (Always fly and fast) bool true
 #    mouse button.
 repeat_rightclick_time (Rightclick repetition interval) float 0.25
 
+#    Automatically jump up single-node obstacles.
+#    type: bool
+autojump (Automatic jumping) bool false
+
 #    Prevent digging and placing from repeating when holding the mouse buttons.
 #    Enable this when you dig or place too often by accident.
 safe_dig_and_place (Safe digging and placing) bool false
index 8d69733a1d71d9bc99dddd70a59b0fa434599c93..ccb57520ce7412f016ac3f8e1f62c2bae9637fe9 100644 (file)
 #    type: bool
 # always_fly_fast = true
 
+#    Automatically jump up single-node obstacles.
+#    type: bool
+# autojump = false
+
 #    The time in seconds it takes between repeated right clicks when holding the right mouse button.
 #    type: float
 # repeat_rightclick_time = 0.25
index 48a681dca543f0e699dd2a1d6e59dc24f31e165e..9626221a0c973104f2ae650953962030a3512f00 100644 (file)
@@ -515,6 +515,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
 
                        info.node_p = nearest_info.position;
                        info.old_speed = *speed_f;
+                       info.plane = nearest_collided;
 
                        // Set the speed component that caused the collision to zero
                        if (step_up) {
index 4d47171eaff9f6e0bfdf7d841f43a9829a3d72d1..4c5594528d101dd136924647a3b5578e671c9c56 100644 (file)
@@ -41,6 +41,7 @@ struct CollisionInfo
        v3s16 node_p = v3s16(-32768,-32768,-32768); // COLLISION_NODE
        v3f old_speed;
        v3f new_speed;
+       int plane = -1;
 };
 
 struct collisionMoveResult
index 6a0474e744b46e19f38d85e0500461f8d2010c42..2408bffebcef030b67eb76048385e72140516306 100644 (file)
@@ -247,6 +247,11 @@ void set_default_settings(Settings *settings)
        settings->setDefault("aux1_descends", "false");
        settings->setDefault("doubletap_jump", "false");
        settings->setDefault("always_fly_fast", "true");
+#ifdef __ANDROID__
+       settings->setDefault("autojump", "true");
+#else
+       settings->setDefault("autojump", "false");
+#endif
        settings->setDefault("continuous_forward", "false");
        settings->setDefault("enable_joysticks", "false");
        settings->setDefault("joystick_id", "0");
index 227b21db5d95d5e784c94255d3d1d9da6699f9ba..1cb9a165092adb56e4bbb09633947b0ad4e81b11 100644 (file)
@@ -2449,8 +2449,15 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
        }
 #endif
 
-       client->setPlayerControl(control);
        LocalPlayer *player = client->getEnv().getLocalPlayer();
+
+       // autojump if set: simulate "jump" key
+       if (player->getAutojump()) {
+               control.jump = true;
+               keypress_bits |= 1U << 4;
+       }
+
+       client->setPlayerControl(control);
        player->keyPressed = keypress_bits;
 
        //tt.stop();
index ca97e0b9eb590f66456d9b30450a20129e19e426..f3d8e8eef4ac1847511e3df0b1af96f57cc86987 100644 (file)
@@ -77,6 +77,7 @@ enum
        // other
        GUI_ID_CB_AUX1_DESCENDS,
        GUI_ID_CB_DOUBLETAP_JUMP,
+       GUI_ID_CB_AUTOJUMP,
 };
 
 GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env,
@@ -195,6 +196,21 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
                offset += v2s32(0, 25);
        }
 
+       {
+               s32 option_x = offset.X;
+               s32 option_y = offset.Y + 5;
+               u32 option_w = 280;
+               {
+                       core::rect<s32> rect(0, 0, option_w, 30);
+                       rect += topleft + v2s32(option_x, option_y);
+                       const wchar_t *text = wgettext("Automatic jumping");
+                       Environment->addCheckBox(g_settings->getBool("autojump"), rect, this,
+                                       GUI_ID_CB_AUTOJUMP, text);
+                       delete[] text;
+               }
+               offset += v2s32(0, 25);
+       }
+
        {
                core::rect < s32 > rect(0, 0, 100, 30);
                rect += topleft + v2s32(size.X / 2 - 105, size.Y - 40);
@@ -239,14 +255,19 @@ bool GUIKeyChangeMenu::acceptInput()
 
        {
                gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUX1_DESCENDS);
-               if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
+               if(e && e->getType() == gui::EGUIET_CHECK_BOX)
                        g_settings->setBool("aux1_descends", ((gui::IGUICheckBox*)e)->isChecked());
        }
        {
                gui::IGUIElement *e = getElementFromId(GUI_ID_CB_DOUBLETAP_JUMP);
-               if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
+               if(e && e->getType() == gui::EGUIET_CHECK_BOX)
                        g_settings->setBool("doubletap_jump", ((gui::IGUICheckBox*)e)->isChecked());
        }
+       {
+               gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUTOJUMP);
+               if(e && e->getType() == gui::EGUIET_CHECK_BOX)
+                       g_settings->setBool("autojump", ((gui::IGUICheckBox*)e)->isChecked());
+       }
 
        clearKeyCache();
 
index 4bf6894283c94365e8b9f5fa3e2898afe3b8f608..1c65d3b4d210d3aeeefa74215cbd6a3425fb1efc 100644 (file)
@@ -287,16 +287,9 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
        float player_stepheight = (m_cao == nullptr) ? 0.0f :
                (touching_ground ? m_cao->getStepHeight() : (0.2f * BS));
 
-       // TODO this is a problematic hack.
-       // Use a better implementation for autojump, or apply a custom stepheight
-       // to all players, as this currently creates unintended special movement
-       // abilities and advantages for Android players on a server.
-#ifdef __ANDROID__
-       if (touching_ground)
-               player_stepheight += (0.6f * BS);
-#endif
-
        v3f accel_f = v3f(0,0,0);
+       const v3f initial_position = position;
+       const v3f initial_speed = m_speed;
 
        collisionMoveResult result = collisionMoveSimple(env, m_client,
                pos_max_d, m_collisionbox, player_stepheight, dtime,
@@ -461,6 +454,9 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
                setSpeed(m_speed);
                m_can_jump = false;
        }
+
+       // Autojump
+       handleAutojump(dtime, env, result, initial_position, initial_speed, pos_max_d);
 }
 
 void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d)
@@ -891,11 +887,9 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
        // this shouldn't be hardcoded but transmitted from server
        float player_stepheight = touching_ground ? (BS * 0.6) : (BS * 0.2);
 
-#ifdef __ANDROID__
-       player_stepheight += (0.6 * BS);
-#endif
-
        v3f accel_f = v3f(0, 0, 0);
+       const v3f initial_position = position;
+       const v3f initial_speed = m_speed;
 
        collisionMoveResult result = collisionMoveSimple(env, m_client,
                pos_max_d, m_collisionbox, player_stepheight, dtime,
@@ -1059,6 +1053,9 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
                setSpeed(m_speed);
                m_can_jump = false;
        }
+
+       // Autojump
+       handleAutojump(dtime, env, result, initial_position, initial_speed, pos_max_d);
 }
 
 float LocalPlayer::getSlipFactor(Environment *env, const v3f &speedH)
@@ -1080,3 +1077,82 @@ float LocalPlayer::getSlipFactor(Environment *env, const v3f &speedH)
        }
        return 1.0f;
 }
+
+void LocalPlayer::handleAutojump(f32 dtime, Environment *env,
+               const collisionMoveResult &result, const v3f &initial_position,
+               const v3f &initial_speed, f32 pos_max_d)
+{
+       PlayerSettings &player_settings = getPlayerSettings();
+       if (!player_settings.autojump)
+               return;
+
+       if (m_autojump) {
+               // release autojump after a given time
+               m_autojump_time -= dtime;
+               if (m_autojump_time <= 0.0f)
+                       m_autojump = false;
+               return;
+       }
+
+       bool control_forward = control.up ||
+                              (!control.up && !control.down &&
+                                              control.forw_move_joystick_axis < -0.05);
+       bool could_autojump =
+                       m_can_jump && !control.jump && !control.sneak && control_forward;
+       if (!could_autojump)
+               return;
+
+       bool horizontal_collision = false;
+       for (const auto &colinfo : result.collisions) {
+               if (colinfo.type == COLLISION_NODE && colinfo.plane != 1) {
+                       horizontal_collision = true;
+                       break; // one is enough
+               }
+       }
+
+       // must be running against something to trigger autojumping
+       if (!horizontal_collision)
+               return;
+
+       // check for nodes above
+       v3f headpos_min = m_position + m_collisionbox.MinEdge * 0.99f;
+       v3f headpos_max = m_position + m_collisionbox.MaxEdge * 0.99f;
+       headpos_min.Y = headpos_max.Y; // top face of collision box
+       v3s16 ceilpos_min = floatToInt(headpos_min, BS) + v3s16(0, 1, 0);
+       v3s16 ceilpos_max = floatToInt(headpos_max, BS) + v3s16(0, 1, 0);
+       const NodeDefManager *ndef = env->getGameDef()->ndef();
+       bool is_position_valid;
+       for (s16 z = ceilpos_min.Z; z <= ceilpos_max.Z; z++) {
+               for (s16 x = ceilpos_min.X; x <= ceilpos_max.X; x++) {
+                       MapNode n = env->getMap().getNodeNoEx(v3s16(x, ceilpos_max.Y, z), &is_position_valid);
+
+                       if (!is_position_valid)
+                               break;  // won't collide with the void outside
+                       if (n.getContent() == CONTENT_IGNORE)
+                               return; // players collide with ignore blocks -> same as walkable
+                       const ContentFeatures &f = ndef->get(n);
+                       if (f.walkable)
+                               return; // would bump head, don't jump
+               }
+       }
+
+       float jump_height = 1.1f; // TODO: better than a magic number
+       v3f jump_pos = initial_position + v3f(0.0f, jump_height * BS, 0.0f);
+       v3f jump_speed = initial_speed;
+
+       // try at peak of jump, zero step height
+       collisionMoveResult jump_result = collisionMoveSimple(env, m_client, pos_max_d,
+                       m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed,
+                       v3f(0, 0, 0));
+
+       // see if we can get a little bit farther horizontally if we had
+       // jumped
+       v3f run_delta = m_position - initial_position;
+       run_delta.Y = 0.0f;
+       v3f jump_delta = jump_pos - initial_position;
+       jump_delta.Y = 0.0f;
+       if (jump_delta.getLengthSQ() > run_delta.getLengthSQ() * 1.01f) {
+               m_autojump = true;
+               m_autojump_time = 0.1f;
+       }
+}
index dc59c4179a09dd1a28ad3e818482f1464213d33f..7148bc4ded9c8837a8372d2c42583dc1aa9dbf15 100644 (file)
@@ -31,6 +31,7 @@ class GenericCAO;
 class ClientActiveObject;
 class ClientEnvironment;
 class IGameDef;
+struct collisionMoveResult;
 
 enum LocalPlayerAnimations
 {
@@ -145,11 +146,17 @@ public:
        float getZoomFOV() const { return m_zoom_fov; }
        void setZoomFOV(float zoom_fov) { m_zoom_fov = zoom_fov; }
 
+       bool getAutojump() const { return m_autojump; }
+
 private:
        void accelerateHorizontal(const v3f &target_speed, const f32 max_increase);
        void accelerateVertical(const v3f &target_speed, const f32 max_increase);
        bool updateSneakNode(Map *map, const v3f &position, const v3f &sneak_max);
        float getSlipFactor(Environment *env, const v3f &speedH);
+       void handleAutojump(f32 dtime, Environment *env,
+                       const collisionMoveResult &result,
+                       const v3f &position_before_move, const v3f &speed_before_move,
+                       f32 pos_max_d);
 
        v3f m_position;
        v3s16 m_standing_node;
@@ -183,6 +190,8 @@ private:
                        BS * 1.75f, BS * 0.30f);
        float m_eye_height = 1.625f;
        float m_zoom_fov = 0.0f;
+       bool m_autojump = false;
+       float m_autojump_time = 0.0f;
 
        GenericCAO *m_cao = nullptr;
        Client *m_client;
index ccc753834c4ce92bc885151f922d32684e8a86c5..4b104a71b924c65082de297a827807b7d47df4f9 100644 (file)
@@ -144,6 +144,7 @@ void PlayerSettings::readGlobalSettings()
        always_fly_fast = g_settings->getBool("always_fly_fast");
        aux1_descends = g_settings->getBool("aux1_descends");
        noclip = g_settings->getBool("noclip");
+       autojump = g_settings->getBool("autojump");
 }
 
 void Player::settingsChangedCallback(const std::string &name, void *data)
index 66cd0f5cfa8f494063126d1b684dd4627e8e5e08..67449154616b1a6f12b13200ef0eaa01f765a0b8 100644 (file)
@@ -92,10 +92,11 @@ struct PlayerSettings
        bool always_fly_fast = false;
        bool aux1_descends = false;
        bool noclip = false;
+       bool autojump = false;
 
-       const std::string setting_names[6] = {
+       const std::string setting_names[7] = {
                "free_move", "fast_move", "continuous_forward", "always_fly_fast",
-               "aux1_descends", "noclip"
+               "aux1_descends", "noclip", "autojump"
        };
        void readGlobalSettings();
 };
index c2390c1898ee3fcf1a4feb0b30a8c4be0c4d1508..2b56115dffddf65307dcba54fb9e616978998592 100644 (file)
@@ -27,6 +27,7 @@ fake_function() {
        gettext("Double tap jump for fly");
        gettext("Double-tapping the jump key toggles fly mode.");
        gettext("Always fly and fast");
+       gettext("Automatic jumping");
        gettext("If disabled, \"special\" key is used to fly fast if both fly and fast mode are enabled.");
        gettext("Rightclick repetition interval");
        gettext("The time in seconds it takes between repeated right clicks when holding the right mouse button.");