From 9363cf4c4006a66ec1de83d4d6b24af0d747bcfb Mon Sep 17 00:00:00 2001 From: Misa Date: Sun, 28 Jun 2020 02:12:56 -0700 Subject: [PATCH 01/13] Fix Game::anything_unlocked() always evaluating to true Whoops. --- desktop_version/src/Game.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop_version/src/Game.cpp b/desktop_version/src/Game.cpp index 75699a49..b8f22bf8 100644 --- a/desktop_version/src/Game.cpp +++ b/desktop_version/src/Game.cpp @@ -7641,7 +7641,7 @@ bool Game::anything_unlocked() { if (unlock[i] && (i == 8 // Secret Lab - || i >= 9 || i <= 14 // any Time Trial + || (i >= 9 && i <= 14) // any Time Trial || i == 16 // Intermission replays || i == 17 // No Death Mode || i == 18)) // Flip Mode From 7627a372167b56da181a96c3cd40c359e148193a Mon Sep 17 00:00:00 2001 From: Misa Date: Sun, 28 Jun 2020 02:07:22 -0700 Subject: [PATCH 02/13] Change a '||' to a '==' Whoops. --- desktop_version/src/Logic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop_version/src/Logic.cpp b/desktop_version/src/Logic.cpp index 048067a5..11c4fb6a 100644 --- a/desktop_version/src/Logic.cpp +++ b/desktop_version/src/Logic.cpp @@ -83,7 +83,7 @@ void maplogic() { game.shouldreturntopausemenu = false; graphics.backgrounddrawn = false; - if (map.background == 3 || map.background || 4) + if (map.background == 3 || map.background == 4) { graphics.updatebackground(map.background); } From a0f10d80e5e4ecc5bfb7f42be90d6a64205cd1d9 Mon Sep 17 00:00:00 2001 From: Misa Date: Sat, 27 Jun 2020 23:59:47 -0700 Subject: [PATCH 03/13] Refactor, de-duplicate, and clean up tilesheet processing The tilesheets in question are font.png, tiles.png, tiles2.png, tiles3.png, entcolours.png, teleporter.png, sprites.png, and flipsprites.png. This patch removes the hardcoded dimensions when scanning the tilesheets, because it's simpler that way. It also de-duplicates it so it isn't a bunch of copy-paste, by using macros. (I had to use macros because it was the easiest way to optionally pass in some extra code in the innermost for-loop.) Also, if the dimensions of a scanned tilesheet aren't exactly multiples of the dimensions of the tile unit for that given tilesheet (e.g. if the dimensions of a scanned tiles.png are not exact multiples of 8), then an SDL_SimpleMessageBox will show up with the error message, a puts() of the error message will be called, and the program will exit. --- desktop_version/src/Graphics.cpp | 120 +++++++++++++++---------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/desktop_version/src/Graphics.cpp b/desktop_version/src/Graphics.cpp index cde449e1..d21bd4bc 100644 --- a/desktop_version/src/Graphics.cpp +++ b/desktop_version/src/Graphics.cpp @@ -200,20 +200,58 @@ void Graphics::updatetitlecolours() col_trinket = ct.colour; } +#define PROCESS_TILESHEET_CHECK_ERROR(tilesheet, tile_square) \ + if (grphx.im_##tilesheet->w % tile_square != 0 \ + || grphx.im_##tilesheet->h % tile_square != 0) \ + { \ + const char* error = "Error: %s.png dimensions not exact multiples of %i!"; \ + char message[128]; \ + SDL_snprintf(message, sizeof(message), error, #tilesheet, tile_square); \ + \ + const char* error_title = "Error with %s.png"; \ + char message_title[128]; \ + SDL_snprintf(message_title, sizeof(message_title), error_title, #tilesheet); \ + \ + puts(message); \ + \ + SDL_ShowSimpleMessageBox( \ + SDL_MESSAGEBOX_ERROR, \ + message_title, \ + message, \ + NULL \ + ); \ + \ + exit(1); \ + } + +#define PROCESS_TILESHEET_RENAME(tilesheet, vector, tile_square, extra_code) \ + PROCESS_TILESHEET_CHECK_ERROR(tilesheet, tile_square) \ + \ + for (int j = 0; j < grphx.im_##tilesheet->h / tile_square; j++) \ + { \ + for (int i = 0; i < grphx.im_##tilesheet->w / tile_square; i++) \ + { \ + SDL_Surface* temp = GetSubSurface( \ + grphx.im_##tilesheet, \ + i * tile_square, j * tile_square, \ + tile_square, tile_square \ + ); \ + vector.push_back(temp); \ + \ + extra_code \ + } \ + } + +#define PROCESS_TILESHEET(tilesheet, tile_square, extra_code) \ + PROCESS_TILESHEET_RENAME(tilesheet, tilesheet, tile_square, extra_code) + void Graphics::Makebfont() { - for (int j = 0; j < (grphx.im_bfont->h / 8); j++) + PROCESS_TILESHEET(bfont, 8, { - for (int i = 0; i < 16; i++) - { - - SDL_Surface* temp = GetSubSurface(grphx.im_bfont,i*8,j*8,8,8); - bfont.push_back(temp); - - SDL_Surface* TempFlipped = FlipSurfaceVerticle(temp); - flipbfont.push_back(TempFlipped); - } - } + SDL_Surface* TempFlipped = FlipSurfaceVerticle(temp); + flipbfont.push_back(TempFlipped); + }) unsigned char* charmap = NULL; size_t length; @@ -241,65 +279,27 @@ int Graphics::bfontlen(uint32_t ch) { void Graphics::MakeTileArray() { - for(int j = 0; j <30; j++) - { - for(int i = 0; i <40; i++) - { - SDL_Surface* temp = GetSubSurface(grphx.im_tiles,i*8,j*8,8,8); - tiles.push_back(temp); - } - } - for(int j = 0; j <30; j++) - { - for(int i = 0; i <40; i++) - { - SDL_Surface* temp = GetSubSurface(grphx.im_tiles2,i*8,j*8,8,8); - tiles2.push_back(temp); - } - } - - for(int j = 0; j <30; j++) - { - for(int i = 0; i <30; i++) - { - SDL_Surface* temp = GetSubSurface(grphx.im_tiles3,i*8,j*8,8,8); - tiles3.push_back(temp); - } - } - - for(int j = 0; j <60; j++) - { - for(int i = 0; i <12; i++) - { - SDL_Surface* temp = GetSubSurface(grphx.im_entcolours,i*8,j*8,8,8); - entcolours.push_back(temp); - } - } + PROCESS_TILESHEET(tiles, 8, ) + PROCESS_TILESHEET(tiles2, 8, ) + PROCESS_TILESHEET(tiles3, 8, ) + PROCESS_TILESHEET(entcolours, 8, ) } void Graphics::maketelearray() { - for (int i = 0; i < 10; i++) - { - SDL_Surface* temp = GetSubSurface(grphx.im_teleporter,i*96,0,96,96); - tele.push_back(temp); - } + PROCESS_TILESHEET_RENAME(teleporter, tele, 96, ) } void Graphics::MakeSpriteArray() { - for(int j = 0; j <16; j++) - { - for(int i = 0; i <12; i++) - { - SDL_Surface* temp = GetSubSurface(grphx.im_sprites,i*32,j*32,32,32); - sprites.push_back(temp); - temp = GetSubSurface(grphx.im_flipsprites,i*32,j*32,32,32); - flipsprites.push_back(temp); - } - } + PROCESS_TILESHEET(sprites, 32, ) + PROCESS_TILESHEET(flipsprites, 32, ) } +#undef PROCESS_TILESHEET +#undef PROCESS_TILESHEET_RENAME +#undef PROCESS_TILESHEET_CHECK_ERROR + void Graphics::map_tab(int opt, const std::string& text, bool selected /*= false*/) { From c95c1ab25006147ede1ea8aa3b24a50f0c17ba3b Mon Sep 17 00:00:00 2001 From: Misa Date: Sun, 28 Jun 2020 10:40:58 -0700 Subject: [PATCH 04/13] Add bounds check to textbox functions that use `m` It seems to be a bit bad to blindly use `m` without checking it. In fact, this has caused a few segfaults already, actually. --- desktop_version/src/Graphics.cpp | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/desktop_version/src/Graphics.cpp b/desktop_version/src/Graphics.cpp index d21bd4bc..36f5091a 100644 --- a/desktop_version/src/Graphics.cpp +++ b/desktop_version/src/Graphics.cpp @@ -1141,16 +1141,31 @@ void Graphics::textboxremove() void Graphics::textboxtimer( int t ) { + if (!INBOUNDS(m, textbox)) + { + return; + } + textbox[m].timer=t; } void Graphics::addline( std::string t ) { + if (!INBOUNDS(m, textbox)) + { + return; + } + textbox[m].addline(t); } void Graphics::textboxadjust() { + if (!INBOUNDS(m, textbox)) + { + return; + } + textbox[m].adjust(); } @@ -2787,33 +2802,63 @@ void Graphics::setwarprect( int a, int b, int c, int d ) void Graphics::textboxcenter() { + if (!INBOUNDS(m, textbox)) + { + return; + } + textbox[m].centerx(); textbox[m].centery(); } void Graphics::textboxcenterx() { + if (!INBOUNDS(m, textbox)) + { + return; + } + textbox[m].centerx(); } int Graphics::textboxwidth() { + if (!INBOUNDS(m, textbox)) + { + return 0; + } + return textbox[m].w; } void Graphics::textboxmove(int xo, int yo) { + if (!INBOUNDS(m, textbox)) + { + return; + } + textbox[m].xp += xo; textbox[m].yp += yo; } void Graphics::textboxmoveto(int xo) { + if (!INBOUNDS(m, textbox)) + { + return; + } + textbox[m].xp = xo; } void Graphics::textboxcentery() { + if (!INBOUNDS(m, textbox)) + { + return; + } + textbox[m].centery(); } From 18b34d006630f184fac146dda54b2c8e4881e7fb Mon Sep 17 00:00:00 2001 From: Misa Date: Sun, 28 Jun 2020 12:43:12 -0700 Subject: [PATCH 05/13] Add logs if a Graphics func was stopped from indexing OoB This doesn't happen too often, but it'll be useful to people making custom levels so they know that it can happen, when it does. --- desktop_version/src/Graphics.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/desktop_version/src/Graphics.cpp b/desktop_version/src/Graphics.cpp index 36f5091a..55e852c7 100644 --- a/desktop_version/src/Graphics.cpp +++ b/desktop_version/src/Graphics.cpp @@ -1143,6 +1143,7 @@ void Graphics::textboxtimer( int t ) { if (!INBOUNDS(m, textbox)) { + puts("textboxtimer() out-of-bounds!"); return; } @@ -1153,6 +1154,7 @@ void Graphics::addline( std::string t ) { if (!INBOUNDS(m, textbox)) { + puts("addline() out-of-bounds!"); return; } @@ -1163,6 +1165,7 @@ void Graphics::textboxadjust() { if (!INBOUNDS(m, textbox)) { + puts("textboxadjust() out-of-bounds!"); return; } @@ -2804,6 +2807,7 @@ void Graphics::textboxcenter() { if (!INBOUNDS(m, textbox)) { + puts("textboxcenter() out-of-bounds!"); return; } @@ -2815,6 +2819,7 @@ void Graphics::textboxcenterx() { if (!INBOUNDS(m, textbox)) { + puts("textboxcenterx() out-of-bounds!"); return; } @@ -2825,6 +2830,7 @@ int Graphics::textboxwidth() { if (!INBOUNDS(m, textbox)) { + puts("textboxwidth() out-of-bounds!"); return 0; } @@ -2835,6 +2841,7 @@ void Graphics::textboxmove(int xo, int yo) { if (!INBOUNDS(m, textbox)) { + puts("textboxmove() out-of-bounds!"); return; } @@ -2846,6 +2853,7 @@ void Graphics::textboxmoveto(int xo) { if (!INBOUNDS(m, textbox)) { + puts("textboxmoveto() out-of-bounds!"); return; } @@ -2856,6 +2864,7 @@ void Graphics::textboxcentery() { if (!INBOUNDS(m, textbox)) { + puts("textboxcentery() out-of-bounds!"); return; } From 779083b417ffcb9f4ef53897782254c8e00ebf2a Mon Sep 17 00:00:00 2001 From: Misa Date: Thu, 25 Jun 2020 14:31:37 -0700 Subject: [PATCH 06/13] Add glitchrunner mode, in game options Glitchrunner mode is intended to re-enable glitches that existed in older versions of VVVVVV. These glitches were removed because they could legitimately affect a casual player's experience. Glitches like various R-pressing screwery, Space Station 1 skip, telejumping, Gravitron out-of-bounds, etc. will not be patched. --- desktop_version/src/Game.cpp | 13 ++++++++++++- desktop_version/src/Game.h | 1 + desktop_version/src/Input.cpp | 15 ++++++++++----- desktop_version/src/Render.cpp | 21 +++++++++++++++++---- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/desktop_version/src/Game.cpp b/desktop_version/src/Game.cpp index b8f22bf8..32bfeea3 100644 --- a/desktop_version/src/Game.cpp +++ b/desktop_version/src/Game.cpp @@ -387,6 +387,7 @@ void Game::init(void) #endif over30mode = false; + glitchrunnermode = false; ingame_titlemode = false; kludge_ingametemp = Menu::mainmenu; @@ -4756,6 +4757,11 @@ void Game::loadstats() over30mode = atoi(pText); } + if (pKey == "glitchrunnermode") + { + glitchrunnermode = atoi(pText); + } + if (pKey == "vsync") { graphics.vsync = atoi(pText); @@ -5010,6 +5016,10 @@ void Game::savestats() msg->LinkEndChild(doc.NewText(help.String((int) over30mode).c_str())); dataNode->LinkEndChild(msg); + msg = doc.NewElement("glitchrunnermode"); + msg->LinkEndChild(doc.NewText(help.String((int) glitchrunnermode).c_str())); + dataNode->LinkEndChild(msg); + msg = doc.NewElement("vsync"); msg->LinkEndChild(doc.NewText(help.String((int) graphics.vsync).c_str())); dataNode->LinkEndChild(msg); @@ -7129,6 +7139,7 @@ void Game::createmenu( enum Menu::MenuName t, bool samemenu/*= false*/ ) break; case Menu::options: option("accessibility options"); + option("glitchrunner mode"); #if !defined(MAKEANDPLAY) option("unlock play modes"); #endif @@ -7140,7 +7151,7 @@ void Game::createmenu( enum Menu::MenuName t, bool samemenu/*= false*/ ) } option("return"); - menuxoff = -40; + menuxoff = -50; menuyoff = 0; break; case Menu::accessibility: diff --git a/desktop_version/src/Game.h b/desktop_version/src/Game.h index 0b431c3c..4c15f33b 100644 --- a/desktop_version/src/Game.h +++ b/desktop_version/src/Game.h @@ -404,6 +404,7 @@ public: } bool over30mode; + bool glitchrunnermode; // Have fun speedrunners! <3 Misa bool ingame_titlemode; diff --git a/desktop_version/src/Input.cpp b/desktop_version/src/Input.cpp index 2cdb3845..1f631459 100644 --- a/desktop_version/src/Input.cpp +++ b/desktop_version/src/Input.cpp @@ -564,21 +564,26 @@ void menuactionpress() game.createmenu(Menu::accessibility); map.nexttowercolour(); break; -#if !defined(MAKEANDPLAY) case 1: + // Glitchrunner mode + music.playef(11); + game.glitchrunnermode = !game.glitchrunnermode; + break; +#if !defined(MAKEANDPLAY) + case 2: //unlock play options music.playef(11); game.createmenu(Menu::unlockmenu); map.nexttowercolour(); break; #endif - case OFFSET+2: + case OFFSET+3: //clear data menu music.playef(11); game.createmenu(Menu::controller); map.nexttowercolour(); break; - case OFFSET+3: + case OFFSET+4: //clear data menu music.playef(11); game.createmenu(Menu::cleardatamenu); @@ -587,7 +592,7 @@ void menuactionpress() } int mmmmmm_offset = music.mmmmmm ? 0 : -1; - if (game.currentmenuoption == OFFSET+4+mmmmmm_offset) + if (game.currentmenuoption == OFFSET+5+mmmmmm_offset) { //**** TOGGLE MMMMMM if(game.usingmmmmmm > 0){ @@ -600,7 +605,7 @@ void menuactionpress() music.play(music.currentsong); game.savestats(); } - else if (game.currentmenuoption == OFFSET+5+mmmmmm_offset) + else if (game.currentmenuoption == OFFSET+6+mmmmmm_offset) { //back music.playef(11); diff --git a/desktop_version/src/Render.cpp b/desktop_version/src/Render.cpp index f272f99f..09cfc15f 100644 --- a/desktop_version/src/Render.cpp +++ b/desktop_version/src/Render.cpp @@ -88,24 +88,37 @@ void menurender() graphics.Print( -1, 65, "Disable screen effects, enable", tr, tg, tb, true); graphics.Print( -1, 75, "slowdown modes or invincibility", tr, tg, tb, true); break; -#if !defined(MAKEANDPLAY) case 1: + graphics.bigprint( -1, 30, "Glitchrunner Mode", tr, tg, tb, true); + graphics.Print( -1, 65, "Re-enable glitches that existed", tr, tg, tb, true); + graphics.Print( -1, 75, "in previous versions of the game", tr, tg, tb, true); + if (game.glitchrunnermode) + { + graphics.Print( -1, 95, "Glitchrunner mode is ON", tr, tg, tb, true); + } + else + { + graphics.Print( -1, 95, "Glitchrunner mode is OFF", tr/2, tg/2, tb/2, true); + } + break; +#if !defined(MAKEANDPLAY) + case 2: graphics.bigprint( -1, 30, "Unlock Play Modes", tr, tg, tb, true); graphics.Print( -1, 65, "Unlock parts of the game normally", tr, tg, tb, true); graphics.Print( -1, 75, "unlocked as you progress", tr, tg, tb, true); break; #endif - case OFFSET+2: + case OFFSET+3: graphics.bigprint( -1, 30, "Game Pad Options", tr, tg, tb, true); graphics.Print( -1, 65, "Rebind your controller's buttons", tr, tg, tb, true); graphics.Print( -1, 75, "and adjust sensitivity", tr, tg, tb, true); break; - case OFFSET+3: + case OFFSET+4: graphics.bigprint( -1, 30, "Clear Data", tr, tg, tb, true); graphics.Print( -1, 65, "Delete your save data", tr, tg, tb, true); graphics.Print( -1, 75, "and unlocked play modes", tr, tg, tb, true); break; - case OFFSET+4: + case OFFSET+5: if(music.mmmmmm){ graphics.bigprint( -1, 30, "Soundtrack", tr, tg, tb, true); graphics.Print( -1, 65, "Toggle between MMMMMM and PPPPPP", tr, tg, tb, true); From 5848330e660af63d7e7f1f0c7758fba6a88386ab Mon Sep 17 00:00:00 2001 From: Misa Date: Thu, 25 Jun 2020 14:34:22 -0700 Subject: [PATCH 07/13] Re-enable unbounded gamestate increment in glitchrunner mode This is the first part of what is necessary for credits warp to work. If the "- Press ACTION to advance text -" prompt is up, and you manage to keep it up, then you can indefinitely increment the gamestate by pressing ACTION. This is first used in credits warp to teleport to the start of Space Station 2 (by utilizing the Eurogame expo script, triggered by a gamestate), and then again later by using a teleporter that has a high gamestate number to increment to the [C[C[C[C[Captain!] cutscene. --- desktop_version/src/Input.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/desktop_version/src/Input.cpp b/desktop_version/src/Input.cpp index 1f631459..58b9b534 100644 --- a/desktop_version/src/Input.cpp +++ b/desktop_version/src/Input.cpp @@ -1597,10 +1597,11 @@ void gameinput() } else { - if(!game.glitchrunkludge) game.state++; + if(game.glitchrunnermode || !game.glitchrunkludge) game.state++; game.jumpheld = true; game.glitchrunkludge=true; //Bug fix! You should only be able to do this ONCE. + //...Unless you're in glitchrunner mode } } } From 9bab6bd0cb81e0a05633f594e302e78f78aba81c Mon Sep 17 00:00:00 2001 From: Misa Date: Thu, 25 Jun 2020 14:39:51 -0700 Subject: [PATCH 08/13] Don't hardreset() game.advancetext in glitchrunner mode This is the second part of what is necessary for credits warp to work. The speedrunners call this "text storage". You need to get the advancetext prompt up without a text box in order to be able to increment the gamestate without bound. In 2.0, script.hardreset() reset the text boxes, but not the prompt. --- desktop_version/src/Script.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/desktop_version/src/Script.cpp b/desktop_version/src/Script.cpp index 32389eb6..0e9aad48 100644 --- a/desktop_version/src/Script.cpp +++ b/desktop_version/src/Script.cpp @@ -3564,7 +3564,12 @@ void scriptclass::hardreset() game.statedelay = 0; game.hascontrol = true; - game.advancetext = false; + if (!game.glitchrunnermode) + { + // Keep the "- Press ACTION to advance text -" prompt around, + // apparently the speedrunners call it the "text storage" glitch + game.advancetext = false; + } game.pausescript = false; From cb8540d7bd579e7b32e634d256510a5fa66f78d1 Mon Sep 17 00:00:00 2001 From: Misa Date: Thu, 25 Jun 2020 14:41:44 -0700 Subject: [PATCH 09/13] Restore janky gamestate-based quit-to-title system in glitchrunnermode This was fixed in 2.3 because one of the side effects of this janky system was being able to accidentally immediately quit to the title if the screen was black during a cutscene, which is something very likely to happen to casual players. Anyway, credits warp uses this gamestate-based system because it utilizes quitting to the title screen doing gamestate 80. From there, you increment the gamestate to gamestate 94 to use the Space Station 2 expo script. --- desktop_version/src/Input.cpp | 49 ++++++++++++++++++++++++++++++---- desktop_version/src/Render.cpp | 5 +++- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/desktop_version/src/Input.cpp b/desktop_version/src/Input.cpp index 58b9b534..925602d1 100644 --- a/desktop_version/src/Input.cpp +++ b/desktop_version/src/Input.cpp @@ -1916,7 +1916,41 @@ void mapinput() game.press_action = false; game.press_map = false; - if (game.fadetomenu) + if (game.glitchrunnermode && graphics.fademode == 1 && graphics.menuoffset == 0) + { + // Deliberate re-addition of the glitchy gamestate-based fadeout! + + // First of all, detecting a black screen means if the glitchy fadeout + // gets interrupted but you're still on a black screen, opening a menu + // immediately quits you to the title. This has the side effect that if + // you accidentally press Esc during a cutscene when it's black, you'll + // immediately be quit and lose all your progress, but that's fair in + // glitchrunner mode. + // Also have to check graphics.menuoffset so this doesn't run every frame + + // Have to close the menu in order to run gamestates. This adds + // about an extra half second of completely black screen. + graphics.resumegamemode = true; + + // Technically this was in <=2.2 as well + obj.removeallblocks(); + + if (game.menupage >= 20 && game.menupage <= 21) + { + game.state = 96; + game.statedelay = 0; + } + else + { + // Produces more glitchiness! Necessary for credits warp to work. + script.hardreset(); + + game.state = 80; + game.statedelay = 0; + } + } + + if (game.fadetomenu && !game.glitchrunnermode) { if (game.fadetomenudelay > 0) { @@ -1929,7 +1963,7 @@ void mapinput() } } - if (game.fadetolab) + if (game.fadetolab && !game.glitchrunnermode) { if (game.fadetolabdelay > 0) { @@ -1942,7 +1976,9 @@ void mapinput() } } - if(graphics.menuoffset==0 && game.fadetomenudelay <= 0 && game.fadetolabdelay <= 0) + if(graphics.menuoffset==0 + && (!game.glitchrunnermode || graphics.fademode == 0) + && game.fadetomenudelay <= 0 && game.fadetolabdelay <= 0) { if (graphics.flipmode) { @@ -2119,8 +2155,11 @@ void mapmenuactionpress() graphics.fademode = 2; music.fadeout(); map.nexttowercolour(); - game.fadetomenu = true; - game.fadetomenudelay = 16; + if (!game.glitchrunnermode) + { + game.fadetomenu = true; + game.fadetomenudelay = 16; + } break; case 20: diff --git a/desktop_version/src/Render.cpp b/desktop_version/src/Render.cpp index 09cfc15f..e8cd1f43 100644 --- a/desktop_version/src/Render.cpp +++ b/desktop_version/src/Render.cpp @@ -2356,7 +2356,10 @@ void maprender() - if (graphics.fademode == 3 || graphics.fademode == 5) + // We need to draw the black screen above the menu in order to disguise it + // being jankily brought down in glitchrunner mode when exiting to the title + // Otherwise, there's no reason to obscure the menu + if (game.glitchrunnermode || graphics.fademode == 3 || graphics.fademode == 5) { graphics.drawfade(); } From 387ee4dc795a5dca24974251f773ebfe5875a413 Mon Sep 17 00:00:00 2001 From: Misa Date: Thu, 25 Jun 2020 14:49:54 -0700 Subject: [PATCH 10/13] Un-hardreset certain variables for glitchrunner mode Ironically enough, resetting more variables in script.hardreset() makes the glitchy fadeout system even more glitchy. Resetting map.towermode, for example, makes it so that if you're in towers when you quit to the menu, script.hardreset() makes it so that the game thinks you're no longer inbounds (because it no longer thinks you're in a tower and thus considers coordinates in the space of 40x30 tiles to be inbounds instead of 40x700 or 40x100 tiles to be inbounds), calls map.gotoroom(), which resets the gamestate to 0. So if we're using the old system, it's better to reset only as much as needed. And furthermore, we shouldn't be relying on script.hardreset() to initialize variables for us. That should be done at the class constructor level. So I've gone ahead and initialized the variables in class constructors, too. --- desktop_version/src/Game.cpp | 7 +++++++ desktop_version/src/Map.cpp | 4 ++++ desktop_version/src/Script.cpp | 22 +++++++++++++++++----- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/desktop_version/src/Game.cpp b/desktop_version/src/Game.cpp index 32bfeea3..ea55b8a2 100644 --- a/desktop_version/src/Game.cpp +++ b/desktop_version/src/Game.cpp @@ -116,6 +116,13 @@ bool GetButtonFromString(const char *pText, SDL_GameControllerButton *button) void Game::init(void) { + roomx = 0; + roomy = 0; + prevroomx = 0; + prevroomy = 0; + saverx = 0; + savery = 0; + mutebutton = 0; muted = false; musicmuted = false; diff --git a/desktop_version/src/Map.cpp b/desktop_version/src/Map.cpp index 30a456a8..02f4ea43 100644 --- a/desktop_version/src/Map.cpp +++ b/desktop_version/src/Map.cpp @@ -31,6 +31,10 @@ mapclass::mapclass() cursorstate = 0; cursordelay = 0; + towermode = false; + cameraseekframe = 0; + resumedelay = 0; + final_colormode = false; final_colorframe = 0; final_colorframedelay = 0; diff --git a/desktop_version/src/Script.cpp b/desktop_version/src/Script.cpp index 0e9aad48..b93cb277 100644 --- a/desktop_version/src/Script.cpp +++ b/desktop_version/src/Script.cpp @@ -3482,8 +3482,12 @@ void scriptclass::hardreset() game.teleport = false; game.companion = 0; game.roomchange = false; - game.roomx = 0; - game.roomy = 0; + if (!game.glitchrunnermode) + { + // Ironically, resetting more variables makes the janky fadeout system in glitchrunnermode even more glitchy + game.roomx = 0; + game.roomy = 0; + } game.prevroomx = 0; game.prevroomy = 0; game.teleport_to_new_area = false; @@ -3521,8 +3525,12 @@ void scriptclass::hardreset() game.savetime = "00:00"; game.savearea = "nowhere"; game.savetrinkets = 0; - game.saverx = 0; - game.savery = 0; + if (!game.glitchrunnermode) + { + // Ironically, resetting more variables makes the janky fadeout system in glitchrunnermode even more glitchy + game.saverx = 0; + game.savery = 0; + } game.intimetrial = false; game.timetrialcountdown = 0; @@ -3606,7 +3614,11 @@ void scriptclass::hardreset() map.resetnames(); map.custommode=false; map.custommodeforreal=false; - map.towermode=false; + if (!game.glitchrunnermode) + { + // Ironically, resetting more variables makes the janky fadeout system even more glitchy + map.towermode=false; + } map.cameraseekframe = 0; map.resumedelay = 0; map.scrolldir = 0; From e1a114d1a5acaf1facc35d985b132a141725f8cb Mon Sep 17 00:00:00 2001 From: Misa Date: Thu, 25 Jun 2020 14:56:09 -0700 Subject: [PATCH 11/13] Un-fix hitbox persistence in map.resetplayer() It's annoying for casuals to have to close the game if they manage to get themselves to turn into VVVVVV-Man, but it's amusing enough to glitchrunners that they mess about with VVVVVV-Man in the main game, clipping through walls everywhere (well, they call it Big Viridian, but still). --- desktop_version/src/Map.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/desktop_version/src/Map.cpp b/desktop_version/src/Map.cpp index 02f4ea43..690835ab 100644 --- a/desktop_version/src/Map.cpp +++ b/desktop_version/src/Map.cpp @@ -792,10 +792,13 @@ void mapclass::resetplayer() obj.entities[i].colour = 0; game.lifeseq = 10; obj.entities[i].invis = true; - obj.entities[i].size = 0; - obj.entities[i].cx = 6; - obj.entities[i].cy = 2; - obj.entities[i].h = 21; + if (!game.glitchrunnermode) + { + obj.entities[i].size = 0; + obj.entities[i].cx = 6; + obj.entities[i].cy = 2; + obj.entities[i].h = 21; + } // If we entered a tower as part of respawn, reposition camera if (!was_in_tower && towermode) From baf879d9fd2b6fffecdfc72c8bd637f1611c0ce2 Mon Sep 17 00:00:00 2001 From: Misa Date: Sat, 27 Jun 2020 15:56:18 -0700 Subject: [PATCH 12/13] Remove unused tmap vars from mapclass These used to be relevant when the main game tilemaps were stored in strings, but now they no longer are. --- desktop_version/src/Map.cpp | 2 -- desktop_version/src/Map.h | 1 - 2 files changed, 3 deletions(-) diff --git a/desktop_version/src/Map.cpp b/desktop_version/src/Map.cpp index 690835ab..c915a9f6 100644 --- a/desktop_version/src/Map.cpp +++ b/desktop_version/src/Map.cpp @@ -1170,8 +1170,6 @@ void mapclass::loadlevel(int rx, int ry) obj.customwarpmodevon=false; obj.customwarpmodehon=false; - std::vector tmap; - if (finalmode) { t = 6; diff --git a/desktop_version/src/Map.h b/desktop_version/src/Map.h index 79536310..870cd71b 100644 --- a/desktop_version/src/Map.h +++ b/desktop_version/src/Map.h @@ -82,7 +82,6 @@ public: std::vector contents; std::vector explored; std::vector vmult; - std::vector tmap; int temp; int temp2; From ebd381c228d0cb1a4e3723c36b4e8224edb2bdb5 Mon Sep 17 00:00:00 2001 From: Misa Date: Sat, 27 Jun 2020 17:08:57 -0700 Subject: [PATCH 13/13] Fix the two-frame-delay when entering a room with an "init" script This patch is very kludge-y, but at least it fixes a semi-noticeable visual issue in custom levels that use internal scripts to spawn entities when loading a room. Basically, the problem here is that when the game checks for script boxes and sets newscript, newscript has already been processed for that frame, and when the game does load a script, script.run() has already been processed for that frame. That issue can be fixed, but it turns out that due to my over-30-FPS game loop changes, there's now ANOTHER visible frame of delay between room load and entity creation, because the render function gets called in between the script being loaded at the end of gamelogic() and the script actually getting run. So... I have to temporary move script.run() to the end of gamelogic() (in map.twoframedelayfix()), and make sure it doesn't get run next frame, because double-evaluations are bad. To do that, I have to introduce the kludge variable script.dontrunnextframe, which does exactly as it says. And with all that work, the two-frame (now three-frame) delay is fixed. --- desktop_version/src/Entity.cpp | 1 + desktop_version/src/Game.cpp | 1 + desktop_version/src/Logic.cpp | 11 +++++++++++ desktop_version/src/Map.cpp | 27 +++++++++++++++++++++++++++ desktop_version/src/Map.h | 2 ++ desktop_version/src/Script.cpp | 1 + desktop_version/src/Script.h | 2 +- desktop_version/src/main.cpp | 9 ++++++++- 8 files changed, 52 insertions(+), 2 deletions(-) diff --git a/desktop_version/src/Entity.cpp b/desktop_version/src/Entity.cpp index 9c47e408..b426d39e 100644 --- a/desktop_version/src/Entity.cpp +++ b/desktop_version/src/Entity.cpp @@ -4780,6 +4780,7 @@ void entityclass::entitycollisioncheck() } } + // WARNING: If updating this code, don't forget to update Map.cpp mapclass::twoframedelayfix() activetrigger = -1; if (checktrigger() > -1) { diff --git a/desktop_version/src/Game.cpp b/desktop_version/src/Game.cpp index ea55b8a2..af0832e2 100644 --- a/desktop_version/src/Game.cpp +++ b/desktop_version/src/Game.cpp @@ -1753,6 +1753,7 @@ void Game::updatestate() break; + // WARNING: If updating this code, make sure to update Map.cpp mapclass::twoframedelayfix() case 300: startscript = true; newscript="custom_"+customscript[0]; diff --git a/desktop_version/src/Logic.cpp b/desktop_version/src/Logic.cpp index 11c4fb6a..1d5bc411 100644 --- a/desktop_version/src/Logic.cpp +++ b/desktop_version/src/Logic.cpp @@ -1117,6 +1117,8 @@ void gamelogic() } } + bool screen_transition = false; + if (!map.warpy && !map.towermode) { //Normal! Just change room @@ -1125,11 +1127,13 @@ void gamelogic() { obj.entities[player].yp -= 240; map.gotoroom(game.roomx, game.roomy + 1); + screen_transition = true; } if (player > -1 && game.door_up > -2 && obj.entities[player].yp < -2) { obj.entities[player].yp += 240; map.gotoroom(game.roomx, game.roomy - 1); + screen_transition = true; } } @@ -1141,11 +1145,13 @@ void gamelogic() { obj.entities[player].xp += 320; map.gotoroom(game.roomx - 1, game.roomy); + screen_transition = true; } if (player > -1 && game.door_right > -2 && obj.entities[player].xp >= 308) { obj.entities[player].xp -= 320; map.gotoroom(game.roomx + 1, game.roomy); + screen_transition = true; } } @@ -1364,6 +1370,11 @@ void gamelogic() } } } + + if (screen_transition) + { + map.twoframedelayfix(); + } } //Update colour cycling for final level diff --git a/desktop_version/src/Map.cpp b/desktop_version/src/Map.cpp index c915a9f6..2d6e0289 100644 --- a/desktop_version/src/Map.cpp +++ b/desktop_version/src/Map.cpp @@ -1980,3 +1980,30 @@ void mapclass::loadlevel(int rx, int ry) } } } + +void mapclass::twoframedelayfix() +{ + // Fixes the two-frame delay in custom levels that use scripts to spawn an entity upon room load. + // Because when the room loads and newscript is set to run, newscript has already ran for that frame, + // and when the script gets loaded script.run() has already ran for that frame, too. + // A bit kludge-y, but it's the least we can do without changing the frame ordering. + + if (game.deathseq != -1 + // obj.checktrigger() sets obj.activetrigger + || obj.checktrigger() <= -1 + || obj.activetrigger < 300) + { + return; + } + + game.newscript = "custom_" + game.customscript[obj.activetrigger - 300]; + obj.removetrigger(obj.activetrigger); + game.state = 0; + game.statedelay = 0; + script.load(game.newscript); + if (script.running) + { + script.run(); + script.dontrunnextframe = true; + } +} diff --git a/desktop_version/src/Map.h b/desktop_version/src/Map.h index 870cd71b..2a46bd32 100644 --- a/desktop_version/src/Map.h +++ b/desktop_version/src/Map.h @@ -75,6 +75,8 @@ public: void loadlevel(int rx, int ry); + void twoframedelayfix(); + std::vector roomdeaths; std::vector roomdeathsfinal; diff --git a/desktop_version/src/Script.cpp b/desktop_version/src/Script.cpp index b93cb277..e8800e5a 100644 --- a/desktop_version/src/Script.cpp +++ b/desktop_version/src/Script.cpp @@ -16,6 +16,7 @@ scriptclass::scriptclass() position = 0; scriptdelay = 0; running = false; + dontrunnextframe = false; b = 0; g = 0; diff --git a/desktop_version/src/Script.h b/desktop_version/src/Script.h index c1aa6bb2..1cdbd066 100644 --- a/desktop_version/src/Script.h +++ b/desktop_version/src/Script.h @@ -56,7 +56,7 @@ public: int looppoint, loopcount; int scriptdelay; - bool running; + bool running, dontrunnextframe; std::string tempword; std::string currentletter; diff --git a/desktop_version/src/main.cpp b/desktop_version/src/main.cpp index ed9ec51a..ca923e49 100644 --- a/desktop_version/src/main.cpp +++ b/desktop_version/src/main.cpp @@ -542,7 +542,14 @@ void inline fixedloop() titlelogic(); break; case GAMEMODE: - if (script.running) + // WARNING: If updating this code, don't forget to update Map.cpp mapclass::twoframedelayfix() + + // Ugh, I hate this kludge variable but it's the only way to do it + if (script.dontrunnextframe) + { + script.dontrunnextframe = false; + } + else if (script.running) { script.run(); }