2020-01-01 21:29:24 +01:00
|
|
|
#include <SDL.h>
|
2020-07-19 21:43:29 +02:00
|
|
|
#include <stdio.h>
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "editor.h"
|
2020-07-19 21:05:41 +02:00
|
|
|
#include "Enums.h"
|
|
|
|
#include "Entity.h"
|
2021-02-16 03:53:17 +01:00
|
|
|
#include "Exit.h"
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "FileSystemUtils.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "Game.h"
|
|
|
|
#include "Graphics.h"
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "Input.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "KeyPoll.h"
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "Logic.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "Map.h"
|
2020-07-19 21:05:41 +02:00
|
|
|
#include "Music.h"
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "Network.h"
|
|
|
|
#include "preloader.h"
|
|
|
|
#include "Render.h"
|
2020-11-08 00:47:49 +01:00
|
|
|
#include "RenderFixed.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "Screen.h"
|
|
|
|
#include "Script.h"
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "SoundSystem.h"
|
|
|
|
#include "UtilityClass.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
scriptclass script;
|
|
|
|
|
2020-02-10 03:21:19 +01:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
2020-04-02 23:43:50 +02:00
|
|
|
std::vector<edentities> edentity;
|
|
|
|
editorclass ed;
|
2020-02-10 01:53:01 +01:00
|
|
|
#endif
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Allow using help/graphics/music/game/key/map/obj everywhere
This commit makes `help`, `graphics`, `music`, `game`, `key`, `map`, and
`obj` essentially static global objects that can be used everywhere.
This is useful in case we ever need to add a new function in the future,
so we don't have to bother with passing a new argument in which means we
have to pass a new argument in to the function that calls that function
which means having to pass a new argument into the function that calls
THAT function, etc. which is a real headache when working on fan mods of
the source code.
Note that this changes NONE of the existing function signatures, it
merely just makes those variables accessible everywhere in the same way
`script` and `ed` are.
Also note that some classes had to be initialized after the filesystem
was initialized, but C++ would keep initializing them before the
filesystem got initialized, because I *had* to put them at the top of
`main.cpp`, or else they wouldn't be global variables.
The only way to work around this was to use entityclass's initialization
style (which I'm pretty sure entityclass of all things doesn't need to
be initialized this way), where you actually initialize the class in an
`init()` function, and so then you do `graphics.init()` after the
filesystem initialization, AFTER doing `Graphics graphics` up at the
top.
I've had to do this for `graphics` (but only because its child
GraphicsResources `grphx` needs to be initialized this way), `music`,
and `game`. I don't think this will affect anything. Other than that,
`help`, `key`, and `map` are still using the C++-intended method of
having ClassName::ClassName() functions.
2020-01-29 08:35:03 +01:00
|
|
|
UtilityClass help;
|
|
|
|
Graphics graphics;
|
|
|
|
musicclass music;
|
|
|
|
Game game;
|
|
|
|
KeyPoll key;
|
|
|
|
mapclass map;
|
|
|
|
entityclass obj;
|
2020-06-13 20:44:01 +02:00
|
|
|
Screen gameScreen;
|
Allow using help/graphics/music/game/key/map/obj everywhere
This commit makes `help`, `graphics`, `music`, `game`, `key`, `map`, and
`obj` essentially static global objects that can be used everywhere.
This is useful in case we ever need to add a new function in the future,
so we don't have to bother with passing a new argument in which means we
have to pass a new argument in to the function that calls that function
which means having to pass a new argument into the function that calls
THAT function, etc. which is a real headache when working on fan mods of
the source code.
Note that this changes NONE of the existing function signatures, it
merely just makes those variables accessible everywhere in the same way
`script` and `ed` are.
Also note that some classes had to be initialized after the filesystem
was initialized, but C++ would keep initializing them before the
filesystem got initialized, because I *had* to put them at the top of
`main.cpp`, or else they wouldn't be global variables.
The only way to work around this was to use entityclass's initialization
style (which I'm pretty sure entityclass of all things doesn't need to
be initialized this way), where you actually initialize the class in an
`init()` function, and so then you do `graphics.init()` after the
filesystem initialization, AFTER doing `Graphics graphics` up at the
top.
I've had to do this for `graphics` (but only because its child
GraphicsResources `grphx` needs to be initialized this way), `music`,
and `game`. I don't think this will affect anything. Other than that,
`help`, `key`, and `map` are still using the C++-intended method of
having ClassName::ClassName() functions.
2020-01-29 08:35:03 +01:00
|
|
|
|
2021-01-10 18:14:37 +01:00
|
|
|
static bool startinplaytest = false;
|
|
|
|
static bool savefileplaytest = false;
|
|
|
|
static int savex = 0;
|
|
|
|
static int savey = 0;
|
|
|
|
static int saverx = 0;
|
|
|
|
static int savery = 0;
|
|
|
|
static int savegc = 0;
|
|
|
|
static int savemusic = 0;
|
|
|
|
static std::string playassets;
|
|
|
|
|
|
|
|
static std::string playtestname;
|
|
|
|
|
|
|
|
static volatile Uint32 time_ = 0;
|
|
|
|
static volatile Uint32 timePrev = 0;
|
|
|
|
static volatile Uint32 accumulator = 0;
|
|
|
|
static volatile Uint32 f_time = 0;
|
|
|
|
static volatile Uint32 f_timePrev = 0;
|
2020-06-14 20:45:36 +02:00
|
|
|
|
2020-11-13 02:05:18 +01:00
|
|
|
static inline Uint32 get_framerate(const int slowdown)
|
|
|
|
{
|
|
|
|
switch (slowdown)
|
|
|
|
{
|
|
|
|
case 30:
|
|
|
|
return 34;
|
|
|
|
case 24:
|
|
|
|
return 41;
|
|
|
|
case 18:
|
|
|
|
return 55;
|
|
|
|
case 12:
|
|
|
|
return 83;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 34;
|
|
|
|
}
|
|
|
|
|
2021-01-10 18:14:37 +01:00
|
|
|
static void inline deltaloop();
|
|
|
|
static void inline fixedloop();
|
2020-06-14 20:45:36 +02:00
|
|
|
|
2021-02-16 02:52:41 +01:00
|
|
|
static void cleanup();
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
2020-02-09 00:49:03 +01:00
|
|
|
char* baseDir = NULL;
|
|
|
|
char* assetsPath = NULL;
|
2020-02-03 00:28:26 +01:00
|
|
|
|
De-duplicate and add safety checks to CLI args, fix brace style
The command-line argument parsing code has a lot of copy-paste. This
copy-paste would get even worse if I added safety checks to make sure
you couldn't index argv out-of-bounds by having an argument like
`-renderer` without having anything after it, i.e. you'd be doing the
command `./VVVVVV -renderer`.
Previously, only the playtest arguments (apart from the recently-added
`playassets`) had this safety check, but the message it printed whenever
the safety check failed was always "-playing option requires one
argument" regardless of whatever argument actually failed to be parsed.
So I fixed it so that all arguments actually output the correct
corresponding failed argument instead.
Also, `strcmp(argv[i], <name>) == 0` is really kind of noisy, even if
you understand what it does perfectly well.
So I refactored it with a bunch of macros. ARG() just does the strcmp()
char* comparison, and ARG_INNER() does the safety check and returns 1,
along with printing a message, if the safety check fails.
2020-06-22 01:15:28 +02:00
|
|
|
for (int i = 1; i < argc; ++i)
|
|
|
|
{
|
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 01:17:45 +01:00
|
|
|
#define ARG(name) (SDL_strcmp(argv[i], name) == 0)
|
De-duplicate and add safety checks to CLI args, fix brace style
The command-line argument parsing code has a lot of copy-paste. This
copy-paste would get even worse if I added safety checks to make sure
you couldn't index argv out-of-bounds by having an argument like
`-renderer` without having anything after it, i.e. you'd be doing the
command `./VVVVVV -renderer`.
Previously, only the playtest arguments (apart from the recently-added
`playassets`) had this safety check, but the message it printed whenever
the safety check failed was always "-playing option requires one
argument" regardless of whatever argument actually failed to be parsed.
So I fixed it so that all arguments actually output the correct
corresponding failed argument instead.
Also, `strcmp(argv[i], <name>) == 0` is really kind of noisy, even if
you understand what it does perfectly well.
So I refactored it with a bunch of macros. ARG() just does the strcmp()
char* comparison, and ARG_INNER() does the safety check and returns 1,
along with printing a message, if the safety check fails.
2020-06-22 01:15:28 +02:00
|
|
|
#define ARG_INNER(code) \
|
|
|
|
if (i + 1 < argc) \
|
|
|
|
{ \
|
|
|
|
code \
|
|
|
|
} \
|
|
|
|
else \
|
|
|
|
{ \
|
|
|
|
printf("%s option requires one argument.\n", argv[i]); \
|
2021-02-16 03:53:17 +01:00
|
|
|
VVV_exit(1); \
|
De-duplicate and add safety checks to CLI args, fix brace style
The command-line argument parsing code has a lot of copy-paste. This
copy-paste would get even worse if I added safety checks to make sure
you couldn't index argv out-of-bounds by having an argument like
`-renderer` without having anything after it, i.e. you'd be doing the
command `./VVVVVV -renderer`.
Previously, only the playtest arguments (apart from the recently-added
`playassets`) had this safety check, but the message it printed whenever
the safety check failed was always "-playing option requires one
argument" regardless of whatever argument actually failed to be parsed.
So I fixed it so that all arguments actually output the correct
corresponding failed argument instead.
Also, `strcmp(argv[i], <name>) == 0` is really kind of noisy, even if
you understand what it does perfectly well.
So I refactored it with a bunch of macros. ARG() just does the strcmp()
char* comparison, and ARG_INNER() does the safety check and returns 1,
along with printing a message, if the safety check fails.
2020-06-22 01:15:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ARG("-renderer"))
|
|
|
|
{
|
|
|
|
ARG_INNER({
|
2020-06-22 03:37:44 +02:00
|
|
|
i++;
|
De-duplicate and add safety checks to CLI args, fix brace style
The command-line argument parsing code has a lot of copy-paste. This
copy-paste would get even worse if I added safety checks to make sure
you couldn't index argv out-of-bounds by having an argument like
`-renderer` without having anything after it, i.e. you'd be doing the
command `./VVVVVV -renderer`.
Previously, only the playtest arguments (apart from the recently-added
`playassets`) had this safety check, but the message it printed whenever
the safety check failed was always "-playing option requires one
argument" regardless of whatever argument actually failed to be parsed.
So I fixed it so that all arguments actually output the correct
corresponding failed argument instead.
Also, `strcmp(argv[i], <name>) == 0` is really kind of noisy, even if
you understand what it does perfectly well.
So I refactored it with a bunch of macros. ARG() just does the strcmp()
char* comparison, and ARG_INNER() does the safety check and returns 1,
along with printing a message, if the safety check fails.
2020-06-22 01:15:28 +02:00
|
|
|
SDL_SetHintWithPriority(SDL_HINT_RENDER_DRIVER, argv[i], SDL_HINT_OVERRIDE);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else if (ARG("-basedir"))
|
|
|
|
{
|
|
|
|
ARG_INNER({
|
2020-06-22 03:37:44 +02:00
|
|
|
i++;
|
De-duplicate and add safety checks to CLI args, fix brace style
The command-line argument parsing code has a lot of copy-paste. This
copy-paste would get even worse if I added safety checks to make sure
you couldn't index argv out-of-bounds by having an argument like
`-renderer` without having anything after it, i.e. you'd be doing the
command `./VVVVVV -renderer`.
Previously, only the playtest arguments (apart from the recently-added
`playassets`) had this safety check, but the message it printed whenever
the safety check failed was always "-playing option requires one
argument" regardless of whatever argument actually failed to be parsed.
So I fixed it so that all arguments actually output the correct
corresponding failed argument instead.
Also, `strcmp(argv[i], <name>) == 0` is really kind of noisy, even if
you understand what it does perfectly well.
So I refactored it with a bunch of macros. ARG() just does the strcmp()
char* comparison, and ARG_INNER() does the safety check and returns 1,
along with printing a message, if the safety check fails.
2020-06-22 01:15:28 +02:00
|
|
|
baseDir = argv[i];
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else if (ARG("-assets"))
|
|
|
|
{
|
|
|
|
ARG_INNER({
|
2020-06-22 03:37:44 +02:00
|
|
|
i++;
|
De-duplicate and add safety checks to CLI args, fix brace style
The command-line argument parsing code has a lot of copy-paste. This
copy-paste would get even worse if I added safety checks to make sure
you couldn't index argv out-of-bounds by having an argument like
`-renderer` without having anything after it, i.e. you'd be doing the
command `./VVVVVV -renderer`.
Previously, only the playtest arguments (apart from the recently-added
`playassets`) had this safety check, but the message it printed whenever
the safety check failed was always "-playing option requires one
argument" regardless of whatever argument actually failed to be parsed.
So I fixed it so that all arguments actually output the correct
corresponding failed argument instead.
Also, `strcmp(argv[i], <name>) == 0` is really kind of noisy, even if
you understand what it does perfectly well.
So I refactored it with a bunch of macros. ARG() just does the strcmp()
char* comparison, and ARG_INNER() does the safety check and returns 1,
along with printing a message, if the safety check fails.
2020-06-22 01:15:28 +02:00
|
|
|
assetsPath = argv[i];
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else if (ARG("-playing") || ARG("-p"))
|
|
|
|
{
|
|
|
|
ARG_INNER({
|
2020-06-22 03:37:44 +02:00
|
|
|
i++;
|
2020-04-09 21:03:24 +02:00
|
|
|
startinplaytest = true;
|
|
|
|
playtestname = std::string("levels/");
|
|
|
|
playtestname.append(argv[i]);
|
|
|
|
playtestname.append(std::string(".vvvvvv"));
|
De-duplicate and add safety checks to CLI args, fix brace style
The command-line argument parsing code has a lot of copy-paste. This
copy-paste would get even worse if I added safety checks to make sure
you couldn't index argv out-of-bounds by having an argument like
`-renderer` without having anything after it, i.e. you'd be doing the
command `./VVVVVV -renderer`.
Previously, only the playtest arguments (apart from the recently-added
`playassets`) had this safety check, but the message it printed whenever
the safety check failed was always "-playing option requires one
argument" regardless of whatever argument actually failed to be parsed.
So I fixed it so that all arguments actually output the correct
corresponding failed argument instead.
Also, `strcmp(argv[i], <name>) == 0` is really kind of noisy, even if
you understand what it does perfectly well.
So I refactored it with a bunch of macros. ARG() just does the strcmp()
char* comparison, and ARG_INNER() does the safety check and returns 1,
along with printing a message, if the safety check fails.
2020-06-22 01:15:28 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
else if (ARG("-playx") || ARG("-playy") ||
|
|
|
|
ARG("-playrx") || ARG("-playry") ||
|
|
|
|
ARG("-playgc") || ARG("-playmusic"))
|
|
|
|
{
|
|
|
|
ARG_INNER({
|
2020-04-09 21:03:24 +02:00
|
|
|
savefileplaytest = true;
|
2020-08-07 06:31:29 +02:00
|
|
|
int v = help.Int(argv[i+1]);
|
De-duplicate and add safety checks to CLI args, fix brace style
The command-line argument parsing code has a lot of copy-paste. This
copy-paste would get even worse if I added safety checks to make sure
you couldn't index argv out-of-bounds by having an argument like
`-renderer` without having anything after it, i.e. you'd be doing the
command `./VVVVVV -renderer`.
Previously, only the playtest arguments (apart from the recently-added
`playassets`) had this safety check, but the message it printed whenever
the safety check failed was always "-playing option requires one
argument" regardless of whatever argument actually failed to be parsed.
So I fixed it so that all arguments actually output the correct
corresponding failed argument instead.
Also, `strcmp(argv[i], <name>) == 0` is really kind of noisy, even if
you understand what it does perfectly well.
So I refactored it with a bunch of macros. ARG() just does the strcmp()
char* comparison, and ARG_INNER() does the safety check and returns 1,
along with printing a message, if the safety check fails.
2020-06-22 01:15:28 +02:00
|
|
|
if (ARG("-playx")) savex = v;
|
|
|
|
else if (ARG("-playy")) savey = v;
|
|
|
|
else if (ARG("-playrx")) saverx = v;
|
|
|
|
else if (ARG("-playry")) savery = v;
|
|
|
|
else if (ARG("-playgc")) savegc = v;
|
|
|
|
else if (ARG("-playmusic")) savemusic = v;
|
2020-06-22 03:37:44 +02:00
|
|
|
i++;
|
De-duplicate and add safety checks to CLI args, fix brace style
The command-line argument parsing code has a lot of copy-paste. This
copy-paste would get even worse if I added safety checks to make sure
you couldn't index argv out-of-bounds by having an argument like
`-renderer` without having anything after it, i.e. you'd be doing the
command `./VVVVVV -renderer`.
Previously, only the playtest arguments (apart from the recently-added
`playassets`) had this safety check, but the message it printed whenever
the safety check failed was always "-playing option requires one
argument" regardless of whatever argument actually failed to be parsed.
So I fixed it so that all arguments actually output the correct
corresponding failed argument instead.
Also, `strcmp(argv[i], <name>) == 0` is really kind of noisy, even if
you understand what it does perfectly well.
So I refactored it with a bunch of macros. ARG() just does the strcmp()
char* comparison, and ARG_INNER() does the safety check and returns 1,
along with printing a message, if the safety check fails.
2020-06-22 01:15:28 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
else if (ARG("-playassets"))
|
|
|
|
{
|
|
|
|
ARG_INNER({
|
2020-06-22 03:37:44 +02:00
|
|
|
i++;
|
|
|
|
// Even if this is a directory, FILESYSTEM_mountassets() expects '.vvvvvv' on the end
|
2020-06-22 00:56:31 +02:00
|
|
|
playassets = "levels/" + std::string(argv[i]) + ".vvvvvv";
|
De-duplicate and add safety checks to CLI args, fix brace style
The command-line argument parsing code has a lot of copy-paste. This
copy-paste would get even worse if I added safety checks to make sure
you couldn't index argv out-of-bounds by having an argument like
`-renderer` without having anything after it, i.e. you'd be doing the
command `./VVVVVV -renderer`.
Previously, only the playtest arguments (apart from the recently-added
`playassets`) had this safety check, but the message it printed whenever
the safety check failed was always "-playing option requires one
argument" regardless of whatever argument actually failed to be parsed.
So I fixed it so that all arguments actually output the correct
corresponding failed argument instead.
Also, `strcmp(argv[i], <name>) == 0` is really kind of noisy, even if
you understand what it does perfectly well.
So I refactored it with a bunch of macros. ARG() just does the strcmp()
char* comparison, and ARG_INNER() does the safety check and returns 1,
along with printing a message, if the safety check fails.
2020-06-22 01:15:28 +02:00
|
|
|
})
|
2020-04-09 21:03:24 +02:00
|
|
|
}
|
De-duplicate and add safety checks to CLI args, fix brace style
The command-line argument parsing code has a lot of copy-paste. This
copy-paste would get even worse if I added safety checks to make sure
you couldn't index argv out-of-bounds by having an argument like
`-renderer` without having anything after it, i.e. you'd be doing the
command `./VVVVVV -renderer`.
Previously, only the playtest arguments (apart from the recently-added
`playassets`) had this safety check, but the message it printed whenever
the safety check failed was always "-playing option requires one
argument" regardless of whatever argument actually failed to be parsed.
So I fixed it so that all arguments actually output the correct
corresponding failed argument instead.
Also, `strcmp(argv[i], <name>) == 0` is really kind of noisy, even if
you understand what it does perfectly well.
So I refactored it with a bunch of macros. ARG() just does the strcmp()
char* comparison, and ARG_INNER() does the safety check and returns 1,
along with printing a message, if the safety check fails.
2020-06-22 01:15:28 +02:00
|
|
|
#undef ARG_INNER
|
2020-07-16 03:27:54 +02:00
|
|
|
#undef ARG
|
2020-08-03 06:30:08 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
printf("Error: invalid option: %s\n", argv[i]);
|
2021-02-16 03:53:17 +01:00
|
|
|
VVV_exit(1);
|
2020-08-03 06:30:08 +02:00
|
|
|
}
|
2020-02-03 00:28:26 +01:00
|
|
|
}
|
|
|
|
|
2020-02-09 00:49:03 +01:00
|
|
|
if(!FILESYSTEM_init(argv[0], baseDir, assetsPath))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-22 01:24:51 +02:00
|
|
|
puts("Unable to initialize filesystem!");
|
2021-02-16 03:53:17 +01:00
|
|
|
VVV_exit(1);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-02-19 18:44:36 +01:00
|
|
|
SDL_Init(
|
|
|
|
SDL_INIT_VIDEO |
|
|
|
|
SDL_INIT_AUDIO |
|
|
|
|
SDL_INIT_JOYSTICK |
|
|
|
|
SDL_INIT_GAMECONTROLLER
|
|
|
|
);
|
Axe manual state trackers and use SDL_IsTextInputActive()
After looking at pull request #446, I got a bit annoyed that we have TWO
variables, key.textentrymode and ed.textentry, that we rolled ourselves
to track the state of something SDL already provides us a function to
easily query: SDL_IsTextInputActive(). We don't need to have either of
these two variables, and we shouldn't.
So that's what I do in this patch. Both variables have been axed in
favor of using this function, and I just made a wrapper out of it, named
key.textentry().
For bonus points, this gets rid of the ugly NO_CUSTOM_LEVELS and
NO_EDITOR ifdef in main.cpp, since text entry is enabled when entering
the script list and disabled when exiting it. This makes the code there
easier to read, too.
Furthermore, apparently key.textentrymode was initialized to *true*
instead of false... for whatever reason. But that's gone now, too.
Now, you'd think there wouldn't be any downside to using
SDL_IsTextInputActive(). After all, it's a function that SDL itself
provides, right?
Wrong. For whatever reason, it seems like text input is active *from the
start of the program*, meaning that what would happen is I would go into
the editor, and find that I can't move around nor place tiles nor
anything else. Then I would press Esc, and then suddenly become able to
do those things I wanted to do before.
I have no idea why the above happens, but all I can do is to just insert
an SDL_StopTextInput() immediately after the SDL_Init() in main.cpp. Of
course, I have to surround it with an SDL_IsTextInputActive() check to
make sure I don't do anything extraneous by stopping input when it's
already stopped.
2020-08-13 08:43:25 +02:00
|
|
|
if (SDL_IsTextInputActive() == SDL_TRUE)
|
|
|
|
{
|
|
|
|
SDL_StopTextInput();
|
|
|
|
}
|
2020-02-19 18:44:36 +01:00
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
NETWORK_init();
|
|
|
|
|
2020-04-02 23:43:50 +02:00
|
|
|
printf("\t\t\n");
|
|
|
|
printf("\t\t\n");
|
|
|
|
printf("\t\t VVVVVV\n");
|
|
|
|
printf("\t\t\n");
|
|
|
|
printf("\t\t\n");
|
|
|
|
printf("\t\t 8888888888888888 \n");
|
|
|
|
printf("\t\t88888888888888888888\n");
|
|
|
|
printf("\t\t888888 8888 88\n");
|
|
|
|
printf("\t\t888888 8888 88\n");
|
|
|
|
printf("\t\t88888888888888888888\n");
|
|
|
|
printf("\t\t88888888888888888888\n");
|
|
|
|
printf("\t\t888888 88\n");
|
|
|
|
printf("\t\t88888888 8888\n");
|
|
|
|
printf("\t\t 8888888888888888 \n");
|
|
|
|
printf("\t\t 88888888 \n");
|
|
|
|
printf("\t\t 8888888888888888 \n");
|
|
|
|
printf("\t\t88888888888888888888\n");
|
|
|
|
printf("\t\t88888888888888888888\n");
|
|
|
|
printf("\t\t88888888888888888888\n");
|
|
|
|
printf("\t\t8888 88888888 8888\n");
|
|
|
|
printf("\t\t8888 88888888 8888\n");
|
|
|
|
printf("\t\t 888888888888 \n");
|
|
|
|
printf("\t\t 8888 8888 \n");
|
|
|
|
printf("\t\t 888888 888888 \n");
|
|
|
|
printf("\t\t 888888 888888 \n");
|
|
|
|
printf("\t\t 888888 888888 \n");
|
|
|
|
printf("\t\t\n");
|
|
|
|
printf("\t\t\n");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//Set up screen
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Load Ini
|
|
|
|
|
|
|
|
|
Allow using help/graphics/music/game/key/map/obj everywhere
This commit makes `help`, `graphics`, `music`, `game`, `key`, `map`, and
`obj` essentially static global objects that can be used everywhere.
This is useful in case we ever need to add a new function in the future,
so we don't have to bother with passing a new argument in which means we
have to pass a new argument in to the function that calls that function
which means having to pass a new argument into the function that calls
THAT function, etc. which is a real headache when working on fan mods of
the source code.
Note that this changes NONE of the existing function signatures, it
merely just makes those variables accessible everywhere in the same way
`script` and `ed` are.
Also note that some classes had to be initialized after the filesystem
was initialized, but C++ would keep initializing them before the
filesystem got initialized, because I *had* to put them at the top of
`main.cpp`, or else they wouldn't be global variables.
The only way to work around this was to use entityclass's initialization
style (which I'm pretty sure entityclass of all things doesn't need to
be initialized this way), where you actually initialize the class in an
`init()` function, and so then you do `graphics.init()` after the
filesystem initialization, AFTER doing `Graphics graphics` up at the
top.
I've had to do this for `graphics` (but only because its child
GraphicsResources `grphx` needs to be initialized this way), `music`,
and `game`. I don't think this will affect anything. Other than that,
`help`, `key`, and `map` are still using the C++-intended method of
having ClassName::ClassName() functions.
2020-01-29 08:35:03 +01:00
|
|
|
graphics.init();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Allow using help/graphics/music/game/key/map/obj everywhere
This commit makes `help`, `graphics`, `music`, `game`, `key`, `map`, and
`obj` essentially static global objects that can be used everywhere.
This is useful in case we ever need to add a new function in the future,
so we don't have to bother with passing a new argument in which means we
have to pass a new argument in to the function that calls that function
which means having to pass a new argument into the function that calls
THAT function, etc. which is a real headache when working on fan mods of
the source code.
Note that this changes NONE of the existing function signatures, it
merely just makes those variables accessible everywhere in the same way
`script` and `ed` are.
Also note that some classes had to be initialized after the filesystem
was initialized, but C++ would keep initializing them before the
filesystem got initialized, because I *had* to put them at the top of
`main.cpp`, or else they wouldn't be global variables.
The only way to work around this was to use entityclass's initialization
style (which I'm pretty sure entityclass of all things doesn't need to
be initialized this way), where you actually initialize the class in an
`init()` function, and so then you do `graphics.init()` after the
filesystem initialization, AFTER doing `Graphics graphics` up at the
top.
I've had to do this for `graphics` (but only because its child
GraphicsResources `grphx` needs to be initialized this way), `music`,
and `game`. I don't think this will affect anything. Other than that,
`help`, `key`, and `map` are still using the C++-intended method of
having ClassName::ClassName() functions.
2020-01-29 08:35:03 +01:00
|
|
|
game.init();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-12 22:24:21 +02:00
|
|
|
// This loads music too...
|
2020-06-01 01:31:02 +02:00
|
|
|
graphics.reloadresources();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-08 20:30:57 +02:00
|
|
|
game.gamestate = PRELOADER;
|
|
|
|
|
|
|
|
game.menustart = false;
|
|
|
|
game.mainmenu = 0;
|
|
|
|
|
Move title screen color initialization to main()
This fixes a bug where if you completed a custom level during
command-line playtesting, when returning to the title screen, the
background would be red and the text would be white.
This is because playtesting skips over the code path of pressing ACTION
to start the game and advance to the title screen, and the code path of
that ACTION press specifically initializes the title screen colors to
cyan.
This is also caused by the fact that completing a custom level doesn't
call map.nexttowercolour(), but my guess is that the intent there was
that the player would select a custom level, complete it, and return to
the title screen on the same screen with the same colors, so I decided
not to add a map.nexttowercolour() there.
Instead, I've moved the cyan color initialization to main(), so that it
is always executed no matter what, and doesn't require you to take a
specific code path to do it.
2021-01-06 02:18:00 +01:00
|
|
|
// Initialize title screen to cyan
|
|
|
|
graphics.titlebg.colstate = 10;
|
|
|
|
map.nexttowercolour();
|
|
|
|
|
2020-07-08 20:30:57 +02:00
|
|
|
map.ypos = (700-29) * 8;
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.bypos = map.ypos / 2;
|
2020-11-03 00:23:53 +01:00
|
|
|
graphics.titlebg.bypos = map.ypos / 2;
|
2020-07-08 20:30:57 +02:00
|
|
|
|
2020-11-13 01:45:51 +01:00
|
|
|
{
|
|
|
|
// Prioritize unlock.vvv first (2.2 and below),
|
|
|
|
// but settings have been migrated to settings.vvv (2.3 and up)
|
|
|
|
ScreenSettings screen_settings;
|
|
|
|
game.loadstats(&screen_settings);
|
|
|
|
game.loadsettings(&screen_settings);
|
|
|
|
gameScreen.init(screen_settings);
|
|
|
|
}
|
2020-07-08 20:30:57 +02:00
|
|
|
graphics.screenbuffer = &gameScreen;
|
|
|
|
|
2021-02-16 01:18:45 +01:00
|
|
|
graphics.create_buffers(gameScreen.GetFormat());
|
2020-05-04 22:19:47 +02:00
|
|
|
|
2020-01-13 02:45:44 +01:00
|
|
|
if (game.skipfakeload)
|
|
|
|
game.gamestate = TITLEMODE;
|
2020-01-01 21:29:24 +01:00
|
|
|
if (game.slowdown == 0) game.slowdown = 30;
|
|
|
|
|
2020-04-02 23:43:50 +02:00
|
|
|
//Check to see if you've already unlocked some achievements here from before the update
|
|
|
|
if (game.swnbestrank > 0){
|
2020-08-01 21:49:07 +02:00
|
|
|
if(game.swnbestrank >= 1) game.unlockAchievement("vvvvvvsupgrav5");
|
|
|
|
if(game.swnbestrank >= 2) game.unlockAchievement("vvvvvvsupgrav10");
|
|
|
|
if(game.swnbestrank >= 3) game.unlockAchievement("vvvvvvsupgrav15");
|
|
|
|
if(game.swnbestrank >= 4) game.unlockAchievement("vvvvvvsupgrav20");
|
|
|
|
if(game.swnbestrank >= 5) game.unlockAchievement("vvvvvvsupgrav30");
|
|
|
|
if(game.swnbestrank >= 6) game.unlockAchievement("vvvvvvsupgrav60");
|
2020-04-02 23:43:50 +02:00
|
|
|
}
|
|
|
|
|
2020-08-01 21:49:07 +02:00
|
|
|
if(game.unlock[5]) game.unlockAchievement("vvvvvvgamecomplete");
|
|
|
|
if(game.unlock[19]) game.unlockAchievement("vvvvvvgamecompleteflip");
|
|
|
|
if(game.unlock[20]) game.unlockAchievement("vvvvvvmaster");
|
2020-04-02 23:43:50 +02:00
|
|
|
|
|
|
|
if (game.bestgamedeaths > -1) {
|
|
|
|
if (game.bestgamedeaths <= 500) {
|
2020-08-01 21:49:07 +02:00
|
|
|
game.unlockAchievement("vvvvvvcomplete500");
|
2020-04-02 23:43:50 +02:00
|
|
|
}
|
|
|
|
if (game.bestgamedeaths <= 250) {
|
2020-08-01 21:49:07 +02:00
|
|
|
game.unlockAchievement("vvvvvvcomplete250");
|
2020-04-02 23:43:50 +02:00
|
|
|
}
|
|
|
|
if (game.bestgamedeaths <= 100) {
|
2020-08-01 21:49:07 +02:00
|
|
|
game.unlockAchievement("vvvvvvcomplete100");
|
2020-04-02 23:43:50 +02:00
|
|
|
}
|
|
|
|
if (game.bestgamedeaths <= 50) {
|
2020-08-01 21:49:07 +02:00
|
|
|
game.unlockAchievement("vvvvvvcomplete50");
|
2020-04-02 23:43:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-01 21:49:07 +02:00
|
|
|
if(game.bestrank[0]>=3) game.unlockAchievement("vvvvvvtimetrial_station1_fixed");
|
|
|
|
if(game.bestrank[1]>=3) game.unlockAchievement("vvvvvvtimetrial_lab_fixed");
|
|
|
|
if(game.bestrank[2]>=3) game.unlockAchievement("vvvvvvtimetrial_tower_fixed");
|
|
|
|
if(game.bestrank[3]>=3) game.unlockAchievement("vvvvvvtimetrial_station2_fixed");
|
|
|
|
if(game.bestrank[4]>=3) game.unlockAchievement("vvvvvvtimetrial_warp_fixed");
|
|
|
|
if(game.bestrank[5]>=3) game.unlockAchievement("vvvvvvtimetrial_final_fixed");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
obj.init();
|
|
|
|
|
2020-04-16 01:51:58 +02:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
2020-04-09 21:03:24 +02:00
|
|
|
if (startinplaytest) {
|
|
|
|
game.levelpage = 0;
|
|
|
|
game.playcustomlevel = 0;
|
2020-06-22 00:56:31 +02:00
|
|
|
game.playassets = playassets;
|
2020-08-13 14:59:14 +02:00
|
|
|
game.menustart = true;
|
2020-04-09 21:03:24 +02:00
|
|
|
|
2020-06-03 20:14:58 +02:00
|
|
|
ed.directoryList.clear();
|
|
|
|
ed.directoryList.push_back(playtestname);
|
2020-04-09 21:03:24 +02:00
|
|
|
|
|
|
|
LevelMetaData meta;
|
|
|
|
if (ed.getLevelMetaData(playtestname, meta)) {
|
2020-06-03 20:14:58 +02:00
|
|
|
ed.ListOfMetaData.clear();
|
|
|
|
ed.ListOfMetaData.push_back(meta);
|
2020-04-09 21:03:24 +02:00
|
|
|
} else {
|
2020-06-03 20:05:09 +02:00
|
|
|
ed.loadZips();
|
|
|
|
if (ed.getLevelMetaData(playtestname, meta)) {
|
2020-06-03 20:14:58 +02:00
|
|
|
ed.ListOfMetaData.clear();
|
|
|
|
ed.ListOfMetaData.push_back(meta);
|
2020-06-03 20:05:09 +02:00
|
|
|
} else {
|
|
|
|
printf("Level not found\n");
|
2021-02-16 03:53:17 +01:00
|
|
|
VVV_exit(1);
|
2020-06-03 20:05:09 +02:00
|
|
|
}
|
2020-04-09 21:03:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
game.loadcustomlevelstats();
|
|
|
|
game.customleveltitle=ed.ListOfMetaData[game.playcustomlevel].title;
|
|
|
|
game.customlevelfilename=ed.ListOfMetaData[game.playcustomlevel].filename;
|
|
|
|
if (savefileplaytest) {
|
|
|
|
game.playx = savex;
|
|
|
|
game.playy = savey;
|
|
|
|
game.playrx = saverx;
|
|
|
|
game.playry = savery;
|
|
|
|
game.playgc = savegc;
|
2020-07-11 20:55:26 +02:00
|
|
|
game.playmusic = savemusic;
|
2020-04-09 21:03:24 +02:00
|
|
|
game.cliplaytest = true;
|
|
|
|
script.startgamemode(23);
|
|
|
|
} else {
|
|
|
|
script.startgamemode(22);
|
|
|
|
}
|
|
|
|
|
|
|
|
graphics.fademode = 0;
|
|
|
|
}
|
2020-04-16 01:51:58 +02:00
|
|
|
#endif
|
2020-04-09 21:03:24 +02:00
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
key.isActive = true;
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
game.gametimer = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
while(!key.quitProgram)
|
|
|
|
{
|
2020-05-04 21:52:57 +02:00
|
|
|
f_time = SDL_GetTicks();
|
2020-07-14 06:17:20 +02:00
|
|
|
|
|
|
|
const Uint32 f_timetaken = f_time - f_timePrev;
|
|
|
|
if (!game.over30mode && f_timetaken < 34)
|
2020-05-04 21:52:57 +02:00
|
|
|
{
|
2020-07-14 06:17:20 +02:00
|
|
|
const volatile Uint32 f_delay = 34 - f_timetaken;
|
|
|
|
SDL_Delay(f_delay);
|
|
|
|
f_time = SDL_GetTicks();
|
2020-05-04 21:52:57 +02:00
|
|
|
}
|
|
|
|
|
2020-07-14 06:17:20 +02:00
|
|
|
f_timePrev = f_time;
|
|
|
|
|
|
|
|
timePrev = time_;
|
|
|
|
time_ = SDL_GetTicks();
|
|
|
|
|
|
|
|
deltaloop();
|
2020-06-14 20:45:36 +02:00
|
|
|
}
|
|
|
|
|
2021-02-16 02:52:41 +01:00
|
|
|
cleanup();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cleanup()
|
|
|
|
{
|
2021-02-16 03:43:35 +01:00
|
|
|
/* Order matters! */
|
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-22 01:03:19 +01:00
|
|
|
game.savestatsandsettings();
|
2021-02-16 03:43:35 +01:00
|
|
|
gameScreen.destroy();
|
|
|
|
graphics.grphx.destroy();
|
|
|
|
graphics.destroy_buffers();
|
|
|
|
graphics.destroy();
|
|
|
|
music.destroy();
|
2020-06-14 20:45:36 +02:00
|
|
|
NETWORK_shutdown();
|
|
|
|
SDL_Quit();
|
|
|
|
FILESYSTEM_deinit();
|
|
|
|
}
|
|
|
|
|
2021-02-16 03:47:24 +01:00
|
|
|
void VVV_exit(const int exit_code)
|
|
|
|
{
|
|
|
|
cleanup();
|
|
|
|
exit(exit_code);
|
|
|
|
}
|
|
|
|
|
2021-01-10 18:14:37 +01:00
|
|
|
static void inline deltaloop()
|
2020-06-14 20:53:41 +02:00
|
|
|
{
|
2020-06-14 20:55:19 +02:00
|
|
|
//timestep limit to 30
|
|
|
|
const float rawdeltatime = static_cast<float>(time_ - timePrev);
|
|
|
|
accumulator += rawdeltatime;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-14 20:55:19 +02:00
|
|
|
Uint32 timesteplimit;
|
|
|
|
if (game.gamestate == EDITORMODE)
|
|
|
|
{
|
|
|
|
timesteplimit = 24;
|
|
|
|
}
|
2020-07-01 12:04:17 +02:00
|
|
|
else if (game.gamestate == GAMEMODE)
|
2020-06-14 20:55:19 +02:00
|
|
|
{
|
2020-11-13 02:05:18 +01:00
|
|
|
timesteplimit = get_framerate(game.slowdown);
|
2020-06-14 20:55:19 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
timesteplimit = 34;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (accumulator >= timesteplimit)
|
|
|
|
{
|
2020-11-07 00:11:24 +01:00
|
|
|
accumulator = SDL_fmodf(accumulator, timesteplimit);
|
2020-06-14 20:55:19 +02:00
|
|
|
|
2020-06-14 20:57:33 +02:00
|
|
|
fixedloop();
|
|
|
|
}
|
|
|
|
const float alpha = game.over30mode ? static_cast<float>(accumulator) / timesteplimit : 1.0f;
|
|
|
|
graphics.alpha = alpha;
|
|
|
|
|
2020-06-27 02:45:53 +02:00
|
|
|
if (key.isActive)
|
2020-06-14 20:57:33 +02:00
|
|
|
{
|
|
|
|
switch (game.gamestate)
|
|
|
|
{
|
|
|
|
case PRELOADER:
|
|
|
|
preloaderrender();
|
|
|
|
break;
|
2020-06-20 00:23:28 +02:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS) && !defined(NO_EDITOR)
|
2020-06-14 20:57:33 +02:00
|
|
|
case EDITORMODE:
|
|
|
|
graphics.flipmode = false;
|
|
|
|
editorrender();
|
|
|
|
break;
|
2020-06-14 21:03:40 +02:00
|
|
|
#endif
|
2020-06-14 20:57:33 +02:00
|
|
|
case TITLEMODE:
|
|
|
|
titlerender();
|
|
|
|
break;
|
|
|
|
case GAMEMODE:
|
|
|
|
gamerender();
|
|
|
|
break;
|
|
|
|
case MAPMODE:
|
|
|
|
maprender();
|
|
|
|
break;
|
|
|
|
case TELEPORTERMODE:
|
|
|
|
teleporterrender();
|
|
|
|
break;
|
|
|
|
case GAMECOMPLETE:
|
|
|
|
gamecompleterender();
|
|
|
|
break;
|
|
|
|
case GAMECOMPLETE2:
|
|
|
|
gamecompleterender2();
|
|
|
|
break;
|
|
|
|
case CLICKTOSTART:
|
|
|
|
help.updateglow();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
gameScreen.FlipScreen();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-10 18:14:37 +01:00
|
|
|
static void inline fixedloop()
|
2020-06-14 20:57:33 +02:00
|
|
|
{
|
2020-06-14 20:58:27 +02:00
|
|
|
// Update network per frame.
|
|
|
|
NETWORK_update();
|
|
|
|
|
2020-06-14 20:57:57 +02:00
|
|
|
key.Poll();
|
|
|
|
if(key.toggleFullscreen)
|
|
|
|
{
|
|
|
|
gameScreen.toggleFullScreen();
|
|
|
|
key.toggleFullscreen = false;
|
|
|
|
|
|
|
|
key.keymap.clear(); //we lost the input due to a new window.
|
2020-06-30 06:06:19 +02:00
|
|
|
if (game.glitchrunnermode)
|
|
|
|
{
|
|
|
|
game.press_left = false;
|
|
|
|
game.press_right = false;
|
|
|
|
game.press_action = true;
|
|
|
|
game.press_map = false;
|
|
|
|
}
|
2020-06-14 20:57:57 +02:00
|
|
|
}
|
2020-06-14 20:55:19 +02:00
|
|
|
|
2020-06-27 02:45:53 +02:00
|
|
|
if(!key.isActive)
|
2020-06-14 20:57:57 +02:00
|
|
|
{
|
|
|
|
Mix_Pause(-1);
|
|
|
|
Mix_PauseMusic();
|
|
|
|
|
|
|
|
if (!game.blackout)
|
|
|
|
{
|
|
|
|
FillRect(graphics.backBuffer, 0x00000000);
|
|
|
|
graphics.bprint(5, 110, "Game paused", 196 - help.glow, 255 - help.glow, 196 - help.glow, true);
|
|
|
|
graphics.bprint(5, 120, "[click to resume]", 196 - help.glow, 255 - help.glow, 196 - help.glow, true);
|
|
|
|
graphics.bprint(5, 220, "Press M to mute in game", 164 - help.glow, 196 - help.glow, 164 - help.glow, true);
|
|
|
|
graphics.bprint(5, 230, "Press N to mute music only", 164 - help.glow, 196 - help.glow, 164 - help.glow, true);
|
2020-06-14 20:49:23 +02:00
|
|
|
}
|
2020-06-14 20:57:57 +02:00
|
|
|
graphics.render();
|
|
|
|
gameScreen.FlipScreen();
|
|
|
|
//We are minimised, so lets put a bit of a delay to save CPU
|
|
|
|
SDL_Delay(100);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Mix_Resume(-1);
|
|
|
|
Mix_ResumeMusic();
|
|
|
|
game.gametimer++;
|
|
|
|
graphics.cutscenebarstimer();
|
2020-06-14 20:55:19 +02:00
|
|
|
|
2020-06-14 20:57:57 +02:00
|
|
|
switch(game.gamestate)
|
2020-06-14 20:49:23 +02:00
|
|
|
{
|
2020-06-14 20:57:57 +02:00
|
|
|
case PRELOADER:
|
2020-12-21 00:19:22 +01:00
|
|
|
preloaderinput();
|
2020-11-08 00:47:49 +01:00
|
|
|
preloaderrenderfixed();
|
2020-06-14 20:57:57 +02:00
|
|
|
break;
|
2020-06-20 00:23:28 +02:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS) && !defined(NO_EDITOR)
|
2020-06-14 20:57:57 +02:00
|
|
|
case EDITORMODE:
|
|
|
|
//Input
|
|
|
|
editorinput();
|
|
|
|
////Logic
|
|
|
|
editorlogic();
|
2020-11-08 00:47:49 +01:00
|
|
|
|
|
|
|
editorrenderfixed();
|
2020-06-14 20:57:57 +02:00
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
case TITLEMODE:
|
|
|
|
//Input
|
|
|
|
titleinput();
|
|
|
|
////Logic
|
|
|
|
titlelogic();
|
2020-11-08 00:47:49 +01:00
|
|
|
|
|
|
|
titlerenderfixed();
|
2020-06-14 20:57:57 +02:00
|
|
|
break;
|
|
|
|
case GAMEMODE:
|
Fix the two-frame-delay when entering a room with an "init" script
This patch is very kludge-y, but at least it fixes a semi-noticeable
visual issue in custom levels that use internal scripts to spawn
entities when loading a room.
Basically, the problem here is that when the game checks for script
boxes and sets newscript, newscript has already been processed for that
frame, and when the game does load a script, script.run() has already
been processed for that frame.
That issue can be fixed, but it turns out that due to my over-30-FPS
game loop changes, there's now ANOTHER visible frame of delay between
room load and entity creation, because the render function gets called
in between the script being loaded at the end of gamelogic() and the
script actually getting run.
So... I have to temporary move script.run() to the end of gamelogic()
(in map.twoframedelayfix()), and make sure it doesn't get run next
frame, because double-evaluations are bad. To do that, I have to
introduce the kludge variable script.dontrunnextframe, which does
exactly as it says.
And with all that work, the two-frame (now three-frame) delay is fixed.
2020-06-28 02:08:57 +02:00
|
|
|
// WARNING: If updating this code, don't forget to update Map.cpp mapclass::twoframedelayfix()
|
|
|
|
|
|
|
|
// Ugh, I hate this kludge variable but it's the only way to do it
|
|
|
|
if (script.dontrunnextframe)
|
|
|
|
{
|
|
|
|
script.dontrunnextframe = false;
|
|
|
|
}
|
2020-12-24 05:20:18 +01:00
|
|
|
else
|
2020-06-14 20:57:57 +02:00
|
|
|
{
|
|
|
|
script.run();
|
|
|
|
}
|
2020-06-14 20:55:19 +02:00
|
|
|
|
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-10 05:58:58 +02:00
|
|
|
//Update old lerp positions of entities - has to be done BEFORE gameinput!
|
2020-06-14 20:57:57 +02:00
|
|
|
for (size_t i = 0; i < obj.entities.size(); i++)
|
2020-06-14 20:55:19 +02:00
|
|
|
{
|
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-10 05:58:58 +02:00
|
|
|
obj.entities[i].lerpoldxp = obj.entities[i].xp;
|
|
|
|
obj.entities[i].lerpoldyp = obj.entities[i].yp;
|
2020-06-14 20:55:19 +02:00
|
|
|
}
|
2020-06-14 20:49:23 +02:00
|
|
|
|
2020-06-14 20:57:57 +02:00
|
|
|
gameinput();
|
2020-11-08 00:47:49 +01:00
|
|
|
gamerenderfixed();
|
2021-01-10 02:04:43 +01:00
|
|
|
gamelogic();
|
2020-06-14 20:57:57 +02:00
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
case MAPMODE:
|
|
|
|
mapinput();
|
|
|
|
maplogic();
|
2020-11-08 00:47:49 +01:00
|
|
|
maprenderfixed();
|
2020-06-14 20:57:57 +02:00
|
|
|
break;
|
|
|
|
case TELEPORTERMODE:
|
|
|
|
if(game.useteleporter)
|
|
|
|
{
|
|
|
|
teleporterinput();
|
|
|
|
}
|
|
|
|
else
|
Don't print useless false error message when toggling fullscreen
Whenever you would press Alt+Enter, or Alt+F, or on macOS Command+Enter,
or on macOS Command+F, or F11, the game would print this useless error
message to console, every single time: "Error: failed: " and it would
concatenate SDL_GetError() after it, but most of the time SDL_GetError()
is blank, so it would print just that.
Instead, what the fullscreen shortcut will now do is check the result of
the relevant SDL functions, BEFORE it decides to print an error message.
And when it DOES print an error message, it will be less vague and will
say instead "Error: toggling fullscreen failed: <output of
SDL_GetError()>".
This means Screen::ResizeScreen() and Screen::toggleFullScreen() are now
int-returning functions. Ideally, every function interfacing with SDL
would return an error code, but that's too much for this simple patch.
Additionally, I took the opportunity to clean up the surrounding
formatting of the code a bit, most notably dedenting the
keypress-clearing stuff by one tab level, converting the
shortcut-handling code to spaces, and removing commented-out code.
2020-03-13 08:23:48 +01:00
|
|
|
{
|
2020-12-24 05:20:18 +01:00
|
|
|
script.run();
|
2020-06-14 20:55:19 +02:00
|
|
|
gameinput();
|
2020-06-14 20:49:23 +02:00
|
|
|
}
|
2020-06-14 20:57:57 +02:00
|
|
|
maplogic();
|
2020-11-08 00:47:49 +01:00
|
|
|
maprenderfixed();
|
2020-06-14 20:57:57 +02:00
|
|
|
break;
|
|
|
|
case GAMECOMPLETE:
|
|
|
|
//Input
|
|
|
|
gamecompleteinput();
|
|
|
|
//Logic
|
|
|
|
gamecompletelogic();
|
2020-11-08 00:47:49 +01:00
|
|
|
|
|
|
|
gamecompleterenderfixed();
|
2020-06-14 20:57:57 +02:00
|
|
|
break;
|
|
|
|
case GAMECOMPLETE2:
|
|
|
|
//Input
|
|
|
|
gamecompleteinput2();
|
|
|
|
//Logic
|
|
|
|
gamecompletelogic2();
|
|
|
|
break;
|
|
|
|
case CLICKTOSTART:
|
|
|
|
break;
|
|
|
|
default:
|
Fix frame-ordering backspacing empty line bug in script editor
There is a long-standing bug with the script editor where if you delete
the last character of a line, it IMMEDIATELY deletes the line you're on,
and then moves your cursor back to the previous line. This is annoying,
to say the least.
The reason for this is that, in the sequence of events that happens in
one frame (known as frame ordering), the code that backspaces one
character from the line when you press Backspace is ran BEFORE the code
to remove an empty line if you backspace it is ran. The former is
located in key.Poll(), and the latter is located in editorinput().
Thus, when you press Backspace, the game first runs key.Poll(), sees
that you've pressed Backspace, and dutifully removes the last character
from a line. The line is now empty. Then, when the game gets around to
the "Are you pressing Backspace on an empty line?" check in
editorinput(), it thinks that you're pressing Backspace on an empty
line, and then does the usual line-removing stuff.
And actually, when it does the check in editorinput(), it ACTUALLY asks
"Are you pressing Backspace on THIS frame and was the line empty LAST
frame?" because it's checking against its own copy of the input buffer,
before copying the input buffer to its own local copy. So the problem
only happens if you press and hold Backspace for more than 1 frame.
It's a small consolation prize for this annoyance, getting to
tap-tap-tap Backspace in the hopes that you only press it for 1 frame,
while in the middle of something more important to do like, oh I don't
know, writing a script.
So there are two potential solutions here:
(1) Just change the frame ordering around.
This is risky to say the least, because I'm not sure what behavior
depends on exactly which frame order. It's not like it's key.Poll()
and then IMMEDIATELY afterwards editorinput() is run, it's more
like key.Poll(), some things that obviously depend on key.Poll()
running before them, and THEN editorinput(). Also, editorinput() is
only one possible thing that could be ran afterwards, on the next
frame we could be running something else entirely instead.
(2) Add a kludge variable to signal when the line is ALREADY empty so
the game doesn't re-check the already-empty line and conclude that
you're already immediately backspacing an empty line.
I went with (2) for this commit, and I've added the kludge variable
key.linealreadyemptykludge.
However, that by itself isn't enough to fix it. It only adds about a
frame or so of delay before the game goes right back to saying "Oh,
you're ALREADY somehow pressing backspace again? I'll just delete this
line real quick" and the behavior is basically the same as before,
except now you have to hit Backspace for TWO frames or less instead of
one in order to not have it happen.
What we need is to have a delay set as well, when the game deletes the
last line of a char. So I set ed.keydelay to 6 as well if editorinput()
sses that key.linealreadyemptykludge is on.
2020-01-19 03:17:46 +01:00
|
|
|
|
2020-06-14 20:57:57 +02:00
|
|
|
break;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-14 20:55:19 +02:00
|
|
|
}
|
2020-06-14 20:49:23 +02:00
|
|
|
|
2020-06-14 20:57:57 +02:00
|
|
|
}
|
2020-06-14 20:55:19 +02:00
|
|
|
|
2020-06-14 20:57:57 +02:00
|
|
|
//Screen effects timers
|
2020-06-27 02:45:53 +02:00
|
|
|
if (key.isActive && game.flashlight > 0)
|
2020-06-14 20:57:57 +02:00
|
|
|
{
|
|
|
|
game.flashlight--;
|
|
|
|
}
|
2020-06-27 02:45:53 +02:00
|
|
|
if (key.isActive && game.screenshake > 0)
|
2020-06-14 20:57:57 +02:00
|
|
|
{
|
|
|
|
game.screenshake--;
|
|
|
|
graphics.updatescreenshake();
|
|
|
|
}
|
2020-06-14 20:55:19 +02:00
|
|
|
|
2020-06-14 20:57:57 +02:00
|
|
|
if (graphics.screenbuffer->badSignalEffect)
|
|
|
|
{
|
|
|
|
UpdateFilter();
|
|
|
|
}
|
|
|
|
|
|
|
|
//We did editorinput, now it's safe to turn this off
|
|
|
|
key.linealreadyemptykludge = false;
|
2020-06-14 20:49:23 +02:00
|
|
|
|
2020-06-14 20:57:57 +02:00
|
|
|
//Mute button
|
Axe manual state trackers and use SDL_IsTextInputActive()
After looking at pull request #446, I got a bit annoyed that we have TWO
variables, key.textentrymode and ed.textentry, that we rolled ourselves
to track the state of something SDL already provides us a function to
easily query: SDL_IsTextInputActive(). We don't need to have either of
these two variables, and we shouldn't.
So that's what I do in this patch. Both variables have been axed in
favor of using this function, and I just made a wrapper out of it, named
key.textentry().
For bonus points, this gets rid of the ugly NO_CUSTOM_LEVELS and
NO_EDITOR ifdef in main.cpp, since text entry is enabled when entering
the script list and disabled when exiting it. This makes the code there
easier to read, too.
Furthermore, apparently key.textentrymode was initialized to *true*
instead of false... for whatever reason. But that's gone now, too.
Now, you'd think there wouldn't be any downside to using
SDL_IsTextInputActive(). After all, it's a function that SDL itself
provides, right?
Wrong. For whatever reason, it seems like text input is active *from the
start of the program*, meaning that what would happen is I would go into
the editor, and find that I can't move around nor place tiles nor
anything else. Then I would press Esc, and then suddenly become able to
do those things I wanted to do before.
I have no idea why the above happens, but all I can do is to just insert
an SDL_StopTextInput() immediately after the SDL_Init() in main.cpp. Of
course, I have to surround it with an SDL_IsTextInputActive() check to
make sure I don't do anything extraneous by stopping input when it's
already stopped.
2020-08-13 08:43:25 +02:00
|
|
|
if (key.isDown(KEYBOARD_m) && game.mutebutton<=0 && !key.textentry())
|
2020-06-14 20:57:57 +02:00
|
|
|
{
|
|
|
|
game.mutebutton = 8;
|
|
|
|
if (game.muted)
|
2020-06-14 20:55:19 +02:00
|
|
|
{
|
2020-06-14 20:57:57 +02:00
|
|
|
game.muted = false;
|
2020-06-14 20:55:19 +02:00
|
|
|
}
|
2020-06-14 20:57:57 +02:00
|
|
|
else
|
2020-06-14 20:55:19 +02:00
|
|
|
{
|
2020-06-14 20:57:57 +02:00
|
|
|
game.muted = true;
|
2020-06-14 20:55:19 +02:00
|
|
|
}
|
2020-06-14 20:57:57 +02:00
|
|
|
}
|
|
|
|
if(game.mutebutton>0)
|
|
|
|
{
|
|
|
|
game.mutebutton--;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Axe manual state trackers and use SDL_IsTextInputActive()
After looking at pull request #446, I got a bit annoyed that we have TWO
variables, key.textentrymode and ed.textentry, that we rolled ourselves
to track the state of something SDL already provides us a function to
easily query: SDL_IsTextInputActive(). We don't need to have either of
these two variables, and we shouldn't.
So that's what I do in this patch. Both variables have been axed in
favor of using this function, and I just made a wrapper out of it, named
key.textentry().
For bonus points, this gets rid of the ugly NO_CUSTOM_LEVELS and
NO_EDITOR ifdef in main.cpp, since text entry is enabled when entering
the script list and disabled when exiting it. This makes the code there
easier to read, too.
Furthermore, apparently key.textentrymode was initialized to *true*
instead of false... for whatever reason. But that's gone now, too.
Now, you'd think there wouldn't be any downside to using
SDL_IsTextInputActive(). After all, it's a function that SDL itself
provides, right?
Wrong. For whatever reason, it seems like text input is active *from the
start of the program*, meaning that what would happen is I would go into
the editor, and find that I can't move around nor place tiles nor
anything else. Then I would press Esc, and then suddenly become able to
do those things I wanted to do before.
I have no idea why the above happens, but all I can do is to just insert
an SDL_StopTextInput() immediately after the SDL_Init() in main.cpp. Of
course, I have to surround it with an SDL_IsTextInputActive() check to
make sure I don't do anything extraneous by stopping input when it's
already stopped.
2020-08-13 08:43:25 +02:00
|
|
|
if (key.isDown(KEYBOARD_n) && game.musicmutebutton <= 0 && !key.textentry())
|
2020-06-14 20:57:57 +02:00
|
|
|
{
|
|
|
|
game.musicmutebutton = 8;
|
|
|
|
game.musicmuted = !game.musicmuted;
|
|
|
|
}
|
|
|
|
if (game.musicmutebutton > 0)
|
|
|
|
{
|
|
|
|
game.musicmutebutton--;
|
|
|
|
}
|
2020-04-28 23:44:53 +02:00
|
|
|
|
2020-06-14 20:57:57 +02:00
|
|
|
if (game.muted)
|
|
|
|
{
|
|
|
|
Mix_VolumeMusic(0) ;
|
|
|
|
Mix_Volume(-1,0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Mix_Volume(-1,MIX_MAX_VOLUME);
|
|
|
|
|
2020-10-11 08:42:02 +02:00
|
|
|
if (game.musicmuted)
|
2020-06-14 20:55:19 +02:00
|
|
|
{
|
2020-06-14 20:57:57 +02:00
|
|
|
Mix_VolumeMusic(0);
|
2020-06-14 20:55:19 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-10-11 08:42:02 +02:00
|
|
|
Mix_VolumeMusic(music.musicVolume);
|
2020-06-14 20:55:19 +02:00
|
|
|
}
|
2020-06-14 20:57:57 +02:00
|
|
|
}
|
2020-04-29 00:02:37 +02:00
|
|
|
|
2020-06-14 20:57:57 +02:00
|
|
|
if (key.resetWindow)
|
|
|
|
{
|
|
|
|
key.resetWindow = false;
|
|
|
|
gameScreen.ResizeScreen(-1, -1);
|
|
|
|
}
|
2020-06-14 20:49:23 +02:00
|
|
|
|
2020-06-14 20:57:57 +02:00
|
|
|
music.processmusic();
|
|
|
|
graphics.processfade();
|
|
|
|
game.gameclock();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|