1
0
Fork 0
mirror of https://github.com/TerryCavanagh/VVVVVV.git synced 2024-11-05 02:39:41 +01:00
Commit graph

335 commits

Author SHA1 Message Date
Misa
2c8d338e47 Add graphic options and game options to editor settings
This is a small quality-of-life tweak that makes it so if you're in the
middle of editing a level, you don't have to save the level, exit to the
menu, change whatever setting you wanted, re-enter the editor, and type
in the level name, just to change one setting. This is the same as
adding Graphic Options and Game Options to the in-game pause menu,
except for the editor, too.

To do this, I'm reusing Game::returntopausemenu() (because all of its
callers are the same callers for returning to editor settings) and
renamed it to returntoingame(), then added a variable named
ingame_editormode to Game. When we're in the options menus but still in
the editor, BOTH ingame_titlemode and ingame_editormode will be true.
2021-03-18 23:01:00 -04:00
Misa
22d71affba De-duplicate number of menu text bytes
I've moved this to a define that gets declared in Game.h. I could've
made it a const int, but that's only legal in C++ mode.
2021-03-06 22:14:24 -05:00
Misa
40c6a01917 Make saveFilePath not an std::string
Since it only ever gets assigned from FILESYSTEM_getUserSaveDirectory(),
and that function returns a C string, and the variable is only ever read
from again, this doesn't need to be an std::string.
2021-03-06 22:13:39 -05:00
Misa
22ced8b59b Don't use std::strings when comparing key names
There's no need to create an std::string for every single element just
to see if it's a key name.

At least in libstdc++, there's an optimization where std::strings that
are 16 characters or less don't allocate on the heap, and instead use
the internal 16-char buffer directly in the control structure of the
std::string. However, it's not guaranteed that all the element names
we'll get will always be 16 chars or less, and in case the std::string
does end up allocating on the heap, we have no reason for it to allocate
on the heap; so we should just convert these string comparisons to C
strings instead.
2021-03-06 13:40:31 -05:00
Misa
37947814aa Remove unnecessary currentmenuoption reassignments
Now that recreating the same menu keeps currentmenuoption, we can remove
all these superfluous assignments. This means repeating ourselves less;
in case the option numbers change in the future, we won't have to
remember to update these reassignments, too.
2021-03-05 10:03:35 -05:00
Misa
ac04281a9f Keep currentmenuoption if it's the same menu
When recreating the same menu, there's basically no reason to reset the
currently-selected menu option. (Also, no need to worry about indexing
out of bounds or anything - the number gets checked while iterating over
all menu options; it's never used to actually index anything. At worst
there might be a 1-frame flicker as the bounds code in gameinput() kicks
in, but that shouldn't happen anyways.)
2021-03-05 10:03:35 -05:00
Misa
502a34bf64 Add previous song option to editor music screen
This is just a small quality-of-life feature - it's annoying to have to
press ACTION 15 times in order to cycle back through to the previous
song.
2021-03-05 00:57:11 -08:00
Misa
6a3a1fe147
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.

This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.

I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 17:23:59 -05:00
Misa
3927bb9713 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.
2021-02-16 19:31:23 -05:00
Misa
84ac4a40c1 Refactor loading arrays from XML to not use the STL
The current way "arrays" from XML files are loaded (before this commit
is applied) goes something like this:

1. Read the buffer of the contents of the tag using TinyXML-2.

2. Allocate a buffer on the heap of the same size, and copy the
   existing buffer to it. (This is what the statement `std::string
   TextString = pText;` does.)

3. For each delimiter in the heap-allocated buffer...

   a. Allocate another buffer on the heap, and copy the characters from
      the previous delimiter to the delimiter you just hit.

   b. Then allocate the buffer AGAIN, to copy it into an std::vector.

4. Then re-allocate every single buffer YET AGAIN, because you need to
   make a copy of the std::vector in split() to return it to the caller.

As you can see, the existing way uses a lot of memory allocations and
data marshalling, just to split some text.

The problem here is mostly making a temporary std::vector of split text,
before doing any actual useful work (most likely, putting it into an
array or ANOTHER std::vector - if the latter, then that's yet another
memory allocation on top of the memory allocation you already did; this
memory allocation is unavoidable, unlike the ones mentioned earlier,
which should be removed).

So I noticed that since we're iterating over the entire string once
(just to shove its contents into a temporary std::vector), and then
basically iterating over it again - why can't the whole thing just be
more immediate, and just be iterated over once?

So that's what I've done here. I've axed the split() function (both of
them, actually), and made next_split() and next_split_s().

next_split() will take an existing string and a starting index, and it
will find the next occurrence of the given delimiter in the string. Once
it does so, it will return the length from the previous starting index,
and modify your starting index as well. The price for immediateness is
that you're supposed to handle keeping the index of the previous
starting index around in order to be able to use the function; updating
it after each iteration is also your responsibility.

(By the way, next_split() doesn't use SDL_strchr(), because we can't get
the length of the substring for the last substring. We could handle this
special case specifically, but it'd be uglier; it also introduces
iterating over the last substring twice, when we only need to do it
once.)

next_split_s() does the same thing as next_split(), except it will copy
the resulting substring into a buffer that you provide (along with its
size). Useful if you don't particularly care about the length of the
substring.

All callers have been updated accordingly. This new system does not make
ANY heap allocations at all; at worst, it allocates a temporary buffer
on the stack, but that's only if you use next_split_s(); plus, it'd be a
fixed-size buffer, and stack allocations are negligible anyway.

This improves performance when loading any sort of XML file, especially
loading custom levels - which, on my system at least, I can noticeably
tell (there's less of a freeze when I load in to a custom level with
lots of scripts). It also decreases memory usage, because the heap isn't
being used just to iterate over some delimiters when XML files are
loaded.
2021-02-15 23:24:31 -05:00
Misa
ca973a6547 Unindent from collapsing empty pText check
As always, the diff algorithms do terribly with unindentation, so I'm
doing the actual unindenting in this commit.
2021-02-15 23:24:31 -05:00
Misa
8d5829dd26 Remove indentation level from checking for empty pText
Instead of checking the length() of an std::string, just check if
pText[0] is equal to '\0'.

