VVVVVV/desktop_version/src/Input.cpp

3558 lines
113 KiB
C++
Raw Normal View History

#include <tinyxml2.h>
Evaluate flipping eligibility per-entity 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.
2023-03-19 04:24:08 +01:00
#include <vector>
2020-01-01 21:29:24 +01:00
#include "ButtonGlyphs.h"
#include "Credits.h"
#include "CustomLevels.h"
#include "Editor.h"
#include "Entity.h"
#include "Enums.h"
#include "FileSystemUtils.h"
#include "Game.h"
#include "GlitchrunnerMode.h"
#include "Graphics.h"
#include "GraphicsUtil.h"
#include "KeyPoll.h"
#include "LevelDebugger.h"
#include "Localization.h"
#include "LocalizationMaint.h"
#include "LocalizationStorage.h"
#include "MakeAndPlay.h"
#include "Map.h"
#include "Music.h"
#include "RoomnameTranslator.h"
#include "Screen.h"
#include "Script.h"
#include "UtilityClass.h"
#include "Vlogging.h"
static void updatebuttonmappings(int bind)
2020-01-01 21:29:24 +01:00
{
for (
SDL_GameControllerButton i = SDL_CONTROLLER_BUTTON_A;
i < SDL_CONTROLLER_BUTTON_DPAD_UP;
i = (SDL_GameControllerButton) (i + 1)
) {
if (key.isDown(i))
{
bool dupe = false;
switch (bind)
{
case 1:
{
size_t j;
for (j = 0; j < game.controllerButton_flip.size(); j += 1)
{
if (i == game.controllerButton_flip[j])
{
dupe = true;
}
}
if (!dupe)
{
game.controllerButton_flip.push_back(i);
music.playef(Sound_VIRIDIAN);
}
for (j = 0; j < game.controllerButton_map.size(); j += 1)
{
if (i == game.controllerButton_map[j])
{
game.controllerButton_map.erase(game.controllerButton_map.begin() + j);
}
}
for (j = 0; j < game.controllerButton_esc.size(); j += 1)
{
if (i == game.controllerButton_esc[j])
{
game.controllerButton_esc.erase(game.controllerButton_esc.begin() + j);
}
}
for (j = 0; j < game.controllerButton_restart.size(); j += 1)
{
if (i == game.controllerButton_restart[j])
{
game.controllerButton_restart.erase(game.controllerButton_restart.begin() + j);
}
}
for (j = 0; j < game.controllerButton_interact.size(); j += 1)
{
if (i == game.controllerButton_interact[j])
{
game.controllerButton_interact.erase(game.controllerButton_interact.begin() + j);
}
}
break;
}
case 2:
{
size_t j;
for (j = 0; j < game.controllerButton_map.size(); j += 1)
{
if (i == game.controllerButton_map[j])
{
dupe = true;
}
}
if (!dupe)
{
game.controllerButton_map.push_back(i);
music.playef(Sound_VIRIDIAN);
}
for (j = 0; j < game.controllerButton_flip.size(); j += 1)
{
if (i == game.controllerButton_flip[j])
{
game.controllerButton_flip.erase(game.controllerButton_flip.begin() + j);
}
}
for (j = 0; j < game.controllerButton_esc.size(); j += 1)
{
if (i == game.controllerButton_esc[j])
{
game.controllerButton_esc.erase(game.controllerButton_esc.begin() + j);
}
}
for (j = 0; j < game.controllerButton_restart.size(); j += 1)
{
if (i == game.controllerButton_restart[j])
{
game.controllerButton_restart.erase(game.controllerButton_restart.begin() + j);
}
}
for (j = 0; j < game.controllerButton_interact.size(); j += 1)
{
if (i == game.controllerButton_interact[j])
{
game.controllerButton_interact.erase(game.controllerButton_interact.begin() + j);
}
}
break;
}
case 3:
{
size_t j;
for (j = 0; j < game.controllerButton_esc.size(); j += 1)
{
if (i == game.controllerButton_esc[j])
{
dupe = true;
}
}
if (!dupe)
{
game.controllerButton_esc.push_back(i);
music.playef(Sound_VIRIDIAN);
}
for (j = 0; j < game.controllerButton_flip.size(); j += 1)
{
if (i == game.controllerButton_flip[j])
{
game.controllerButton_flip.erase(game.controllerButton_flip.begin() + j);
}
}
for (j = 0; j < game.controllerButton_map.size(); j += 1)
{
if (i == game.controllerButton_map[j])
{
game.controllerButton_map.erase(game.controllerButton_map.begin() + j);
}
}
for (j = 0; j < game.controllerButton_restart.size(); j += 1)
{
if (i == game.controllerButton_restart[j])
{
game.controllerButton_restart.erase(game.controllerButton_restart.begin() + j);
}
}
for (j = 0; j < game.controllerButton_interact.size(); j += 1)
{
if (i == game.controllerButton_interact[j])
{
game.controllerButton_interact.erase(game.controllerButton_interact.begin() + j);
}
}
break;
}
case 4:
{
size_t j;
for (j = 0; j < game.controllerButton_restart.size(); j += 1)
{
if (i == game.controllerButton_restart[j])
{
dupe = true;
}
}
if (!dupe)
{
game.controllerButton_restart.push_back(i);
music.playef(Sound_VIRIDIAN);
}
for (j = 0; j < game.controllerButton_flip.size(); j += 1)
{
if (i == game.controllerButton_flip[j])
{
game.controllerButton_flip.erase(game.controllerButton_flip.begin() + j);
}
}
for (j = 0; j < game.controllerButton_map.size(); j += 1)
{
if (i == game.controllerButton_map[j])
{
game.controllerButton_map.erase(game.controllerButton_map.begin() + j);
}
}
for (j = 0; j < game.controllerButton_esc.size(); j += 1)
{
if (i == game.controllerButton_esc[j])
{
game.controllerButton_esc.erase(game.controllerButton_esc.begin() + j);
}
}
for (j = 0; j < game.controllerButton_interact.size(); j += 1)
{
if (i == game.controllerButton_interact[j])
{
game.controllerButton_interact.erase(game.controllerButton_interact.begin() + j);
}
}
break;
}
case 5:
{
size_t j;
for (j = 0; j < game.controllerButton_interact.size(); j += 1)
{
if (i == game.controllerButton_interact[j])
{
dupe = true;
}
}
if (!dupe)
{
game.controllerButton_interact.push_back(i);
music.playef(Sound_VIRIDIAN);
}
for (j = 0; j < game.controllerButton_flip.size(); j += 1)
{
if (i == game.controllerButton_flip[j])
{
game.controllerButton_flip.erase(game.controllerButton_flip.begin() + j);
}
}
for (j = 0; j < game.controllerButton_map.size(); j += 1)
{
if (i == game.controllerButton_map[j])
{
game.controllerButton_map.erase(game.controllerButton_map.begin() + j);
}
}
for (j = 0; j < game.controllerButton_esc.size(); j += 1)
{
if (i == game.controllerButton_esc[j])
{
game.controllerButton_esc.erase(game.controllerButton_esc.begin() + j);
}
}
for (j = 0; j < game.controllerButton_restart.size(); j += 1)
{
if (i == game.controllerButton_restart[j])
{
game.controllerButton_restart.erase(game.controllerButton_restart.begin() + j);
}
}
break;
}
}
}
}
2020-01-01 21:29:24 +01:00
}
static void recomputetextboxes(void)
{
/* Retranslate and reposition all text boxes.
* WARNING: Needs to update in linear order, starting from 0. */
for (size_t i = 0; i < graphics.textboxes.size(); i++)
{
graphics.textboxes[i].updatetext();
graphics.textboxes[i].applyposition();
}
}
static void toggleflipmode(void)
{
graphics.setflipmode = !graphics.setflipmode;
game.savestatsandsettings_menu();
if (graphics.setflipmode)
{
music.playef(Sound_GAMESAVED);
game.screenshake = 10;
game.flashlight = 5;
}
else
{
music.playef(Sound_VIRIDIAN);
}
/* Some text boxes change depending on Flip Mode, so update text boxes. */
const bool temp = graphics.flipmode;
graphics.flipmode = graphics.setflipmode;
recomputetextboxes();
graphics.flipmode = temp;
}
static bool fadetomode = false;
static int fadetomodedelay = 0;
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
static enum StartMode gotomode = Start_MAINGAME;
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
static void startmode(const enum StartMode mode)
{
gotomode = mode;
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
graphics.fademode = FADE_START_FADEOUT;
fadetomode = true;
fadetomodedelay = 19;
}
static void handlefadetomode(void)
{
if (game.ingame_titlemode)
{
/* We shouldn't be here! */
SDL_assert(0 && "Loading a mode from in-game options!");
return;
}
if (fadetomodedelay > 0)
{
--fadetomodedelay;
}
else
{
fadetomode = false;
script.startgamemode(gotomode);
}
}
static int* user_changing_volume = NULL;
static int previous_volume = 0;
static void initvolumeslider(const int menuoption)
{
switch (menuoption)
{
case 0:
game.slidermode = SLIDER_MUSICVOLUME;
user_changing_volume = &music.user_music_volume;
break;
case 1:
game.slidermode = SLIDER_SOUNDVOLUME;
user_changing_volume = &music.user_sound_volume;
break;
default:
SDL_assert(0 && "Unhandled volume slider option!");
game.slidermode = SLIDER_NONE;
user_changing_volume = NULL;
return;
}
previous_volume = *user_changing_volume;
}
static void deinitvolumeslider(void)
{
user_changing_volume = NULL;
game.savestatsandsettings_menu();
game.slidermode = SLIDER_NONE;
}
static void slidermodeinput(void)
{
if (user_changing_volume == NULL)
{
SDL_assert(0 && "user_changing_volume is NULL!");
return;
}
if (game.press_left)
{
*user_changing_volume -= USER_VOLUME_STEP;
}
else if (game.press_right)
{
*user_changing_volume += USER_VOLUME_STEP;
}
*user_changing_volume = SDL_clamp(*user_changing_volume, 0, USER_VOLUME_MAX);
}
static void menuactionpress(void)
2020-01-01 21:29:24 +01:00
{
if (game.menutestmode)
{
music.playef(Sound_CRUMBLE);
Menu::MenuName nextmenu = (Menu::MenuName) (game.currentmenuname + 1);
game.returnmenu();
game.createmenu(nextmenu);
return;
}
switch (game.currentmenuname)
2020-01-01 21:29:24 +01:00
{
case Menu::mainmenu:
{
int option_id = -1;
int option_seq = 0; /* option number in YOUR configuration */
#define OPTION_ID(id) \
if (option_seq == game.currentmenuoption) \
{ \
option_id = id; \
} \
option_seq++;
#if !defined(MAKEANDPLAY)
OPTION_ID(0) /* play */
#endif
OPTION_ID(1) /* levels */
OPTION_ID(2) /* options */
if (loc::show_translator_menu)
{
OPTION_ID(3) /* translator */
}
OPTION_ID(4) /* credits */
OPTION_ID(5) /* quit */
#undef OPTION_ID
switch (option_id)
{
#if !defined(MAKEANDPLAY)
case 0:
//Play
if (!game.save_exists() && !game.anything_unlocked())
2020-01-01 21:29:24 +01:00
{
//No saves exist, just start a new game
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_MAINGAME);
2020-01-01 21:29:24 +01:00
}
else
{
//Bring you to the normal playmenu
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::play);
map.nexttowercolour();
}
break;
#endif
case 1:
//Bring you to the normal playmenu
music.playef(Sound_VIRIDIAN);
game.editor_disabled = !BUTTONGLYPHS_keyboard_is_available();
game.createmenu(Menu::playerworlds);
map.nexttowercolour();
break;
case 2:
//Options
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::options);
map.nexttowercolour();
break;
case 3:
//Translator
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::translator_main);
map.nexttowercolour();
break;
case 4:
//Credits
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::credits);
map.nexttowercolour();
break;
case 5:
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::youwannaquit);
map.nexttowercolour();
break;
}
break;
}
case Menu::levellist:
{
const bool nextlastoptions = cl.ListOfMetaData.size() > 8;
if(game.currentmenuoption==(int)game.menuoptions.size()-1){
//go back to menu
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
}else if(nextlastoptions && game.currentmenuoption==(int)game.menuoptions.size()-2){
//previous page
music.playef(Sound_VIRIDIAN);
if(game.levelpage==0){
game.levelpage=(cl.ListOfMetaData.size()-1)/8;
}else{
game.levelpage--;
}
game.createmenu(Menu::levellist, true);
game.currentmenuoption=game.menuoptions.size()-2;
map.nexttowercolour();
}else if(nextlastoptions && game.currentmenuoption==(int)game.menuoptions.size()-3){
//next page
music.playef(Sound_VIRIDIAN);
if((size_t) ((game.levelpage*8)+8) >= cl.ListOfMetaData.size()){
game.levelpage=0;
}else{
game.levelpage++;
}
game.createmenu(Menu::levellist, true);
game.currentmenuoption=game.menuoptions.size()-3;
map.nexttowercolour();
}else{
//Ok, launch the level!
//PLAY CUSTOM LEVEL HOOK
music.playef(Sound_VIRIDIAN);
game.playcustomlevel=(game.levelpage*8)+game.currentmenuoption;
game.customleveltitle=cl.ListOfMetaData[game.playcustomlevel].title;
game.customlevelfilename=cl.ListOfMetaData[game.playcustomlevel].filename;
std::string name = "saves/" + cl.ListOfMetaData[game.playcustomlevel].filename.substr(7) + ".vvv";
tinyxml2::XMLDocument doc;
if (!FILESYSTEM_loadTiXml2Document(name.c_str(), doc)){
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_CUSTOM);
}else{
game.createmenu(Menu::quickloadlevel);
map.nexttowercolour();
}
}
break;
}
case Menu::quickloadlevel:
switch (game.currentmenuoption)
{
case 0: //continue save
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_CUSTOM_QUICKSAVE);
break;
case 1:
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_CUSTOM);
break;
case 2:
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::deletequicklevel);
map.nexttowercolour();
break;
default:
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::deletequicklevel:
switch (game.currentmenuoption)
{
default:
music.playef(Sound_VIRIDIAN);
game.returnmenu();
break;
case 1:
game.customdeletequick(cl.ListOfMetaData[game.playcustomlevel].filename);
game.returntomenu(Menu::levellist);
game.flashlight = 5;
game.screenshake = 15;
music.playef(Sound_DESTROY);
break;
}
map.nexttowercolour();
break;
case Menu::playerworlds:
if (game.currentmenuoption == 0)
{
music.playef(Sound_VIRIDIAN);
game.levelpage = 0;
cl.getDirectoryData();
game.loadcustomlevelstats(); //Should only load a file if it's needed
game.createmenu(Menu::levellist);
if (FILESYSTEM_levelDirHasError())
{
game.createmenu(Menu::warninglevellist);
}
map.nexttowercolour();
}
else if (game.currentmenuoption == 1)
{
// LEVEL EDITOR HOOK
if (game.editor_disabled)
{
music.playef(Sound_CRY);
}
else
{
music.playef(Sound_VIRIDIAN);
startmode(Start_EDITOR);
ed.filename = "";
}
}
else if (!game.editor_disabled && game.currentmenuoption == 2)
{
//"OPENFOLDERHOOK"
if (FILESYSTEM_openDirectoryEnabled()
&& FILESYSTEM_openDirectory(FILESYSTEM_getUserLevelDirectory()))
{
music.playef(Sound_VIRIDIAN);
SDL_MinimizeWindow(gameScreen.m_window);
}
else
{
music.playef(Sound_CRY);
}
}
else if (!game.editor_disabled && game.currentmenuoption == 3)
{
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::confirmshowlevelspath);
map.nexttowercolour();
}
else if (game.currentmenuoption == 4 || (game.editor_disabled && game.currentmenuoption == 2))
{
// back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
}
break;
case Menu::confirmshowlevelspath:
{
int prevmenuoption = game.currentmenuoption; /* returnmenu destroys this */
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
if (prevmenuoption == 1)
{
game.createmenu(Menu::showlevelspath);
}
break;
}
case Menu::showlevelspath:
music.playef(Sound_VIRIDIAN);
game.editor_disabled = !BUTTONGLYPHS_keyboard_is_available();
game.returntomenu(Menu::playerworlds);
map.nexttowercolour();
break;
case Menu::errornostart:
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::mainmenu);
map.nexttowercolour();
break;
case Menu::graphicoptions:
{
int offset = 0;
bool processed = false;
if (game.currentmenuoption == offset + 0 && !gameScreen.isForcedFullscreen())
{
processed = true;
music.playef(Sound_VIRIDIAN);
gameScreen.toggleFullScreen();
}
if (gameScreen.isForcedFullscreen())
{
--offset;
}
if (game.currentmenuoption == offset + 1)
{
processed = true;
music.playef(Sound_VIRIDIAN);
gameScreen.toggleScalingMode();
game.savestatsandsettings_menu();
}
if (game.currentmenuoption == offset + 2 && !gameScreen.isForcedFullscreen())
{
processed = true;
// resize to nearest multiple
if (gameScreen.isWindowed)
{
music.playef(Sound_VIRIDIAN);
gameScreen.ResizeToNearestMultiple();
game.savestatsandsettings_menu();
}
else
{
music.playef(Sound_CRY);
}
}
if (gameScreen.isForcedFullscreen())
{
--offset;
}
if (game.currentmenuoption == offset + 3)
{
processed = true;
music.playef(Sound_VIRIDIAN);
gameScreen.toggleLinearFilter();
game.savestatsandsettings_menu();
}
if (game.currentmenuoption == offset + 4)
{
processed = true;
//change smoothing
music.playef(Sound_VIRIDIAN);
gameScreen.badSignalEffect= !gameScreen.badSignalEffect;
game.savestatsandsettings_menu();
}
if (game.currentmenuoption == offset + 5)
{
processed = true;
//toggle vsync
music.playef(Sound_VIRIDIAN);
gameScreen.toggleVSync();
game.savestatsandsettings_menu();
}
if (!processed)
{
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
}
break;
}
case Menu::youwannaquit:
switch (game.currentmenuoption)
{
case 0:
//bye!
music.playef(Sound_CRY);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_QUIT);
break;
default:
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
}
break;
case Menu::setinvincibility:
switch (game.currentmenuoption)
{
case 0:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
default:
map.invincibility = !map.invincibility;
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
game.savestatsandsettings_menu();
break;
}
break;
case Menu::setslowdown:
switch (game.currentmenuoption)
{
case 0:
//back
game.slowdown = 30;
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
game.savestatsandsettings_menu();
break;
case 1:
game.slowdown = 24;
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
game.savestatsandsettings_menu();
break;
case 2:
game.slowdown = 18;
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
game.savestatsandsettings_menu();
break;
case 3:
game.slowdown = 12;
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
game.savestatsandsettings_menu();
break;
}
break;
case Menu::speedrunneroptions:
switch (game.currentmenuoption)
{
case 0:
// Glitchrunner mode
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::setglitchrunner);
game.currentmenuoption = GlitchrunnerMode_get();
map.nexttowercolour();
break;
case 1:
/* Input delay */
music.playef(Sound_VIRIDIAN);
game.inputdelay = !game.inputdelay;
game.savestatsandsettings_menu();
break;
case 2:
/* Interact button toggle */
music.playef(Sound_VIRIDIAN);
game.separate_interact = !game.separate_interact;
game.savestatsandsettings_menu();
break;
case 3:
// toggle fake load screen
game.skipfakeload = !game.skipfakeload;
game.savestatsandsettings_menu();
music.playef(Sound_VIRIDIAN);
break;
2021-08-05 23:31:20 +02:00
case 4:
// toggle in game timer
game.showingametimer = !game.showingametimer;
game.savestatsandsettings_menu();
music.playef(Sound_VIRIDIAN);
break;
case 5:
// english sprites
loc::english_sprites = !loc::english_sprites;
if (!loc::english_sprites)
{
graphics.grphx.init_translations();
}
game.savestatsandsettings_menu();
music.playef(Sound_VIRIDIAN);
2021-08-05 23:31:20 +02:00
break;
default:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::setglitchrunner:
GlitchrunnerMode_set((enum GlitchrunnerMode) game.currentmenuoption);
music.playef(Sound_VIRIDIAN);
game.returnmenu();
game.savestatsandsettings_menu();
map.nexttowercolour();
break;
case Menu::advancedoptions:
switch (game.currentmenuoption)
{
case 0:
// toggle unfocus pause
game.disablepause = !game.disablepause;
game.savestatsandsettings_menu();
music.playef(Sound_VIRIDIAN);
break;
case 1:
/* toggle unfocus music pause */
game.disableaudiopause = !game.disableaudiopause;
game.savestatsandsettings_menu();
music.playef(Sound_VIRIDIAN);
break;
case 2:
// toggle translucent roomname BG
graphics.translucentroomname = !graphics.translucentroomname;
game.savestatsandsettings_menu();
music.playef(Sound_VIRIDIAN);
break;
default:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::accessibility:
{
int accessibilityoffset = 0;
#if !defined(MAKEANDPLAY)
accessibilityoffset = 1;
if (game.currentmenuoption == 0) {
//unlock play options
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::unlockmenu);
map.nexttowercolour();
}
#endif
if (game.currentmenuoption == accessibilityoffset + 0) {
//invincibility
if (!game.ingame_titlemode || !game.incompetitive())
{
if (!map.invincibility)
{
game.createmenu(Menu::setinvincibility);
map.nexttowercolour();
}
else
{
map.invincibility = !map.invincibility;
game.savestatsandsettings_menu();
}
music.playef(Sound_VIRIDIAN);
}
else
{
music.playef(Sound_CRY);
map.invincibility = false;
}
}
else if (game.currentmenuoption == accessibilityoffset + 1) {
//change game speed
if (!game.ingame_titlemode || !game.incompetitive())
{
game.createmenu(Menu::setslowdown);
map.nexttowercolour();
music.playef(Sound_VIRIDIAN);
}
else
{
music.playef(Sound_CRY);
game.slowdown = 30;
}
}
else if (game.currentmenuoption == accessibilityoffset + 2) {
//disable animated backgrounds
game.colourblindmode = !game.colourblindmode;
game.savestatsandsettings_menu();
graphics.towerbg.tdrawback = true;
graphics.titlebg.tdrawback = true;
music.playef(Sound_VIRIDIAN);
}
else if (game.currentmenuoption == accessibilityoffset + 3) {
//disable screeneffects
game.noflashingmode = !game.noflashingmode;
game.savestatsandsettings_menu();
if (!game.noflashingmode)
{
music.playef(Sound_GAMESAVED);
game.screenshake = 10;
game.flashlight = 5;
}
else {
music.playef(Sound_VIRIDIAN);
}
}
else if (game.currentmenuoption == accessibilityoffset + 4) {
//disable text outline
graphics.notextoutline = !graphics.notextoutline;
game.savestatsandsettings_menu();
music.playef(Sound_VIRIDIAN);
}
else if (game.currentmenuoption == accessibilityoffset + 5) {
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
}
break;
}
case Menu::gameplayoptions:
{
int gameplayoptionsoffset = 0;
#if !defined(MAKEANDPLAY)
if (game.ingame_titlemode && game.unlock[Unlock_FLIPMODE])
#endif
{
gameplayoptionsoffset = 1;
if (game.currentmenuoption == 0) {
toggleflipmode();
// Fix wrong area music in Tower (Positive Force vs. ecroF evitisoP)
if (map.custommode)
{
break;
}
int area = map.area(game.roomx, game.roomy);
if (area == 3 || area == 11)
{
if (graphics.setflipmode)
{
music.play(Music_POSITIVEFORCEREVERSED);
}
else
{
music.play(Music_POSITIVEFORCE);
}
}
}
}
if (game.currentmenuoption == gameplayoptionsoffset + 0)
{
//Toggle 30+ FPS
music.playef(Sound_VIRIDIAN);
game.over30mode = !game.over30mode;
game.savestatsandsettings_menu();
}
else if (game.currentmenuoption == gameplayoptionsoffset + 1)
{
//Speedrunner options
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::speedrunneroptions);
map.nexttowercolour();
}
else if (game.currentmenuoption == gameplayoptionsoffset + 2)
{
//Advanced options
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::advancedoptions);
map.nexttowercolour();
}
else if (game.currentmenuoption == gameplayoptionsoffset + 3)
{
//Clear Data
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::cleardatamenu);
map.nexttowercolour();
}
else if (game.currentmenuoption == gameplayoptionsoffset + 4)
{
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::clearcustomdatamenu);
map.nexttowercolour();
}
else if (game.currentmenuoption == gameplayoptionsoffset + 5) {
//return to previous menu
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
}
break;
}
case Menu::options:
switch (game.currentmenuoption)
{
case 0:
//gameplay options
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::gameplayoptions);
map.nexttowercolour();
break;
case 1:
//graphic options
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::graphicoptions);
map.nexttowercolour();
break;
case 2:
/* Audio options */
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::audiooptions);
map.nexttowercolour();
break;
case 3:
//gamepad options
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::controller);
map.nexttowercolour();
break;
case 4:
//accessibility options
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::accessibility);
map.nexttowercolour();
break;
case 5:
//language options
music.playef(Sound_VIRIDIAN);
loc::loadlanguagelist();
loc::pre_title_lang_menu = false;
game.createmenu(Menu::language);
game.currentmenuoption = loc::languagelist_curlang;
map.nexttowercolour();
break;
default:
/* Return */
music.playef(Sound_VIRIDIAN);
if (game.ingame_titlemode)
{
game.returntoingame();
}
else
{
game.returnmenu();
map.nexttowercolour();
}
break;
}
break;
case Menu::audiooptions:
switch (game.currentmenuoption)
{
case 0:
case 1:
music.playef(Sound_VIRIDIAN);
if (game.slidermode == SLIDER_NONE)
{
initvolumeslider(game.currentmenuoption);
}
else
{
deinitvolumeslider();
}
break;
case 2:
if (!music.mmmmmm)
{
break;
}
/* Toggle MMMMMM */
music.usingmmmmmm = !music.usingmmmmmm;
music.playef(Sound_VIRIDIAN);
if (music.currentsong > -1)
{
music.play(music.currentsong);
}
game.savestatsandsettings_menu();
break;
}
if (game.currentmenuoption == 2 + (int) music.mmmmmm)
{
/* Return */
game.returnmenu();
map.nexttowercolour();
music.playef(Sound_VIRIDIAN);
}
break;
case Menu::language:
{
std::string prev_lang = std::string(loc::lang);
music.playef(Sound_VIRIDIAN);
if (loc::languagelist.size() != 0 && (unsigned)game.currentmenuoption < loc::languagelist.size())
{
loc::lang = loc::languagelist[game.currentmenuoption].code;
loc::loadtext(false);
loc::lang_set = true;
Add support for translatable sprites Language folders can now have a graphics folder, with these files: - sprites.png and flipsprites.png: spritesheets which contain translated versions of the word enemies and checkpoints - spritesmask.xml: an XML file containing all the sprites that should be copied from the translated sprites and flipsprites images to the original sprites/flipsprites. This means that the translated spritesheets don't have to contain ALL sprites - they only have to contain the translated ones. When loading them, the game assembles a combined spritesheet with translated sprites replacing English ones as needed, and this sheet is used to visually substitute the normal sprites at rendering time. It's important to note that even if 32x32 enemies have pixel-perfect hitboxes, this is only a visual change. This has been discussed several times on Discord - basically we don't want to give people unfair advantages or disadvantages because of their language setting, or change existing gameplay and speedruns tactics, which may depend on the exact pixel arrangements of the enemies. Therefore, the hitboxes are still based on the English sprites. This should be basically unnoticeable for casual players, especially with some thought from translators and artists, but there will be an option in the speedrunner menu to display the original sprites all the time. I removed the `VVV_freefunc(SDL_FreeSurface, *tilesheet)` in make_array() in Graphics.cpp, which frees grphx.im_sprites_surf and grphx.im_flipsprites_surf. Since GraphicsResources::destroy() already frees these, it looks like the only purpose the one in make_array() serves is to do it earlier. But now we need them again later (when switching languages) so let's just not free them early.
2023-09-27 03:53:36 +02:00
graphics.grphx.init_translations();
}
if (loc::pre_title_lang_menu)
{
/* Make the title screen appear, we haven't seen it yet.
* game.returnmenu() works because Menu::mainmenu
* is created before the language menu. */
game.menustart = false;
loc::pre_title_lang_menu = false;
}
if (prev_lang != loc::lang)
{
recomputetextboxes();
}
game.returnmenu();
map.nexttowercolour();
game.savestatsandsettings_menu();
break;
}
case Menu::translator_main:
switch (game.currentmenuoption)
{
case 0:
// translator options
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::translator_options);
map.nexttowercolour();
break;
case 1:
// maintenance
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::translator_maintenance);
map.nexttowercolour();
break;
case 2:
// open lang folder
if (FILESYSTEM_openDirectoryEnabled()
&& FILESYSTEM_openDirectory(FILESYSTEM_getUserMainLangDirectory()))
{
music.playef(Sound_VIRIDIAN);
SDL_MinimizeWindow(gameScreen.m_window);
}
else
{
music.playef(Sound_CRY);
}
break;
default:
// return
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::translator_options:
switch (game.currentmenuoption)
{
case 0:
// language statistics
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::translator_options_stats);
map.nexttowercolour();
break;
case 1:
// translate room names
music.playef(Sound_VIRIDIAN);
roomname_translator::set_enabled(!roomname_translator::enabled);
game.savestatsandsettings_menu();
break;
case 2:
// explore game
music.playef(Sound_VIRIDIAN);
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
game.createmenu(Menu::translator_options_exploregame);
map.nexttowercolour();
break;
case 3:
// menu test
music.playef(Sound_GAMESAVED);
game.menutestmode = true;
game.createmenu((Menu::MenuName) 0);
map.nexttowercolour();
break;
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
case 4:
// cutscene test
if (loc::lang == "en")
{
music.playef(Sound_CRY);
}
else
{
music.playef(Sound_VIRIDIAN);
game.cutscenetest_menu_page = 0;
loc::populate_testable_script_ids();
game.createmenu(Menu::translator_options_cutscenetest);
map.nexttowercolour();
}
break;
case 5:
// limits check
music.playef(Sound_VIRIDIAN);
loc::local_limits_check();
game.createmenu(Menu::translator_options_limitscheck);
map.nexttowercolour();
break;
default:
// return
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::translator_options_limitscheck:
switch (game.currentmenuoption)
{
case 0:
// next
if (loc::limitscheck_current_overflow < loc::text_overflows.size())
{
music.playef(Sound_VIRIDIAN);
loc::limitscheck_current_overflow++;
}
break;
default:
// return
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::translator_options_stats:
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
case Menu::translator_options_exploregame:
music.playef(Sound_VIRIDIAN);
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
switch (game.currentmenuoption)
{
case 0:
game.start_translator_exploring = true;
startmode(Start_TIMETRIAL_SPACESTATION1);
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
break;
case 1:
game.start_translator_exploring = true;
startmode(Start_TIMETRIAL_LABORATORY);
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
break;
case 2:
game.start_translator_exploring = true;
startmode(Start_TIMETRIAL_TOWER);
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
break;
case 3:
game.start_translator_exploring = true;
startmode(Start_TIMETRIAL_SPACESTATION2);
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
break;
case 4:
game.start_translator_exploring = true;
startmode(Start_TIMETRIAL_WARPZONE);
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
break;
case 5:
game.createmenu(Menu::playint1);
game.start_translator_exploring = true;
map.nexttowercolour();
break;
case 6:
game.createmenu(Menu::playint2);
game.start_translator_exploring = true;
map.nexttowercolour();
break;
case 7:
game.start_translator_exploring = true;
startmode(Start_TIMETRIAL_FINALLEVEL);
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
break;
default:
// return
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::translator_options_cutscenetest:
if (game.currentmenuoption == (int)game.menuoptions.size()-4)
{
// next page
music.playef(Sound_VIRIDIAN);
if ((size_t) ((game.cutscenetest_menu_page*14)+14) >= loc::testable_script_ids.size())
{
game.cutscenetest_menu_page = 0;
}
else
{
game.cutscenetest_menu_page++;
}
game.createmenu(Menu::translator_options_cutscenetest, true);
game.currentmenuoption=game.menuoptions.size()-4;
map.nexttowercolour();
}
else if (game.currentmenuoption == (int)game.menuoptions.size()-3)
{
// previous page
music.playef(Sound_VIRIDIAN);
if (game.cutscenetest_menu_page == 0)
{
game.cutscenetest_menu_page = (loc::testable_script_ids.size()-1)/14;
}
else
{
game.cutscenetest_menu_page--;
}
game.createmenu(Menu::translator_options_cutscenetest, true);
game.currentmenuoption=game.menuoptions.size()-3;
map.nexttowercolour();
}
else if (game.currentmenuoption == (int)game.menuoptions.size()-2)
{
// play the cutscene, from clipboard
game.cutscenetest_menu_play_id = std::string(SDL_GetClipboardText());
startmode(Start_CUTSCENETEST);
}
else if (game.currentmenuoption == (int)game.menuoptions.size()-1)
{
// go back to menu
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
}
else
{
// play the cutscene!
game.cutscenetest_menu_play_id = loc::testable_script_ids[(game.cutscenetest_menu_page*14)+game.currentmenuoption];
startmode(Start_CUTSCENETEST);
}
break;
case Menu::translator_maintenance:
music.playef(Sound_VIRIDIAN);
switch (game.currentmenuoption)
{
case 0:
// sync languages
game.createmenu(Menu::translator_maintenance_sync);
map.nexttowercolour();
break;
case 1:
// global statistics
// TODO
map.nexttowercolour();
break;
case 2:
// global limits check
loc::global_limits_check();
game.createmenu(Menu::translator_options_limitscheck);
map.nexttowercolour();
break;
default:
// return
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::translator_maintenance_sync:
{
music.playef(Sound_VIRIDIAN);
bool sync_success = true;
if (game.currentmenuoption == 0)
{
// yes, sync files
sync_success = loc::sync_lang_files();
}
game.returnmenu();
map.nexttowercolour();
if (!sync_success)
{
game.createmenu(Menu::translator_error_setlangwritedir);
}
break;
}
case Menu::translator_error_setlangwritedir:
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
case Menu::unlockmenutrials:
switch (game.currentmenuoption)
{
case 0: //unlock 1
game.unlock[Unlock_TIMETRIAL_SPACESTATION1] = true;
game.unlocknotify[Unlock_TIMETRIAL_SPACESTATION1] = true;
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::unlockmenutrials, true);
game.savestatsandsettings_menu();
break;
case 1: //unlock 2
game.unlock[Unlock_TIMETRIAL_LABORATORY] = true;
game.unlocknotify[Unlock_TIMETRIAL_LABORATORY] = true;
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::unlockmenutrials, true);
game.savestatsandsettings_menu();
break;
case 2: //unlock 3
game.unlock[Unlock_TIMETRIAL_TOWER] = true;
game.unlocknotify[Unlock_TIMETRIAL_TOWER] = true;
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::unlockmenutrials, true);
game.savestatsandsettings_menu();
break;
case 3: //unlock 4
game.unlock[Unlock_TIMETRIAL_SPACESTATION2] = true;
game.unlocknotify[Unlock_TIMETRIAL_SPACESTATION2] = true;
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::unlockmenutrials, true);
game.savestatsandsettings_menu();
break;
case 4: //unlock 5
game.unlock[Unlock_TIMETRIAL_WARPZONE] = true;
game.unlocknotify[Unlock_TIMETRIAL_WARPZONE] = true;
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::unlockmenutrials, true);
game.savestatsandsettings_menu();
break;
case 5: //unlock 6
game.unlock[Unlock_TIMETRIAL_FINALLEVEL] = true;
game.unlocknotify[Unlock_TIMETRIAL_FINALLEVEL] = true;
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::unlockmenutrials, true);
game.savestatsandsettings_menu();
break;
case 6: //back
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::unlockmenu:
switch (game.currentmenuoption)
{
case 0:
//unlock time trials separately...
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::unlockmenutrials);
map.nexttowercolour();
break;
case 1:
//unlock intermissions
music.playef(Sound_VIRIDIAN);
game.unlock[Unlock_INTERMISSION_REPLAYS] = true;
game.unlocknotify[Unlock_INTERMISSION_REPLAYS] = true;
game.unlock[Unlock_INTERMISSION1_COMPLETE] = true;
game.unlock[Unlock_INTERMISSION2_COMPLETE] = true;
game.createmenu(Menu::unlockmenu, true);
game.savestatsandsettings_menu();
break;
case 2:
//unlock no death mode
music.playef(Sound_VIRIDIAN);
game.unlock[Unlock_NODEATHMODE] = true;
game.unlocknotify[Unlock_NODEATHMODE] = true;
game.createmenu(Menu::unlockmenu, true);
game.savestatsandsettings_menu();
break;
case 3:
//unlock flip mode
music.playef(Sound_VIRIDIAN);
game.unlock[Unlock_FLIPMODE] = true;
game.unlocknotify[Unlock_FLIPMODE] = true;
game.createmenu(Menu::unlockmenu, true);
game.savestatsandsettings_menu();
break;
case 4:
//unlock jukebox
music.playef(Sound_VIRIDIAN);
game.stat_trinkets = 20;
game.createmenu(Menu::unlockmenu, true);
game.savestatsandsettings_menu();
break;
case 5:
//unlock secret lab
music.playef(Sound_VIRIDIAN);
game.unlock[Unlock_SECRETLAB] = true;
game.unlocknotify[Unlock_SECRETLAB] = true;
game.createmenu(Menu::unlockmenu, true);
game.savestatsandsettings_menu();
break;
default:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::credits:
switch (game.currentmenuoption)
{
case 0:
//next page
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::credits2, true);
map.nexttowercolour();
break;
case 1:
//last page
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::credits6, true);
map.nexttowercolour();
break;
default:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::credits2:
switch (game.currentmenuoption)
{
case 0:
//next page
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::credits25, true);
map.nexttowercolour();
break;
case 1:
//previous page
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::credits, true);
map.nexttowercolour();
break;
case 2:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::credits25:
switch (game.currentmenuoption)
{
case 0:
//next page
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::credits_localisations_implementation, true);
map.nexttowercolour();
break;
case 1:
//previous page
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::credits2, true);
map.nexttowercolour();
break;
default:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::credits_localisations_implementation:
switch (game.currentmenuoption)
{
case 0:
//next page
music.playef(Sound_VIRIDIAN);
game.translator_credits_pagenum = 0;
game.current_credits_list_index = 0;
game.createmenu(Menu::credits_localisations_translations, true);
map.nexttowercolour();
break;
case 1:
//previous page
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::credits25, true);
map.nexttowercolour();
break;
default:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::credits_localisations_translations:
switch (game.currentmenuoption)
{
case 0:
//next page
music.playef(Sound_VIRIDIAN);
game.translator_credits_pagenum++;
if (game.translator_credits_pagenum >= (int)SDL_arraysize(Credits::translator_pagesize))
{
// No more translators. Move to the next credits section
game.current_credits_list_index = 0;
game.createmenu(Menu::credits3, true);
}
else
{
// There are more translators. Refresh the menu with the next ones
game.current_credits_list_index = 0;
for (int i = 0; i < game.translator_credits_pagenum; i += 1)
{
game.current_credits_list_index += Credits::translator_pagesize[i];
}
game.createmenu(Menu::credits_localisations_translations, true);
}
map.nexttowercolour();
break;
case 1:
//previous page
music.playef(Sound_VIRIDIAN);
game.translator_credits_pagenum--;
if (game.translator_credits_pagenum >= 0)
{
game.current_credits_list_index = 0;
for (int i = 0; i < game.translator_credits_pagenum; i += 1)
{
game.current_credits_list_index += Credits::translator_pagesize[i];
}
game.createmenu(Menu::credits_localisations_translations, true);
}else {
//No more translators. Move to the previous credits section
game.current_credits_list_index = 0;
game.createmenu(Menu::credits_localisations_implementation, true);
}
map.nexttowercolour();
break;
default:
//back
music.playef(Sound_VIRIDIAN);
game.current_credits_list_index = 0;
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::credits3:
switch (game.currentmenuoption)
{
case 0:
//next page
music.playef(Sound_VIRIDIAN);
game.current_credits_list_index += 9;
if (game.current_credits_list_index >= (int)SDL_arraysize(Credits::superpatrons))
{
// No more super patrons. Move to the next credits section
game.current_credits_list_index = 0;
game.createmenu(Menu::credits4, true);
}
else
{
// There are more super patrons. Refresh the menu with the next ones
game.createmenu(Menu::credits3, true);
}
map.nexttowercolour();
break;
case 1:
//previous page
music.playef(Sound_VIRIDIAN);
game.current_credits_list_index -= 9;
if (game.current_credits_list_index < 0)
{
//No more super patrons. Move to the previous credits section
game.translator_credits_pagenum = (int)SDL_arraysize(Credits::translator_pagesize) - 1;
game.current_credits_list_index = 0;
for (int i = 0; i < game.translator_credits_pagenum; i += 1)
{
game.current_credits_list_index += Credits::translator_pagesize[i];
}
game.createmenu(Menu::credits_localisations_translations, true);
}
else
{
//There are more super patrons. Refresh the menu with the next ones
game.createmenu(Menu::credits3, true);
}
map.nexttowercolour();
break;
default:
//back
music.playef(Sound_VIRIDIAN);
game.current_credits_list_index = 0;
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::credits4:
switch (game.currentmenuoption)
{
case 0:
//next page
music.playef(Sound_VIRIDIAN);
game.current_credits_list_index += 14;
if (game.current_credits_list_index >= (int)SDL_arraysize(Credits::patrons))
{
// No more patrons. Move to the next credits section
game.current_credits_list_index = 0;
game.createmenu(Menu::credits5, true);
}
else
{
// There are more patrons. Refresh the menu with the next ones
game.createmenu(Menu::credits4, true);
}
map.nexttowercolour();
break;
case 1:
//previous page
music.playef(Sound_VIRIDIAN);
game.current_credits_list_index -= 14;
if (game.current_credits_list_index < 0)
{
//No more patrons. Move to the previous credits section
game.current_credits_list_index = SDL_arraysize(Credits::superpatrons) - 1 - (SDL_arraysize(Credits::superpatrons)-1)%9;
game.createmenu(Menu::credits3, true);
}
else
{
//There are more patrons. Refresh the menu with the next ones
game.createmenu(Menu::credits4, true);
}
map.nexttowercolour();
break;
default:
//back
music.playef(Sound_VIRIDIAN);
game.current_credits_list_index = 0;
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::credits5:
switch (game.currentmenuoption)
{
case 0:
//next page
music.playef(Sound_VIRIDIAN);
game.current_credits_list_index += 9;
if (game.current_credits_list_index >= (int)SDL_arraysize(Credits::githubfriends))
{
// No more GitHub contributors. Move to the next credits section
game.current_credits_list_index = 0;
game.createmenu(Menu::credits6, true);
}
else
{
// There are more GitHub contributors. Refresh the menu with the next ones
game.createmenu(Menu::credits5, true);
}
map.nexttowercolour();
break;
case 1:
//previous page
music.playef(Sound_VIRIDIAN);
game.current_credits_list_index -= 9;
if (game.current_credits_list_index < 0)
{
//No more GitHub contributors. Move to the previous credits section
game.current_credits_list_index = SDL_arraysize(Credits::patrons) - 1 - (SDL_arraysize(Credits::patrons)-1)%14;
game.createmenu(Menu::credits4, true);
}
else
{
//There are more GitHub contributors. Refresh the menu with the next ones
game.createmenu(Menu::credits5, true);
}
map.nexttowercolour();
break;
default:
//back
music.playef(Sound_VIRIDIAN);
game.current_credits_list_index = 0;
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::credits6:
switch (game.currentmenuoption)
{
case 0:
//first page
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::credits, true);
map.nexttowercolour();
break;
case 1:
//previous page
music.playef(Sound_VIRIDIAN);
game.current_credits_list_index = SDL_arraysize(Credits::githubfriends) - 1 - (SDL_arraysize(Credits::githubfriends)-1)%9;
game.createmenu(Menu::credits5, true);
map.nexttowercolour();
break;
default:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::play:
{
//Do we have the Secret Lab option?
int sloffset = game.unlock[Unlock_SECRETLAB] ? 0 : -1;
//Do we have a telesave or quicksave?
int ngoffset = game.save_exists() ? 0 : -1;
if (game.currentmenuoption == 0)
{
//continue
//right, this depends on what saves you've got
if (!game.save_exists())
{
//You have no saves but have something unlocked, or you couldn't have gotten here
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_MAINGAME);
}
else if (!game.last_telesave.exists)
{
//You at least have a quicksave, or you couldn't have gotten here
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_MAINGAME_QUICKSAVE);
}
else if (!game.last_quicksave.exists)
{
//You at least have a telesave, or you couldn't have gotten here
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_MAINGAME_TELESAVE);
}
else
{
//go to a menu!
music.playef(Sound_VIRIDIAN);
game.loadsummary(); //Prepare save slots to display
game.createmenu(Menu::continuemenu);
}
}
else if (game.currentmenuoption == 1 && game.unlock[Unlock_SECRETLAB])
{
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_SECRETLAB);
}
else if (game.currentmenuoption == sloffset+2)
{
//play modes
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::playmodes);
map.nexttowercolour();
}
else if (game.currentmenuoption == sloffset+3 && game.save_exists())
{
//newgame
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::newgamewarning);
map.nexttowercolour();
}
else if (game.currentmenuoption == sloffset+ngoffset+4)
{
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
}
break;
}
case Menu::newgamewarning:
switch (game.currentmenuoption)
{
case 0:
//yep
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_MAINGAME);
game.deletequick();
game.deletetele();
break;
default:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::controller:
switch (game.currentmenuoption)
{
case 0:
key.sensitivity++;
music.playef(Sound_VIRIDIAN);
if(key.sensitivity > 4)
{
key.sensitivity = 0;
}
game.savestatsandsettings_menu();
break;
case 6:
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::cleardatamenu:
switch (game.currentmenuoption)
{
case 0:
//back
music.playef(Sound_VIRIDIAN);
break;
default:
//yep
music.playef(Sound_DESTROY);
game.deletequick();
game.deletetele();
game.deletestats();
game.deletesettings();
game.flashlight = 5;
game.screenshake = 15;
break;
}
Don't go back to main menu when deleting main game save data Going back to the main menu allowed for glitchiness to occur if you deleted your save data while in in-game options. This meant you could then load back in to the game, and then quit to the menu, then open the options and then jump back in-game, exploring the state of the game after hardreset() had been called on it. Which is: pretty glitchy. For example, this meant having your room coordinates be 0,0 (which is different from 100,100, which is the actual 0,0, thanks for the 100-indexing Terry), which caused some of the room transitions to be disabled because room transitions were disabled if the game.door_up/down/left/right variables were -2 or less, and they were computed based on room coordinates, which meant some of them went negative if you were 0,0 and not 100,100. At least this was the case until I removed those variables for, at best, doing nothing, and at worst, being actively harmful. Anyways, so deleting your save data now just takes you back to the previous menu, much like deleting custom level data does. I don't know why deleting save data put you back on the main menu in the first place. It's not like the options menu needed to be reloaded or anything. I checked and this was the behavior in 2.0 as well, so it was probably added for a dumb reason. I considered prohibiting data deletion if you were ingame_titlemode, but as of the moment it seems to be okay (if albeit weird, e.g. returning to menu while in Secret Lab doesn't place your cursor on the "play" button), and I can always add such a prohibition later if it was really causing problems. Can't think of anything bad off of the top of my head, though. Btw thanks to Elomavi for discovering that you could do this glitch.
2021-12-18 08:01:47 +01:00
game.returnmenu();
map.nexttowercolour();
break;
case Menu::clearcustomdatamenu:
switch (game.currentmenuoption)
{
default:
music.playef(Sound_VIRIDIAN);
break;
case 1:
game.deletecustomlevelstats();
FILESYSTEM_deleteLevelSaves();
music.playef(Sound_DESTROY);
game.flashlight = 5;
game.screenshake = 15;
break;
}
game.returnmenu();
map.nexttowercolour();
break;
case Menu::playmodes:
if (game.currentmenuoption == 0
&& !game.nocompetitive_unless_translator()) //go to the time trial menu
{
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::timetrials);
map.nexttowercolour();
}
else if (game.currentmenuoption == 1
&& game.unlock[Unlock_INTERMISSION_REPLAYS])
{
//intermission mode menu
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::intermissionmenu);
map.nexttowercolour();
}
else if (game.currentmenuoption == 2
&& game.unlock[Unlock_NODEATHMODE]
&& !game.nocompetitive()) //start a game in no death mode
{
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::startnodeathmode);
map.nexttowercolour();
}
else if (game.currentmenuoption == 3
&& game.unlock[Unlock_FLIPMODE]) //enable/disable flip mode
{
toggleflipmode();
}
else if (game.currentmenuoption == 4)
{
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
}
else
{
//Can't do yet!
music.playef(Sound_CRY);
}
break;
case Menu::startnodeathmode:
switch (game.currentmenuoption)
{
case 0: //start no death mode, disabling cutscenes
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_NODEATHMODE_NOCUTSCENES);
break;
case 1:
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_NODEATHMODE_WITHCUTSCENES);
break;
case 2:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::continuemenu:
switch (game.currentmenuoption)
{
case 0:
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_MAINGAME_TELESAVE);
break;
case 1:
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_MAINGAME_QUICKSAVE);
break;
case 2:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::intermissionmenu:
switch (game.currentmenuoption)
{
case 0:
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::playint1);
map.nexttowercolour();
break;
case 1:
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::playint2);
map.nexttowercolour();
break;
case 2:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::playint1:
switch (game.currentmenuoption)
{
case 0:
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_INTERMISSION1_VITELLARY);
break;
case 1:
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_INTERMISSION1_VERMILION);
break;
case 2:
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_INTERMISSION1_VERDIGRIS);
break;
case 3:
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_INTERMISSION1_VICTORIA);
break;
case 4:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::playint2:
switch (game.currentmenuoption)
{
case 0:
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_INTERMISSION2_VITELLARY);
break;
case 1:
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_INTERMISSION2_VERMILION);
break;
case 2:
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_INTERMISSION2_VERDIGRIS);
break;
case 3:
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_INTERMISSION2_VICTORIA);
break;
case 4:
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::gameover2:
//back
music.playef(Sound_VIRIDIAN);
music.play(Music_PRESENTINGVVVVVV);
game.returntomenu(Menu::playmodes);
map.nexttowercolour();
break;
case Menu::unlocktimetrials:
case Menu::unlocktimetrial:
case Menu::unlocknodeathmode:
case Menu::unlockintermission:
case Menu::unlockflipmode:
//back
music.playef(Sound_VIRIDIAN);
game.createmenu(Menu::play, true);
map.nexttowercolour();
break;
case Menu::timetrials:
if (game.currentmenuoption == 0
&& game.unlock[Unlock_TIMETRIAL_SPACESTATION1])
{
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_TIMETRIAL_SPACESTATION1);
}
else if (game.currentmenuoption == 1
&& game.unlock[Unlock_TIMETRIAL_LABORATORY])
{
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_TIMETRIAL_LABORATORY);
}
else if (game.currentmenuoption == 2
&& game.unlock[Unlock_TIMETRIAL_TOWER])
{
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_TIMETRIAL_TOWER);
}
else if (game.currentmenuoption == 3
&& game.unlock[Unlock_TIMETRIAL_SPACESTATION2])
{
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_TIMETRIAL_SPACESTATION2);
}
else if (game.currentmenuoption == 4
&& game.unlock[Unlock_TIMETRIAL_WARPZONE])
{
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_TIMETRIAL_WARPZONE);
}
else if (game.currentmenuoption == 5
&& game.unlock[Unlock_TIMETRIAL_FINALLEVEL])
{
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode(Start_TIMETRIAL_FINALLEVEL);
}
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
else if (game.currentmenuoption == 6)
{
//back
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
}
else
{
//Can't do yet!
music.playef(Sound_CRY);
}
break;
case Menu::timetrialcomplete3:
switch (game.currentmenuoption)
{
case 0:
//back
music.playef(Sound_VIRIDIAN);
music.play(Music_PRESENTINGVVVVVV);
game.returntomenu(Menu::timetrials);
map.nexttowercolour();
/* FIXME: This is kinda bad kludge... but if we unlocked No Death Mode
* while in a Time Trial, the player wouldn't be notified until they went
* back to Menu::play first. This is the only case where something can be
* unlocked without being immediately notified after returning to title. */
if (game.can_unlock_ndm())
{
game.unlock_ndm();
}
break;
case 1:
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
/* Replay time trial */
music.playef(Sound_VIRIDIAN);
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
startmode((enum StartMode) (game.timetriallevel + Start_FIRST_TIMETRIAL));
break;
}
break;
case Menu::gamecompletecontinue:
case Menu::nodeathmodecomplete2:
music.play(Music_PRESENTINGVVVVVV);
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
case Menu::errorsavingsettings:
if (game.currentmenuoption == 1)
{
game.silence_settings_error = true;
}
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
case Menu::errorloadinglevel:
case Menu::warninglevellist:
music.playef(Sound_VIRIDIAN);
game.returnmenu();
map.nexttowercolour();
break;
default:
break;
}
}
void titleinput(void)
{
//game.mx = (mouseX / 4);
//game.my = (mouseY / 4);
game.press_left = false;
game.press_right = false;
game.press_action = false;
game.press_map = false;
game.press_interact = false;
bool lang_press_horizontal = false;
if (graphics.flipmode)
{
if (key.isDown(KEYBOARD_LEFT) || key.isDown(KEYBOARD_DOWN) || key.isDown(KEYBOARD_a) || key.isDown(KEYBOARD_s) || key.controllerWantsRight(true)) game.press_left = true;
if (key.isDown(KEYBOARD_RIGHT) || key.isDown(KEYBOARD_UP) || key.isDown(KEYBOARD_d) || key.isDown(KEYBOARD_w) || key.controllerWantsLeft(true)) game.press_right = true;
}
else if (game.currentmenuname == Menu::language)
{
if (key.isDown(KEYBOARD_UP) || key.isDown(KEYBOARD_w) || key.controllerWantsUp())
{
game.press_left = true;
}
if (key.isDown(KEYBOARD_DOWN) || key.isDown(KEYBOARD_s) || key.controllerWantsDown())
{
game.press_right = true;
}
if (key.isDown(KEYBOARD_LEFT) || key.isDown(KEYBOARD_a) || key.controllerWantsLeft(false)
|| key.isDown(KEYBOARD_RIGHT) || key.isDown(KEYBOARD_d) || key.controllerWantsRight(false))
{
lang_press_horizontal = true;
game.press_right = true;
}
}
else
{
SDL_Keycode left, right, a, d;
bool controller_up = key.controllerWantsUp();
bool controller_down = key.controllerWantsDown();
if (!font::is_rtl(PR_FONT_INTERFACE))
{
left = KEYBOARD_LEFT;
right = KEYBOARD_RIGHT;
a = KEYBOARD_a;
d = KEYBOARD_d;
controller_up |= key.controllerWantsLeft(false);
controller_down |= key.controllerWantsRight(false);
}
else
{
left = KEYBOARD_RIGHT;
right = KEYBOARD_LEFT;
a = KEYBOARD_d;
d = KEYBOARD_a;
controller_up |= key.controllerWantsRight(false);
controller_down |= key.controllerWantsLeft(false);
}
if (key.isDown(left) || key.isDown(KEYBOARD_UP) || key.isDown(a) || key.isDown(KEYBOARD_w) || controller_up)
{
game.press_left = true;
}
if (key.isDown(right) || key.isDown(KEYBOARD_DOWN) || key.isDown(d) || key.isDown(KEYBOARD_s) || controller_down)
{
game.press_right = true;
}
}
if (key.isDown(KEYBOARD_z) || key.isDown(KEYBOARD_SPACE) || key.isDown(KEYBOARD_v) || key.isDown(game.controllerButton_flip)) game.press_action = true;
//|| key.isDown(KEYBOARD_UP) || key.isDown(KEYBOARD_DOWN)) game.press_action = true; //on menus, up and down don't work as action
if (key.isDown(KEYBOARD_ENTER)) game.press_map = true;
//In the menu system, all keypresses are single taps rather than holds. Therefore this test has to be done for all presses
if (!game.press_action && !game.press_left && !game.press_right && !key.isDown(27) && !key.isDown(game.controllerButton_esc)) game.jumpheld = false;
if (!game.press_map) game.mapheld = false;
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
if (!game.jumpheld && graphics.fademode == FADE_NONE)
{
if (game.press_action || game.press_left || game.press_right || game.press_map || key.isDown(27) || key.isDown(game.controllerButton_esc))
{
game.jumpheld = true;
}
2020-01-01 21:29:24 +01:00
if ( game.currentmenuname == Menu::controller &&
game.currentmenuoption > 0 &&
game.currentmenuoption < 6 &&
(game.separate_interact || game.currentmenuoption < 5) &&
key.controllerButtonDown() )
{
updatebuttonmappings(game.currentmenuoption);
music.playef(Sound_VIRIDIAN);
game.savestatsandsettings_menu();
return;
}
if (game.menustart
&& game.menucountdown <= 0
&& (key.isDown(27) || key.isDown(game.controllerButton_esc)))
{
if (game.currentmenuname == Menu::language && loc::pre_title_lang_menu)
{
/* Don't exit from the initial language screen,
* you can't do this on the loading/title screen either. */
return;
}
else
{
music.playef(Sound_VIRIDIAN);
}
if (game.menutestmode)
{
game.menutestmode = false;
game.returnmenu();
map.nexttowercolour();
}
else if (game.currentmenuname == Menu::mainmenu)
{
game.createmenu(Menu::youwannaquit);
map.nexttowercolour();
}
else
{
if (game.slidermode != SLIDER_NONE)
{
switch (game.slidermode)
{
/* Cancel volume change. */
case SLIDER_MUSICVOLUME:
case SLIDER_SOUNDVOLUME:
if (user_changing_volume == NULL)
{
SDL_assert(0 && "user_changing_volume is NULL!");
break;
}
*user_changing_volume = previous_volume;
deinitvolumeslider();
break;
default:
SDL_assert(0 && "Unhandled slider mode!");
break;
}
}
else if (game.ingame_titlemode
&& game.currentmenuname == Menu::options)
{
game.returntoingame();
}
else
{
game.returnmenu();
map.nexttowercolour();
}
}
}
if(game.menustart)
{
if (game.slidermode == SLIDER_NONE)
{
if (game.currentmenuname == Menu::language)
{
/* The language screen has two columns and navigation in four directions.
* The second column may have one less option than the first. */
int n_options = game.menuoptions.size();
int twocol_voptions = n_options - (n_options/2);
if (lang_press_horizontal)
{
if (game.currentmenuoption < twocol_voptions)
{
game.currentmenuoption += twocol_voptions;
if (game.currentmenuoption >= n_options)
{
game.currentmenuoption = n_options - 1;
}
}
else
{
game.currentmenuoption -= twocol_voptions;
}
}
else
{
/* Vertical movement */
int min_option;
int max_option;
if (game.currentmenuoption < twocol_voptions)
{
min_option = 0;
max_option = twocol_voptions-1;
}
else
{
min_option = twocol_voptions;
max_option = n_options-1;
}
if (game.press_left) /* Up, lol */
{
game.currentmenuoption--;
if (game.currentmenuoption < min_option)
{
game.currentmenuoption = max_option;
}
}
else if (game.press_right) /* Down, lol */
{
game.currentmenuoption++;
if (game.currentmenuoption > max_option)
{
game.currentmenuoption = min_option;
}
}
}
}
else if (game.press_left)
{
game.currentmenuoption--;
}
else if (game.press_right)
{
game.currentmenuoption++;
}
}
else
{
slidermodeinput();
}
}
if (game.currentmenuoption < 0) game.currentmenuoption = game.menuoptions.size()-1;
if (game.currentmenuoption >= (int) game.menuoptions.size() ) game.currentmenuoption = 0;
if (game.press_action)
{
if (!game.menustart)
{
game.menustart = true;
music.play(Music_PRESENTINGVVVVVV);
music.playef(Sound_GAMESAVED);
game.screenshake = 10;
game.flashlight = 5;
}
else
{
menuactionpress();
2020-01-01 21:29:24 +01:00
}
}
}
if (fadetomode)
{
handlefadetomode();
}
2020-01-01 21:29:24 +01:00
}
void gameinput(void)
2020-01-01 21:29:24 +01:00
{
//TODO mouse input
//game.mx = (mouseX / 2);
//game.my = (mouseY / 2);
if(!script.running)
{
if (roomname_translator::enabled && roomname_translator::overlay_input())
{
return;
}
2020-01-01 21:29:24 +01:00
game.press_left = false;
game.press_right = false;
game.press_action = false;
game.press_interact = false;
2020-01-01 21:29:24 +01:00
if (key.isDown(KEYBOARD_LEFT) || key.isDown(KEYBOARD_a) || key.controllerWantsLeft(false))
2020-01-01 21:29:24 +01:00
{
game.press_left = true;
2020-01-01 21:29:24 +01:00
}
if (key.isDown(KEYBOARD_RIGHT) || key.isDown(KEYBOARD_d) || key.controllerWantsRight(false))
2020-01-01 21:29:24 +01:00
{
game.press_right = true;
2020-01-01 21:29:24 +01:00
}
if (key.isDown(KEYBOARD_z) || key.isDown(KEYBOARD_SPACE) || key.isDown(KEYBOARD_v)
|| key.isDown(KEYBOARD_UP) || key.isDown(KEYBOARD_DOWN) || key.isDown(KEYBOARD_w) || key.isDown(KEYBOARD_s)|| key.isDown(game.controllerButton_flip))
2020-01-01 21:29:24 +01:00
{
game.press_action = true;
}
if (key.isDown(KEYBOARD_e) || key.isDown(game.controllerButton_interact))
{
game.press_interact = true;
}
}
game.press_map = false;
if (key.isDown(KEYBOARD_ENTER) || key.isDown(SDLK_KP_ENTER) || key.isDown(game.controllerButton_map) )
{
game.press_map = true;
}
2020-01-01 21:29:24 +01:00
2023-07-02 16:27:20 +02:00
level_debugger::input();
if (level_debugger::is_pausing())
{
return;
}
2020-01-01 21:29:24 +01:00
if (game.advancetext)
{
if (game.pausescript)
{
game.press_action = false;
if (key.isDown(KEYBOARD_z) || key.isDown(KEYBOARD_SPACE) || key.isDown(KEYBOARD_v)
|| key.isDown(KEYBOARD_UP) || key.isDown(KEYBOARD_DOWN) || key.isDown(KEYBOARD_w) || key.isDown(KEYBOARD_s) || key.isDown(game.controllerButton_flip)) game.press_action = true;
}
if (game.press_action && !game.jumpheld)
{
if (game.pausescript)
{
game.pausescript = false;
game.hascontrol = true;
game.jumpheld = true;
}
else
{
if (GlitchrunnerMode_less_than_or_equal(Glitchrunner2_0)
|| !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
2020-01-01 21:29:24 +01:00
}
}
}
if (!game.press_map
//Extra conditionals as a kludge fix so if you open the quit menu during
//the script command gamemode(teleporter) and close it with Esc, it won't
//immediately open again
//We really need a better input system soon...
&& !key.isDown(27)
&& !key.isDown(game.controllerButton_esc))
{
game.mapheld = false;
}
2020-01-01 21:29:24 +01:00
if (!game.press_interact)
{
game.interactheld = false;
}
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
if (game.intimetrial && graphics.fademode == FADE_FULLY_BLACK && game.quickrestartkludge && !game.translator_exploring)
2020-01-01 21:29:24 +01:00
{
//restart the time trial
game.quickrestartkludge = false;
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
script.startgamemode((enum StartMode) (game.timetriallevel + Start_FIRST_TIMETRIAL));
2020-01-01 21:29:24 +01:00
game.deathseq = -1;
game.completestop = false;
game.hascontrol = false;
2020-01-01 21:29:24 +01:00
}
//Returning to editor mode must always be possible
if (map.custommode && !map.custommodeforreal)
{
if ((game.press_map || key.isDown(27)) && !game.mapheld)
{
if (!game.separate_interact
&& game.press_map
&& (INBOUNDS_VEC(game.activeactivity, obj.blocks)
|| (game.activetele && game.readytotele > 20)))
{
/* Pass, let code block below handle it */
}
else
{
game.returntoeditor();
game.mapheld = true;
}
2020-01-01 21:29:24 +01:00
}
}
//Entity type 0 is player controled
bool has_control = false;
bool enter_pressed = game.press_map && !game.mapheld;
bool enter_already_processed = false;
bool interact_pressed;
if (game.separate_interact)
{
interact_pressed = game.press_interact && !game.interactheld;
}
else
{
interact_pressed = enter_pressed;
}
for (size_t ie = 0; ie < obj.entities.size(); ++ie)
2020-01-01 21:29:24 +01:00
{
if (obj.entities[ie].rule == 0)
{
if (game.hascontrol && game.deathseq == -1 && game.lifeseq <= 5)
{
has_control = true;
if (interact_pressed)
2020-01-01 21:29:24 +01:00
{
game.interactheld = true;
if (!game.separate_interact)
{
game.mapheld = true;
}
}
2020-01-01 21:29:24 +01:00
if (interact_pressed && !script.running)
{
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
if (game.activetele && game.readytotele > 20 && (!game.intimetrial || game.translator_exploring_allowtele))
2020-01-01 21:29:24 +01:00
{
enter_already_processed = true;
if(int(SDL_fabsf(obj.entities[ie].vx))<=1 && int(obj.entities[ie].vy)==0)
2020-01-01 21:29:24 +01:00
{
//wait! space station 2 debug thingy
if (game.teleportscript != "")
{
//trace(game.recordstring);
//We're teleporting! Yey!
game.activetele = false;
game.hascontrol = false;
music.fadeout();
2020-01-01 21:29:24 +01:00
int player = obj.getplayer();
if (INBOUNDS_VEC(player, obj.entities))
{
obj.entities[player].colour = 102;
}
2020-01-01 21:29:24 +01:00
int teleporter = obj.getteleporter();
if (INBOUNDS_VEC(teleporter, obj.entities))
{
obj.entities[teleporter].tile = 6;
obj.entities[teleporter].colour = 102;
}
2020-01-01 21:29:24 +01:00
//which teleporter script do we use? it depends on the companion!
2022-12-07 00:20:48 +01:00
game.setstate(4000);
2022-12-07 00:35:06 +01:00
game.setstatedelay(0);
2020-01-01 21:29:24 +01:00
}
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
else if (game.companion == 0 && !game.translator_exploring_allowtele)
2020-01-01 21:29:24 +01:00
{
//Alright, normal teleporting
game.mapmenuchange(TELEPORTERMODE, true);
2020-01-01 21:29:24 +01:00
game.useteleporter = true;
game.initteleportermode();
2020-01-01 21:29:24 +01:00
}
else
{
//We're teleporting! Yey!
game.activetele = false;
game.hascontrol = false;
music.fadeout();
2020-01-01 21:29:24 +01:00
int player = obj.getplayer();
if (INBOUNDS_VEC(player, obj.entities))
{
obj.entities[player].colour = 102;
}
int companion = obj.getcompanion();
if(INBOUNDS_VEC(companion, obj.entities)) obj.entities[companion].colour = 102;
2020-01-01 21:29:24 +01:00
int teleporter = obj.getteleporter();
if (INBOUNDS_VEC(teleporter, obj.entities))
{
obj.entities[teleporter].tile = 6;
obj.entities[teleporter].colour = 102;
}
2020-01-01 21:29:24 +01:00
//which teleporter script do we use? it depends on the companion!
2022-12-07 00:20:48 +01:00
game.setstate(3000);
2022-12-07 00:35:06 +01:00
game.setstatedelay(0);
2020-01-01 21:29:24 +01:00
}
}
}
else if (INBOUNDS_VEC(game.activeactivity, obj.blocks))
2020-01-01 21:29:24 +01:00
{
enter_already_processed = true;
if((int(SDL_fabsf(obj.entities[ie].vx))<=1) && (int(obj.entities[ie].vy) == 0) )
2020-01-01 21:29:24 +01:00
{
script.load(obj.blocks[game.activeactivity].script);
Fix entity and block indices after destroying them This patch restores some 2.2 behavior, fixing a regression caused by the refactor of properly using std::vectors. In 2.2, the game allocated 200 items in obj.entities, but used a system where each entity had an `active` attribute to signify if the entity actually existed or not. When dealing with entities, you would have to check this `active` flag, or else you'd be dealing with an entity that didn't actually exist. (By the way, what I'm saying applies to blocks and obj.blocks as well, except for some small differing details like the game allocating 500 block slots versus obj.entities's 200.) As a consequence, the game had to use a separate tracking variable, obj.nentity, because obj.entities.size() would just report 200, instead of the actual amount of entities. Needless to say, having to check for `active` and use `obj.nentity` is a bit error-prone, and it's messier than simply using the std::vector the way it was intended. Also, this resulted in a hard limit of 200 entities, which custom level makers ran into surprisingly quite often. 2.3 comes along, and removes the whole system. Now, std::vectors are properly being used, and obj.entities.size() reports the actual number of entities in the vector; you no longer have to check for `active` when dealing with entities of any sort. But there was one previous behavior of 2.2 that this system kind of forgets about - namely, the ability to have holes in between entities. You see, when an entity got disabled in 2.2 (which just meant turning its `active` off), the indices of all other entities stayed the same; the indice of the entity that got disabled stays there as a hole in the array. But when an entity gets removed in 2.3 (previous to this patch), the indices of every entity afterwards in the array get shifted down by one. std::vector isn't really meant to be able to contain holes. Do the indices of entities and blocks matter? Yes; they determine the order in which entities and blocks get evaluated (the highest indice gets evaluated first), and I had to fix some block evaluation order stuff in previous PRs. And in the case of entities, they matter hugely when using the recently-discovered Arbitrary Entity Manipulation glitch (where crewmate script commands are used on arbitrary entities by setting the `i` attribute of `scriptclass` and passing invalid crewmate identifiers to the commands). If you use Arbitrary Entity Manipulation after destroying some entities, there is a chance that your script won't work between 2.2 and 2.3. The indices also still determine the rendering order of entities (highest indice gets drawn first, which means lowest indice gets drawn in front of other entities). As an example: let's say we have the player at 0, a gravity line at 1, and a checkpoint at 2; then we destroy the gravity line and create a crewmate (let's do Violet). If we're able to have holes, then after removing the gravity line, none of the other indices shift. Then Violet will be created at indice 1, and will be drawn in front of the checkpoint. But if we can't have holes, then removing the gravity line results in the indice of the checkpoint shifting down to indice 1. Then Violet is created at indice 2, and gets drawn behind the checkpoint! This is a clear illustration of changing the behavior that existed in 2.2. However, I also don't want to go back to the `active` system of having to check an attribute before operating on an entity. So... what do we do to restore the holes? Well, we don't need to have an `active` attribute, or modify any existing code that operates on entities. Instead, we can just set the attributes of the entities so that they naturally get ignored by everything that comes into contact with it. For entities, we set their invis to true, and their size, type, and rule to -1 (the game never uses a size, type, or rule of -1 anywhere); for blocks, we set their type to -1, and their width and height to 0. obj.entities.size() will no longer necessarily equal the amount of entities in the room; rather, it will be the amount of entity SLOTS that have been allocated. But nothing that uses obj.entities.size() needs to actually know the amount of entities; it's mostly used for iterating over every entity in the vector. Excess entity slots get cleaned up upon every call of mapclass::gotoroom(), which will now deallocate entity slots starting from the end until it hits a player, at which point it will switch to disabling entity slots instead of removing them entirely. The entclass::clear() and blockclass::clear() functions have been restored because we need to call their initialization functions when reusing a block/entity slot; it's possible to create an entity with an invalid type number (it creates a glitchy Viridian), and without calling the initialization function again, it would simply not create anything. After this patch is applied, entity and block indices will be restored to how they behaved in 2.2.
2020-12-27 07:11:34 +01:00
obj.disableblock(game.activeactivity);
game.activeactivity = -1;
2020-01-01 21:29:24 +01:00
}
}
}
if(game.press_left)
2020-01-01 21:29:24 +01:00
{
obj.entities[ie].ax = -3;
obj.entities[ie].dir = 0;
2020-01-01 21:29:24 +01:00
}
else if (game.press_right)
{
obj.entities[ie].ax = 3;
obj.entities[ie].dir = 1;
}
}
}
}
if (has_control)
{
if (game.press_left)
{
game.tapleft++;
}
else
{
if (game.tapleft <= 4 && game.tapleft > 0)
{
for (size_t ie = 0; ie < obj.entities.size(); ++ie)
2020-01-01 21:29:24 +01:00
{
if (obj.entities[ie].rule == 0)
2020-01-01 21:29:24 +01:00
{
if (obj.entities[ie].vx < 0.0f)
{
obj.entities[ie].vx = 0.0f;
}
2020-01-01 21:29:24 +01:00
}
}
}
game.tapleft = 0;
}
if (game.press_right)
{
game.tapright++;
}
else
{
if (game.tapright <= 4 && game.tapright > 0)
{
for (size_t ie = 0; ie < obj.entities.size(); ++ie)
2020-01-01 21:29:24 +01:00
{
if (obj.entities[ie].rule == 0)
2020-01-01 21:29:24 +01:00
{
if (obj.entities[ie].vx > 0.0f)
{
obj.entities[ie].vx = 0.0f;
}
2020-01-01 21:29:24 +01:00
}
}
}
game.tapright = 0;
}
2020-01-01 21:29:24 +01:00
if (!game.press_action)
{
game.jumppressed = 0;
game.jumpheld = false;
}
2020-01-01 21:29:24 +01:00
if (game.press_action && !game.jumpheld)
{
game.jumppressed = 5;
game.jumpheld = true;
}
2020-01-01 21:29:24 +01:00
Evaluate flipping eligibility per-entity 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.
2023-03-19 04:24:08 +01:00
std::vector<size_t> player_entities;
for (size_t ie = 0; ie < obj.entities.size(); ie++)
{
Evaluate flipping eligibility per-entity 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.
2023-03-19 04:24:08 +01:00
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--;
Evaluate flipping eligibility per-entity 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.
2023-03-19 04:24:08 +01:00
if (obj.entities[ie].onground > 0 && game.gravitycontrol == 0)
{
game.gravitycontrol = 1;
Evaluate flipping eligibility per-entity 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.
2023-03-19 04:24:08 +01:00
for (size_t j = 0; j < player_entities.size(); j++)
2020-01-01 21:29:24 +01:00
{
Evaluate flipping eligibility per-entity 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.
2023-03-19 04:24:08 +01:00
const size_t e = player_entities[j];
if (obj.entities[e].onground > 0 || obj.entities[e].onroof > 0)
{
Evaluate flipping eligibility per-entity 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.
2023-03-19 04:24:08 +01:00
obj.entities[e].vy = -4;
obj.entities[e].ay = -3;
}
}
music.playef(Sound_FLIP);
game.jumppressed = 0;
game.totalflips++;
}
Evaluate flipping eligibility per-entity 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.
2023-03-19 04:24:08 +01:00
if (obj.entities[ie].onroof > 0 && game.gravitycontrol == 1)
{
game.gravitycontrol = 0;
Evaluate flipping eligibility per-entity 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.
2023-03-19 04:24:08 +01:00
for (size_t j = 0; j < player_entities.size(); j++)
{
Evaluate flipping eligibility per-entity 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.
2023-03-19 04:24:08 +01:00
const size_t e = player_entities[j];
if (obj.entities[e].onground > 0 || obj.entities[e].onroof > 0)
{
Evaluate flipping eligibility per-entity 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.
2023-03-19 04:24:08 +01:00
obj.entities[e].vy = 4;
obj.entities[e].ay = 3;
}
2020-01-01 21:29:24 +01:00
}
music.playef(Sound_UNFLIP);
game.jumppressed = 0;
game.totalflips++;
2020-01-01 21:29:24 +01:00
}
}
}
else
{
//Simple detection of keypresses outside player control, will probably scrap this (expand on
//advance text function)
if (!game.press_action)
{
game.jumppressed = 0;
game.jumpheld = false;
}
if (game.press_action && !game.jumpheld)
{
game.jumppressed = 5;
game.jumpheld = true;
}
}
/* The rest of the if-tree runs only if enter is pressed and it has not
* already been processed with 'separate interact' off.
*/
if (!enter_pressed || (enter_already_processed && !game.separate_interact))
{
// Do nothing
}
else if (game.swnmode == 1
&& (game.swngame == SWN_SUPERGRAVITRON ||
game.swngame == SWN_START_SUPERGRAVITRON_STEP_1 ||
game.swngame == SWN_START_SUPERGRAVITRON_STEP_2))
{
//quitting the super gravitron
game.mapheld = true;
//Quit menu, same conditions as in game menu
game.mapmenuchange(MAPMODE, true);
game.gamesaved = false;
2020-11-04 03:45:33 +01:00
game.gamesavefailed = false;
game.menupage = 20; // The Map Page
}
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
else if (game.intimetrial && graphics.fademode == FADE_NONE && !game.translator_exploring)
{
//Quick restart of time trial
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
graphics.fademode = FADE_START_FADEOUT;
game.completestop = true;
Revert part of "Fix music stopping when restarting time trial" This reverts only a part of f196fcd896defc0b24690851c701b8273ba8074b - as the original commit author did not do their changes atomically, they also squashed in a de-duplication within the same commit. So I'm only reverting the part of the commit that wasn't the de-duplication, which is simply the changes to the music.fadeout() calls. This is being (partially) reverted for several reasons: 1. It's not the correct behavior. What this does instead is persist the track through after you restart the time trial, instead of fading it out, then restarting it again. This is in contrast to behavior in 2.2, and I see no reason to not keep the same behavior. 2. It's a single-case patch. The time trials are not the only time in the game a music track could fade out and then be restarted with the same track - custom levels could do the same thing too. Instead of fixing only one case, we should strive to fix EVERY case. The original commit author (trelbutate) also didn't write anything in the commit description of f196fcd896defc0b24690851c701b8273ba8074b. What you should write in the commit description is things like rationale, analysis, and other good information that would be useful to anyone looking at your commit to understand why you did what you did. Having no commit description leaves readers in the dark as to why you did what you did. Thus, I don't know why trelbutate went with this solution, or if they knew that it was only a single-case patching, or if they knew that it wasn't 2.2 behavior. By not writing the commit description, they miss a chance for reflection; speaking from personal experience, I myself have gone back and improved my commits countless times because I wrote commit descriptions for every single one of them, and sometimes whenever I write them, I think to myself "hang on a minute, that doesn't sound quite right" and end up finding improvements. If trelbutate wrote a commit description, they might have realized that it wasn't 2.2 behavior, and gone back and fixed up their commit to be correct. As it stands, though, they didn't have to think about it in the first place because they never bothered to write a commit description.
2021-04-14 18:20:33 +02:00
music.fadeout();
game.quickrestartkludge = true;
}
Add level exploring menu for translators I would, of course, recommend translators to translate the roomnames while playing the full game (optionally in invincibility) so they can immediately get all the context and maybe the most inspiration. And if you want to go back into a specific level, then there's always the time trials and intermission replays which will give you full coverage of all the room names. However, the time trials weren't really made for room name translation. They have some annoying features like the instant restart when you press ENTER at the wrong time, they remove context clues like teleporters and companions, but the worst problem is that the last room in a level is often completely untranslatable inside the time trials because you immediately get sent to the results screen... So, I added a new menu in the translator options, "explore game", which gives you access to all the time trials and the two intermissions, from the same menu. All these time trials (which they're still based off of, under the hood) are stripped of the annoying features that come with time trials. These are the changes I made to time trial behavior in translator exploring mode: - No 3-2-1-Go! countdown - No on-screen time/death/shiny/par - ENTER doesn't restart, and the map menu works. The entire map is also revealed. - Prize for the Reckless is in its normal form - The teleporters in Entanglement Generator, Wheeler's Wormhole and Level Complete are restored as context for room names (actually, we should probably restore them in time trials anyway? Their "press to teleport" prompt is already blocked out in time trials and they do nothing other than being a checkpoint. I guess the reason they were removed was to stop people from opening the teleporter menu when that was not specifically blocked out in time trials yet.) - The companions are there at the end of levels, and behave like in no death mode (become happy and follow you to the teleporter). Also for context. - At the end of each level, you're not suddenly sent to the menu, but you can use the teleporter at your leisure just like in the intermission replays. In the Final Level, you do get sent to the menu automatically, but after a longer delay. I made another mark on VVVVVV: don't be startled, I added gamestates. I wanted all teleporters at the end of levels to behave like the ones at the end of the intermission replays, and all handling for teleporting with specific companions is already done in gamestates, so rather than adding conditional blocks across 5 or so different gamestates, it made more sense to make a single gamestate for "teleporting in translator exploring mode" (3090). I also added an alternative to having to use gamestate 3500 or 82 for the end of the final level: 3091-3092. One other thing I want to add to the "explore game" menu: a per-level count of how many room names are left to translate. That shouldn't be too difficult, and I'm planning that for the next commit.
2022-11-26 03:33:17 +01:00
else if (game.intimetrial && !game.translator_exploring)
{
//Do nothing if we're in a Time Trial but a fade animation is playing
}
2022-11-25 21:39:35 +01:00
else if (map.custommode && !map.custommodeforreal)
{
// We're playtesting in the editor so don't do anything
}
else
{
//Normal map screen, do transition later
game.mapmenuchange(MAPMODE, true);
map.cursordelay = 0;
map.cursorstate = 0;
game.gamesaved = false;
2020-11-04 03:45:33 +01:00
game.gamesavefailed = false;
if (script.running)
{
game.menupage = 3; // Only allow saving
}
else
{
game.menupage = 0; // The Map Page
}
2020-01-01 21:29:24 +01:00
}
if (!game.mapheld
&& (key.isDown(27) || key.isDown(game.controllerButton_esc))
&& (!map.custommode || map.custommodeforreal))
{
game.mapheld = true;
//Quit menu, same conditions as in game menu
game.mapmenuchange(MAPMODE, true);
game.gamesaved = false;
2020-11-04 03:45:33 +01:00
game.gamesavefailed = false;
game.menupage = 30; // Pause screen
}
if (game.deathseq == -1 && (key.isDown(SDLK_r) || key.isDown(game.controllerButton_restart)) && !game.nodeathmode)// && map.custommode) //Have fun glitchrunners!
{
game.deathseq = 30;
}
2020-01-01 21:29:24 +01:00
}
static void mapmenuactionpress(bool version2_2);
void mapinput(void)
2020-01-01 21:29:24 +01:00
{
const bool version2_2 = GlitchrunnerMode_less_than_or_equal(Glitchrunner2_2);
2020-01-01 21:29:24 +01:00
//TODO Mouse Input!
//game.mx = (mouseX / 2);
//game.my = (mouseY / 2);
game.press_left = false;
game.press_right = false;
game.press_action = false;
game.press_map = false;
game.press_interact = false;
2020-01-01 21:29:24 +01:00
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
if (version2_2 && graphics.fademode == FADE_FULLY_BLACK && 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
graphics.resumegamemode = true;
// Remove half-second delay
graphics.menuoffset = 250;
// Technically this was in <=2.2 as well
obj.removeallblocks();
if (game.menupage >= 20 && game.menupage <= 21)
{
2022-12-07 00:20:48 +01:00
game.setstate(96);
2022-12-07 00:35:06 +01:00
game.setstatedelay(0);
}
else
{
// Produces more glitchiness! Necessary for credits warp to work.
script.running = false;
graphics.textboxes.clear();
2022-12-07 00:20:48 +01:00
game.setstate(80);
2022-12-07 00:35:06 +01:00
game.setstatedelay(0);
}
}
if (game.fadetomenu && !version2_2)
{
if (game.fadetomenudelay > 0)
{
game.fadetomenudelay--;
}
else
{
game.quittomenu();
music.play(Music_PRESENTINGVVVVVV); // should be after game.quittomenu()
game.fadetomenu = false;
}
}
if (game.fadetolab && !version2_2)
{
if (game.fadetolabdelay > 0)
{
game.fadetolabdelay--;
}
else
{
game.returntolab();
game.fadetolab = false;
}
}
if(graphics.menuoffset==0
&& ((!version2_2 && !game.fadetomenu && game.fadetomenudelay <= 0 && !game.fadetolab && game.fadetolabdelay <= 0)
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
|| graphics.fademode == FADE_NONE))
2020-01-01 21:29:24 +01:00
{
SDL_Keycode left, right, a, d;
bool controller_up = key.controllerWantsUp();
bool controller_down = key.controllerWantsDown();
if (!font::is_rtl(PR_FONT_INTERFACE))
{
left = KEYBOARD_LEFT;
right = KEYBOARD_RIGHT;
a = KEYBOARD_a;
d = KEYBOARD_d;
controller_up |= key.controllerWantsLeft(false);
controller_down |= key.controllerWantsRight(false);
}
else
{
left = KEYBOARD_RIGHT;
right = KEYBOARD_LEFT;
a = KEYBOARD_d;
d = KEYBOARD_a;
controller_up |= key.controllerWantsRight(false);
controller_down |= key.controllerWantsLeft(false);
}
if (key.isDown(left) || key.isDown(KEYBOARD_UP) || key.isDown(a) || key.isDown(KEYBOARD_w)|| controller_up)
2020-01-01 21:29:24 +01:00
{
Fix up/down being reversed in in-game menu in Flip Mode This bug is technically NOT a regression - the code responsible for it has been around since the source release. However, it hasn't been a problem until Graphic Options and Game Options were added to the pause screen. Since then, if you opened the pause menu in Flip Mode, pressing up would move to the menu option below, and pressing down would move to the menu option above. Notably, left and right still remain the same. This is because the map screen input code assumes that the menu options will be flipped around - however, this has never been the case. What happens instead is that the menu options get flipped around time when in Flip Mode - flipping what's already flipped - so it ends up the same again. (Incidentally enough, the up/down reversing code is present on the title screen, and is correct - if you happen to set graphics.flipmode to true on the title screen, the title screen doesn't negate the flipped menu options, so pressing up SHOULD be treated like pressing down, and vice versa. However, in 2.3, it's not really possible to set graphics.flipmode to true on the title screen without using GDB or modifying the game. In 2.2 and previous, you can just complete the game in Flip Mode, and the variable won't be reset; 2.3 cleaned up all exit paths to the menu to make sure everything got reset.) This isn't a problem when there's only two options, but since 2.3 adds two more options to the pause screen, it's pretty noticeable. Anyway, this is fixed by simply removing the branch of the graphics.flipmode if-else in mapinput(). The 'else' branch is now the code that gets executed unconditionally. Don't get confused by the diff; I decided to unindent in the same commit because it's not that many lines of code.
2021-03-06 04:34:15 +01:00
game.press_left = true;
2020-01-01 21:29:24 +01:00
}
if (key.isDown(right) || key.isDown(KEYBOARD_DOWN) || key.isDown(d) || key.isDown(KEYBOARD_s)|| controller_down)
2020-01-01 21:29:24 +01:00
{
Fix up/down being reversed in in-game menu in Flip Mode This bug is technically NOT a regression - the code responsible for it has been around since the source release. However, it hasn't been a problem until Graphic Options and Game Options were added to the pause screen. Since then, if you opened the pause menu in Flip Mode, pressing up would move to the menu option below, and pressing down would move to the menu option above. Notably, left and right still remain the same. This is because the map screen input code assumes that the menu options will be flipped around - however, this has never been the case. What happens instead is that the menu options get flipped around time when in Flip Mode - flipping what's already flipped - so it ends up the same again. (Incidentally enough, the up/down reversing code is present on the title screen, and is correct - if you happen to set graphics.flipmode to true on the title screen, the title screen doesn't negate the flipped menu options, so pressing up SHOULD be treated like pressing down, and vice versa. However, in 2.3, it's not really possible to set graphics.flipmode to true on the title screen without using GDB or modifying the game. In 2.2 and previous, you can just complete the game in Flip Mode, and the variable won't be reset; 2.3 cleaned up all exit paths to the menu to make sure everything got reset.) This isn't a problem when there's only two options, but since 2.3 adds two more options to the pause screen, it's pretty noticeable. Anyway, this is fixed by simply removing the branch of the graphics.flipmode if-else in mapinput(). The 'else' branch is now the code that gets executed unconditionally. Don't get confused by the diff; I decided to unindent in the same commit because it's not that many lines of code.
2021-03-06 04:34:15 +01:00
game.press_right = true;
2020-01-01 21:29:24 +01:00
}
if (key.isDown(KEYBOARD_z) || key.isDown(KEYBOARD_SPACE) || key.isDown(KEYBOARD_v) || key.isDown(game.controllerButton_flip))
{
game.press_action = true;
}
if (game.menupage < 12
|| (game.menupage >= 20 && game.menupage <= 21)
|| (game.menupage >= 30 && game.menupage <= 32))
2020-01-01 21:29:24 +01:00
{
if (key.isDown(KEYBOARD_ENTER) || key.isDown(game.controllerButton_map) ) game.press_map = true;
if (key.isDown(27) && !game.mapheld)
2020-01-01 21:29:24 +01:00
{
game.mapheld = true;
if (game.menupage < 9
|| (game.menupage >= 20 && game.menupage <= 21))
{
game.menupage = 30;
}
else if (game.menupage < 12)
{
game.menupage = 32;
}
else
{
graphics.resumegamemode = true;
}
music.playef(Sound_VIRIDIAN);
2020-01-01 21:29:24 +01:00
}
}
else
{
if (key.isDown(KEYBOARD_ENTER) || key.isDown(27)|| key.isDown(game.controllerButton_map) ) game.press_map = true;
}
//In the menu system, all keypresses are single taps rather than holds. Therefore this test has to be done for all presses
if (!game.press_action && !game.press_left && !game.press_right)
{
game.jumpheld = false;
}
if (!game.press_map && !key.isDown(27))
{
game.mapheld = false;
}
}
else
{
game.mapheld = true;
game.jumpheld = true;
}
if (!game.mapheld)
{
if(game.press_map && game.menupage < 10)
{
//Normal map screen, do transition later
graphics.resumegamemode = true;
2020-01-01 21:29:24 +01:00
}
}
if (!game.jumpheld)
{
if (game.press_action || game.press_left || game.press_right || game.press_map)
{
game.jumpheld = true;
}
if (script.running && game.menupage == 3)
{
// Force the player to stay in the SAVE tab while in a cutscene
}
else if (game.press_left)
2020-01-01 21:29:24 +01:00
{
game.menupage--;
}
else if (game.press_right)
{
game.menupage++;
}
if (game.press_action)
{
mapmenuactionpress(version2_2);
}
if (game.menupage < 0) game.menupage = 3;
if (game.menupage > 3 && game.menupage < 9) game.menupage = 0;
if (game.menupage == 9) game.menupage = 11;
if (game.menupage == 12) game.menupage = 10;
if (game.menupage == 19) game.menupage = 21;
if (game.menupage == 22) game.menupage = 20;
if (game.menupage == 29) game.menupage = 32;
if (game.menupage == 33) game.menupage = 30;
}
}
static void mapmenuactionpress(const bool version2_2)
{
switch (game.menupage)
{
case 1:
if (obj.flags[67] && !game.inspecial() && !map.custommode)
{
//Warp back to the ship
graphics.resumegamemode = true;
2020-01-01 21:29:24 +01:00
game.teleport_to_x = 2;
game.teleport_to_y = 11;
2020-01-01 21:29:24 +01:00
//trace(game.recordstring);
//We're teleporting! Yey!
game.activetele = false;
game.hascontrol = false;
2020-01-01 21:29:24 +01:00
int i = obj.getplayer();
if (INBOUNDS_VEC(i, obj.entities))
{
obj.entities[i].colour = 102;
2020-01-01 21:29:24 +01:00
}
//which teleporter script do we use? it depends on the companion!
2022-12-08 02:10:12 +01:00
game.setstate(4000);
game.setstatedelay(0);
2022-12-07 00:20:48 +01:00
game.lockstate();
}
break;
case 3:
2020-11-04 03:45:33 +01:00
if (!game.gamesaved && !game.gamesavefailed && !game.inspecial())
{
game.flashlight = 5;
game.screenshake = 10;
music.playef(Sound_GAMESAVED);
2020-01-01 21:29:24 +01:00
game.savetime = game.timestring();
game.savetrinkets = game.trinkets();
2020-01-01 21:29:24 +01:00
2020-11-04 03:45:33 +01:00
bool success;
if(map.custommodeforreal)
2020-01-01 21:29:24 +01:00
{
success = game.customsavequick(cl.ListOfMetaData[game.playcustomlevel].filename);
2020-01-01 21:29:24 +01:00
}
else
2020-01-01 21:29:24 +01:00
{
2020-11-04 03:45:33 +01:00
success = game.savequick();
2020-01-01 21:29:24 +01:00
}
2020-11-04 03:45:33 +01:00
game.gamesaved = success;
game.gamesavefailed = !success;
}
break;
2020-01-01 21:29:24 +01:00
case 10:
//return to pause menu
music.playef(Sound_VIRIDIAN);
game.menupage = 32;
break;
case 11:
//quit to menu
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
graphics.fademode = FADE_START_FADEOUT;
music.fadeout();
map.nexttowercolour();
if (!version2_2)
{
game.fadetomenu = true;
game.fadetomenudelay = 19;
}
music.playef(Sound_VIRIDIAN);
break;
case 20:
//return to game
graphics.resumegamemode = true;
music.playef(Sound_VIRIDIAN);
break;
case 21:
//quit to menu
game.swnmode = false;
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
graphics.fademode = FADE_START_FADEOUT;
music.fadeout();
if (!version2_2)
{
game.fadetolab = true;
game.fadetolabdelay = 19;
}
music.playef(Sound_VIRIDIAN);
break;
case 30:
// Return to game
graphics.resumegamemode = true;
music.playef(Sound_VIRIDIAN);
break;
case 31:
// Graphic options and game options
music.playef(Sound_VIRIDIAN);
game.gamestate = TITLEMODE;
graphics.flipmode = false;
game.ingame_titlemode = true;
graphics.ingame_fademode = graphics.fademode;
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
graphics.fademode = FADE_NONE;
// Set this before we create the menu
game.kludge_ingametemp = game.currentmenuname;
game.createmenu(Menu::options);
map.nexttowercolour();
break;
case 32:
// Go to quit prompt
music.playef(Sound_VIRIDIAN);
game.menupage = 10;
break;
}
2020-01-01 21:29:24 +01:00
}
void teleporterinput(void)
2020-01-01 21:29:24 +01:00
{
//Todo Mouseinput!
//game.mx = (mouseX / 2);
//game.my = (mouseY / 2);
int tempx, tempy;
game.press_left = false;
game.press_right = false;
game.press_action = false;
game.press_map = false;
game.press_interact = false;
2020-01-01 21:29:24 +01:00
if(graphics.menuoffset==0)
2020-01-01 21:29:24 +01:00
{
if (key.isDown(KEYBOARD_LEFT)|| key.isDown(KEYBOARD_a) || key.controllerWantsLeft(false) ) game.press_left = true;
if (key.isDown(KEYBOARD_RIGHT) || key.isDown(KEYBOARD_d)|| key.controllerWantsRight(false) ) game.press_right = true;
if (key.isDown(KEYBOARD_z) || key.isDown(KEYBOARD_SPACE) || key.isDown(KEYBOARD_v)
|| key.isDown(KEYBOARD_UP) || key.isDown(KEYBOARD_DOWN)|| key.isDown(KEYBOARD_w)|| key.isDown(KEYBOARD_s) || key.isDown(game.controllerButton_flip)) game.press_action = true;
if (!game.separate_interact && (key.isDown(KEYBOARD_ENTER) || key.isDown(game.controllerButton_map)))
{
game.press_map = true;
}
if (key.isDown(KEYBOARD_e) || key.isDown(game.controllerButton_interact))
{
game.press_interact = true;
}
2020-01-01 21:29:24 +01:00
//In the menu system, all keypresses are single taps rather than holds. Therefore this test has to be done for all presses
if (!game.press_action && !game.press_left && !game.press_right && !game.press_interact) game.jumpheld = false;
2020-01-01 21:29:24 +01:00
if (!game.press_map) game.mapheld = false;
if (key.isDown(27))
{
if (!map.custommode || map.custommodeforreal)
{
// Go to pause menu
game.mapheld = true;
game.menupage = 30;
game.gamestate = MAPMODE;
}
else
{
// Close teleporter menu
graphics.resumegamemode = true;
}
music.playef(Sound_VIRIDIAN);
}
2020-01-01 21:29:24 +01:00
}
else
{
game.mapheld = true;
game.jumpheld = true;
}
if (!game.jumpheld)
{
if (game.press_action || game.press_left || game.press_right || game.press_map || game.press_interact)
2020-01-01 21:29:24 +01:00
{
game.jumpheld = true;
}
bool any_tele_unlocked = false;
if (game.press_left || game.press_right)
{
for (size_t i = 0; i < map.teleporters.size(); i++)
{
SDL_Point& tele = map.teleporters[i];
if (map.isexplored(tele.x, tele.y))
{
any_tele_unlocked = true;
break;
}
}
}
if (game.press_left && any_tele_unlocked)
2020-01-01 21:29:24 +01:00
{
do
2020-01-01 21:29:24 +01:00
{
game.teleport_to_teleporter--;
if (game.teleport_to_teleporter < 0) game.teleport_to_teleporter = map.teleporters.size() - 1;
2020-01-01 21:29:24 +01:00
tempx = map.teleporters[game.teleport_to_teleporter].x;
tempy = map.teleporters[game.teleport_to_teleporter].y;
}
while (!map.isexplored(tempx, tempy));
2020-01-01 21:29:24 +01:00
}
else if (game.press_right && any_tele_unlocked)
2020-01-01 21:29:24 +01:00
{
do
2020-01-01 21:29:24 +01:00
{
game.teleport_to_teleporter++;
if (game.teleport_to_teleporter >= (int) map.teleporters.size()) game.teleport_to_teleporter = 0;
2020-01-01 21:29:24 +01:00
tempx = map.teleporters[game.teleport_to_teleporter].x;
tempy = map.teleporters[game.teleport_to_teleporter].y;
}
while (!map.isexplored(tempx, tempy));
2020-01-01 21:29:24 +01:00
}
if ((game.separate_interact && game.press_interact) || game.press_map)
2020-01-01 21:29:24 +01:00
{
tempx = map.teleporters[game.teleport_to_teleporter].x;
tempy = map.teleporters[game.teleport_to_teleporter].y;
if (game.roomx == tempx + 100 && game.roomy == tempy + 100)
{
//cancel!
graphics.resumegamemode = true;
2020-01-01 21:29:24 +01:00
}
else
{
//teleport
graphics.resumegamemode = true;
2020-01-01 21:29:24 +01:00
game.teleport_to_x = tempx;
game.teleport_to_y = tempy;
//trace(game.recordstring);
//We're teleporting! Yey!
game.activetele = false;
game.hascontrol = false;
int i = obj.getplayer();
if (INBOUNDS_VEC(i, obj.entities))
{
obj.entities[i].colour = 102;
}
2020-01-01 21:29:24 +01:00
i = obj.getteleporter();
if (INBOUNDS_VEC(i, obj.entities))
{
obj.entities[i].tile = 6;
obj.entities[i].colour = 102;
}
2020-01-01 21:29:24 +01:00
//which teleporter script do we use? it depends on the companion!
2022-12-07 00:20:48 +01:00
game.setstate(4000);
2022-12-07 00:35:06 +01:00
game.setstatedelay(0);
2020-01-01 21:29:24 +01:00
}
}
}
}
void gamecompleteinput(void)
2020-01-01 21:29:24 +01:00
{
game.press_left = false;
game.press_right = false;
game.press_action = false;
game.press_map = false;
game.press_interact = false;
2020-01-01 21:29:24 +01:00
//Do this before we update map.bypos
if (!game.colourblindmode)
{
graphics.updatetowerbackground(graphics.titlebg);
}
//Do these here because input comes first
graphics.titlebg.bypos += graphics.titlebg.bscroll;
game.oldcreditposition = game.creditposition;
Don't re-draw credits scroll background every frame While I was working on my over-30-FPS patch, I found out that the tower background in the credits scroll was being completely re-drawn every single frame, which was a bit wasteful and expensive. It's also harder to interpolate for my over-30-FPS patch. I'm guessing this constant re-draw was done because the math to get the surface scroll properly working is a bit subtle, but I've figured the precise math out! The first changes of this patch is just removing the unconditional `map.tdrawback = true;`, and having to set `map.scrolldir` everywhere to get the credits scrolling in the right direction but make sure the title screen doesn't start scrolling like a descending tower, too. After that, the first problem is that it looks like the ACTION press to speed up the credits scrolling doesn't speed up the background, too. No problem, just shove a `!game.press_action` check in `gamecompletelogic()`. However, this introduces a mini-problem, which is that NOW when you hold down ACTION, the background appears to be slowly getting out of sync with the credits text by a one-pixel-per-second difference. This is actually due to the fact that, as a result of me adding the conditional, `map.bscroll` is no longer always unconditionally getting set to 1, while `game.creditposition` IS always unconditionally getting decremented by 1. And when you hold down ACTION, `game.creditposition` gets decremented by 6. Thus, I need to set `map.bscroll` when holding down ACTION to be 7, which is 6 plus 1. Then we have another problem, which is that the incoming textures desync when you press ACTION, and when you release ACTION. They desync by precisely 6 pixels, which should be a familiar number. I (eventually) tracked this down to `map.bypos` being updated at the same time `map.bscroll` is, even though `map.bypos` should be updated a frame later AFTER updating `map.bscroll`. So I had to change the `map.bypos` update in `gamecompleteinput()` and `gamecompletelogic()` to be `map.bypos += map.bscroll;` and then place it before any `map.bscroll` update, thus ensuring that `map.bscroll` updates exactly one frame before `map.ypos` does. I had to move the `map.bypos += map.bscroll;` to be in `gamecompleteinput()`, because `gamecompleteinput()` comes first before `gamecompletelogic()` in the `main.cpp` game loop, otherwise the `map.bypos` update won't be delayed by one frame for when you press ACTION to make it go faster, and thus cause a desync when you press ACTION. Oh and then after that, I had to make the descending tower background draw a THIRD row of incoming tiles, otherwise you could see some black flickering at the bottom of the screen when you held down ACTION. All of this took me way too long to figure out, but now the credits scroll works perfectly while being more optimized.
2020-04-30 05:52:33 +02:00
2020-01-01 21:29:24 +01:00
if (key.isDown(KEYBOARD_z) || key.isDown(KEYBOARD_SPACE) || key.isDown(KEYBOARD_v) || key.isDown(game.controllerButton_flip))
{
game.creditposition -= 6;
if (game.creditposition <= -Credits::creditmaxposition)
2020-01-01 21:29:24 +01:00
{
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
if (graphics.fademode == FADE_NONE)
2020-01-01 21:29:24 +01:00
{
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
graphics.fademode = FADE_START_FADEOUT;
2020-01-01 21:29:24 +01:00
}
game.creditposition = -Credits::creditmaxposition;
2020-01-01 21:29:24 +01:00
}
else
{
graphics.titlebg.bscroll = +7;
2020-01-01 21:29:24 +01:00
}
game.press_action = true;
}
if (key.isDown(KEYBOARD_ENTER)|| key.isDown(game.controllerButton_map)) game.press_map = true;
if (!game.mapheld)
{
if(game.press_map)
{
//Return to game
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
if(graphics.fademode == FADE_NONE)
2020-01-01 21:29:24 +01:00
{
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
graphics.fademode = FADE_START_FADEOUT;
2020-01-01 21:29:24 +01:00
}
}
}
if (!game.press_map)
{
game.mapheld = false;
}
2020-01-01 21:29:24 +01:00
}
void gamecompleteinput2(void)
2020-01-01 21:29:24 +01:00
{
game.press_left = false;
game.press_right = false;
game.press_action = false;
game.press_map = false;
game.press_interact = false;
2020-01-01 21:29:24 +01:00
//Do this here because input comes first
game.oldcreditposx = game.creditposx;
2020-01-01 21:29:24 +01:00
if (key.isDown(KEYBOARD_z) || key.isDown(KEYBOARD_SPACE) || key.isDown(KEYBOARD_v) || key.isDown(game.controllerButton_flip))
{
game.creditposx++;
game.oldcreditposx++;
2020-01-01 21:29:24 +01:00
if (game.creditposy >= 30)
{
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
if(graphics.fademode == FADE_NONE)
2020-01-01 21:29:24 +01:00
{
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
graphics.fademode = FADE_START_FADEOUT;
2020-01-01 21:29:24 +01:00
music.fadeout();
}
}
game.press_action = true;
}
if (key.isDown(KEYBOARD_ENTER) || key.isDown(game.controllerButton_map)) game.press_map = true;
if (!game.mapheld)
{
if(game.press_map)
{
//Return to game
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
if(graphics.fademode == FADE_NONE)
2020-01-01 21:29:24 +01:00
{
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
graphics.fademode = FADE_START_FADEOUT;
2020-01-01 21:29:24 +01:00
music.fadeout();
}
}
}
if (!game.press_map)
{
game.mapheld = false;
}
2020-01-01 21:29:24 +01:00
}