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 62598925..c4e440d7 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; @@ -387,6 +394,7 @@ void Game::init(void) #endif over30mode = false; + glitchrunnermode = false; ingame_titlemode = false; kludge_ingametemp = Menu::mainmenu; @@ -1745,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]; @@ -4756,6 +4765,11 @@ void Game::loadstats() over30mode = atoi(pText); } + if (pKey == "glitchrunnermode") + { + glitchrunnermode = atoi(pText); + } + if (pKey == "vsync") { graphics.vsync = atoi(pText); @@ -5010,6 +5024,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); @@ -7126,6 +7144,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 @@ -7630,7 +7649,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 diff --git a/desktop_version/src/Game.h b/desktop_version/src/Game.h index 06f3b813..07b4b1ec 100644 --- a/desktop_version/src/Game.h +++ b/desktop_version/src/Game.h @@ -405,6 +405,7 @@ public: } bool over30mode; + bool glitchrunnermode; // Have fun speedrunners! <3 Misa bool ingame_titlemode; diff --git a/desktop_version/src/Graphics.cpp b/desktop_version/src/Graphics.cpp index 140b6763..91ee4b2e 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*/) { @@ -1141,16 +1141,34 @@ void Graphics::textboxremove() void Graphics::textboxtimer( int t ) { + if (!INBOUNDS(m, textbox)) + { + puts("textboxtimer() out-of-bounds!"); + return; + } + textbox[m].timer=t; } void Graphics::addline( std::string t ) { + if (!INBOUNDS(m, textbox)) + { + puts("addline() out-of-bounds!"); + return; + } + textbox[m].addline(t); } void Graphics::textboxadjust() { + if (!INBOUNDS(m, textbox)) + { + puts("textboxadjust() out-of-bounds!"); + return; + } + textbox[m].adjust(); } @@ -2787,33 +2805,69 @@ void Graphics::setwarprect( int a, int b, int c, int d ) void Graphics::textboxcenter() { + if (!INBOUNDS(m, textbox)) + { + puts("textboxcenter() out-of-bounds!"); + return; + } + textbox[m].centerx(); textbox[m].centery(); } void Graphics::textboxcenterx() { + if (!INBOUNDS(m, textbox)) + { + puts("textboxcenterx() out-of-bounds!"); + return; + } + textbox[m].centerx(); } int Graphics::textboxwidth() { + if (!INBOUNDS(m, textbox)) + { + puts("textboxwidth() out-of-bounds!"); + return 0; + } + return textbox[m].w; } void Graphics::textboxmove(int xo, int yo) { + if (!INBOUNDS(m, textbox)) + { + puts("textboxmove() out-of-bounds!"); + return; + } + textbox[m].xp += xo; textbox[m].yp += yo; } void Graphics::textboxmoveto(int xo) { + if (!INBOUNDS(m, textbox)) + { + puts("textboxmoveto() out-of-bounds!"); + return; + } + textbox[m].xp = xo; } void Graphics::textboxcentery() { + if (!INBOUNDS(m, textbox)) + { + puts("textboxcentery() out-of-bounds!"); + return; + } + textbox[m].centery(); } diff --git a/desktop_version/src/Input.cpp b/desktop_version/src/Input.cpp index 2cdb3845..925602d1 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); @@ -1592,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 } } } @@ -1910,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) { @@ -1923,7 +1963,7 @@ void mapinput() } } - if (game.fadetolab) + if (game.fadetolab && !game.glitchrunnermode) { if (game.fadetolabdelay > 0) { @@ -1936,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) { @@ -2113,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/Logic.cpp b/desktop_version/src/Logic.cpp index 048067a5..1d5bc411 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); } @@ -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 30a456a8..2d6e0289 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; @@ -788,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) @@ -1163,8 +1170,6 @@ void mapclass::loadlevel(int rx, int ry) obj.customwarpmodevon=false; obj.customwarpmodehon=false; - std::vector tmap; - if (finalmode) { t = 6; @@ -1975,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 79536310..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; @@ -82,7 +84,6 @@ public: std::vector contents; std::vector explored; std::vector vmult; - std::vector tmap; int temp; int temp2; diff --git a/desktop_version/src/Render.cpp b/desktop_version/src/Render.cpp index ae935c69..b46f5e60 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); @@ -2323,7 +2336,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(); } diff --git a/desktop_version/src/Script.cpp b/desktop_version/src/Script.cpp index 32389eb6..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; @@ -3482,8 +3483,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 +3526,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; @@ -3564,7 +3573,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; @@ -3601,7 +3615,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; 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(); }