This will have to be done anyway, because I'm going to get rid of the
std::string allocation here, and I noticed this inefficiency in the
indentation, so I'm going to remove it.

The actual unindent will be done in the next commit.
2021-02-15 23:24:31 -05:00
Misa
968bb5f960 De-duplicate <customlevelscore> loading to use LOAD_ARRAY_RENAME()
This now means every XML array loading is done with common,
re-duplicated code. The only exceptions to this are special cases other
than the the majority of cases; the majority being a simple matter of
reading an array of integers and putting it into another array.

Seems like the only reason I hadn't caught the <customlevelscore> tag
until now was because I was focused on de-duplicating all the array
loads in Game::loadstats() and below, forgetting about
Game::loadcustomlevelstats().
2021-02-15 23:24:31 -05:00
Misa
6dc423e78e Move LOAD_ARRAY[_RENAME] macros to before loadcustomlevelstats()
In order to be able to use the LOAD_ARRAY() and LOAD_ARRAY_RENAME()
macros in Game::loadcustomlevelstats(), they have to be moved to earlier
in the file.
2021-02-15 23:24:31 -05:00
Misa
fe8d163041 Fix reading uninitialized memory when creating Menu::levellist
Valgrind reported this.

The error here is that the buffer here is only guaranteed to be
initialized up until (and including) the null-terminator, by
SDL_snprintf(). Iterating over the entire allocated buffer is bad and I
should feel bad as the girl who wrote this code; doing that reads
uninitialized memory and passes it to SDL_tolower().

As a bonus, the iterator increment is now a preincrement instead of a
postincrement.
2021-02-15 23:07:35 -05:00
Misa
39e316828e Add NULL guards to Game::savestats() and savesettings()
This is so those functions can safely be called when
graphics.screenbuffer is NULL.
2021-02-15 23:07:35 -05:00
Misa
ef4418a7cb Remove commented-out code in Game::gameclock()
This code involves stringstreams, which causes a false-positive any time
I ripgrep for stringstream in the codebase.
2021-02-07 18:30:13 -05:00
Misa
626aac59fb Fix No Death Mode results being reset before being shown
This does the same thing as the last commit, but for No Death Mode
instead of Time Trials. Whenever you die in No Death Mode, or complete
it, all the relevant variables get copied to variables prefixed with
'ndmresult' that never get reset by script.hardreset(), and these
variables are what titlerender() use, instead of the "live" ones.
2021-01-18 13:06:15 -05:00
Misa
4d7baa9e9e Fix Time Trial results being reset before being shown
This makes it so when a Time Trial gets completed, all the relevant
variables get copied onto variables prefixed with 'timetrialresult',
which never get reset by script.hardreset(). Then titlerender() will use
those variables accordingly.
2021-01-18 13:06:15 -05:00
Misa
ee0ba8a723 Clean up all exit paths to the menu to use common code
There are multiple different exit paths to the main menu. In 2.2, they
all had a bunch of copy-pasted code. In 2.3 currently, most of them use
game.quittomenu(), but there are some stragglers that still use
hand-copied code.

This is a bit of a problem, because all exit paths should consistently
have FILESYSTEM_unmountassets(), as part of the 2.3 feature of per-level
custom assets. Furthermore, most (but not all) of the paths call
script.hardreset() too, and some of the stragglers don't. So there could
be something persisting through to the title screen (like a really long
flash/shake timer) that could only persist if exiting to the title
screen through those paths.

But, actually, it seems like there's a good reason for some of those to
not call script.hardreset() - namely, dying or completing No Death Mode
and completing a Time Trial presents some information onscreen that
would get reset by script.hardreset(), so I'll fix that in a later
commit.

So what I've done for this commit is found every exit path that didn't
already use game.quittomenu(), and made them use game.quittomenu(). As
well, some of them had special handling that existed on top of them
already having a corresponding entry in game.quittomenu() (but the path
would take the special handling because it never did game.quittomenu()),
so I removed that special handling as well (e.g. exiting from a custom
level used returntomenu(Menu::levellist) when quittomenu() already had
that same returntomenu()).

The menu that exiting from the level editor returns to is now handled in
game.quittomenu() as well, where the map.custommode branch now also
checks for map.custommodeforreal. Unfortunately, it seems like entering
the level editor doesn't properly initialize map.custommode, so entering
the level editor now initializes map.custommode, too.

I've also taken the music.play(6) out of game.quittomenu(), because not
all exit paths immediately play Presenting VVVVVV, so all exit paths
that DO immediately play Presenting VVVVVV now have music.play(6)
special-cased for them, which is fine enough for me.

Here is the list of all exit paths to the menu:
- Exiting through the pause menu (without glitchrunner mode)
- Exiting through the pause menu (with glitchrunner mode)
- Completing a custom level
- Completing a Time Trial
- Dying in No Death Mode
- Completing No Death Mode
- Completing an Intermission replay
- Exiting from the level editor
- Completing the main game
2021-01-18 13:06:15 -05:00
Misa
07cc5f68ac Remove commented-out code from gamestates 3101 and 3500
Comments in general don't get verified by the compiler, but
commented-out code is even worse. Especially since this looks to be
outdated code.

As always, if we need some of this code, then we can just look back in
the Git history.
2021-01-18 13:06:15 -05:00
Misa
769f99f590 Reduce dependency on libc functions
During 2.3 development, there's been a gradual shift to using SDL stdlib
functions instead of libc functions, but there are still some libc
functions (or the same libc function but from the STL) in the code.

Well, this patch replaces all the rest of them in one fell swoop.

SDL's stdlib can replace most of these, but its SDL_min() and SDL_max()
are inadequate - they aren't really functions, they're more like macros
with a nasty penchant for double-evaluation. So I just made my own
VVV_min() and VVV_max() functions and placed them in Maths.h instead,
then replaced all the previous usages of min(), max(), std::min(),
std::max(), SDL_min(), and SDL_max() with VVV_min() and VVV_max().

