2020-01-01 21:29:24 +01:00
|
|
|
#include <SDL.h>
|
2021-09-20 22:47:53 +02:00
|
|
|
#ifdef __EMSCRIPTEN__
|
|
|
|
#include <emscripten.h>
|
|
|
|
#include <emscripten/html5.h>
|
|
|
|
#endif
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Add support for button glyph display
This adds a function that converts an action (such as interacting
in-game) to the corresponding button text ("ENTER", "E") or button
glyph (PlayStation triangle, Steam Deck Y, etc). This function
currently only gives the existing ENTERs or Es, because I don't know
how best to detect controller usage, or whether the game is running on
a Steam Deck, or what buttons need to be displayed there. Still, it
should now be really easy to adapt the rendering of keyboard keys to
consoles, controllers, or rebound keys.
To identify the actions that currently need to be displayed, this
commit also adds the initial enums for action sets as described by
Ethan in a comment in #834 (Jan 18, 2022).
2023-03-18 22:30:16 +01:00
|
|
|
#include "ButtonGlyphs.h"
|
2021-02-20 08:19:09 +01:00
|
|
|
#include "CustomLevels.h"
|
2020-12-24 08:24:31 +01:00
|
|
|
#include "DeferCallbacks.h"
|
2021-02-20 08:21:58 +01: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"
|
Start rewrite of font system
This is still a work in progress, but the existing font system has been
removed and replaced by a new one, in Font.cpp.
Design goals of the new font system include supporting colored button
glyphs, different fonts for different languages, and larger fonts than
8x8 for Chinese, Japanese and Korean, while being able to support their
30000+ characters without hiccups, slowdowns or high memory usage. And
to have more flexibility with fonts in general. Plus, Graphics.cpp was
long enough as-is, so it's good to have a dedicated file for font
storage.
The old font system worked with a std::vector<SDL_Surface*> to store
8x8 surfaces for each character, and a std::map<int,int> to store
mappings between codepoints and vector indexes.
The new system has a per-font collection of pages for every block of
0x1000 (4096) codepoints, that may be allocated as needed. A glyph on
a page contains the index of the glyph in the image (giving its
coordinates), the advance (how much the cursor should advance, so the
width of that glyph) and some flags which would be at least whether the
glyph exists and whether it is colored.
Most of the *new* features aren't implemented yet; it's currently
hardcoded to the regular 8x8 font.png, but it should be functionally
equivalent to the previous behavior. The only thing that doesn't really
work yet is level-specific font.png, but that'll be supported again
soon enough.
This commit also adds fontmeta (xml) support.
Since the fonts folder is mounted at graphics/, there are two main
options for recognizing non-font.png fonts: the font files have to be
prefixed with font (or font_) or some special file extension is
involved to signal what files are fonts. I always had a font.xml in
mind (so font_cn.xml, font_ja.xml, etc) but if there's ever gonna be
a need for further xml files inside the graphics folder, we have a
problem. So I named them .fontmeta instead.
A .fontmeta file looks somewhat like this:
<?xml version="1.0" encoding="UTF-8"?>
<font_metadata>
<width>12</width>
<height>12</height>
<white_teeth>1</white_teeth>
<chars>
<range start="0x20" end="0x7E"/>
<range start="0x80" end="0x80"/>
<range start="0xA0" end="0xDF"/>
<range start="0x250" end="0x2A8"/>
<range start="0x2AD" end="0x2AD"/>
<range start="0x2C7" end="0x2C7"/>
<range start="0x2C9" end="0x2CB"/>
...
</chars>
<special>
<range start="0x00" end="0x1F" advance="6"/>
<range start="0x61" end="0x66" color="1"/>
<range start="0x63" end="0x63" color="0"/>
</special>
</font_metadata>
The <chars> tag can be used to specify characters instead of in a .txt.
The original idea was to just always use the existing .txt system for
specifying the font charset, and only use the XML for the other stuff
that the .txt doesn't cover. However, it's probably better to keep it
simple if possible - having to only have a .png and a .fontmeta seems
simpler than having the data spread out over three files. And a major
advantage: Chinese fonts can have about 30000 characters! It's more
efficient to be able to have a tag saying "now there's 20902 characters
starting at U+4E00" than to include them all in a text file and having
to UTF-8 decode every single one of them.
If a font.txt exists, it takes priority over the <chars> tag, and in
that case, there's no reason to include the <chars> tag in the XML.
But font.txt has to be in the same directory as font.png, otherwise it
is rejected. Same for font.fontmeta. If neither font.txt nor <chars>
exist, then the font is seen as a 2.2-and-below-style ASCII font.
In <special>: advance is the number of pixels the cursor advances after
drawing the character (so the width of the character, without affecting
the grid in the source image), color is whether the character should
have its original colors retained when printed (for button glyphs).
As for <white_teeth>:
The renderer PR has replaced draw-time whitening of sprites/etc
(using BlitSurfaceColoured) by load-time whitening of entire images
(using LoadImage with TEX_WHITE as an argument).
This means we have a problem: fonts have always had their glyphs
whitened at printing time, and since I'm adding support for colored
button glyphs, I changed it so glyphs would sometimes not be whitened.
But if we can't whiten at print time, then we'd need to whiten at load
time, and if we whiten the entire font, any colored glyphs will get
destroyed too. If you whiten the image selectively, well, we need more
code to target specific squares in the image, and it's kind of a waste
when you need to whiten 30000 12x12 Chinese characters when you're only
going to need a handful, but you don't know which ones.
The solution: Whitening fonts is useless if all the non-colored glyphs
are already white, so we don't need to do it anyway! However, any
existing fonts that have non-white glyphs (and I know of at least one
level like that) will still need to be whitened. So there is now a
font property <white_teeth> that can be specified in the fontmeta,
which indicates that the font is already pre-whitened. If not
specified, traditional whitening behavior will be used, and the font
cannot use colored glyphs.
2023-01-02 05:14:53 +01:00
|
|
|
#include "Font.h"
|
2024-01-02 05:01:39 +01:00
|
|
|
#include "FontBidi.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "Game.h"
|
|
|
|
#include "Graphics.h"
|
2023-01-02 01:36:43 +01:00
|
|
|
#include "GraphicsUtil.h"
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "Input.h"
|
Optimize recompilation from changing commit hash
This reworks how the commit hash and date are compiled so that if
they're changed (and they're changed often), only one source file needs
to be recompiled in order to update it everywhere in the game, no matter
how many source files use the hash or date.
The commit hash and date are now declared in InterimVersion.h (and they
need `extern "C"` guards because otherwise it results in a link fail on
MSVC because MSVC is stupid).
To do this, what now happens is that upon every rebuild,
InterimVersion.in.c is processed to create InterimVersion.out.c, then
InterimVersion.out.c is compiled into its own static library that is
then linked with VVVVVV.
(Why didn't I just simply add it to the list of VVVVVV source files?
Well, doing it _now_ does nothing because at that point the horse is
already out of the barn, and the VVVVVV executable has already been
declared, so I can't modify its sources. And I can't do it before
either, because we depend on the VVVVVV executable existing to do the
interim version logic. I could probably work around this by cleverly
moving around lines, but that'd separate related logic from each other.)
And yes, the naming convention has changed. Not only did I rename
Version to InterimVersion (to clearly differentiate it from
ReleaseVersion, which I'll be adding later), I also named the files
InterimVersion.in.c and InterimVersion.out.c instead of
InterimVersion.c.in and InterimVersion.c.out. I needed to put the file
extension on the end because otherwise CMake wouldn't recognize what
kind of language it is, and I mean like yeah duh of course it doesn't,
my text editor doesn't recognize it either.
2022-08-23 06:21:23 +02:00
|
|
|
#include "InterimVersion.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "KeyPoll.h"
|
2023-07-03 19:34:02 +02:00
|
|
|
#include "LevelDebugger.h"
|
2022-12-30 22:57:24 +01:00
|
|
|
#include "Localization.h"
|
|
|
|
#include "LocalizationStorage.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"
|
2022-08-23 06:22:57 +02:00
|
|
|
#include "ReleaseVersion.h"
|
2020-07-19 21:43:29 +02:00
|
|
|
#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 "UtilityClass.h"
|
2021-02-24 00:21:29 +01:00
|
|
|
#include "Vlogging.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
scriptclass script;
|
|
|
|
|
2021-02-21 01:01:39 +01:00
|
|
|
std::vector<CustomEntity> customentities;
|
2021-02-21 00:40:11 +01:00
|
|
|
customlevelclass cl;
|
2020-04-02 23:43:50 +02:00
|
|
|
editorclass ed;
|
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;
|
CLI: Allow no music to be played if save pos used
If you provided any one of -playx, -playy, -playrx, -playry, -playgc, or
-playmusic in command-line arguments for command-line playtesting, then
the game would always try to play music, even if you passed a negative
-playmusic. This wouldn't do anything in that case, unless you had
MMMMMM installed, in which case it would play MMMMMM track 15
(Predestined Fate Final Level) due to the legacy wraparound bug.
To fix this, only play music if the track provided is greater than -1.
Additionally, to prevent it from playing Path Complete by default if you
specify any of the other save position arguments but n ot -playmusic,
it's now initialized to -1 instead of 0.
2023-06-10 20:41:32 +02:00
|
|
|
static int savemusic = -1;
|
2021-01-10 18:14:37 +01:00
|
|
|
static std::string playassets;
|
|
|
|
|
|
|
|
static std::string playtestname;
|
|
|
|
|
2021-10-26 03:16:47 +02:00
|
|
|
static volatile Uint64 time_ = 0;
|
|
|
|
static volatile Uint64 timePrev = 0;
|
2021-01-10 18:14:37 +01:00
|
|
|
static volatile Uint32 accumulator = 0;
|
2021-03-31 21:06:26 +02:00
|
|
|
|
|
|
|
#ifndef __EMSCRIPTEN__
|
2021-10-26 03:16:47 +02:00
|
|
|
static volatile Uint64 f_time = 0;
|
|
|
|
static volatile Uint64 f_timePrev = 0;
|
2021-03-31 21:06:26 +02:00
|
|
|
#endif
|
2020-06-14 20:45:36 +02:00
|
|
|
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
enum FuncType
|
|
|
|
{
|
|
|
|
Func_null,
|
|
|
|
Func_fixed,
|
2021-03-15 10:56:46 +01:00
|
|
|
Func_input,
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
Func_delta
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ImplFunc
|
|
|
|
{
|
|
|
|
enum FuncType type;
|
|
|
|
void (*func)(void);
|
|
|
|
};
|
|
|
|
|
|
|
|
static void runscript(void)
|
|
|
|
{
|
|
|
|
script.run();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void teleportermodeinput(void)
|
|
|
|
{
|
|
|
|
if (game.useteleporter)
|
|
|
|
{
|
|
|
|
teleporterinput();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
script.run();
|
|
|
|
gameinput();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void flipmodeoff(void)
|
|
|
|
{
|
|
|
|
graphics.flipmode = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void focused_begin(void);
|
|
|
|
static void focused_end(void);
|
|
|
|
|
|
|
|
static const inline struct ImplFunc* get_gamestate_funcs(
|
|
|
|
const int gamestate,
|
|
|
|
int* num_implfuncs
|
|
|
|
) {
|
|
|
|
switch (gamestate)
|
|
|
|
{
|
|
|
|
|
|
|
|
#define FUNC_LIST_BEGIN(GAMESTATE) \
|
|
|
|
case GAMESTATE: \
|
|
|
|
{ \
|
|
|
|
static const struct ImplFunc implfuncs[] = { \
|
|
|
|
{Func_fixed, focused_begin},
|
|
|
|
|
|
|
|
#define FUNC_LIST_END \
|
|
|
|
{Func_fixed, focused_end} \
|
|
|
|
}; \
|
|
|
|
*num_implfuncs = SDL_arraysize(implfuncs); \
|
|
|
|
return implfuncs; \
|
|
|
|
}
|
|
|
|
|
|
|
|
FUNC_LIST_BEGIN(GAMEMODE)
|
2020-11-08 01:49:32 +01:00
|
|
|
{Func_fixed, runscript},
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
{Func_fixed, gamerenderfixed},
|
|
|
|
{Func_delta, gamerender},
|
2021-03-15 10:46:29 +01:00
|
|
|
{Func_input, gameinput},
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
{Func_fixed, gamelogic},
|
|
|
|
FUNC_LIST_END
|
|
|
|
|
|
|
|
FUNC_LIST_BEGIN(TITLEMODE)
|
2021-03-15 10:56:46 +01:00
|
|
|
{Func_input, titleinput},
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
{Func_fixed, titlerenderfixed},
|
|
|
|
{Func_delta, titlerender},
|
|
|
|
{Func_fixed, titlelogic},
|
|
|
|
FUNC_LIST_END
|
|
|
|
|
|
|
|
FUNC_LIST_BEGIN(MAPMODE)
|
|
|
|
{Func_fixed, maprenderfixed},
|
|
|
|
{Func_delta, maprender},
|
2021-03-15 10:56:46 +01:00
|
|
|
{Func_input, mapinput},
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
{Func_fixed, maplogic},
|
|
|
|
FUNC_LIST_END
|
|
|
|
|
|
|
|
FUNC_LIST_BEGIN(TELEPORTERMODE)
|
2021-09-02 20:46:56 +02:00
|
|
|
{Func_fixed, teleporterrenderfixed},
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
{Func_delta, teleporterrender},
|
2021-03-15 10:56:46 +01:00
|
|
|
{Func_input, teleportermodeinput},
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
{Func_fixed, maplogic},
|
|
|
|
FUNC_LIST_END
|
|
|
|
|
|
|
|
FUNC_LIST_BEGIN(GAMECOMPLETE)
|
|
|
|
{Func_fixed, gamecompleterenderfixed},
|
|
|
|
{Func_delta, gamecompleterender},
|
2021-03-15 10:56:46 +01:00
|
|
|
{Func_input, gamecompleteinput},
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
{Func_fixed, gamecompletelogic},
|
|
|
|
FUNC_LIST_END
|
|
|
|
|
|
|
|
FUNC_LIST_BEGIN(GAMECOMPLETE2)
|
2023-05-22 21:18:40 +02:00
|
|
|
{Func_fixed, gamecompleterenderfixed2},
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
{Func_delta, gamecompleterender2},
|
2021-03-15 10:56:46 +01:00
|
|
|
{Func_input, gamecompleteinput2},
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
{Func_fixed, gamecompletelogic2},
|
|
|
|
FUNC_LIST_END
|
|
|
|
|
|
|
|
FUNC_LIST_BEGIN(EDITORMODE)
|
|
|
|
{Func_fixed, flipmodeoff},
|
2021-03-15 10:56:46 +01:00
|
|
|
{Func_input, editorinput},
|
2023-03-02 21:28:02 +01:00
|
|
|
{Func_fixed, editorlogic},
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
{Func_fixed, editorrenderfixed},
|
|
|
|
{Func_delta, editorrender},
|
|
|
|
FUNC_LIST_END
|
|
|
|
|
|
|
|
FUNC_LIST_BEGIN(PRELOADER)
|
2021-03-15 10:56:46 +01:00
|
|
|
{Func_input, preloaderinput},
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
{Func_fixed, preloaderrenderfixed},
|
|
|
|
{Func_delta, preloaderrender},
|
|
|
|
FUNC_LIST_END
|
|
|
|
|
|
|
|
#undef FUNC_LIST_END
|
|
|
|
#undef FUNC_LIST_BEGIN
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_assert(0 && "Invalid gamestate!");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum IndexCode
|
|
|
|
{
|
|
|
|
Index_none,
|
|
|
|
Index_end
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct ImplFunc* gamestate_funcs = NULL;
|
|
|
|
static int num_gamestate_funcs = 0;
|
|
|
|
static int gamestate_func_index = -1;
|
|
|
|
|
|
|
|
static enum IndexCode increment_gamestate_func_index(void)
|
|
|
|
{
|
|
|
|
gamestate_func_index++;
|
|
|
|
|
|
|
|
if (gamestate_func_index == num_gamestate_funcs)
|
|
|
|
{
|
|
|
|
/* Reached the end of current gamestate order.
|
|
|
|
* Re-fetch for new order if gamestate changed.
|
|
|
|
*/
|
|
|
|
gamestate_funcs = get_gamestate_funcs(
|
|
|
|
game.gamestate,
|
|
|
|
&num_gamestate_funcs
|
|
|
|
);
|
|
|
|
|
2020-12-24 08:24:31 +01:00
|
|
|
/* Also run callbacks that were deferred to end of func sequence. */
|
|
|
|
DEFER_execute_callbacks();
|
|
|
|
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
gamestate_func_index = 0;
|
|
|
|
|
|
|
|
return Index_end;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Index_none;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void unfocused_run(void);
|
|
|
|
|
|
|
|
static const struct ImplFunc unfocused_func_list[] = {
|
2021-03-21 21:53:29 +01:00
|
|
|
{
|
|
|
|
Func_input, /* we still need polling when unfocused */
|
2021-09-07 17:45:45 +02:00
|
|
|
NULL
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Func_delta,
|
2021-03-21 21:53:29 +01:00
|
|
|
unfocused_run
|
|
|
|
}
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
};
|
|
|
|
static const struct ImplFunc* unfocused_funcs = unfocused_func_list;
|
|
|
|
static int num_unfocused_funcs = SDL_arraysize(unfocused_func_list);
|
2021-03-21 22:19:08 +01:00
|
|
|
static int unfocused_func_index = 0; // This does not get incremented on start, do NOT use -1!
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
|
|
|
|
static enum IndexCode increment_unfocused_func_index(void)
|
|
|
|
{
|
|
|
|
unfocused_func_index++;
|
|
|
|
|
|
|
|
if (unfocused_func_index == num_unfocused_funcs)
|
|
|
|
{
|
|
|
|
unfocused_func_index = 0;
|
|
|
|
|
|
|
|
return Index_end;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Index_none;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct ImplFunc** active_funcs = NULL;
|
|
|
|
static int* num_active_funcs = NULL;
|
|
|
|
static int* active_func_index = NULL;
|
|
|
|
static enum IndexCode (*increment_func_index)(void) = NULL;
|
|
|
|
|
|
|
|
enum LoopCode
|
|
|
|
{
|
|
|
|
Loop_continue,
|
|
|
|
Loop_stop
|
|
|
|
};
|
|
|
|
|
|
|
|
static enum LoopCode loop_assign_active_funcs(void)
|
|
|
|
{
|
|
|
|
if (key.isActive)
|
|
|
|
{
|
|
|
|
active_funcs = &gamestate_funcs;
|
|
|
|
num_active_funcs = &num_gamestate_funcs;
|
|
|
|
active_func_index = &gamestate_func_index;
|
|
|
|
increment_func_index = &increment_gamestate_func_index;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
active_funcs = &unfocused_funcs;
|
|
|
|
num_active_funcs = &num_unfocused_funcs;
|
|
|
|
active_func_index = &unfocused_func_index;
|
|
|
|
increment_func_index = &increment_unfocused_func_index;
|
|
|
|
}
|
|
|
|
return Loop_continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum LoopCode loop_run_active_funcs(void)
|
|
|
|
{
|
|
|
|
while ((*active_funcs)[*active_func_index].type != Func_delta)
|
|
|
|
{
|
|
|
|
const struct ImplFunc* implfunc = &(*active_funcs)[*active_func_index];
|
|
|
|
enum IndexCode index_code;
|
|
|
|
|
2021-04-02 00:39:56 +02:00
|
|
|
if (implfunc->type == Func_input && !game.inputdelay)
|
2021-03-15 10:56:46 +01:00
|
|
|
{
|
|
|
|
key.Poll();
|
|
|
|
}
|
|
|
|
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
if (implfunc->type != Func_null && implfunc->func != NULL)
|
|
|
|
{
|
|
|
|
implfunc->func();
|
|
|
|
}
|
|
|
|
|
|
|
|
index_code = increment_func_index();
|
|
|
|
|
|
|
|
if (index_code == Index_end)
|
|
|
|
{
|
|
|
|
return Loop_continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Fix filter/screenshake/flash update order
In 2.2, at render time, the game rendered screenshakes and flashes if
their timers were above 0, and then decremented them afterwards. The
game would also update the analogue filter right before rendering it,
too.
In 2.3, this was changed so the flash and screenshake timers were
unified, and also done at the end of the frame - right before rendering
happened. This resulted in 1-frame flashes and screenshakes not
rendering at all. The other changes in this patchset don't fix this
either. The analogue filter was also in the wrong order, but that is
less of an issue than flashes and screenshakes.
So, what I've done is made the flash and screenshake timers update right
before the loop switches over to rendering, and only decrements them
when we switch back to fixed functions (after rendering). The analogue
filter is also updated right before rendering as well. This restores
1-frame flashes and screenshakes, as well as restores the correct order
of analogue filter updates.
2021-03-18 01:53:17 +01:00
|
|
|
/* About to switch over to rendering... but call this first. */
|
|
|
|
graphics.renderfixedpre();
|
|
|
|
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
return Loop_stop;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum LoopCode loop_begin(void);
|
|
|
|
static enum LoopCode loop_end(void);
|
|
|
|
|
|
|
|
static enum LoopCode (*const meta_funcs[])(void) = {
|
|
|
|
loop_begin,
|
|
|
|
loop_assign_active_funcs,
|
|
|
|
loop_run_active_funcs,
|
|
|
|
loop_end
|
|
|
|
};
|
|
|
|
static int meta_func_index = 0;
|
|
|
|
|
|
|
|
static void inline fixedloop(void)
|
|
|
|
{
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
enum LoopCode loop_code = meta_funcs[meta_func_index]();
|
|
|
|
|
|
|
|
if (loop_code == Loop_stop)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
meta_func_index = (meta_func_index + 1) % SDL_arraysize(meta_funcs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 23:23:59 +01:00
|
|
|
static void inline deltaloop(void);
|
2020-06-14 20:45:36 +02:00
|
|
|
|
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 23:23:59 +01:00
|
|
|
static void cleanup(void);
|
2021-02-16 02:52:41 +01:00
|
|
|
|
2021-03-31 21:06:26 +02:00
|
|
|
#ifdef __EMSCRIPTEN__
|
2021-09-20 22:48:49 +02:00
|
|
|
static void emscriptenloop(void)
|
2021-03-31 21:06:26 +02:00
|
|
|
{
|
|
|
|
timePrev = time_;
|
2021-10-26 03:16:47 +02:00
|
|
|
time_ = SDL_GetTicks64();
|
2021-03-31 21:06:26 +02:00
|
|
|
deltaloop();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2024-06-02 20:09:22 +02:00
|
|
|
static void keep_console_open(const bool open_console)
|
|
|
|
{
|
|
|
|
if (!open_console)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("Press ENTER to quit.");
|
|
|
|
|
|
|
|
int c;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
c = getchar();
|
|
|
|
}
|
|
|
|
while (c != '\n' && c != EOF);
|
|
|
|
}
|
|
|
|
|
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;
|
2022-12-30 22:57:24 +01:00
|
|
|
char* langDir = NULL;
|
|
|
|
char* fontsDir = NULL;
|
2022-11-14 23:10:24 +01:00
|
|
|
bool seed_use_sdl_getticks = false;
|
2022-11-15 04:34:48 +01:00
|
|
|
bool open_console = false;
|
2024-06-02 20:09:22 +02:00
|
|
|
bool print_version = false;
|
|
|
|
bool print_addresses = false;
|
|
|
|
int invalid_arg = 0;
|
|
|
|
int invalid_partial_arg = 0;
|
2020-02-03 00:28:26 +01:00
|
|
|
|
2021-02-24 00:22:28 +01:00
|
|
|
vlog_init();
|
|
|
|
|
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 \
|
|
|
|
{ \
|
2024-06-02 20:09:22 +02:00
|
|
|
invalid_partial_arg = 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
|
|
|
}
|
|
|
|
|
2022-05-23 23:34:36 +02:00
|
|
|
if (ARG("-version"))
|
|
|
|
{
|
2024-06-02 20:09:22 +02:00
|
|
|
print_version = true;
|
2022-05-23 23:34:36 +02:00
|
|
|
}
|
2023-02-18 05:41:11 +01:00
|
|
|
else if (ARG("-addresses"))
|
|
|
|
{
|
2024-06-02 20:09:22 +02:00
|
|
|
print_addresses = true;
|
2023-02-18 05:41:11 +01:00
|
|
|
}
|
2022-05-23 23:34:36 +02:00
|
|
|
else if (ARG("-renderer"))
|
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
|
|
|
{
|
|
|
|
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];
|
|
|
|
})
|
|
|
|
}
|
2022-12-30 22:57:24 +01:00
|
|
|
else if (ARG("-langdir"))
|
|
|
|
{
|
|
|
|
ARG_INNER({
|
|
|
|
i++;
|
|
|
|
langDir = argv[i];
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else if (ARG("-fontsdir"))
|
|
|
|
{
|
|
|
|
ARG_INNER({
|
|
|
|
i++;
|
|
|
|
fontsDir = argv[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("-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++;
|
2021-04-03 21:48:47 +02:00
|
|
|
// 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
|
|
|
}
|
2023-07-03 19:34:02 +02:00
|
|
|
else if (ARG("-leveldebugger"))
|
|
|
|
{
|
|
|
|
level_debugger::set_forced();
|
|
|
|
}
|
2021-09-01 22:41:06 +02:00
|
|
|
else if (ARG("-nooutput"))
|
|
|
|
{
|
|
|
|
vlog_toggle_output(0);
|
|
|
|
}
|
2021-09-01 22:28:51 +02:00
|
|
|
else if (ARG("-forcecolor") || ARG("-forcecolour"))
|
|
|
|
{
|
|
|
|
vlog_toggle_color(1);
|
|
|
|
}
|
|
|
|
else if (ARG("-nocolor") || ARG("-nocolour"))
|
|
|
|
{
|
|
|
|
vlog_toggle_color(0);
|
|
|
|
}
|
2021-09-01 23:11:23 +02:00
|
|
|
else if (ARG("-debug"))
|
|
|
|
{
|
|
|
|
vlog_toggle_debug(1);
|
|
|
|
}
|
2021-09-01 22:41:06 +02:00
|
|
|
else if (ARG("-noinfo"))
|
|
|
|
{
|
|
|
|
vlog_toggle_info(0);
|
|
|
|
}
|
|
|
|
else if (ARG("-nowarn"))
|
|
|
|
{
|
|
|
|
vlog_toggle_warn(0);
|
|
|
|
}
|
|
|
|
else if (ARG("-noerror"))
|
|
|
|
{
|
|
|
|
vlog_toggle_error(0);
|
|
|
|
}
|
2022-12-30 22:57:24 +01:00
|
|
|
else if (ARG("-translator"))
|
|
|
|
{
|
|
|
|
loc::show_translator_menu = true;
|
|
|
|
}
|
2022-11-15 04:34:48 +01:00
|
|
|
#ifdef _WIN32
|
|
|
|
else if (ARG("-console"))
|
|
|
|
{
|
|
|
|
open_console = true;
|
|
|
|
}
|
|
|
|
#endif
|
2022-11-14 23:10:24 +01:00
|
|
|
else if (ARG("-seed-use-sdl-getticks"))
|
|
|
|
{
|
|
|
|
seed_use_sdl_getticks = true;
|
|
|
|
}
|
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
|
|
|
|
{
|
2024-06-02 20:09:22 +02:00
|
|
|
invalid_arg = i;
|
2020-08-03 06:30:08 +02:00
|
|
|
}
|
2020-02-03 00:28:26 +01:00
|
|
|
}
|
|
|
|
|
2022-12-30 22:57:24 +01:00
|
|
|
#if defined(ALWAYS_SHOW_TRANSLATOR_MENU)
|
|
|
|
loc::show_translator_menu = true;
|
|
|
|
#endif
|
|
|
|
|
2022-11-15 04:34:48 +01:00
|
|
|
#ifdef _WIN32
|
|
|
|
if (open_console)
|
|
|
|
{
|
|
|
|
vlog_open_console();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2024-06-02 20:09:22 +02:00
|
|
|
if (invalid_arg > 0)
|
|
|
|
{
|
|
|
|
vlog_error("Error: invalid option: %s", argv[invalid_arg]);
|
|
|
|
keep_console_open(open_console);
|
|
|
|
VVV_exit(1);
|
|
|
|
}
|
|
|
|
else if (invalid_partial_arg > 0)
|
|
|
|
{
|
|
|
|
vlog_error("%s option requires one argument.", argv[invalid_partial_arg]);
|
|
|
|
keep_console_open(open_console);
|
|
|
|
VVV_exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (print_version)
|
|
|
|
{
|
|
|
|
/* Just print the version and exit. No vlogging. */
|
|
|
|
puts(
|
|
|
|
"VVVVVV " RELEASE_VERSION
|
|
|
|
#ifdef MAKEANDPLAY
|
|
|
|
" [M&P]"
|
|
|
|
#endif
|
|
|
|
);
|
|
|
|
#ifdef INTERIM_VERSION_EXISTS
|
|
|
|
puts(COMMIT_DATE);
|
|
|
|
puts(INTERIM_COMMIT);
|
|
|
|
puts(BRANCH_NAME);
|
|
|
|
#endif
|
|
|
|
keep_console_open(open_console);
|
|
|
|
VVV_exit(0);
|
|
|
|
}
|
|
|
|
else if (print_addresses)
|
|
|
|
{
|
|
|
|
printf("cl : %p\n", (void*) &cl);
|
|
|
|
printf("ed : %p\n", (void*) &ed);
|
|
|
|
printf("game : %p\n", (void*) &game);
|
|
|
|
printf("gameScreen : %p\n", (void*) &gameScreen);
|
|
|
|
printf("graphics : %p\n", (void*) &graphics);
|
|
|
|
printf("help : %p\n", (void*) &help);
|
|
|
|
printf("key : %p\n", (void*) &key);
|
|
|
|
printf("map : %p\n", (void*) &map);
|
|
|
|
printf("music : %p\n", (void*) &music);
|
|
|
|
printf("obj : %p\n", (void*) &obj);
|
|
|
|
printf("script : %p\n", (void*) &script);
|
|
|
|
|
|
|
|
keep_console_open(open_console);
|
|
|
|
VVV_exit(0);
|
|
|
|
}
|
|
|
|
|
Enable SDL_HINT_IME_SHOW_UI to make typing CJK not guesswork
For some reason, the default behavior of SDL and/or Windows(?) (I only
tested this on Windows) seems to result in the fact that if any SDL app
doesn't account for it, there is no way for Japanese and Chinese
speakers to know what they're typing in.
How IMEs are supposed to work is that you can type words as sort of
WIP versions, and then select out of a list of candidates what the
final result should be. The app may display the WIP text and tell the
IME where the text field is so that the IME's menu can be displayed
around it. But if the app doesn't say where the text field is, then the
candidate list can also be displayed at the corner of the screen, which
is done in Minecraft.
By default, however, SDL apps don't get a candidate list at all, which
means you're basically flying blind as to what you're typing in, and
you would have to basically open notepad and copy-paste everything from
there - unless I'm missing something.
This commit sets the SDL_HINT_IME_SHOW_UI hint (added in SDL 2.0.18
apparently), so that the candidate list is at least shown in the corner.
We can probably deal with positioning and uncommitted text later.
2023-10-25 01:35:00 +02:00
|
|
|
SDL_SetHintWithPriority(SDL_HINT_IME_SHOW_UI, "1", SDL_HINT_OVERRIDE);
|
2024-08-01 19:18:20 +02:00
|
|
|
SDL_SetHintWithPriority(SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, "1", SDL_HINT_OVERRIDE);
|
Enable SDL_HINT_IME_SHOW_UI to make typing CJK not guesswork
For some reason, the default behavior of SDL and/or Windows(?) (I only
tested this on Windows) seems to result in the fact that if any SDL app
doesn't account for it, there is no way for Japanese and Chinese
speakers to know what they're typing in.
How IMEs are supposed to work is that you can type words as sort of
WIP versions, and then select out of a list of candidates what the
final result should be. The app may display the WIP text and tell the
IME where the text field is so that the IME's menu can be displayed
around it. But if the app doesn't say where the text field is, then the
candidate list can also be displayed at the corner of the screen, which
is done in Minecraft.
By default, however, SDL apps don't get a candidate list at all, which
means you're basically flying blind as to what you're typing in, and
you would have to basically open notepad and copy-paste everything from
there - unless I'm missing something.
This commit sets the SDL_HINT_IME_SHOW_UI hint (added in SDL 2.0.18
apparently), so that the candidate list is at least shown in the corner.
We can probably deal with positioning and uncommitted text later.
2023-10-25 01:35:00 +02:00
|
|
|
|
2024-01-10 15:02:09 +01:00
|
|
|
/* We already do the button swapping in ButtonGlyphs, disable SDL's swapping */
|
|
|
|
SDL_SetHintWithPriority(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0", SDL_HINT_OVERRIDE);
|
|
|
|
|
2022-12-30 22:57:24 +01:00
|
|
|
if(!FILESYSTEM_init(argv[0], baseDir, assetsPath, langDir, fontsDir))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2021-02-24 00:21:29 +01:00
|
|
|
vlog_error("Unable to initialize filesystem!");
|
2024-01-01 01:54:53 +01:00
|
|
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Unable to initialize filesystem!", NULL);
|
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();
|
|
|
|
|
2021-02-24 00:21:29 +01:00
|
|
|
vlog_info("\t\t");
|
|
|
|
vlog_info("\t\t");
|
|
|
|
vlog_info("\t\t VVVVVV");
|
|
|
|
vlog_info("\t\t");
|
|
|
|
vlog_info("\t\t");
|
|
|
|
vlog_info("\t\t 8888888888888888 ");
|
|
|
|
vlog_info("\t\t88888888888888888888");
|
|
|
|
vlog_info("\t\t888888 8888 88");
|
|
|
|
vlog_info("\t\t888888 8888 88");
|
|
|
|
vlog_info("\t\t88888888888888888888");
|
|
|
|
vlog_info("\t\t88888888888888888888");
|
|
|
|
vlog_info("\t\t888888 88");
|
|
|
|
vlog_info("\t\t88888888 8888");
|
|
|
|
vlog_info("\t\t 8888888888888888 ");
|
|
|
|
vlog_info("\t\t 88888888 ");
|
|
|
|
vlog_info("\t\t 8888888888888888 ");
|
|
|
|
vlog_info("\t\t88888888888888888888");
|
|
|
|
vlog_info("\t\t88888888888888888888");
|
|
|
|
vlog_info("\t\t88888888888888888888");
|
|
|
|
vlog_info("\t\t8888 88888888 8888");
|
|
|
|
vlog_info("\t\t8888 88888888 8888");
|
|
|
|
vlog_info("\t\t 888888888888 ");
|
|
|
|
vlog_info("\t\t 8888 8888 ");
|
|
|
|
vlog_info("\t\t 888888 888888 ");
|
|
|
|
vlog_info("\t\t 888888 888888 ");
|
|
|
|
vlog_info("\t\t 888888 888888 ");
|
|
|
|
vlog_info("\t\t");
|
|
|
|
vlog_info("\t\t");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2023-01-07 19:28:07 +01:00
|
|
|
// Set up screen
|
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();
|
2022-11-14 23:10:24 +01:00
|
|
|
game.seed_use_sdl_getticks = seed_use_sdl_getticks;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-08 20:30:57 +02:00
|
|
|
game.gamestate = PRELOADER;
|
|
|
|
|
|
|
|
game.menustart = false;
|
|
|
|
|
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;
|
2021-04-23 05:27:45 +02:00
|
|
|
map.oldypos = map.ypos;
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
map.setbgobjlerp(graphics.towerbg);
|
|
|
|
map.setbgobjlerp(graphics.titlebg);
|
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)
|
2021-12-25 09:18:51 +01:00
|
|
|
struct ScreenSettings screen_settings;
|
2021-12-25 09:22:22 +01:00
|
|
|
SDL_zero(screen_settings);
|
2021-12-25 09:18:51 +01:00
|
|
|
ScreenSettings_default(&screen_settings);
|
2020-11-13 01:45:51 +01:00
|
|
|
game.loadstats(&screen_settings);
|
|
|
|
game.loadsettings(&screen_settings);
|
2021-12-25 09:18:51 +01:00
|
|
|
gameScreen.init(&screen_settings);
|
2020-11-13 01:45:51 +01:00
|
|
|
}
|
2020-07-08 20:30:57 +02:00
|
|
|
|
Add support for button glyph display
This adds a function that converts an action (such as interacting
in-game) to the corresponding button text ("ENTER", "E") or button
glyph (PlayStation triangle, Steam Deck Y, etc). This function
currently only gives the existing ENTERs or Es, because I don't know
how best to detect controller usage, or whether the game is running on
a Steam Deck, or what buttons need to be displayed there. Still, it
should now be really easy to adapt the rendering of keyboard keys to
consoles, controllers, or rebound keys.
To identify the actions that currently need to be displayed, this
commit also adds the initial enums for action sets as described by
Ethan in a comment in #834 (Jan 18, 2022).
2023-03-18 22:30:16 +01:00
|
|
|
BUTTONGLYPHS_init();
|
Start rewrite of font system
This is still a work in progress, but the existing font system has been
removed and replaced by a new one, in Font.cpp.
Design goals of the new font system include supporting colored button
glyphs, different fonts for different languages, and larger fonts than
8x8 for Chinese, Japanese and Korean, while being able to support their
30000+ characters without hiccups, slowdowns or high memory usage. And
to have more flexibility with fonts in general. Plus, Graphics.cpp was
long enough as-is, so it's good to have a dedicated file for font
storage.
The old font system worked with a std::vector<SDL_Surface*> to store
8x8 surfaces for each character, and a std::map<int,int> to store
mappings between codepoints and vector indexes.
The new system has a per-font collection of pages for every block of
0x1000 (4096) codepoints, that may be allocated as needed. A glyph on
a page contains the index of the glyph in the image (giving its
coordinates), the advance (how much the cursor should advance, so the
width of that glyph) and some flags which would be at least whether the
glyph exists and whether it is colored.
Most of the *new* features aren't implemented yet; it's currently
hardcoded to the regular 8x8 font.png, but it should be functionally
equivalent to the previous behavior. The only thing that doesn't really
work yet is level-specific font.png, but that'll be supported again
soon enough.
This commit also adds fontmeta (xml) support.
Since the fonts folder is mounted at graphics/, there are two main
options for recognizing non-font.png fonts: the font files have to be
prefixed with font (or font_) or some special file extension is
involved to signal what files are fonts. I always had a font.xml in
mind (so font_cn.xml, font_ja.xml, etc) but if there's ever gonna be
a need for further xml files inside the graphics folder, we have a
problem. So I named them .fontmeta instead.
A .fontmeta file looks somewhat like this:
<?xml version="1.0" encoding="UTF-8"?>
<font_metadata>
<width>12</width>
<height>12</height>
<white_teeth>1</white_teeth>
<chars>
<range start="0x20" end="0x7E"/>
<range start="0x80" end="0x80"/>
<range start="0xA0" end="0xDF"/>
<range start="0x250" end="0x2A8"/>
<range start="0x2AD" end="0x2AD"/>
<range start="0x2C7" end="0x2C7"/>
<range start="0x2C9" end="0x2CB"/>
...
</chars>
<special>
<range start="0x00" end="0x1F" advance="6"/>
<range start="0x61" end="0x66" color="1"/>
<range start="0x63" end="0x63" color="0"/>
</special>
</font_metadata>
The <chars> tag can be used to specify characters instead of in a .txt.
The original idea was to just always use the existing .txt system for
specifying the font charset, and only use the XML for the other stuff
that the .txt doesn't cover. However, it's probably better to keep it
simple if possible - having to only have a .png and a .fontmeta seems
simpler than having the data spread out over three files. And a major
advantage: Chinese fonts can have about 30000 characters! It's more
efficient to be able to have a tag saying "now there's 20902 characters
starting at U+4E00" than to include them all in a text file and having
to UTF-8 decode every single one of them.
If a font.txt exists, it takes priority over the <chars> tag, and in
that case, there's no reason to include the <chars> tag in the XML.
But font.txt has to be in the same directory as font.png, otherwise it
is rejected. Same for font.fontmeta. If neither font.txt nor <chars>
exist, then the font is seen as a 2.2-and-below-style ASCII font.
In <special>: advance is the number of pixels the cursor advances after
drawing the character (so the width of the character, without affecting
the grid in the source image), color is whether the character should
have its original colors retained when printed (for button glyphs).
As for <white_teeth>:
The renderer PR has replaced draw-time whitening of sprites/etc
(using BlitSurfaceColoured) by load-time whitening of entire images
(using LoadImage with TEX_WHITE as an argument).
This means we have a problem: fonts have always had their glyphs
whitened at printing time, and since I'm adding support for colored
button glyphs, I changed it so glyphs would sometimes not be whitened.
But if we can't whiten at print time, then we'd need to whiten at load
time, and if we whiten the entire font, any colored glyphs will get
destroyed too. If you whiten the image selectively, well, we need more
code to target specific squares in the image, and it's kind of a waste
when you need to whiten 30000 12x12 Chinese characters when you're only
going to need a handful, but you don't know which ones.
The solution: Whitening fonts is useless if all the non-colored glyphs
are already white, so we don't need to do it anyway! However, any
existing fonts that have non-white glyphs (and I know of at least one
level like that) will still need to be whitened. So there is now a
font property <white_teeth> that can be specified in the fontmeta,
which indicates that the font is already pre-whitened. If not
specified, traditional whitening behavior will be used, and the font
cannot use colored glyphs.
2023-01-02 05:14:53 +01:00
|
|
|
font::load_main();
|
2024-01-02 05:01:39 +01:00
|
|
|
font::bidi_init();
|
Start rewrite of font system
This is still a work in progress, but the existing font system has been
removed and replaced by a new one, in Font.cpp.
Design goals of the new font system include supporting colored button
glyphs, different fonts for different languages, and larger fonts than
8x8 for Chinese, Japanese and Korean, while being able to support their
30000+ characters without hiccups, slowdowns or high memory usage. And
to have more flexibility with fonts in general. Plus, Graphics.cpp was
long enough as-is, so it's good to have a dedicated file for font
storage.
The old font system worked with a std::vector<SDL_Surface*> to store
8x8 surfaces for each character, and a std::map<int,int> to store
mappings between codepoints and vector indexes.
The new system has a per-font collection of pages for every block of
0x1000 (4096) codepoints, that may be allocated as needed. A glyph on
a page contains the index of the glyph in the image (giving its
coordinates), the advance (how much the cursor should advance, so the
width of that glyph) and some flags which would be at least whether the
glyph exists and whether it is colored.
Most of the *new* features aren't implemented yet; it's currently
hardcoded to the regular 8x8 font.png, but it should be functionally
equivalent to the previous behavior. The only thing that doesn't really
work yet is level-specific font.png, but that'll be supported again
soon enough.
This commit also adds fontmeta (xml) support.
Since the fonts folder is mounted at graphics/, there are two main
options for recognizing non-font.png fonts: the font files have to be
prefixed with font (or font_) or some special file extension is
involved to signal what files are fonts. I always had a font.xml in
mind (so font_cn.xml, font_ja.xml, etc) but if there's ever gonna be
a need for further xml files inside the graphics folder, we have a
problem. So I named them .fontmeta instead.
A .fontmeta file looks somewhat like this:
<?xml version="1.0" encoding="UTF-8"?>
<font_metadata>
<width>12</width>
<height>12</height>
<white_teeth>1</white_teeth>
<chars>
<range start="0x20" end="0x7E"/>
<range start="0x80" end="0x80"/>
<range start="0xA0" end="0xDF"/>
<range start="0x250" end="0x2A8"/>
<range start="0x2AD" end="0x2AD"/>
<range start="0x2C7" end="0x2C7"/>
<range start="0x2C9" end="0x2CB"/>
...
</chars>
<special>
<range start="0x00" end="0x1F" advance="6"/>
<range start="0x61" end="0x66" color="1"/>
<range start="0x63" end="0x63" color="0"/>
</special>
</font_metadata>
The <chars> tag can be used to specify characters instead of in a .txt.
The original idea was to just always use the existing .txt system for
specifying the font charset, and only use the XML for the other stuff
that the .txt doesn't cover. However, it's probably better to keep it
simple if possible - having to only have a .png and a .fontmeta seems
simpler than having the data spread out over three files. And a major
advantage: Chinese fonts can have about 30000 characters! It's more
efficient to be able to have a tag saying "now there's 20902 characters
starting at U+4E00" than to include them all in a text file and having
to UTF-8 decode every single one of them.
If a font.txt exists, it takes priority over the <chars> tag, and in
that case, there's no reason to include the <chars> tag in the XML.
But font.txt has to be in the same directory as font.png, otherwise it
is rejected. Same for font.fontmeta. If neither font.txt nor <chars>
exist, then the font is seen as a 2.2-and-below-style ASCII font.
In <special>: advance is the number of pixels the cursor advances after
drawing the character (so the width of the character, without affecting
the grid in the source image), color is whether the character should
have its original colors retained when printed (for button glyphs).
As for <white_teeth>:
The renderer PR has replaced draw-time whitening of sprites/etc
(using BlitSurfaceColoured) by load-time whitening of entire images
(using LoadImage with TEX_WHITE as an argument).
This means we have a problem: fonts have always had their glyphs
whitened at printing time, and since I'm adding support for colored
button glyphs, I changed it so glyphs would sometimes not be whitened.
But if we can't whiten at print time, then we'd need to whiten at load
time, and if we whiten the entire font, any colored glyphs will get
destroyed too. If you whiten the image selectively, well, we need more
code to target specific squares in the image, and it's kind of a waste
when you need to whiten 30000 12x12 Chinese characters when you're only
going to need a handful, but you don't know which ones.
The solution: Whitening fonts is useless if all the non-colored glyphs
are already white, so we don't need to do it anyway! However, any
existing fonts that have non-white glyphs (and I know of at least one
level like that) will still need to be whitened. So there is now a
font property <white_teeth> that can be specified in the fontmeta,
which indicates that the font is already pre-whitened. If not
specified, traditional whitening behavior will be used, and the font
cannot use colored glyphs.
2023-01-02 05:14:53 +01:00
|
|
|
|
2023-01-07 19:28:07 +01:00
|
|
|
// This loads music too...
|
|
|
|
if (!graphics.reloadresources())
|
|
|
|
{
|
|
|
|
/* Something wrong with the default assets? We can't use them to
|
|
|
|
* display the error message, and we have to bail. */
|
Use levelDirError for graphics errors too
This will actually do several things:
(1) Make the tile size checks apply to the appropriate graphics files
once again.
(2) Make the game print a fallback error message if the error message
hasn't been set on the levelDirError error screen.
(3) Use levelDirError for graphics errors too.
(4) Make the error message for tile size checks failing specify both
width and height, not just a square dimension.
(5) Make the error messages mentioned above translatable.
It turns out that (1) didn't happen after #923 was merged, since #923
removed needing to process a tilesheet into a vector of surfaces for all
graphics files except sprites.png and flipsprites.png. Thus, the game
ended up only checking the correct tile sizes for those files only.
In the process of fixing this, I also got rid of the PROCESS_TILESHEET
macros and turned them into two different functions: One to make the
array, and one to check the tile size of the tilesheet.
I also did (2) just in case FILESYSTEM_levelDirHasError() returns false
even though we know we have an error.
And (3) is needed so things are unified and we have one user-facing
error message system when users load levels. To facilitate this, I
removed the title string, since it's really not needed.
Unfortunately, (1) doesn't apply to font.png again, but that's because
of the new font stuff and I'm not sure what Dav999 has in store for
error checking. But that's also why I did (4), because it looks like
tile sizes in font.png files can be different (i.e. non-square).
2023-05-18 02:00:33 +02:00
|
|
|
const char* message;
|
|
|
|
if (FILESYSTEM_levelDirHasError())
|
|
|
|
{
|
|
|
|
message = FILESYSTEM_getLevelDirError();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-05-24 00:10:56 +02:00
|
|
|
message = loc::gettext("Something went wrong, but we forgot the error message.");
|
Use levelDirError for graphics errors too
This will actually do several things:
(1) Make the tile size checks apply to the appropriate graphics files
once again.
(2) Make the game print a fallback error message if the error message
hasn't been set on the levelDirError error screen.
(3) Use levelDirError for graphics errors too.
(4) Make the error message for tile size checks failing specify both
width and height, not just a square dimension.
(5) Make the error messages mentioned above translatable.
It turns out that (1) didn't happen after #923 was merged, since #923
removed needing to process a tilesheet into a vector of surfaces for all
graphics files except sprites.png and flipsprites.png. Thus, the game
ended up only checking the correct tile sizes for those files only.
In the process of fixing this, I also got rid of the PROCESS_TILESHEET
macros and turned them into two different functions: One to make the
array, and one to check the tile size of the tilesheet.
I also did (2) just in case FILESYSTEM_levelDirHasError() returns false
even though we know we have an error.
And (3) is needed so things are unified and we have one user-facing
error message system when users load levels. To facilitate this, I
removed the title string, since it's really not needed.
Unfortunately, (1) doesn't apply to font.png again, but that's because
of the new font stuff and I'm not sure what Dav999 has in store for
error checking. But that's also why I did (4), because it looks like
tile sizes in font.png files can be different (i.e. non-square).
2023-05-18 02:00:33 +02:00
|
|
|
}
|
|
|
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", message, NULL);
|
2023-01-07 19:28:07 +01:00
|
|
|
|
|
|
|
VVV_exit(1);
|
|
|
|
}
|
|
|
|
|
2022-12-30 22:57:24 +01:00
|
|
|
loc::loadtext(false);
|
|
|
|
loc::loadlanguagelist();
|
|
|
|
game.createmenu(Menu::mainmenu);
|
|
|
|
|
2023-01-07 19:28:07 +01:00
|
|
|
graphics.create_buffers();
|
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;
|
|
|
|
|
2024-02-08 18:30:28 +01:00
|
|
|
int min_lang_set = loc::lang_set_current;
|
|
|
|
if (baseDir != NULL)
|
|
|
|
{
|
|
|
|
/* For people who manage tons of basedirs, never re-ask
|
|
|
|
* the language, except in the default basedir. */
|
|
|
|
min_lang_set = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (loc::lang_set < min_lang_set && !loc::languagelist.empty())
|
2022-12-30 22:57:24 +01:00
|
|
|
{
|
Fix "no language files found" title screen bug
If you've never set a language before (<lang_set> is not 1), then the
language screen will show up before the title screen. Selecting the
language will then make the title screen show up.
If no language files are present, the old logic for handling this was
to simply show the language screen at startup anyway, and let it
display the error message that language files are missing, as a warning
that the game is not packaged correctly. However, this logic has two
flaws:
- If the user has ever had language files and set a language before
(in a VVVVVV on that computer), the warning element is gone because
the language screen is not shown in that case - the game is simply in
English
- If the user has never set a language before, and then goes to the
language screen later via the menu, they will be sent to the title
screen, even if they were in-game. The main menu will also be broken.
The new way is to not show the language screen at startup if language
files are missing, and to change the logic so that you will only be
sent to the title screen if you actually haven't seen the title screen
yet.
I will also add a proper warning that fonts or language files are
missing by adding a message in the bottom left corner (in place of the
MMMMMM installed message).
2023-08-29 23:03:08 +02:00
|
|
|
loc::pre_title_lang_menu = true;
|
2022-12-30 22:57:24 +01:00
|
|
|
game.gamestate = TITLEMODE;
|
|
|
|
game.menustart = true;
|
|
|
|
game.createmenu(Menu::language);
|
|
|
|
game.currentmenuoption = loc::languagelist_curlang;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2023-06-06 02:34:27 +02:00
|
|
|
if (game.unlock[UnlockTrophy_GAME_COMPLETE])
|
|
|
|
{
|
|
|
|
game.unlockAchievement("vvvvvvgamecomplete");
|
|
|
|
}
|
|
|
|
if (game.unlock[UnlockTrophy_FLIPMODE_COMPLETE])
|
|
|
|
{
|
|
|
|
game.unlockAchievement("vvvvvvgamecompleteflip");
|
|
|
|
}
|
|
|
|
if (game.unlock[UnlockTrophy_NODEATHMODE_COMPLETE])
|
|
|
|
{
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-06 02:50:19 +02:00
|
|
|
if (game.bestrank[TimeTrial_SPACESTATION1] >= 3)
|
|
|
|
{
|
|
|
|
game.unlockAchievement("vvvvvvtimetrial_station1_fixed");
|
|
|
|
}
|
|
|
|
if (game.bestrank[TimeTrial_LABORATORY] >= 3)
|
|
|
|
{
|
|
|
|
game.unlockAchievement("vvvvvvtimetrial_lab_fixed");
|
|
|
|
}
|
|
|
|
if (game.bestrank[TimeTrial_TOWER] >= 3)
|
|
|
|
{
|
|
|
|
game.unlockAchievement("vvvvvvtimetrial_tower_fixed");
|
|
|
|
}
|
|
|
|
if (game.bestrank[TimeTrial_SPACESTATION2] >= 3)
|
|
|
|
{
|
|
|
|
game.unlockAchievement("vvvvvvtimetrial_station2_fixed");
|
|
|
|
}
|
|
|
|
if (game.bestrank[TimeTrial_WARPZONE] >= 3)
|
|
|
|
{
|
|
|
|
game.unlockAchievement("vvvvvvtimetrial_warp_fixed");
|
|
|
|
}
|
|
|
|
if (game.bestrank[TimeTrial_FINALLEVEL] >= 3)
|
|
|
|
{
|
|
|
|
game.unlockAchievement("vvvvvvtimetrial_final_fixed");
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
obj.init();
|
|
|
|
|
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
|
|
|
|
|
|
|
LevelMetaData meta;
|
2022-06-19 22:41:48 +02:00
|
|
|
CliPlaytestArgs pt_args;
|
|
|
|
if (cl.getLevelMetaDataAndPlaytestArgs(playtestname, meta, &pt_args)) {
|
2021-02-21 00:40:11 +01:00
|
|
|
cl.ListOfMetaData.clear();
|
|
|
|
cl.ListOfMetaData.push_back(meta);
|
2020-04-09 21:03:24 +02:00
|
|
|
} else {
|
2021-02-21 00:40:11 +01:00
|
|
|
cl.loadZips();
|
2022-06-19 22:41:48 +02:00
|
|
|
if (cl.getLevelMetaDataAndPlaytestArgs(playtestname, meta, &pt_args)) {
|
2021-02-21 00:40:11 +01:00
|
|
|
cl.ListOfMetaData.clear();
|
|
|
|
cl.ListOfMetaData.push_back(meta);
|
2020-06-03 20:05:09 +02:00
|
|
|
} else {
|
2021-02-24 00:21:29 +01:00
|
|
|
vlog_error("Level not found");
|
2024-06-02 20:09:22 +02:00
|
|
|
keep_console_open(open_console);
|
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
|
|
|
}
|
|
|
|
|
2022-06-19 22:41:48 +02:00
|
|
|
if (pt_args.valid)
|
|
|
|
{
|
|
|
|
savefileplaytest = true;
|
|
|
|
savex = pt_args.x;
|
|
|
|
savey = pt_args.y;
|
|
|
|
saverx = pt_args.rx;
|
|
|
|
savery = pt_args.ry;
|
|
|
|
savegc = pt_args.gc;
|
|
|
|
savemusic = pt_args.music;
|
|
|
|
}
|
|
|
|
|
2020-04-09 21:03:24 +02:00
|
|
|
game.loadcustomlevelstats();
|
2021-02-21 00:40:11 +01:00
|
|
|
game.customleveltitle=cl.ListOfMetaData[game.playcustomlevel].title;
|
|
|
|
game.customlevelfilename=cl.ListOfMetaData[game.playcustomlevel].filename;
|
2020-04-09 21:03:24 +02:00
|
|
|
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;
|
Refactor scriptclass::startgamemode
This overhauls scriptclass::gamemode massively.
The first change is that it now uses an enum, and enforces using that
enum via using its type instead of an int. This is because whenever
you're reading any calls to startgamemode, you have no idea what magic
number actually corresponds to what unless you read startgamemode
itself. And when you do read it, not every case is commented adequately,
so you'd have to do more work to figure out what each case is. With the
enum, it's obvious and self-evident, and that also removes the need for
all the comments in the function too. Some math is still done on mode
variables (to simplify time trial code), but it's okay, we can just cast
between int and the enum as needed.
The second is that common code is now de-duplicated. There was a lot of
code that every case does, such as calling hardreset, setting Flip Mode,
resetting the player, calling gotoroom and so on.
Now some code may be duplicated between cases, so I've tried to group up
similar cases where possible (most notable example is grouping up the
main game and No Death Mode cases together). But some code still might
be duplicated in the end. Which is okay - I could've tried to
de-duplicate it further but that just results in logic relevant to a
specific case that's located far from the actual case itself. It's much
better to leave things like setting fademode or loading scripts in the
case itself.
This also fixes a bug since 2.3 where playing No Death Mode (and never
opening and closing the options menu) and beating it would also give you
the Flip Mode trophy, since turning on the flag to invalidate Flip Mode
in startgamemode only happened for the main game cases and in previous
versions the game relied upon this flag being set when using a
teleporter for some reason (which I removed in 2.3). Now instead of
specifying it per case, I just do a !map.custommode check instead so it
covers every single case at once.
2022-12-29 23:01:36 +01:00
|
|
|
script.startgamemode(Start_CUSTOM_QUICKSAVE);
|
2020-04-09 21:03:24 +02:00
|
|
|
} else {
|
Refactor scriptclass::startgamemode
This overhauls scriptclass::gamemode massively.
The first change is that it now uses an enum, and enforces using that
enum via using its type instead of an int. This is because whenever
you're reading any calls to startgamemode, you have no idea what magic
number actually corresponds to what unless you read startgamemode
itself. And when you do read it, not every case is commented adequately,
so you'd have to do more work to figure out what each case is. With the
enum, it's obvious and self-evident, and that also removes the need for
all the comments in the function too. Some math is still done on mode
variables (to simplify time trial code), but it's okay, we can just cast
between int and the enum as needed.
The second is that common code is now de-duplicated. There was a lot of
code that every case does, such as calling hardreset, setting Flip Mode,
resetting the player, calling gotoroom and so on.
Now some code may be duplicated between cases, so I've tried to group up
similar cases where possible (most notable example is grouping up the
main game and No Death Mode cases together). But some code still might
be duplicated in the end. Which is okay - I could've tried to
de-duplicate it further but that just results in logic relevant to a
specific case that's located far from the actual case itself. It's much
better to leave things like setting fademode or loading scripts in the
case itself.
This also fixes a bug since 2.3 where playing No Death Mode (and never
opening and closing the options menu) and beating it would also give you
the Flip Mode trophy, since turning on the flag to invalidate Flip Mode
in startgamemode only happened for the main game cases and in previous
versions the game relied upon this flag being set when using a
teleporter for some reason (which I removed in 2.3). Now instead of
specifying it per case, I just do a !map.custommode check instead so it
covers every single case at once.
2022-12-29 23:01:36 +01:00
|
|
|
script.startgamemode(Start_CUSTOM);
|
2020-04-09 21:03:24 +02:00
|
|
|
}
|
|
|
|
|
Enumify all fade modes
This removes the magic numbers previously used for controlling the fade
mode, which are really not readable at all unless you already know what
they mean.
0: FADE_NONE
1: FADE_FULLY_BLACK
2: FADE_START_FADEOUT
3: FADE_FADING_OUT
4: FADE_START_FADEIN
5: FADE_FADING_IN
There is also the macro FADEMODE_IS_FADING, which indicates when the
intention is to only check if the game is fading right now, which wasn't
clearly conveyed previously.
I also took the opportunity to clean up the style of any lines I
touched. This included rewriting if-else chains into case-switches,
turning one-liner if-then statements into proper blocks, fixing up
comments, and even commenting the `fademode == FADE_NONE` on the tower
spike checks (which, it was previously undocumented why that check was
there, but I think I know why it's there).
As for type safety, we already get some by transforming the variable
types into the enum. Assignment is prohibited without a cast. But,
apparently, comparison is perfectly legal and won't even give so much as
a warning. To work around this and make absolutely sure I made all
existing comparisons now use the enum, I temporarily changed it to be an
`enum class`, which is a C++11 feature that makes it so all comparisons
are illegal. Unfortunately, it scopes them in a namespace with the same
name as a class, so I had to temporarily define macros to make sure my
existing code worked. I also had to temporarily up the standard in
CMakeLists.txt to get it to compile. But after all that was done, I
found the rest of the places where a comparison to an integer was used,
and fixed them.
2022-04-25 09:57:47 +02:00
|
|
|
graphics.fademode = FADE_NONE;
|
2020-04-09 21:03:24 +02:00
|
|
|
}
|
|
|
|
|
2022-06-02 01:42:22 +02:00
|
|
|
/* Only create the window after we have loaded all the assets. */
|
|
|
|
SDL_ShowWindow(gameScreen.m_window);
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
key.isActive = true;
|
|
|
|
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
gamestate_funcs = get_gamestate_funcs(game.gamestate, &num_gamestate_funcs);
|
|
|
|
loop_assign_active_funcs();
|
|
|
|
|
2021-03-31 21:06:26 +02:00
|
|
|
#ifdef __EMSCRIPTEN__
|
|
|
|
emscripten_set_main_loop(emscriptenloop, 0, 0);
|
|
|
|
#else
|
Call VVV_exit() when SDL_QUIT is received
This fixes a regression introduced by #535 where a quit signal (e.g.
Ctrl-C) sent to the window while the game was in unfocus pause wouldn't
close the game.
One problem was that key.quitProgram would only be checked when control
flow switched back to the outer loop in main(), which would only happen
when the loop order state machine switched to a delta function. As the
unfocused func table didn't have any delta functions, this means
key.quitProgram would never be checked.
So a naïve solution to this would just be to add a no-op delta func
entry to the unfocused func table. However, we then run into a separate
issue where a delta function at the end of a func list never reassigns
the active funcs, causing the game to be stuck in the unfocus pause
forever. Active func reassignment only happens after fixed funcs. So
then a naïve solution after that would be to simply add a no-op fixed
func entry after that. And indeed, that would fix the whole issue.
However, I want to do things the right way. And this does not seem like
the right way. Even putting aside the separate last-func-being-delta
issue, it mandates that every func list needs a delta function. Which
seems quite unnecessary to me.
Another solution I considered was copy-pasting the key.quitProgram check
to the inner loops, or adding some sort of signal propagation to
the inner loops - implemented by copy-pasting checks after each loop -
so we didn't need to copy-paste key.quitProgram... but that seems really
messy, too.
So, I realized that we could throw away key.quitProgram, and simply call
VVV_exit() directly when we receive an SDL_QUIT event. This fixes the
issue, this removes an unnecessary middleman variable, and it's pretty
cleanly and simply the right thing to do.
2021-04-02 00:24:14 +02:00
|
|
|
while (true)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2021-10-26 03:16:47 +02:00
|
|
|
f_time = SDL_GetTicks64();
|
2020-07-14 06:17:20 +02:00
|
|
|
|
2021-10-26 03:16:47 +02:00
|
|
|
const Uint64 f_timetaken = f_time - f_timePrev;
|
2024-07-13 22:01:20 +02:00
|
|
|
const int timestep = game.get_timestep();
|
|
|
|
if (!game.over30mode && f_timetaken < (Uint64) timestep)
|
2020-05-04 21:52:57 +02:00
|
|
|
{
|
2024-07-13 22:01:20 +02:00
|
|
|
const volatile Uint64 f_delay = timestep - f_timetaken;
|
2021-10-26 03:16:47 +02:00
|
|
|
SDL_Delay((Uint32) f_delay);
|
|
|
|
f_time = SDL_GetTicks64();
|
2020-05-04 21:52:57 +02:00
|
|
|
}
|
|
|
|
|
2020-07-14 06:17:20 +02:00
|
|
|
f_timePrev = f_time;
|
|
|
|
|
|
|
|
timePrev = time_;
|
2021-10-26 03:16:47 +02:00
|
|
|
time_ = SDL_GetTicks64();
|
2020-07-14 06:17:20 +02:00
|
|
|
|
|
|
|
deltaloop();
|
2020-06-14 20:45:36 +02:00
|
|
|
}
|
|
|
|
|
2021-02-16 02:52:41 +01:00
|
|
|
cleanup();
|
2021-03-31 21:06:26 +02:00
|
|
|
#endif
|
|
|
|
|
2021-02-16 02:52:41 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
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 23:23:59 +01:00
|
|
|
static void cleanup(void)
|
2021-02-16 02:52:41 +01:00
|
|
|
{
|
2021-02-16 03:43:35 +01:00
|
|
|
/* Order matters! */
|
2022-03-14 18:45:19 +01:00
|
|
|
if (FILESYSTEM_isInit()) /* not necessary but silences logs */
|
|
|
|
{
|
|
|
|
game.savestatsandsettings();
|
|
|
|
}
|
2023-01-07 19:28:07 +01:00
|
|
|
|
2021-02-16 03:43:35 +01:00
|
|
|
graphics.grphx.destroy();
|
|
|
|
graphics.destroy_buffers();
|
|
|
|
graphics.destroy();
|
2024-01-02 05:01:39 +01:00
|
|
|
font::bidi_destroy();
|
Start rewrite of font system
This is still a work in progress, but the existing font system has been
removed and replaced by a new one, in Font.cpp.
Design goals of the new font system include supporting colored button
glyphs, different fonts for different languages, and larger fonts than
8x8 for Chinese, Japanese and Korean, while being able to support their
30000+ characters without hiccups, slowdowns or high memory usage. And
to have more flexibility with fonts in general. Plus, Graphics.cpp was
long enough as-is, so it's good to have a dedicated file for font
storage.
The old font system worked with a std::vector<SDL_Surface*> to store
8x8 surfaces for each character, and a std::map<int,int> to store
mappings between codepoints and vector indexes.
The new system has a per-font collection of pages for every block of
0x1000 (4096) codepoints, that may be allocated as needed. A glyph on
a page contains the index of the glyph in the image (giving its
coordinates), the advance (how much the cursor should advance, so the
width of that glyph) and some flags which would be at least whether the
glyph exists and whether it is colored.
Most of the *new* features aren't implemented yet; it's currently
hardcoded to the regular 8x8 font.png, but it should be functionally
equivalent to the previous behavior. The only thing that doesn't really
work yet is level-specific font.png, but that'll be supported again
soon enough.
This commit also adds fontmeta (xml) support.
Since the fonts folder is mounted at graphics/, there are two main
options for recognizing non-font.png fonts: the font files have to be
prefixed with font (or font_) or some special file extension is
involved to signal what files are fonts. I always had a font.xml in
mind (so font_cn.xml, font_ja.xml, etc) but if there's ever gonna be
a need for further xml files inside the graphics folder, we have a
problem. So I named them .fontmeta instead.
A .fontmeta file looks somewhat like this:
<?xml version="1.0" encoding="UTF-8"?>
<font_metadata>
<width>12</width>
<height>12</height>
<white_teeth>1</white_teeth>
<chars>
<range start="0x20" end="0x7E"/>
<range start="0x80" end="0x80"/>
<range start="0xA0" end="0xDF"/>
<range start="0x250" end="0x2A8"/>
<range start="0x2AD" end="0x2AD"/>
<range start="0x2C7" end="0x2C7"/>
<range start="0x2C9" end="0x2CB"/>
...
</chars>
<special>
<range start="0x00" end="0x1F" advance="6"/>
<range start="0x61" end="0x66" color="1"/>
<range start="0x63" end="0x63" color="0"/>
</special>
</font_metadata>
The <chars> tag can be used to specify characters instead of in a .txt.
The original idea was to just always use the existing .txt system for
specifying the font charset, and only use the XML for the other stuff
that the .txt doesn't cover. However, it's probably better to keep it
simple if possible - having to only have a .png and a .fontmeta seems
simpler than having the data spread out over three files. And a major
advantage: Chinese fonts can have about 30000 characters! It's more
efficient to be able to have a tag saying "now there's 20902 characters
starting at U+4E00" than to include them all in a text file and having
to UTF-8 decode every single one of them.
If a font.txt exists, it takes priority over the <chars> tag, and in
that case, there's no reason to include the <chars> tag in the XML.
But font.txt has to be in the same directory as font.png, otherwise it
is rejected. Same for font.fontmeta. If neither font.txt nor <chars>
exist, then the font is seen as a 2.2-and-below-style ASCII font.
In <special>: advance is the number of pixels the cursor advances after
drawing the character (so the width of the character, without affecting
the grid in the source image), color is whether the character should
have its original colors retained when printed (for button glyphs).
As for <white_teeth>:
The renderer PR has replaced draw-time whitening of sprites/etc
(using BlitSurfaceColoured) by load-time whitening of entire images
(using LoadImage with TEX_WHITE as an argument).
This means we have a problem: fonts have always had their glyphs
whitened at printing time, and since I'm adding support for colored
button glyphs, I changed it so glyphs would sometimes not be whitened.
But if we can't whiten at print time, then we'd need to whiten at load
time, and if we whiten the entire font, any colored glyphs will get
destroyed too. If you whiten the image selectively, well, we need more
code to target specific squares in the image, and it's kind of a waste
when you need to whiten 30000 12x12 Chinese characters when you're only
going to need a handful, but you don't know which ones.
The solution: Whitening fonts is useless if all the non-colored glyphs
are already white, so we don't need to do it anyway! However, any
existing fonts that have non-white glyphs (and I know of at least one
level like that) will still need to be whitened. So there is now a
font property <white_teeth> that can be specified in the fontmeta,
which indicates that the font is already pre-whitened. If not
specified, traditional whitening behavior will be used, and the font
cannot use colored glyphs.
2023-01-02 05:14:53 +01:00
|
|
|
font::destroy();
|
2023-03-04 01:46:15 +01:00
|
|
|
gameScreen.destroy();
|
2021-02-16 03:43:35 +01:00
|
|
|
music.destroy();
|
2022-12-01 07:35:42 +01:00
|
|
|
map.destroy();
|
2020-06-14 20:45:36 +02:00
|
|
|
NETWORK_shutdown();
|
2022-12-30 22:57:24 +01:00
|
|
|
loc::resettext(true);
|
2020-06-14 20:45:36 +02:00
|
|
|
SDL_Quit();
|
|
|
|
FILESYSTEM_deinit();
|
|
|
|
}
|
|
|
|
|
2021-09-27 19:32:23 +02:00
|
|
|
SDL_NORETURN void VVV_exit(const int exit_code)
|
2021-02-16 03:47:24 +01:00
|
|
|
{
|
|
|
|
cleanup();
|
|
|
|
exit(exit_code);
|
|
|
|
}
|
|
|
|
|
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 23:23:59 +01:00
|
|
|
static void inline deltaloop(void)
|
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
|
|
|
|
2021-04-02 20:50:30 +02:00
|
|
|
Uint32 timesteplimit = game.get_timestep();
|
2020-06-14 20:55:19 +02:00
|
|
|
|
|
|
|
while (accumulator >= timesteplimit)
|
|
|
|
{
|
2021-04-02 00:25:07 +02:00
|
|
|
enum IndexCode index_code = increment_func_index();
|
|
|
|
|
|
|
|
if (index_code == Index_end)
|
|
|
|
{
|
|
|
|
loop_assign_active_funcs();
|
|
|
|
}
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
|
2020-11-07 00:11:24 +01:00
|
|
|
accumulator = SDL_fmodf(accumulator, timesteplimit);
|
2020-06-14 20:55:19 +02:00
|
|
|
|
Fix filter/screenshake/flash update order
In 2.2, at render time, the game rendered screenshakes and flashes if
their timers were above 0, and then decremented them afterwards. The
game would also update the analogue filter right before rendering it,
too.
In 2.3, this was changed so the flash and screenshake timers were
unified, and also done at the end of the frame - right before rendering
happened. This resulted in 1-frame flashes and screenshakes not
rendering at all. The other changes in this patchset don't fix this
either. The analogue filter was also in the wrong order, but that is
less of an issue than flashes and screenshakes.
So, what I've done is made the flash and screenshake timers update right
before the loop switches over to rendering, and only decrements them
when we switch back to fixed functions (after rendering). The analogue
filter is also updated right before rendering as well. This restores
1-frame flashes and screenshakes, as well as restores the correct order
of analogue filter updates.
2021-03-18 01:53:17 +01:00
|
|
|
/* We are done rendering. */
|
|
|
|
graphics.renderfixedpost();
|
|
|
|
|
2020-06-14 20:57:33 +02:00
|
|
|
fixedloop();
|
|
|
|
}
|
|
|
|
const float alpha = game.over30mode ? static_cast<float>(accumulator) / timesteplimit : 1.0f;
|
|
|
|
graphics.alpha = alpha;
|
|
|
|
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
if (active_func_index == NULL
|
|
|
|
|| *active_func_index == -1
|
|
|
|
|| active_funcs == NULL)
|
2020-06-14 20:57:33 +02:00
|
|
|
{
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
/* Somehow the first deltatime has been too small and things haven't
|
|
|
|
* initialized. We'll just no-op for now.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const struct ImplFunc* implfunc = &(*active_funcs)[*active_func_index];
|
|
|
|
|
|
|
|
if (implfunc->type == Func_delta && implfunc->func != NULL)
|
2020-06-14 20:57:33 +02:00
|
|
|
{
|
2023-01-07 19:28:07 +01:00
|
|
|
graphics.clear();
|
|
|
|
|
|
|
|
graphics.set_render_target(graphics.gameTexture);
|
|
|
|
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
implfunc->func();
|
|
|
|
|
2023-01-07 19:28:07 +01:00
|
|
|
gameScreen.RenderPresent();
|
2020-06-14 20:57:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
static enum LoopCode loop_begin(void)
|
2020-06-14 20:57:33 +02:00
|
|
|
{
|
2021-04-02 00:39:56 +02:00
|
|
|
if (game.inputdelay)
|
|
|
|
{
|
|
|
|
key.Poll();
|
|
|
|
}
|
|
|
|
|
2020-06-14 20:58:27 +02:00
|
|
|
// Update network per frame.
|
|
|
|
NETWORK_update();
|
|
|
|
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
return Loop_continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void unfocused_run(void)
|
|
|
|
{
|
2021-01-16 20:40:26 +01:00
|
|
|
if (!game.blackout)
|
|
|
|
{
|
2023-01-07 19:28:07 +01:00
|
|
|
graphics.fill_rect(0, 0, 0);
|
2021-03-19 23:58:52 +01:00
|
|
|
#define FLIP(YPOS) graphics.flipmode ? 232 - YPOS : YPOS
|
2023-07-12 22:58:47 +02:00
|
|
|
#define FLIP_PR_CJK_LOW (graphics.flipmode ? PR_CJK_HIGH : PR_CJK_LOW)
|
|
|
|
#define FLIP_PR_CJK_HIGH (graphics.flipmode ? PR_CJK_LOW : PR_CJK_HIGH)
|
2023-01-16 21:29:50 +01:00
|
|
|
/* The pause screen can also appear on the language screen, where highlighting
|
|
|
|
* a language changes the used language metadata but not the loaded strings... */
|
2024-01-03 20:09:23 +01:00
|
|
|
uint32_t flags = PR_CEN | PR_BOR | PR_FONT_IDX(loc::langmeta.font_idx, loc::langmeta.rtl);
|
2023-07-12 22:58:47 +02:00
|
|
|
font::print(flags | FLIP_PR_CJK_HIGH, -1, FLIP(110), loc::gettext("Game paused"), 196 - help.glow, 255 - help.glow, 196 - help.glow);
|
Add support for button glyph display
This adds a function that converts an action (such as interacting
in-game) to the corresponding button text ("ENTER", "E") or button
glyph (PlayStation triangle, Steam Deck Y, etc). This function
currently only gives the existing ENTERs or Es, because I don't know
how best to detect controller usage, or whether the game is running on
a Steam Deck, or what buttons need to be displayed there. Still, it
should now be really easy to adapt the rendering of keyboard keys to
consoles, controllers, or rebound keys.
To identify the actions that currently need to be displayed, this
commit also adds the initial enums for action sets as described by
Ethan in a comment in #834 (Jan 18, 2022).
2023-03-18 22:30:16 +01:00
|
|
|
|
|
|
|
if (BUTTONGLYPHS_keyboard_is_available())
|
|
|
|
{
|
2023-07-12 22:58:47 +02:00
|
|
|
font::print(flags | FLIP_PR_CJK_LOW, -1, FLIP(120), loc::gettext("[click to resume]"), 196 - help.glow, 255 - help.glow, 196 - help.glow);
|
Add support for button glyph display
This adds a function that converts an action (such as interacting
in-game) to the corresponding button text ("ENTER", "E") or button
glyph (PlayStation triangle, Steam Deck Y, etc). This function
currently only gives the existing ENTERs or Es, because I don't know
how best to detect controller usage, or whether the game is running on
a Steam Deck, or what buttons need to be displayed there. Still, it
should now be really easy to adapt the rendering of keyboard keys to
consoles, controllers, or rebound keys.
To identify the actions that currently need to be displayed, this
commit also adds the initial enums for action sets as described by
Ethan in a comment in #834 (Jan 18, 2022).
2023-03-18 22:30:16 +01:00
|
|
|
|
2023-07-12 22:58:47 +02:00
|
|
|
font::print(flags | FLIP_PR_CJK_HIGH, -1, FLIP(220), loc::gettext("Press M to mute in game"), 164 - help.glow, 196 - help.glow, 164 - help.glow);
|
Add support for button glyph display
This adds a function that converts an action (such as interacting
in-game) to the corresponding button text ("ENTER", "E") or button
glyph (PlayStation triangle, Steam Deck Y, etc). This function
currently only gives the existing ENTERs or Es, because I don't know
how best to detect controller usage, or whether the game is running on
a Steam Deck, or what buttons need to be displayed there. Still, it
should now be really easy to adapt the rendering of keyboard keys to
consoles, controllers, or rebound keys.
To identify the actions that currently need to be displayed, this
commit also adds the initial enums for action sets as described by
Ethan in a comment in #834 (Jan 18, 2022).
2023-03-18 22:30:16 +01:00
|
|
|
font::print(flags, -1, FLIP(230), loc::gettext("Press N to mute music only"), 164 - help.glow, 196 - help.glow, 164 - help.glow);
|
|
|
|
}
|
2023-07-12 22:58:47 +02:00
|
|
|
#undef FLIP_PR_CJK_HIGH
|
|
|
|
#undef FLIP_PR_CJK_LOW
|
2021-03-19 23:58:52 +01:00
|
|
|
#undef FLIP
|
2021-01-16 20:40:26 +01:00
|
|
|
}
|
|
|
|
graphics.render();
|
|
|
|
//We are minimised, so lets put a bit of a delay to save CPU
|
2021-03-31 21:06:26 +02:00
|
|
|
#ifndef __EMSCRIPTEN__
|
2021-01-16 20:40:26 +01:00
|
|
|
SDL_Delay(100);
|
2021-03-31 21:06:26 +02:00
|
|
|
#endif
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void focused_begin(void)
|
|
|
|
{
|
2021-08-06 01:38:06 +02:00
|
|
|
map.nexttowercolour_set = false;
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
}
|
2020-06-14 20:55:19 +02:00
|
|
|
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
static void focused_end(void)
|
|
|
|
{
|
2021-04-13 23:42:45 +02:00
|
|
|
game.gameclock();
|
|
|
|
music.processmusic();
|
|
|
|
graphics.processfade();
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
}
|
2020-06-14 20:55:19 +02:00
|
|
|
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
static enum LoopCode loop_end(void)
|
|
|
|
{
|
2022-11-14 23:10:24 +01:00
|
|
|
++game.framecounter;
|
|
|
|
|
2020-06-14 20:57:57 +02:00
|
|
|
//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
|
|
|
|
2021-12-26 14:48:23 +01:00
|
|
|
music.updatemutestate();
|
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
|
|
|
|
Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.
This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.
The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.
But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.
Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).
This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.
Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2020-11-07 01:08:22 +01:00
|
|
|
return Loop_continue;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|