diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 7566be59..950de5d1 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -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 diff --git a/minetest.conf.example b/minetest.conf.example index 8d69733a..ccb57520 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -69,6 +69,10 @@ # 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 diff --git a/src/collision.cpp b/src/collision.cpp index 48a681dc..9626221a 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -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) { diff --git a/src/collision.h b/src/collision.h index 4d47171e..4c559452 100644 --- a/src/collision.h +++ b/src/collision.h @@ -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 diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 6a0474e7..2408bffe 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -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"); diff --git a/src/game.cpp b/src/game.cpp index 227b21db..1cb9a165 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -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(); diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index ca97e0b9..f3d8e8ee 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -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 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(); diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 4bf68942..1c65d3b4 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -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; + } +} diff --git a/src/localplayer.h b/src/localplayer.h index dc59c417..7148bc4d 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -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; diff --git a/src/player.cpp b/src/player.cpp index ccc75383..4b104a71 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -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) diff --git a/src/player.h b/src/player.h index 66cd0f5c..67449154 100644 --- a/src/player.h +++ b/src/player.h @@ -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(); }; diff --git a/src/settings_translation_file.cpp b/src/settings_translation_file.cpp index c2390c18..2b56115d 100644 --- a/src/settings_translation_file.cpp +++ b/src/settings_translation_file.cpp @@ -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.");