Additionally, there's no SDL_isxdigit(), so I just implemented my own
VVV_isxdigit().

SDL has SDL_malloc() and SDL_free(), but they have some refcounting
built in to them, so in order to use them with LodePNG, I have to
replace the malloc() and free() that LodePNG uses. Which isn't too hard,
I did it in a new file called ThirdPartyDeps.c, and LodePNG is now
compiled with the LODEPNG_NO_COMPILE_ALLOCATORS definition.

Lastly, I also refactored the awful strcpy() and strcat() usages in
PLATFORM_migrateSaveData() to use SDL_snprintf() instead. I know save
migration is getting axed in 2.4, but it still bothers me to have
something like that in the codebase otherwise.

Without further ado, here is the full list of functions that the
codebase now uses:

- SDL_strlcpy() instead of strcpy()
- SDL_strlcat() instead of strcat()
- SDL_snprintf() instead of sprintf(), strcpy(), or strcat() (see above)
- VVV_min() instead of min(), std::min(), or SDL_min()
- VVV_max() instead of max(), std::max(), or SDL_max()
- VVV_isxdigit() instead of isxdigit()
- SDL_strcmp() instead of strcmp()
- SDL_strcasecmp() instead of strcasecmp() or Win32 strcmpi()
- SDL_strstr() instead of strstr()
- SDL_strlen() instead of strlen()
- SDL_sscanf() instead of sscanf()
- SDL_getenv() instead of getenv()
- SDL_malloc() instead of malloc() (replacing in LodePNG as well)
- SDL_free() instead of free() (replacing in LodePNG as well)
2021-01-12 14:02:31 -05:00
Misa
b1558f574c Remove game.savemystats
This variable seems to have been intended to make sure
game.savestatsandsettings() was called at the end of the frame, or make
sure that it didn't get called more than once per frame. I don't see any
frame ordering-related reason why it needs to be called specifically at
the end of the frame (the function doesn't modify any state), so it's
more plausible that it was added to make sure it didn't get called more
than one per frame.

However, upon further analysis, none of the code paths where
game.savemystats is used ever calls or sets game.savemystats more than
once, and a majority of the code directly calls
game.savestatsandsettings() anyway, so there's no reason for this
variable to exist. If we ever need to make sure it doesn't get called
more than once, and there's no way to change the code paths around to
prevent it otherwise, we can use the defer callbacks system that I added
to #535, when it gets merged.
2021-01-11 00:26:14 -05:00
Misa
17d06f06be Remove map.finalx/y and map.customx/y
These variables basically serve no purpose. map.customx and map.customy
are clearly never used. map.finalx and map.finaly, on the other hand,
are basically always game.roomx and game.roomy respectively if
map.finalmode is on, and if it's off, then they don't matter.

Also, there are some weird and redundant variable assignments going on
with these; most notably in map.gotoroom(), where rx/ry (local
variables) get assigned to finalx/finaly, then finalx/finaly get
assigned to game.roomx/game.roomy, then finalx/finaly get assigned to
rx/ry. If finalx/finaly made a difference, then there'd be no need to
assign finalx/finaly back to rx/ry. So it makes the code clearer to
remove these weird bits of code.
2021-01-11 00:24:59 -05:00
Misa
e9c62ea9a3 Clean up unnecessary exports and add static keywords
This patch cleans up unnecessary exports from header files (there were
only a few), as well as adds the static keyword to all symbols that
aren't exported and are specific to a file. This helps the linker out in
not doing any unnecessary work, speeding it up and avoiding silent
symbol conflicts (otherwise two symbols with the same name (and
type/signature in C++) would quietly resolve as okay by the linker).
2021-01-10 12:23:59 -05:00
Misa
775ac4c40c Fix bringing up map menu during gamemode(teleporter)
When gamemode(teleporter) gets run in a script, it brings up a read-only
version of the teleporter screen, intended only for displaying rooms on
the minimap.

However, ever since 2.3 allowed bringing up the map screen during
cutscenes (in order to prevent softlocks), bringing up the map screen
during this mode would (1) do an unnecessary animation of suddenly
switching back to the game and bringing up the menu screen again (even
though the menu screen has already been brought up), and (2) would let
you close the menu entirely and go back to GAMEMODE, thus
unintentionally closing the teleporter screen and kind of ruining the
cutscene.

To fix this, when you bring up the map screen, it will instead instantly
transition to the map screen. And when you bring it down, it will also
instantly transition back to the teleporter screen.

But that's not all. The previous behavior was actually kind of a nice
failsafe, in that if you somehow got stuck in a state where a script ran
gamemode(teleporter), but stopped running before it could take you out
of that mode by running gamemode(game), then you could return to
GAMEMODE yourself by bringing up the map screen and then bringing it
back down. So I've made sure to keep that failsafe behavior, only as
long as there isn't a script running.
2020-12-28 21:12:51 -05:00
Misa
0eddd2d015 De-duplicate menu animation code when bringing up map screen
When bringing up the map screen, the game does a small menu animation
where the menu comes in from the bottom. The code to calculate the menu
offset is copy-pasted everywhere, so I thought I'd de-duplicate it to
make my life easier when working with it. I also included the
game.gamestate assignment in the de-duplicated function, so it would be
easier for a future bugfix.

At the same time, I'm also removing all the BlitSurfaceStandard()s that
copied menubuffer to backBuffer. The red flag is that this blit happened
for every single entry point to MAPMODE and TELEPORTERMODE, except for
the script command gamemode(teleporter). Pressing Enter to bring up the
map screen, pressing Enter to quit the Super Gravitron, pressing Esc to
bring up the pause screen, and pressing Enter to bring up the teleporter
screen all do this blit, so if this blit was there to fix a bug, then
there's a bug with using the script command gamemode(teleporter)... but,
as far as I can tell, there isn't.

That's because the blit basically does nothing. All the blit does is
copy menubuffer onto backBuffer. Then the next thing that happens is
that either maprender() or teleporterrender() will be called, and the
first thing that those functions will always do is fill backBuffer with
solid black, completely overriding the previous blit. So that's why
removing this blit won't have any effect, and it can be safely removed
for code clarity.
2020-12-28 19:55:23 -05:00
Misa
cbf3da312f Fix segfault when settings.vvv or unlock.vvv is missing
To fix this bug, all we have to do is just pass the existing
ScreenSettings* that we have in loadstats() to savestats(), and in
loadsettings() to savesettings().

Fixes #556. Depends on #558.
2020-12-21 20:16:45 -05:00
Misa
55163e90d5 Allow Game::savestats() to accept a pointer to ScreenSettings
Another step to fix the bug #556 is to allow Game::savestats() to accept
a pointer to an existing ScreenSettings struct. This entails refactoring
Game::savesettings() and Game::serializesettings() to accept the
function as well, along with adding Screen::GetSettings() so the
settings of the current Screen can be easily grabbed.
2020-12-21 20:15:30 -05:00
Misa
b62908f0f4 Refactor Game::savestats() to not use a default argument
In order to be able to fix the bug #556, I'm planning on adding
ScreenSettings* to the settings.vvv write function. However, that
entails adding another argument to Game::savesettings(), which is going
to be really messy given the default argument of Game::savestats().
That, combined with the fact that the code comment at the site of the
implementation of Game::savestats() being wrong (!!!), leads me to
believe that using default function arguments here isn't worth it.

Instead, what I've done is made it so callers are explicit about whether
or not they're calling savestats(), savesettings(), or both at the same
time. If they are calling both at the same time, then they will be using
a new function named savestatsandsettings().

In short, these are the interface changes:
 * bool Game::savestats(bool) has been removed
 * bool Game::savestatsandsettings() has been added
 * void Game::savestats_menu() has been renamed to
   void Game::savestatsandsettings_menu()
 * All previous callers of bool Game::savestats() are now using bool
   Game::savestatsandsettings()
 * The one caller of bool Game::savestats(bool) is now using bool
   Game::savestats()
2020-12-21 19:37:34 -05:00
Dav999-v
04da8085a3 Simplify lua-esque assignment
`success = success && savesettings();` is now changed to
`success &= savesettings();`. It's bitwise, and I think C++ should have
had a &&= for completeness, but it shouldn't matter here.
2020-12-18 14:16:53 -05:00
Dav999-v
444074a931 Add error messages for failed writes to disk of settings
Changing settings would most of the time attempt to save unlock.vvv and
now also settings.vvv, but there would be no feedback whether the files
have been saved successfully or not. Now, if saving fails when changing
settings in the menu, a warning message will be shown. The setting will
still be applied of course, but the user will be informed it couldn't
be saved. This message can be silenced until the game is restarted,
because I can imagine this could get very annoying when someone already
knows their settings aren't writeable.

Also, some options didn't even save settings in the first place. These
are turning off invincibility, and by coincidence precisely all the
options in the advanced options menu. I made sure these options now do
so.
2020-12-18 14:16:53 -05:00
Misa
fb19787489 Remove duplicate game.controllerSensitivity proxy
It wasn't a direct duplicate of key.sensitivity, but it was still
basically the same thing. Although to be fair, at least the case-switch
conversion didn't get duplicated everywhere unlike game.slowdown.

So now key.sensitivity functions the same as game.controllerSensitivity,
and it only gets converted to its actual value whenever a joystick input
happens in key.Poll(), unlike previously where it got converted every
single frame on the title screen (there was even a comment that said
"TODO bit wasteful doing this every poll").
2020-12-18 10:02:18 -05:00
Misa
bc9dff8c2a Remove game.gameframerate as duplicate of game.slowdown
game.gameframerate seems to exist for converting the value of
game.slowdown into an actual timestep value, when really the timestep
value should just use game.slowdown directly with a fast lookup table.
Otherwise, there's a bunch of duplicated game.slowdown case-switches
everywhere, which adds up to a large, annoying pile should the values be
changed in the future. But now the duplicate variable has been removed,
and with it, all the copy-pasted case-switches.

Also, the game speed text rendering in Menu::accessibility and
Menu::setslowdown has been factored out to a function and de-duplicated
as well.
2020-12-18 10:02:18 -05:00
Misa
ee44f81d4c Remove duplicate using-MMMMMM variable from Game
game.usingmmmmmm was a duplicate of music.usingmmmmmm, and has been
removed.
2020-12-18 10:02:18 -05:00
Misa
40b7ddda05 Remove duplicate screen configuration variables
There were some duplicate Screen configuration variables that were on
Game, when there didn't need to be.

 - game.fullScreenEffect_badSignal is a duplicate of
   graphics.screenbuffer->badSignalEffect
 - game.fullscreen is a duplicate of !graphics.screenbuffer->isWindowed
 - game.stretchMode is a duplicate of graphics.screenbuffer->stretchMode
 - game.useLinearFilter is a duplicate of
   graphics.screenbuffer->isFiltered

These duplicate variables have been removed now.

I put indentation when handling the ScreenSettings struct in main() so
the local doesn't live for the entirety of main() (which is the entirety
of the program).
2020-12-18 10:02:18 -05:00
Misa
6f4e89e336 Remove unneeded tdrawback/backgrounddrawn assigns in returntopausemenu
Assigning these variables is now wholly unnecessary ever since #522 got
merged, and in fact setting graphics.backgrounddrawn to false actually
causes the warp background to "skip" when the map screen gets closed. So
this fixes that bug, too.
2020-11-07 16:25:04 -05:00
Misa
5759408ded Remove Game::shouldreturntopausemenu
This kludge variable was used to re-set the warp background after coming
back from the in-game settings menus. But since #522 got merged, this
has no longer been necessary.
2020-11-07 16:25:04 -05:00
Misa
c1ca57e096 Change nicefade to bool instead of int
If it's treated like a bool, why shouldn't it be one?
2020-11-06 06:09:31 -05:00
Misa
5aa8b99113 Move all settings to settings.vvv
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.

