2020-01-01 21:29:24 +01:00
|
|
|
#ifndef SCRIPT_H
|
|
|
|
#define SCRIPT_H
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
|
2020-09-10 06:48:45 +02:00
|
|
|
#include <SDL.h>
|
|
|
|
|
|
|
|
#define filllines(lines) commands.insert(commands.end(), lines, lines + SDL_arraysize(lines))
|
2020-05-18 19:37:00 +02:00
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
struct Script
|
|
|
|
{
|
|
|
|
std::string name;
|
|
|
|
std::vector<std::string> contents;
|
|
|
|
};
|
|
|
|
|
2021-08-12 04:32:36 +02:00
|
|
|
#define NUM_SCRIPT_ARGS 40
|
|
|
|
|
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
|
|
|
enum StartMode
|
|
|
|
{
|
|
|
|
Start_MAINGAME,
|
|
|
|
Start_MAINGAME_TELESAVE,
|
|
|
|
Start_MAINGAME_QUICKSAVE,
|
|
|
|
Start_TIMETRIAL_SPACESTATION1,
|
|
|
|
Start_TIMETRIAL_LABORATORY,
|
|
|
|
Start_TIMETRIAL_TOWER,
|
|
|
|
Start_TIMETRIAL_SPACESTATION2,
|
|
|
|
Start_TIMETRIAL_WARPZONE,
|
|
|
|
Start_TIMETRIAL_FINALLEVEL,
|
|
|
|
Start_NODEATHMODE_WITHCUTSCENES,
|
|
|
|
Start_NODEATHMODE_NOCUTSCENES,
|
|
|
|
Start_SECRETLAB,
|
|
|
|
Start_INTERMISSION1_VITELLARY,
|
|
|
|
Start_INTERMISSION1_VERMILION,
|
|
|
|
Start_INTERMISSION1_VERDIGRIS,
|
|
|
|
Start_INTERMISSION1_VICTORIA,
|
|
|
|
Start_INTERMISSION2_VITELLARY,
|
|
|
|
Start_INTERMISSION2_VERMILION,
|
|
|
|
Start_INTERMISSION2_VERDIGRIS,
|
|
|
|
Start_INTERMISSION2_VICTORIA,
|
|
|
|
Start_EDITOR,
|
|
|
|
Start_EDITORPLAYTESTING,
|
|
|
|
Start_CUSTOM,
|
|
|
|
Start_CUSTOM_QUICKSAVE,
|
|
|
|
Start_QUIT,
|
2022-12-24 04:16:56 +01:00
|
|
|
Start_CUTSCENETEST,
|
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
|
|
|
|
|
|
|
Start_FIRST_NODEATHMODE = Start_NODEATHMODE_WITHCUTSCENES,
|
|
|
|
Start_LAST_NODEATHMODE = Start_NODEATHMODE_NOCUTSCENES,
|
|
|
|
Start_FIRST_INTERMISSION1 = Start_INTERMISSION1_VITELLARY,
|
|
|
|
Start_LAST_INTERMISSION1 = Start_INTERMISSION1_VICTORIA,
|
|
|
|
Start_FIRST_INTERMISSION2 = Start_INTERMISSION2_VITELLARY,
|
|
|
|
Start_LAST_INTERMISSION2 = Start_INTERMISSION2_VICTORIA,
|
|
|
|
|
|
|
|
Start_FIRST_TIMETRIAL = Start_TIMETRIAL_SPACESTATION1
|
|
|
|
};
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
class scriptclass
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
|
|
|
|
|
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
|
|
|
scriptclass(void);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-05 01:01:35 +02:00
|
|
|
void load(const std::string& name);
|
2020-07-05 00:59:21 +02:00
|
|
|
void loadother(const char* t);
|
2020-07-05 01:01:35 +02:00
|
|
|
void loadcustom(const std::string& t);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2022-12-24 04:16:56 +01:00
|
|
|
void add_test_line(const std::string& speaker, const std::string& english, char textcase);
|
|
|
|
void loadtest(const std::string& name);
|
|
|
|
|
2020-07-05 01:01:35 +02:00
|
|
|
void inline add(const std::string& t)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Make `commands`, `sb`, and `hooklist` not use separate length-trackers
This is a refactor that turns the script-related arrays `ed.sb`, and
`ed.hooklist` into C++ vectors (`script.commands` was already a vector, it was
just misused). The code handling these vectors now looks more like idiomatic
C++ than sloppily-pasted pseudo-ActionScript. This removes the variables
`script.scriptlength`, `ed.sblength`, and `ed.numhooks`, too.
This reduces the amount of code needed to e.g. simply remove something from
any of these vectors. Previously the code had to manually shift the rest of
the elements down one-by-one, and doing it manually is definitely error-prone
and tedious.
But now we can just use fancy functions like `std::vector::erase()` and
`std::remove()` to do it all in one line!
Don't worry, I checked and `std::remove()` is in the C++ standard since at least
1998.
This patch makes it so the `commands` vector gets cleared when
`scriptclass::load()` is ran. Previously, the `commands` vector never actually
properly got cleared, so there could potentially be glitches that rely on the
game indexing past the bounds set by `scriptlength` but still in-bounds in the
eyes of C++, and people could potentially rely on such an exploit...
However, I checked, and I'm pretty sure that no such glitch previously existed
at all, because the only times the vector gets indexed are when `scriptlength`
is either being incremented after starting from 0 (`add()`) or when it's
underneath a `position < scriptlength` conditional.
Furthermore, I'm unaware of anyone who has actually found or used such an
exploit, and I've been in the custom level community for 6 years.
So I think it's fine.
2020-02-20 18:43:52 +01:00
|
|
|
commands.push_back(t);
|
2020-01-01 21:29:24 +01: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
|
|
|
void clearcustom(void);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-05 01:01:35 +02:00
|
|
|
void tokenize(const std::string& t);
|
2020-01-01 21:29:24 +01: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
|
|
|
void run(void);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2022-12-30 22:57:24 +01:00
|
|
|
void translate_dialogue(void);
|
|
|
|
|
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
|
|
|
void startgamemode(enum StartMode mode);
|
2020-01-01 21:29:24 +01: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
|
|
|
void teleport(void);
|
2020-01-01 21:29:24 +01: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
|
|
|
void hardreset(void);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//Script contents
|
|
|
|
std::vector<std::string> commands;
|
2021-08-12 04:32:36 +02:00
|
|
|
std::string words[NUM_SCRIPT_ARGS];
|
2020-01-01 21:29:24 +01:00
|
|
|
std::vector<std::string> txt;
|
|
|
|
std::string scriptname;
|
Make `commands`, `sb`, and `hooklist` not use separate length-trackers
This is a refactor that turns the script-related arrays `ed.sb`, and
`ed.hooklist` into C++ vectors (`script.commands` was already a vector, it was
just misused). The code handling these vectors now looks more like idiomatic
C++ than sloppily-pasted pseudo-ActionScript. This removes the variables
`script.scriptlength`, `ed.sblength`, and `ed.numhooks`, too.
This reduces the amount of code needed to e.g. simply remove something from
any of these vectors. Previously the code had to manually shift the rest of
the elements down one-by-one, and doing it manually is definitely error-prone
and tedious.
But now we can just use fancy functions like `std::vector::erase()` and
`std::remove()` to do it all in one line!
Don't worry, I checked and `std::remove()` is in the C++ standard since at least
1998.
This patch makes it so the `commands` vector gets cleared when
`scriptclass::load()` is ran. Previously, the `commands` vector never actually
properly got cleared, so there could potentially be glitches that rely on the
game indexing past the bounds set by `scriptlength` but still in-bounds in the
eyes of C++, and people could potentially rely on such an exploit...
However, I checked, and I'm pretty sure that no such glitch previously existed
at all, because the only times the vector gets indexed are when `scriptlength`
is either being incremented after starting from 0 (`add()`) or when it's
underneath a `position < scriptlength` conditional.
Furthermore, I'm unaware of anyone who has actually found or used such an
exploit, and I've been in the custom level community for 6 years.
So I think it's fine.
2020-02-20 18:43:52 +01:00
|
|
|
int position;
|
2020-01-01 21:29:24 +01:00
|
|
|
int looppoint, loopcount;
|
|
|
|
|
|
|
|
int scriptdelay;
|
2020-11-08 01:49:32 +01:00
|
|
|
bool running;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//Textbox stuff
|
|
|
|
int textx;
|
|
|
|
int texty;
|
|
|
|
int r,g,b;
|
2021-03-20 04:08:41 +01:00
|
|
|
bool textflipme;
|
2022-12-30 22:57:24 +01:00
|
|
|
bool textcentertext;
|
|
|
|
size_t textpad_left;
|
|
|
|
size_t textpad_right;
|
|
|
|
size_t textpadtowidth;
|
|
|
|
char textcase;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//Misc
|
|
|
|
int i, j, k;
|
|
|
|
|
|
|
|
//Custom level stuff
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
std::vector<Script> customscripts;
|
2020-01-01 21:29:24 +01:00
|
|
|
};
|
|
|
|
|
2020-09-28 04:15:06 +02:00
|
|
|
#ifndef SCRIPT_DEFINITION
|
2020-05-18 19:12:11 +02:00
|
|
|
extern scriptclass script;
|
2020-09-28 04:15:06 +02:00
|
|
|
#endif
|
2020-05-18 19:12:11 +02:00
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
#endif /* SCRIPT_H */
|