From 88d49547d42ab58ef3df802c1670dede47748639 Mon Sep 17 00:00:00 2001 From: Misa Date: Sat, 18 Mar 2023 20:24:08 -0700 Subject: [PATCH] Evaluate flipping eligibility per-entity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a regression where the behavior of duplicate player entities is different, causing a gameplay section in Vespera Scientifica to be impossible, as described in #903. In the level, you are allowed to flip in mid-air. Vespera accomplishes this by having two duplicate player entities stuck in a platform. One of them is responsible for letting the player flip in one direction, and one of them is responsible for letting them flip in the other. In 2.3, this works because in order for a player entity to flip, `game.jumppressed` is checked, and the entity will flip if `game.jumppressed` is greater than 0, then `game.jumppressed` will be set to 0. In this way, whenever a player entity flips, it will set `game.jumppressed` to 0, so whenever the next player entity is evaluated, `game.jumppressed` is 0 and thus _that_ entity will not flip. This is because the for-loop surrounds both the `game.jumppressed` check and flipping the entity. In 2.4 after #609 and subsequent patches, however, this is not the case. Here, the for-loop only surrounds flipping the entity. Therefore, the `game.jumppressed` check is evaluated only once. So, it will end up flipping every player entity if the entities are eligible. In this case, one of them is eligible twice, resulting in the game flipping gravitycontrol four times, which is essentially the same as not flipping at all (except for all the sound effects). Hence, the fix here is to make it so the for-loop surrounds the `game.jumppressed` check. Now, this doesn't mean that the intent of #609 - that duplicate player entities have the same initial velocity applied to them when flipping - has to be removed. We can just put the for-loops back in. But I carefully implemented them in such a way that the overall function is not quadratic, i.e. O(n²). Instead, there's a pass done over the `obj.entities` vector beforehand to store all indexes of a player entity in a temporary vector, and then that vector is used to update all the player entities. In this manner, the function is still linear - O(n) - over the number of entities in the room. I tested this to make sure that no previous regressions popped up again, including #839, #855, and #887. And #484 is still resolved. Fixes #903. --- desktop_version/src/Input.cpp | 52 ++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/desktop_version/src/Input.cpp b/desktop_version/src/Input.cpp index ab14f37c..58b4858f 100644 --- a/desktop_version/src/Input.cpp +++ b/desktop_version/src/Input.cpp @@ -1,4 +1,5 @@ #include +#include #include "Credits.h" #include "CustomLevels.h" @@ -2508,8 +2509,6 @@ void gameinput(void) bool has_control = false; bool enter_pressed = game.press_map && !game.mapheld; bool enter_already_processed = false; - bool any_onground = false; - bool any_onroof = false; bool interact_pressed; if (game.separate_interact) { @@ -2626,15 +2625,6 @@ void gameinput(void) obj.entities[ie].dir = 1; } } - - if (obj.entities[ie].onground > 0) - { - any_onground = true; - } - if (obj.entities[ie].onroof > 0) - { - any_onroof = true; - } } } @@ -2695,33 +2685,51 @@ void gameinput(void) game.jumpheld = true; } - if (game.jumppressed > 0) + std::vector player_entities; + for (size_t ie = 0; ie < obj.entities.size(); ie++) { + if (obj.entities[ie].rule == 0) + { + player_entities.push_back(ie); + } + } + + for (size_t ie = 0; ie < obj.entities.size(); ie++) + { + const bool process_flip = obj.entities[ie].rule == 0 && + game.jumppressed > 0; + if (!process_flip) + { + continue; + } + game.jumppressed--; - if (any_onground && game.gravitycontrol == 0) + if (obj.entities[ie].onground > 0 && game.gravitycontrol == 0) { game.gravitycontrol = 1; - for (size_t ie = 0; ie < obj.entities.size(); ++ie) + for (size_t j = 0; j < player_entities.size(); j++) { - if (obj.entities[ie].rule == 0 && (obj.entities[ie].onground > 0 || obj.entities[ie].onroof > 0)) + const size_t e = player_entities[j]; + if (obj.entities[e].onground > 0 || obj.entities[e].onroof > 0) { - obj.entities[ie].vy = -4; - obj.entities[ie].ay = -3; + obj.entities[e].vy = -4; + obj.entities[e].ay = -3; } } music.playef(0); game.jumppressed = 0; game.totalflips++; } - if (any_onroof && game.gravitycontrol == 1) + if (obj.entities[ie].onroof > 0 && game.gravitycontrol == 1) { game.gravitycontrol = 0; - for (size_t ie = 0; ie < obj.entities.size(); ++ie) + for (size_t j = 0; j < player_entities.size(); j++) { - if (obj.entities[ie].rule == 0 && (obj.entities[ie].onground > 0 || obj.entities[ie].onroof > 0)) + const size_t e = player_entities[j]; + if (obj.entities[e].onground > 0 || obj.entities[e].onroof > 0) { - obj.entities[ie].vy = 4; - obj.entities[ie].ay = 3; + obj.entities[e].vy = 4; + obj.entities[e].ay = 3; } } music.playef(1);