The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.

The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).

Closes #373 fully.
2020-11-04 12:06:57 -05:00
Misa
9e1ba97f63 Fix style of showmousecursor boolean check
Same-line brace (but not previous-line brace) AND an unnecessary boolean
comparison to constant boolean? Two-for-one whammy.
2020-11-04 12:06:57 -05:00
Misa
6de14d95ee Don't unset Flip Mode when deleting all data in M&P
Whenever you delete all your save data, your settings aren't changed at
all, and you could resave them if you fiddled with a setting somewhere.
But the full game doesn't count Flip Mode as a setting, instead it
counts it as an unlock. This means deleting your save data would unset
Flip Mode in M&P, which would seem weird because in M&P it's just a
simple setting.

For consistency, Flip Mode shouldn't be unset when deleting save data in
M&P.
2020-11-04 01:00:49 -05:00
Dav999-v
4ebf89e844 Add error message if quicksave fails 2020-11-03 22:05:26 -05:00
Dav999-v
5dbb90c7fd Add error message if telesave fails
At least, whenever it would say "Game Saved", it now instead gives an
error message if saving is not successful.
2020-11-03 22:05:26 -05:00
Misa
ab446790e8 Axe BoolToString() in favor of int casts
When I encountered this function, I asked myself, why make a dedicated
function instead of casting to int?

Well, as it turns out, you can't just cast to int, because you have to
convert it to a string by doing help.String(number).c_str(), like all
the other ints. So it's actually more wasteful to do it the normal way
instead of using BoolToString().

That is, until my recent changes came along and made it so that you can
just provide an int and it'll automatically be converted to a string, no
questions asked. Now, it's more optimal to do a straight cast to int
instead of having to go through a middleman function. So this function
is getting axed in favor of casting to int instead.
2020-11-03 13:30:53 -05:00
Misa
58795a1381 Add XML forwards compatibility to custom level quicksaves
Now these files might have things added to them in the future? I'm not
so sure. But at least they're XML forwards-compatible now.
2020-11-03 13:30:53 -05:00
Misa
0eb39644c1 Add XML forwards compatibility to tsave.vvv and qsave.vvv
These files are probably not of much consequence. The main game isn't
going to have things to be added to it anytime soon.
2020-11-03 13:30:53 -05:00
Misa
17b8d0308e Add XML forwards compatibility to unlock.vvv
This file is probably the biggest one, as there will be more settings
added in the future and we don't want people's settings to be erased. Of
course, this file will be migrated to a settings.vvv sometime later in
2.3 in order to prevent 2.2 from erasing 2.3 settings.
2020-11-03 13:30:53 -05:00
Misa
43e57f5483 Add XML forwards compatibility to levelstats.vvv
Not that hard to do for the smallest XML file in the game.
2020-11-03 13:30:53 -05:00
Misa
a1957fa518 Remove kludge_to_bg() and bg_to_kludge()
Now that tower, title, and horizontal/veritcal warp backgrounds all use
separate buffers, there's no longer any need to temporarily store
variables as a workaround for the buffers stepping on each other.
2020-11-03 13:25:03 -05:00
Misa
70f3d457dd Move title usages of towerbg to titlebg
With the previous commit in place, we can now simply move some usages of
the previous towerbg to use a separate object instead. That way, we
don't have to mess with a monolithic state, or some better way to phrase
what I just said, and we instead have two separate objects that can
coexist side-by-side.
2020-11-03 13:25:03 -05:00
Misa
72c048d71e Refactor tower background to use a separate object instead
Previously, the tower background was controlled by a disparate set of
attributes on Graphics and mapclass, and wasn't really encapsulated. (If
that's what that word means, I don't particularly care about
object-oriented lingo.) But now, all relevant things that a tower
background has has been put into a TowerBG struct, so it will be easy to
make multiple copies without having to duplicate the code that handles
it.
2020-11-03 13:25:03 -05:00
Misa
b8a4b4dfe7 Restore previous oldxp/oldyp variables in favor of lerpoldxp/lerpoldyp
I was investigating a desync in my Nova TAS, and it turns out that
the gravity line collision functions check for the `oldxp` and `oldyp`
of the player, i.e. their position on the previous frame, along with
their position on the current frame. So, if the player either collided
with the gravity line last frame or this frame, then the player collided
with the gravity line this frame.

Except, that's not actually true. It turns out that `oldxp` and `oldyp`
don't necessarily always correspond to the `xp` and `yp` of the player
on the previous frame. It turns out that your `oldyp` will be updated if
you stand on a vertically moving platform, before the gravity line
collision function gets ran. So, if you were colliding with a gravity
line on the previous frame, but you got moved out of there by a
vertically moving platform, then you just don't collide with the gravity
line at all.

However, this behavior changed in 2.3 after my over-30-FPS patch got
merged (#220). That patch took advantage of the existing `oldxp` and
`oldyp` entity attributes, and uses them to interpolate their positions
during rendering to make everything look real smooth.

Previously, `oldxp` and `oldyp` would both be updated in
`entityclass::updateentitylogic()`. However, I moved it in that patch to
update right before `gameinput()` in `main.cpp`.

As a result, `oldyp` no longer gets updated whenever the player stands
on a vertically moving platform. This ends up desyncing my TAS.

As expected, updating `oldyp` in `entityclass::movingplatformfix()` (the
function responsible for moving the player whenever they stand on a
vertically moving platform) makes it so that my TAS syncs, but the
visuals are glitchy when standing on a vertically moving platform. And
as much as I'd like to get rid of gravity lines checking for whether
you've collided with them on the previous frame, doing that desyncs my
TAS, too.

In the end, it seems like I should just leave `oldxp` and `oldyp` alone,
and switch to using dedicated variables that are never used in the
physics of the game. So I'm introducing `lerpoldxp` and `lerpoldyp`, and
replacing all instances of using `oldxp` and `oldyp` that my over-30-FPS
patch added, with `lerpoldxp` and `lerpoldyp` instead.

After doing this, and applying #503 as well, my Nova TAS syncs after
some minor but acceptable fixes with Viridian's walkingframe.
2020-10-11 16:19:26 -04:00
Misa
cbceeccf78 Clean up and prevent unnecessary qualifiers to self
By "unnecessary qualifiers to self", I mean something like using the
'game.' qualifier for a variable on the Game class when you're inside a
function on the Game class itself. This patch is to enforce consistency
as most of the code doesn't have these unnecessary qualifiers.

To prevent further unnecessary qualifiers to self, I made it so the
extern in each header file can be omitted by using a define. That way,
if someone writes something referring to 'game.' on a Game function,
there will be a compile error.

However, if you really need to have a reference to the global name, and
you're within the same .cpp file as the implementation of that object,
you can just do the extern at the function-level. A good example of this
is editorinput()/editorrender()/editorlogic() in editor.cpp. In my
opinion, they should probably be split off into their own separate file
because editor.cpp is getting way too big, but this will do for now.
2020-09-28 01:34:40 -04:00
Misa
96be0fc7a9 Fix unwinnable save from rescuing Violet out of order
You're intended to rescue Violet first, and not second, third, or
fourth, and especially not last.

If you rescue her second, third, or fourth, your crewmate progress will
be reset, but you won't be able to re-rescue them again. This is because
Vitellary, Verdigris, Victoria, and Vermilion will be temporarily marked
as rescued during the `bigopenworld` cutscene, so duplicate versions of
them don't spawn during the cutscene, and then will be marked as missing
later to undo it.

This first issue can be trivially fixed by simply toggling flags to
prevent duplicates of them from spawning during the cutscene instead of
fiddling with their rescue statuses.

However, there is still another issue. If you rescue Violet last, then
you won't be warped to the Final Level, meaning you can't properly
complete the game. This can be fixed by adding a `crewrescued() == 6`
check to the Space Station 1 Level Complete cutscene. There is
additionally a temporary unrescuing of Violet so she doesn't get
duplicated during the `bigopenworld` cutscene, and I've had to move that
to the start of the `bigopenworld` and `bigopenworldskip` scripts,
otherwise the `crewrescued() == 6` check won't work properly.

I haven't added hooks for Intermission 1 or 2 because you're not really
meant to play the intermissions with Violet (but you probably could
anyway, there'd just be no dialogue).

Oh, and the pre-Final Level cutscene expects the player to already be
hidden before it starts playing, but if you rescue Violet last the
player is still visible, so I've fixed that. But there still ends up
being two Violets, so I'll probably replace it with a special cutscene
later that's not so nonsensical.
2020-09-25 14:02:09 -04:00
Misa
b34be3f1ac Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.

'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.

Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.

It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-25 13:51:47 -04:00
Misa
a098448e89 Remove unused variable from gamestate 115
For some reason it called obj.getplayer() and did nothing with the
result. Weird. But it does say "Test script for space station" above.

Removing this fixes an 'unused variable' warning.
2020-09-25 13:51:47 -04:00
Misa
fad0071966 Reduce scope of temporary variables in Game::updatestate()
With the scope of these variables reduced, it makes analyzing this
function easier, as you can now clearly see all temporary variables are
actually initialized before they're used.
2020-09-25 13:51:47 -04:00
Misa
7fcc1c8cdc De-duplicate reading/writing main game telesave/quicksave
The game has four different functions for the same XML schema:
Game::loadtele(), Game::savetele(), Game::loadquick(), and
Game::savequick(). This essentially means one XML schema has been
copy-pasted three different times.

I can at least trim that number down to being copy-pasted only one time
by de-duplicating the reading and writing part. So both Game::loadtele()
and Game::loadquick() now use Game::readmaingamesave(), and
Game::savetele() and Game::savequick() now use
Game::writemaingamesave().

This will make it take less work to add XML forwards compatibility
(#373).
2020-09-25 13:32:41 -04:00
Misa
dcf1a81e80 Fix duplicate levels in levelstats if you don't have a higher score
This is a regression from 25779606b4
(PR #74).

On the one hand, I should've thought this through carefully when
implementing the fix for the lower-score overwrite bug. On the other
hand, flibit didn't seem to notice either. And on the third hand, no one
else seems to have noticed, when they have had over 8 months to do so.
Not even the person who originally reported the lower-score overwrite
bug and also reported other bugs I hadn't noticed (e.g. the "You have
rescued a crewmate!" in Flip Mode) noticed this bug, which I believe was
weee50. But to be fair, he does seem to be less active nowadays.

On the fourth hand, I only realized the cause of the duplicate bug after
stepping through it in GDB, instead of just looking at it and going "hey
wait a minute" earlier. I'm surprised it didn't take me longer to
realize the problem.

I'm not sure what all these hands mean anymore, or where I'm getting
extra hands from. Whatever. This regression is fixed now.
2020-09-13 01:33:13 -04:00
Nichole Mattera
b4da818e8c Missed adding the option to the createmenu method. 2020-08-08 19:10:31 -04:00
Nichole Mattera
1376e65b5d Added ability to bind restart to a controller. 2020-08-08 19:02:14 -04:00
Misa
78e87effe7 Replace all usages of atoi() with help.Int()
This will prevent any undefined behavior that might occur as a result of
having invalid input passed into atoi().
2020-08-07 01:00:49 -04:00
Misa
43cf3c4f19 Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.

However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).

So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-06 22:12:15 -04:00
Misa
2710ebeff9 Fix silent music saves playing MMMMMM track 15 if...
This commit fixes a bug that's existed since MMMMMM was added (so, 2.2),
where if you quicksaved in a custom level while no music was playing,
then quit and loaded that quicksave, and you were using PPPPPP while
having MMMMMM available, it would play MMMMMM track 15, even though the
game intends for the music to simply be silent.

This is due to the same bug that lets you play MMMMMM tracks if you're
on PPPPPP - musicclass::play() does a modulo, but C++ modulo is not
guaranteed to be positive given negative inputs, so the 16-track offset
is added to a negative number, resulting in targeting the MMMMMM
soundtrack instead of PPPPPP.

That exploit doesn't harm anyone and shouldn't be fixed, EXCEPT it
causes a problem in this specific case. But this bug can be fixed
without removing that exploit.

Note that I made the check do not-equal to -1 instead of greater-than
-1, so levels that intend on using track -2, -3, -4, etc. upon loading a
quicksave will still work as their creator intended. It's just that
specifically -1 is patched out, just to fix this issue.
2020-08-06 22:11:11 -04:00
Misa
690929d29a Fix dupe stack frame when creating unlock notif menu
The samemenu argument to createmenu() just has to be set to true so
another stack frame doesn't get added.

Fixes #415.
2020-08-03 01:06:54 -04:00
Misa
3aa407e981 Add catch-all softlock protection
What this simply does is make it so that in the event that
game.hascontrol is somehow set to false when there isn't a cutscene
running (i.e. when game.state is 0 and script.running is false), it gets
set back to true again.

There's many ways to interrupt a gamestate and/or a running script, most
notably telejumping and doing a screen transition in the middle of the
animation, interrupting it.

This implements the first part of my idea in this comment:
https://github.com/TerryCavanagh/VVVVVV/issues/391#issuecomment-659757071
2020-08-02 20:21:02 -04:00
AllyTally
d813956879 add newline at the end of game.cpp 2020-08-01 16:09:24 -04:00
AllyTally
0cc765ce7d replace stray tab with spaces
whoops, recently reinstalled windows so my editor settings still arent perfect yet
2020-08-01 16:09:24 -04:00
AllyTally
06e5eb38d9 Fix #380
Achievements could be unlocked in custom levels/make and play, so this adds the wrapper function `game.unlockAchievement` which calls `NETWORK_unlockAchievement` if `map.custommode` is false.
Also, this function and `Game::unlocknum` have both been `ifdef`ed to be empty if MAKEANDPLAY is defined.
2020-08-01 16:09:24 -04:00
AllyTally
720cd7642a fix #382 and fix #370 2020-08-01 16:09:24 -04:00
Misa
fdb01adc68 Set oldxp/oldyp when being teleported around during teleport
This fixes the bug where Viridian would appear to "zip" when they would
be teleported to the position of the teleporter before being flung out
of it.

As discussed in #393, I've also set the oldxp/oldyp when Viridian is
temporarily positioned in the center of the room, even though at this
point they should already be invisible. This is just to be safe.

Fixes #393.
2020-07-21 18:06:41 -04:00
Misa
52f7a587fe Separate includes into sections and alphabetize them
Okay, so basically here's the include layout that this game now
consistently uses:

[The "main" header file, if any (e.g. Graphics.h for Graphics.cpp)]
[blank line]
[All system includes, such as tinyxml2/physfs/utfcpp/SDL]
[blank line]
[All project includes, such as Game.h/Entity.h/etc.]

And if applicable, another blank line, and then some special-case
include screwy stuff (take a look at editor.cpp or FileSystemUtils.cpp,
for example, they have ifdefs and defines with their includes).
2020-07-19 21:37:40 -04:00
Misa
b5ff65c84e Remove unnecessary includes from header files
Including a header file inside another header file means a bunch of
files are going to be unnecessarily recompiled whenever that inner
header file is changed. So I minimized the amount of header files
included in a header file, and only included the ones that were
necessary (system includes don't count, I'm only talking about includes
from within this project). Then the includes are only in the .cpp files
themselves.

This also minimizes problems such as a NO_CUSTOM_LEVELS build failing
because some file depended on an include that got included in editor.h,
which is another benefit of removing unnecessary includes from header
files.
2020-07-19 21:37:40 -04:00
Misa
d455e38715 Use angle brackets for including tinyxml2.h
Since TinyXML2 is a third-party dependency, we should use angle brackets
and treat it like one.
2020-07-19 21:37:40 -04:00
Ethan Lee
264279d17a Warning fix for uninitialized prefix 2020-07-15 12:11:23 -04:00
Misa
f8190439a9 Reset ed.roomnamehide to 0 when exiting playtesting
Now if your cursor was at the bottom of the screen when you entered
playtesting but then was moved to the top during playtesting, when you
exit, the roomname won't instantly disappear and then sheepishly rise
back up again.

And if your cursor was at the bottom of the screen when you entered
playtesting, and exited still being that way, the roomname will rise
back down again and won't instantly disappear.

Both of these behaviors make the roomname movement much more continuous
than it was previously, and it feels smoother and less janky.
2020-07-15 11:45:29 -04:00
Misa
def540d184 Reset ed.notedelay when exiting playtesting
It's not very useful to show a message that was relevant only when you
started playtesting.
2020-07-15 11:45:29 -04:00
Misa
22d556f4be Disable "Game Saved" text box in custommode
Also, use inspecial() instead of writing out each part of the
conditional separately. This just basically adds the insecretlab
conditional to the if-statement, which shouldn't be a big deal.
2020-07-15 11:45:29 -04:00
Misa
846c6f61d4 Use .clear() when removing text boxes in reset functions
graphics.textbox.clear() should be used instead of
graphics.textboxremove() or graphics.textboxremovefast(), because even
with graphics.textboxremovefast(), you'll still have to process at least
one frame of GAMEMODE logic before the text boxes are actually properly
removed, and this caused a 1-frame glitch when exiting playtesting with
text boxes on-screen and then re-entering playtesting.

Technically I could've only fixed it in Game::returntoeditor(), but I
wanted to be safe, so I also fixed it in scriptclass::hardreset(), too.
2020-07-15 11:45:28 -04:00
Misa
7fada0548b Set ed.settingskey to true when returning to editor
This fixes a bug where the settings menu would immediately be brought up
if you used Esc to exit playtesting, unless you were an incredible ninja
and only pressed it for exactly one frame.
2020-07-15 11:45:28 -04:00
Misa
6c52b79ddf Set ed.keydelay to 6 when returning to editor
This fixes an annoying bug where if you use Up or Down to press ACTION
on the "All crewmates rescued!" dialogue whenever you rescue the last
crewmate during playtesting, it'll move you to the room above or below
you. This is because ed.keydelay isn't set to 6 (which is the standard
value that it gets set to whenever you press most keys in the editor),
but now it is.
2020-07-15 11:45:28 -04:00
Misa
1caabe374b Fix return to editor via crewmate rescue not using shouldreturntoeditor
The shouldreturntoeditor variable is supposed to be used because it
fixes the warp background not being reset if you exit into a
horizontally/vertically warping room with a different background. It
also properly resets other variables, which is good, too.
2020-07-15 11:45:28 -04:00
Ethan Lee
11312976fc Fall back to default settings when screenbuffer is not present 2020-07-13 16:09:48 -04:00
Misa
47eba09edb Fix missing newline on printf for customsavequick() failure
A missing newline in a printf() is annoying, it means the next line of
output is concatenated to the previous one.
2020-07-11 17:52:26 -04:00
Misa
199a8f45d6 Fix -playmusic command line option not working
There's a bug where playtesting from Ved doesn't properly play the music
of the level, due to no fault with Ved.

This was because the music was being faded out by
scriptclass::startgamemode() case 23 after main() called music.play().
To fix this, just call music.play() when all the other variables are
being set in Game::customloadquick().
2020-07-11 15:03:37 -04:00
Misa
15319b9ed0 Fix being able to circumvent not-in-Flip-Mode detection
So you get a trophy and achievement for completing the game in Flip
Mode. Which begs the question, how does the game know that you've played
through the game in Flip Mode the entire way, and haven't switched it
off at any point? It looks like if you play normally all the way up
until the checkpoint in V, and then turn on Flip Mode, the game won't
give you the trophy. What gives?

Well, actually, what happens is that every time you press Enter on a
teleporter, the game will set flag 73 to true if you're NOT in Flip
Mode. Then when Game Complete runs, the game will check if flag 73 is
off, and then give you the achievement and trophy accordingly.

However, what this means is that you could just save your game before
pressing Enter on a teleporter, then quit and go into options, turn on
Flip Mode, use the teleporter, then save your game (it's automatically
saved since you just used a teleporter), quit and go into options, and
turn it off. Then you'd get the Flip Mode trophy even though you haven't
actually played the entire game in Flip Mode.

Furthermore, in 2.3 you can bring up the pause menu to toggle Flip Mode,
so you don't even have to quit to circumvent this detection.

To fix both of these exploits, I moved the turning on of flag 73 to
starting a new game, loading a quicksave, and loading a telesave (cases
0, 1, and 2 respectively in scriptclass::startgamemode()). I also added
a Flip Mode check to the routine that runs whenever you exit an options
menu back to the pause menu, so you can't circumvent the detection that
way, either.
2020-07-10 21:35:47 -04:00
Misa
b419601b41 De-duplicate gamestates 300..336
Gamestates 300..336 are used to start scripts in custom levels. However,
it looks like instead of having the cases have common code, each
individual case was copy-pasted numerous times, which is pretty
wasteful.
2020-07-09 21:55:35 -04:00
Misa
3932c75acc Remove unnecessary stub destructors
It's a bit misleading to have these stub destructors when they don't do
anything special.
2020-07-08 19:14:21 -04:00
Misa
307cdefb8e Remove duplicate advancetext/fullScreenEffect_badSignal initializations
They're already initialized in the constructor above, I have no idea why
they're here.
2020-07-08 19:14:21 -04:00
Misa
6b0b86d434 Remove unused attribute advanced_mode from Game
This attribute does absolutely nothing. In fact, it does so much nothing
I can safely remove reading and writing it!
2020-07-08 19:14:21 -04:00
Misa
7128e9c3ac Remove unnecessary string initializations from constructors
std::string is one of those special types that has a constructor that
just initializes itself to a blank state automatically. This means all
`std::string`s are by default already `""`, so there's no need to set
them. And in fact, cppcheck throws out warnings about performance due to
initializing `std::string`s this way.
2020-07-08 19:14:21 -04:00
Ethan Lee
2716296a10 Haiku: Keep the option visible, but note the bug 2020-07-08 14:43:04 -04:00
Ethan Lee
d3f9a36941 Refactor startup to load config before calling Screen::init 2020-07-08 14:30:57 -04:00
Ethan Lee
f8bb8cde32 HACK: Disable VSync option for Haiku 2020-07-08 14:06:45 -04:00
Misa
8366e08fbe Remove usage of std::string from MenuOption
Instead, the string in MenuOption is just a buffer of 161 chars, which
is 40 chars (160 bytes if each were the largest possible UTF-8 character
size) plus a null terminator. This is because the maximum length of a
menu option that can be fit on the screen without going past is 40
chars.
2020-07-06 11:19:24 -04:00
Misa
5fb0b4396a Remove use of std::transform(), use SDL_toupper/lower
There's no need to use a template here. Just manually call SDL_tolower()
or SDL_toupper() as needed.

Oh yeah, and use SDL_tolower() and SDL_toupper() instead of libc
tolower() and toupper().
2020-07-06 11:19:24 -04:00
Misa
fc03fca838 Turn (super)patrons/githubfriends into arrays & move them to new file
So, originally, I wanted to keep them on Game, but it turns out that if
I initialize it in Game.cpp, the compiler will complain that other files
won't know what's actually inside the array. To do that, I'd have to
initialize it in Game.h. But I don't want to initialize it in Game.h
because that'd mean recompiling a lot of unnecessary files whenever
someone gets added to the credits.

So, I moved all the patrons, superpatrons, and GitHub contributors to a
new file, Credits.h, which only contains the list (and the credits max
position calculation). That way, whenever someone gets added, only the
minimal amount of files need to be recompiled.
2020-07-06 11:19:24 -04:00
Misa
cb3afa295a Turn map.explored, map.roomdeaths(final) into plain arrays
They're always fixed-size anyways, there's no need for them to be
vectors.

Also used the new INBOUNDS_ARR() macro for the map.explored bounds
checks in Script.cpp, and made map.explored a proper bool array instead
of an int array.
2020-07-06 11:19:24 -04:00
Misa
0664eac7fc Turn obj.collect and obj.customcollect into plain arrays
Since they're always fixed-size, there's no need for them to be vectors.

Also added an INBOUNDS_ARR() macro to do bounds checks with plain
arrays.
2020-07-06 11:19:24 -04:00