2020-09-28 04:15:06 +02:00
|
|
|
#define SCRIPT_DEFINITION
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "Script.h"
|
|
|
|
|
2021-02-16 03:49:24 +01:00
|
|
|
#include <limits.h>
|
|
|
|
|
2020-07-19 21:05:41 +02:00
|
|
|
#include "editor.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "Entity.h"
|
2020-07-19 21:05:41 +02:00
|
|
|
#include "Enums.h"
|
2021-02-16 03:53:17 +01:00
|
|
|
#include "Exit.h"
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "Graphics.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "KeyPoll.h"
|
|
|
|
#include "Map.h"
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "Music.h"
|
2020-07-19 21:05:41 +02:00
|
|
|
#include "UtilityClass.h"
|
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
|
|
|
scriptclass::scriptclass(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
position = 0;
|
|
|
|
scriptdelay = 0;
|
|
|
|
running = false;
|
|
|
|
|
2020-01-11 01:37:23 +01:00
|
|
|
b = 0;
|
|
|
|
g = 0;
|
|
|
|
i = 0;
|
|
|
|
j = 0;
|
|
|
|
k = 0;
|
|
|
|
loopcount = 0;
|
|
|
|
looppoint = 0;
|
|
|
|
r = 0;
|
|
|
|
textx = 0;
|
|
|
|
texty = 0;
|
2021-03-20 04:08:41 +01:00
|
|
|
textflipme = false;
|
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 scriptclass::clearcustom(void)
|
2021-02-13 01:35:22 +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
|
|
|
customscripts.clear();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-07-05 01:01:35 +02:00
|
|
|
void scriptclass::tokenize( const std::string& t )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
j = 0;
|
2020-07-03 01:03:40 +02:00
|
|
|
std::string tempword;
|
2020-07-03 01:04:51 +02:00
|
|
|
char currentletter;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
for (size_t i = 0; i < t.length(); i++)
|
|
|
|
{
|
2020-07-03 01:04:51 +02:00
|
|
|
currentletter = t[i];
|
|
|
|
if (currentletter == '(' || currentletter == ')' || currentletter == ',')
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
words[j] = tempword;
|
2020-07-03 23:54:23 +02:00
|
|
|
for (size_t ii = 0; ii < words[j].length(); ii++)
|
|
|
|
{
|
|
|
|
words[j][ii] = SDL_tolower(words[j][ii]);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
j++;
|
|
|
|
tempword = "";
|
|
|
|
}
|
2020-07-03 01:04:51 +02:00
|
|
|
else if (currentletter == ' ')
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//don't do anything - i.e. strip out spaces.
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tempword += currentletter;
|
|
|
|
}
|
2020-07-03 01:09:55 +02:00
|
|
|
if (j >= (int) SDL_arraysize(words))
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-07-03 01:09:55 +02:00
|
|
|
if (tempword != "" && j < (int) SDL_arraysize(words))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
words[j] = tempword;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 scriptclass::run(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-12-24 05:20:18 +01:00
|
|
|
if (!running)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-09-08 05:58:32 +02:00
|
|
|
// This counter here will stop the function when it gets too high
|
|
|
|
short execution_counter = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
while(running && scriptdelay<=0 && !game.pausescript)
|
|
|
|
{
|
2020-09-10 06:58:59 +02:00
|
|
|
if (INBOUNDS_VEC(position, commands))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Let's split or command in an array of words
|
|
|
|
tokenize(commands[position]);
|
|
|
|
|
|
|
|
//For script assisted input
|
|
|
|
game.press_left = false;
|
|
|
|
game.press_right = false;
|
|
|
|
game.press_action = false;
|
|
|
|
game.press_map = false;
|
|
|
|
|
|
|
|
//Ok, now we run a command based on that string
|
|
|
|
if (words[0] == "moveplayer")
|
|
|
|
{
|
|
|
|
//USAGE: moveplayer(x offset, y offset)
|
|
|
|
int player = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(player, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[player].xp += ss_toi(words[1]);
|
|
|
|
obj.entities[player].yp += ss_toi(words[2]);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
scriptdelay = 1;
|
|
|
|
}
|
2020-04-02 22:26:22 +02:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
2020-01-01 21:29:24 +01:00
|
|
|
if (words[0] == "warpdir")
|
|
|
|
{
|
2020-04-02 22:26:22 +02:00
|
|
|
int temprx=ss_toi(words[1])-1;
|
|
|
|
int tempry=ss_toi(words[2])-1;
|
|
|
|
int curlevel=temprx+(ed.maxwidth*(tempry));
|
2020-09-10 07:12:10 +02:00
|
|
|
bool inbounds = INBOUNDS_ARR(curlevel, ed.level);
|
Add bounds checks to script commands that didn't have them
Continuing from #280, another potential source of out-of-bounds indexing
(and thus, Undefined Behavior badness) comes from script commands. A
majority of them don't do any input validation at all, which means the
potential for out-of-bounds indexing and segfaulting in custom levels.
So it's always good to add bounds checks to them.
Interesting note, the only existing command that has bounds checks is
the flag() command. That means you can't turn out-of-bounds flags on or
off. But there's no bounds checks for ifflag(), or customifflag(), which
means you CAN index out-of-bounds with those commands! That's a bit bad
to do, so.
Also, I decided to add the bounds checks for playef() at the
musicclass::playef() level, instead of just the level of the playef()
command. I don't know of any other cases outside of the command where
musicclass::playef() will index out of bounds, but musicclass is the one
containing the indexed vector anyway, I wanted to cover more cases, and
it's better to be safe than sorry.
2020-06-13 06:55:41 +02:00
|
|
|
if (inbounds)
|
|
|
|
{
|
|
|
|
ed.level[curlevel].warpdir=ss_toi(words[3]);
|
|
|
|
}
|
2020-04-02 22:26:22 +02:00
|
|
|
|
|
|
|
//Do we update our own room?
|
Add bounds checks to script commands that didn't have them
Continuing from #280, another potential source of out-of-bounds indexing
(and thus, Undefined Behavior badness) comes from script commands. A
majority of them don't do any input validation at all, which means the
potential for out-of-bounds indexing and segfaulting in custom levels.
So it's always good to add bounds checks to them.
Interesting note, the only existing command that has bounds checks is
the flag() command. That means you can't turn out-of-bounds flags on or
off. But there's no bounds checks for ifflag(), or customifflag(), which
means you CAN index out-of-bounds with those commands! That's a bit bad
to do, so.
Also, I decided to add the bounds checks for playef() at the
musicclass::playef() level, instead of just the level of the playef()
command. I don't know of any other cases outside of the command where
musicclass::playef() will index out of bounds, but musicclass is the one
containing the indexed vector anyway, I wanted to cover more cases, and
it's better to be safe than sorry.
2020-06-13 06:55:41 +02:00
|
|
|
if(inbounds && game.roomx-100==temprx && game.roomy-100==tempry){
|
2020-06-26 07:31:46 +02:00
|
|
|
//If screen warping, then override all that:
|
|
|
|
graphics.backgrounddrawn = false;
|
2020-04-02 22:26:22 +02:00
|
|
|
map.warpx=false; map.warpy=false;
|
|
|
|
if(ed.level[curlevel].warpdir==0){
|
|
|
|
map.background = 1;
|
|
|
|
//Be careful, we could be in a Lab or Warp Zone room...
|
|
|
|
if(ed.level[curlevel].tileset==2){
|
|
|
|
//Lab
|
|
|
|
map.background = 2;
|
|
|
|
graphics.rcol = ed.level[curlevel].tilecol;
|
|
|
|
}else if(ed.level[curlevel].tileset==3){
|
|
|
|
//Warp Zone
|
|
|
|
map.background = 6;
|
|
|
|
}
|
|
|
|
}else if(ed.level[curlevel].warpdir==1){
|
|
|
|
map.warpx=true;
|
|
|
|
map.background=3;
|
|
|
|
graphics.rcol = ed.getwarpbackground(temprx,tempry);
|
|
|
|
}else if(ed.level[curlevel].warpdir==2){
|
|
|
|
map.warpy=true;
|
|
|
|
map.background=4;
|
|
|
|
graphics.rcol = ed.getwarpbackground(temprx,tempry);
|
|
|
|
}else if(ed.level[curlevel].warpdir==3){
|
|
|
|
map.warpx=true; map.warpy=true;
|
|
|
|
map.background = 5;
|
|
|
|
graphics.rcol = ed.getwarpbackground(temprx,tempry);
|
|
|
|
}
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
if (words[0] == "ifwarp")
|
|
|
|
{
|
Add bounds checks to script commands that didn't have them
Continuing from #280, another potential source of out-of-bounds indexing
(and thus, Undefined Behavior badness) comes from script commands. A
majority of them don't do any input validation at all, which means the
potential for out-of-bounds indexing and segfaulting in custom levels.
So it's always good to add bounds checks to them.
Interesting note, the only existing command that has bounds checks is
the flag() command. That means you can't turn out-of-bounds flags on or
off. But there's no bounds checks for ifflag(), or customifflag(), which
means you CAN index out-of-bounds with those commands! That's a bit bad
to do, so.
Also, I decided to add the bounds checks for playef() at the
musicclass::playef() level, instead of just the level of the playef()
command. I don't know of any other cases outside of the command where
musicclass::playef() will index out of bounds, but musicclass is the one
containing the indexed vector anyway, I wanted to cover more cases, and
it's better to be safe than sorry.
2020-06-13 06:55:41 +02:00
|
|
|
int room = ss_toi(words[1])-1+(ed.maxwidth*(ss_toi(words[2])-1));
|
2020-09-10 07:12:10 +02:00
|
|
|
if (INBOUNDS_ARR(room, ed.level) && ed.level[room].warpdir == ss_toi(words[3]))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
load("custom_"+words[4]);
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
2020-04-02 22:26:22 +02:00
|
|
|
#endif
|
2020-01-01 21:29:24 +01:00
|
|
|
if (words[0] == "destroy")
|
|
|
|
{
|
|
|
|
if(words[1]=="gravitylines"){
|
2020-04-03 22:50:16 +02:00
|
|
|
for(size_t edi=0; edi<obj.entities.size(); edi++){
|
Fix entity and block indices after destroying them
This patch restores some 2.2 behavior, fixing a regression caused by the
refactor of properly using std::vectors.
In 2.2, the game allocated 200 items in obj.entities, but used a system
where each entity had an `active` attribute to signify if the entity
actually existed or not. When dealing with entities, you would have to
check this `active` flag, or else you'd be dealing with an entity that
didn't actually exist. (By the way, what I'm saying applies to blocks
and obj.blocks as well, except for some small differing details like the
game allocating 500 block slots versus obj.entities's 200.)
As a consequence, the game had to use a separate tracking variable,
obj.nentity, because obj.entities.size() would just report 200, instead
of the actual amount of entities. Needless to say, having to check for
`active` and use `obj.nentity` is a bit error-prone, and it's messier
than simply using the std::vector the way it was intended. Also, this
resulted in a hard limit of 200 entities, which custom level makers ran
into surprisingly quite often.
2.3 comes along, and removes the whole system. Now, std::vectors are
properly being used, and obj.entities.size() reports the actual number
of entities in the vector; you no longer have to check for `active` when
dealing with entities of any sort.
But there was one previous behavior of 2.2 that this system kind of
forgets about - namely, the ability to have holes in between entities.
You see, when an entity got disabled in 2.2 (which just meant turning
its `active` off), the indices of all other entities stayed the same;
the indice of the entity that got disabled stays there as a hole in the
array. But when an entity gets removed in 2.3 (previous to this patch),
the indices of every entity afterwards in the array get shifted down by
one. std::vector isn't really meant to be able to contain holes.
Do the indices of entities and blocks matter? Yes; they determine the
order in which entities and blocks get evaluated (the highest indice
gets evaluated first), and I had to fix some block evaluation order
stuff in previous PRs.
And in the case of entities, they matter hugely when using the
recently-discovered Arbitrary Entity Manipulation glitch (where crewmate
script commands are used on arbitrary entities by setting the `i`
attribute of `scriptclass` and passing invalid crewmate identifiers to
the commands). If you use Arbitrary Entity Manipulation after destroying
some entities, there is a chance that your script won't work between 2.2
and 2.3.
The indices also still determine the rendering order of entities
(highest indice gets drawn first, which means lowest indice gets drawn
in front of other entities). As an example: let's say we have the player
at 0, a gravity line at 1, and a checkpoint at 2; then we destroy the
gravity line and create a crewmate (let's do Violet).
If we're able to have holes, then after removing the gravity line, none
of the other indices shift. Then Violet will be created at indice 1, and
will be drawn in front of the checkpoint.
But if we can't have holes, then removing the gravity line results in
the indice of the checkpoint shifting down to indice 1. Then Violet is
created at indice 2, and gets drawn behind the checkpoint! This is a
clear illustration of changing the behavior that existed in 2.2.
However, I also don't want to go back to the `active` system of having
to check an attribute before operating on an entity. So... what do we
do to restore the holes?
Well, we don't need to have an `active` attribute, or modify any
existing code that operates on entities. Instead, we can just set the
attributes of the entities so that they naturally get ignored by
everything that comes into contact with it. For entities, we set their
invis to true, and their size, type, and rule to -1 (the game never uses
a size, type, or rule of -1 anywhere); for blocks, we set their type to
-1, and their width and height to 0.
obj.entities.size() will no longer necessarily equal the amount of
entities in the room; rather, it will be the amount of entity SLOTS that
have been allocated. But nothing that uses obj.entities.size() needs to
actually know the amount of entities; it's mostly used for iterating
over every entity in the vector.
Excess entity slots get cleaned up upon every call of
mapclass::gotoroom(), which will now deallocate entity slots starting
from the end until it hits a player, at which point it will switch to
disabling entity slots instead of removing them entirely.
The entclass::clear() and blockclass::clear() functions have been
restored because we need to call their initialization functions when
reusing a block/entity slot; it's possible to create an entity with an
invalid type number (it creates a glitchy Viridian), and without calling
the initialization function again, it would simply not create anything.
After this patch is applied, entity and block indices will be restored
to how they behaved in 2.2.
2020-12-27 07:11:34 +01:00
|
|
|
if(obj.entities[edi].type==9) obj.disableentity(edi);
|
|
|
|
if(obj.entities[edi].type==10) obj.disableentity(edi);
|
2020-04-02 22:26:22 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}else if(words[1]=="warptokens"){
|
2020-04-03 22:50:16 +02:00
|
|
|
for(size_t edi=0; edi<obj.entities.size(); edi++){
|
Fix entity and block indices after destroying them
This patch restores some 2.2 behavior, fixing a regression caused by the
refactor of properly using std::vectors.
In 2.2, the game allocated 200 items in obj.entities, but used a system
where each entity had an `active` attribute to signify if the entity
actually existed or not. When dealing with entities, you would have to
check this `active` flag, or else you'd be dealing with an entity that
didn't actually exist. (By the way, what I'm saying applies to blocks
and obj.blocks as well, except for some small differing details like the
game allocating 500 block slots versus obj.entities's 200.)
As a consequence, the game had to use a separate tracking variable,
obj.nentity, because obj.entities.size() would just report 200, instead
of the actual amount of entities. Needless to say, having to check for
`active` and use `obj.nentity` is a bit error-prone, and it's messier
than simply using the std::vector the way it was intended. Also, this
resulted in a hard limit of 200 entities, which custom level makers ran
into surprisingly quite often.
2.3 comes along, and removes the whole system. Now, std::vectors are
properly being used, and obj.entities.size() reports the actual number
of entities in the vector; you no longer have to check for `active` when
dealing with entities of any sort.
But there was one previous behavior of 2.2 that this system kind of
forgets about - namely, the ability to have holes in between entities.
You see, when an entity got disabled in 2.2 (which just meant turning
its `active` off), the indices of all other entities stayed the same;
the indice of the entity that got disabled stays there as a hole in the
array. But when an entity gets removed in 2.3 (previous to this patch),
the indices of every entity afterwards in the array get shifted down by
one. std::vector isn't really meant to be able to contain holes.
Do the indices of entities and blocks matter? Yes; they determine the
order in which entities and blocks get evaluated (the highest indice
gets evaluated first), and I had to fix some block evaluation order
stuff in previous PRs.
And in the case of entities, they matter hugely when using the
recently-discovered Arbitrary Entity Manipulation glitch (where crewmate
script commands are used on arbitrary entities by setting the `i`
attribute of `scriptclass` and passing invalid crewmate identifiers to
the commands). If you use Arbitrary Entity Manipulation after destroying
some entities, there is a chance that your script won't work between 2.2
and 2.3.
The indices also still determine the rendering order of entities
(highest indice gets drawn first, which means lowest indice gets drawn
in front of other entities). As an example: let's say we have the player
at 0, a gravity line at 1, and a checkpoint at 2; then we destroy the
gravity line and create a crewmate (let's do Violet).
If we're able to have holes, then after removing the gravity line, none
of the other indices shift. Then Violet will be created at indice 1, and
will be drawn in front of the checkpoint.
But if we can't have holes, then removing the gravity line results in
the indice of the checkpoint shifting down to indice 1. Then Violet is
created at indice 2, and gets drawn behind the checkpoint! This is a
clear illustration of changing the behavior that existed in 2.2.
However, I also don't want to go back to the `active` system of having
to check an attribute before operating on an entity. So... what do we
do to restore the holes?
Well, we don't need to have an `active` attribute, or modify any
existing code that operates on entities. Instead, we can just set the
attributes of the entities so that they naturally get ignored by
everything that comes into contact with it. For entities, we set their
invis to true, and their size, type, and rule to -1 (the game never uses
a size, type, or rule of -1 anywhere); for blocks, we set their type to
-1, and their width and height to 0.
obj.entities.size() will no longer necessarily equal the amount of
entities in the room; rather, it will be the amount of entity SLOTS that
have been allocated. But nothing that uses obj.entities.size() needs to
actually know the amount of entities; it's mostly used for iterating
over every entity in the vector.
Excess entity slots get cleaned up upon every call of
mapclass::gotoroom(), which will now deallocate entity slots starting
from the end until it hits a player, at which point it will switch to
disabling entity slots instead of removing them entirely.
The entclass::clear() and blockclass::clear() functions have been
restored because we need to call their initialization functions when
reusing a block/entity slot; it's possible to create an entity with an
invalid type number (it creates a glitchy Viridian), and without calling
the initialization function again, it would simply not create anything.
After this patch is applied, entity and block indices will be restored
to how they behaved in 2.2.
2020-12-27 07:11:34 +01:00
|
|
|
if(obj.entities[edi].type==11) obj.disableentity(edi);
|
2020-04-02 22:26:22 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}else if(words[1]=="platforms"){
|
2020-04-03 22:50:16 +02:00
|
|
|
for(size_t edi=0; edi<obj.entities.size(); edi++){
|
Fix entity and block indices after destroying them
This patch restores some 2.2 behavior, fixing a regression caused by the
refactor of properly using std::vectors.
In 2.2, the game allocated 200 items in obj.entities, but used a system
where each entity had an `active` attribute to signify if the entity
actually existed or not. When dealing with entities, you would have to
check this `active` flag, or else you'd be dealing with an entity that
didn't actually exist. (By the way, what I'm saying applies to blocks
and obj.blocks as well, except for some small differing details like the
game allocating 500 block slots versus obj.entities's 200.)
As a consequence, the game had to use a separate tracking variable,
obj.nentity, because obj.entities.size() would just report 200, instead
of the actual amount of entities. Needless to say, having to check for
`active` and use `obj.nentity` is a bit error-prone, and it's messier
than simply using the std::vector the way it was intended. Also, this
resulted in a hard limit of 200 entities, which custom level makers ran
into surprisingly quite often.
2.3 comes along, and removes the whole system. Now, std::vectors are
properly being used, and obj.entities.size() reports the actual number
of entities in the vector; you no longer have to check for `active` when
dealing with entities of any sort.
But there was one previous behavior of 2.2 that this system kind of
forgets about - namely, the ability to have holes in between entities.
You see, when an entity got disabled in 2.2 (which just meant turning
its `active` off), the indices of all other entities stayed the same;
the indice of the entity that got disabled stays there as a hole in the
array. But when an entity gets removed in 2.3 (previous to this patch),
the indices of every entity afterwards in the array get shifted down by
one. std::vector isn't really meant to be able to contain holes.
Do the indices of entities and blocks matter? Yes; they determine the
order in which entities and blocks get evaluated (the highest indice
gets evaluated first), and I had to fix some block evaluation order
stuff in previous PRs.
And in the case of entities, they matter hugely when using the
recently-discovered Arbitrary Entity Manipulation glitch (where crewmate
script commands are used on arbitrary entities by setting the `i`
attribute of `scriptclass` and passing invalid crewmate identifiers to
the commands). If you use Arbitrary Entity Manipulation after destroying
some entities, there is a chance that your script won't work between 2.2
and 2.3.
The indices also still determine the rendering order of entities
(highest indice gets drawn first, which means lowest indice gets drawn
in front of other entities). As an example: let's say we have the player
at 0, a gravity line at 1, and a checkpoint at 2; then we destroy the
gravity line and create a crewmate (let's do Violet).
If we're able to have holes, then after removing the gravity line, none
of the other indices shift. Then Violet will be created at indice 1, and
will be drawn in front of the checkpoint.
But if we can't have holes, then removing the gravity line results in
the indice of the checkpoint shifting down to indice 1. Then Violet is
created at indice 2, and gets drawn behind the checkpoint! This is a
clear illustration of changing the behavior that existed in 2.2.
However, I also don't want to go back to the `active` system of having
to check an attribute before operating on an entity. So... what do we
do to restore the holes?
Well, we don't need to have an `active` attribute, or modify any
existing code that operates on entities. Instead, we can just set the
attributes of the entities so that they naturally get ignored by
everything that comes into contact with it. For entities, we set their
invis to true, and their size, type, and rule to -1 (the game never uses
a size, type, or rule of -1 anywhere); for blocks, we set their type to
-1, and their width and height to 0.
obj.entities.size() will no longer necessarily equal the amount of
entities in the room; rather, it will be the amount of entity SLOTS that
have been allocated. But nothing that uses obj.entities.size() needs to
actually know the amount of entities; it's mostly used for iterating
over every entity in the vector.
Excess entity slots get cleaned up upon every call of
mapclass::gotoroom(), which will now deallocate entity slots starting
from the end until it hits a player, at which point it will switch to
disabling entity slots instead of removing them entirely.
The entclass::clear() and blockclass::clear() functions have been
restored because we need to call their initialization functions when
reusing a block/entity slot; it's possible to create an entity with an
invalid type number (it creates a glitchy Viridian), and without calling
the initialization function again, it would simply not create anything.
After this patch is applied, entity and block indices will be restored
to how they behaved in 2.2.
2020-12-27 07:11:34 +01:00
|
|
|
if(obj.entities[edi].rule==2 && obj.entities[edi].animate==100) obj.disableentity(edi);
|
2020-04-02 22:26:22 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (words[0] == "customiftrinkets")
|
|
|
|
{
|
2020-04-07 08:46:27 +02:00
|
|
|
if (game.trinkets() >= ss_toi(words[1]))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
load("custom_"+words[2]);
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (words[0] == "customiftrinketsless")
|
|
|
|
{
|
2020-04-07 08:46:27 +02:00
|
|
|
if (game.trinkets() < ss_toi(words[1]))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
load("custom_"+words[2]);
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
2020-04-02 22:26:22 +02:00
|
|
|
else if (words[0] == "customifflag")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-11-02 19:09:13 +01:00
|
|
|
int flag = ss_toi(words[1]);
|
|
|
|
if (INBOUNDS_ARR(flag, obj.flags) && obj.flags[flag])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
load("custom_"+words[2]);
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (words[0] == "custommap")
|
|
|
|
{
|
|
|
|
if(words[1]=="on"){
|
2020-04-02 22:26:22 +02:00
|
|
|
map.customshowmm=true;
|
2020-01-01 21:29:24 +01:00
|
|
|
}else if(words[1]=="off"){
|
2020-04-02 22:26:22 +02:00
|
|
|
map.customshowmm=false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (words[0] == "delay")
|
|
|
|
{
|
|
|
|
//USAGE: delay(frames)
|
|
|
|
scriptdelay = ss_toi(words[1]);
|
|
|
|
}
|
2020-04-02 22:26:22 +02:00
|
|
|
if (words[0] == "flag")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-11-02 19:09:13 +01:00
|
|
|
int flag = ss_toi(words[1]);
|
|
|
|
if (INBOUNDS_ARR(flag, obj.flags))
|
|
|
|
{
|
|
|
|
if (words[2] == "on")
|
|
|
|
{
|
|
|
|
obj.flags[flag] = true;
|
|
|
|
}
|
|
|
|
else if (words[2] == "off")
|
|
|
|
{
|
|
|
|
obj.flags[flag] = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (words[0] == "flash")
|
|
|
|
{
|
|
|
|
//USAGE: flash(frames)
|
|
|
|
game.flashlight = ss_toi(words[1]);
|
|
|
|
}
|
|
|
|
if (words[0] == "shake")
|
|
|
|
{
|
|
|
|
//USAGE: shake(frames)
|
|
|
|
game.screenshake = ss_toi(words[1]);
|
|
|
|
}
|
|
|
|
if (words[0] == "walk")
|
|
|
|
{
|
|
|
|
//USAGE: walk(dir,frames)
|
|
|
|
if (words[1] == "left")
|
|
|
|
{
|
|
|
|
game.press_left = true;
|
|
|
|
}
|
|
|
|
else if (words[1] == "right")
|
|
|
|
{
|
|
|
|
game.press_right = true;
|
|
|
|
}
|
|
|
|
scriptdelay = ss_toi(words[2]);
|
|
|
|
}
|
|
|
|
if (words[0] == "flip")
|
|
|
|
{
|
|
|
|
game.press_action = true;
|
|
|
|
scriptdelay = 1;
|
|
|
|
}
|
|
|
|
if (words[0] == "tofloor")
|
|
|
|
{
|
2020-06-13 05:36:08 +02:00
|
|
|
int player = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if(INBOUNDS_VEC(player, obj.entities) && obj.entities[player].onroof>0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
game.press_action = true;
|
|
|
|
scriptdelay = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (words[0] == "playef")
|
|
|
|
{
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(ss_toi(words[1]));
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
if (words[0] == "play")
|
|
|
|
{
|
|
|
|
music.play(ss_toi(words[1]));
|
|
|
|
}
|
|
|
|
if (words[0] == "stopmusic")
|
|
|
|
{
|
|
|
|
music.haltdasmusik();
|
|
|
|
}
|
|
|
|
if (words[0] == "resumemusic")
|
|
|
|
{
|
Fix resumemusic/musicfadein not working
It seems like they were unfinished. This commit makes them properly
work.
When a track is stopped with stopmusic() or musicfadeout(),
resumemusic() will resume from where the track stopped. musicfadein()
does the same but does it with a gradual fade instead of suddenly
playing it at full volume.
I changed several interfaces around for this. First, setting currentsong
to -1 when music is stopped is handled in the hook callback that gets
called by SDL_mixer whenever the music stops. Otherwise, it'd be
problematic if currentsong was set to -1 when the song starts fading out
instead of when the song actually ends.
Also, music.play() has a few optional arguments now, to reduce the
copying-and-pasting of music code.
Lastly, we have to roll our own tracker of music length by using
SDL_GetPerformanceCounter(), because there's no way to get the music
position if a song fades out. (We could implicitly keep the music
position if we abruptly stopped the song using Mix_PauseMusic(), and
resume it using Mix_ResumeMusic(), but ignoring the fact that those two
functions are also used on the unfocus-pause (which, as it turns out, is
basically a non-issue because the unfocus-pause can use some other
functions), there's no equivalent for fading out, i.e. there's no
"fade out and pause when it fully fades out" function in SDL_mixer.) And
then we have to account for the unfocus-pause in our manual tracker.
Other than that, these commands are now fully functional.
2020-06-27 10:31:09 +02:00
|
|
|
music.resume();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-04-02 22:26:22 +02:00
|
|
|
if (words[0] == "musicfadeout")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-11-06 09:51:04 +01:00
|
|
|
music.fadeout(false);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
if (words[0] == "musicfadein")
|
|
|
|
{
|
Fix resumemusic/musicfadein not working
It seems like they were unfinished. This commit makes them properly
work.
When a track is stopped with stopmusic() or musicfadeout(),
resumemusic() will resume from where the track stopped. musicfadein()
does the same but does it with a gradual fade instead of suddenly
playing it at full volume.
I changed several interfaces around for this. First, setting currentsong
to -1 when music is stopped is handled in the hook callback that gets
called by SDL_mixer whenever the music stops. Otherwise, it'd be
problematic if currentsong was set to -1 when the song starts fading out
instead of when the song actually ends.
Also, music.play() has a few optional arguments now, to reduce the
copying-and-pasting of music code.
Lastly, we have to roll our own tracker of music length by using
SDL_GetPerformanceCounter(), because there's no way to get the music
position if a song fades out. (We could implicitly keep the music
position if we abruptly stopped the song using Mix_PauseMusic(), and
resume it using Mix_ResumeMusic(), but ignoring the fact that those two
functions are also used on the unfocus-pause (which, as it turns out, is
basically a non-issue because the unfocus-pause can use some other
functions), there's no equivalent for fading out, i.e. there's no
"fade out and pause when it fully fades out" function in SDL_mixer.) And
then we have to account for the unfocus-pause in our manual tracker.
Other than that, these commands are now fully functional.
2020-06-27 10:31:09 +02:00
|
|
|
music.fadein();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
if (words[0] == "trinketscriptmusic")
|
|
|
|
{
|
|
|
|
music.play(4);
|
|
|
|
}
|
|
|
|
if (words[0] == "gotoposition")
|
|
|
|
{
|
|
|
|
//USAGE: gotoposition(x position, y position, gravity position)
|
|
|
|
int player = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(player, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[player].xp = ss_toi(words[1]);
|
|
|
|
obj.entities[player].yp = ss_toi(words[2]);
|
2020-11-23 11:02:41 +01:00
|
|
|
obj.entities[player].lerpoldxp = obj.entities[player].xp;
|
|
|
|
obj.entities[player].lerpoldyp = obj.entities[player].yp;
|
2020-06-13 05:36:08 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
game.gravitycontrol = ss_toi(words[3]);
|
|
|
|
|
|
|
|
}
|
|
|
|
if (words[0] == "gotoroom")
|
|
|
|
{
|
|
|
|
//USAGE: gotoroom(x,y) (manually add 100)
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(ss_toi(words[1])+100, ss_toi(words[2])+100);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
if (words[0] == "cutscene")
|
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.showcutscenebars = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
if (words[0] == "endcutscene")
|
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.showcutscenebars = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
if (words[0] == "untilbars")
|
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.showcutscenebars)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.cutscenebarspos < 360)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
scriptdelay = 1;
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.cutscenebarspos > 0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
scriptdelay = 1;
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "text")
|
|
|
|
{
|
|
|
|
//oh boy
|
|
|
|
//first word is the colour.
|
|
|
|
if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
r = 164;
|
|
|
|
g = 164;
|
|
|
|
b = 255;
|
|
|
|
}
|
|
|
|
else if (words[1] == "player")
|
|
|
|
{
|
|
|
|
r = 164;
|
|
|
|
g = 164;
|
|
|
|
b = 255;
|
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
|
|
|
r = 255;
|
|
|
|
g = 60;
|
|
|
|
b = 60;
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
r = 144;
|
|
|
|
g = 255;
|
|
|
|
b = 144;
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
r = 255;
|
|
|
|
g = 255;
|
|
|
|
b = 134;
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
r = 95;
|
|
|
|
g = 95;
|
|
|
|
b = 255;
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
r = 255;
|
|
|
|
g = 134;
|
|
|
|
b = 255;
|
|
|
|
}
|
2020-07-01 12:03:35 +02:00
|
|
|
else if (words[1] == "white")
|
|
|
|
{
|
|
|
|
r = 244;
|
|
|
|
g = 244;
|
|
|
|
b = 244;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
else if (words[1] == "gray")
|
|
|
|
{
|
|
|
|
r = 174;
|
|
|
|
g = 174;
|
|
|
|
b = 174;
|
|
|
|
}
|
2020-07-01 12:03:35 +02:00
|
|
|
else if (words[1] == "orange")
|
|
|
|
{
|
|
|
|
r = 255;
|
|
|
|
g = 130;
|
|
|
|
b = 20;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
//use a gray
|
|
|
|
r = 174;
|
|
|
|
g = 174;
|
|
|
|
b = 174;
|
|
|
|
}
|
|
|
|
|
|
|
|
//next are the x,y coordinates
|
|
|
|
textx = ss_toi(words[2]);
|
|
|
|
texty = ss_toi(words[3]);
|
|
|
|
|
|
|
|
//Number of lines for the textbox!
|
2020-04-04 03:04:55 +02:00
|
|
|
txt.clear();
|
|
|
|
for (int i = 0; i < ss_toi(words[4]); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
position++;
|
2020-09-10 06:58:59 +02:00
|
|
|
if (INBOUNDS_VEC(position, commands))
|
2020-05-27 09:40:24 +02:00
|
|
|
{
|
|
|
|
txt.push_back(commands[position]);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "position")
|
|
|
|
{
|
|
|
|
//are we facing left or right? for some objects we don't care, default at 0.
|
|
|
|
j = 0;
|
|
|
|
|
|
|
|
//the first word is the object to position relative to
|
|
|
|
if (words[1] == "player")
|
|
|
|
{
|
|
|
|
i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
i = obj.getcrewman(0);
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
i = obj.getcrewman(1);
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
i = obj.getcrewman(2);
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
|
|
|
i = obj.getcrewman(3);
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
i = obj.getcrewman(4);
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
i = obj.getcrewman(5);
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
|
|
|
else if (words[1] == "centerx")
|
|
|
|
{
|
|
|
|
words[2] = "donothing";
|
|
|
|
j = -1;
|
|
|
|
textx = -500;
|
|
|
|
}
|
|
|
|
else if (words[1] == "centery")
|
|
|
|
{
|
|
|
|
words[2] = "donothing";
|
|
|
|
j = -1;
|
|
|
|
texty = -500;
|
|
|
|
}
|
|
|
|
else if (words[1] == "center")
|
|
|
|
{
|
|
|
|
words[2] = "donothing";
|
|
|
|
j = -1;
|
|
|
|
textx = -500;
|
|
|
|
texty = -500;
|
|
|
|
}
|
|
|
|
|
|
|
|
//next is whether to position above or below
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities) && words[2] == "above")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (j == 1) //left
|
|
|
|
{
|
|
|
|
textx = obj.entities[i].xp -10000; //tells the box to be oriented correctly later
|
2020-04-04 03:04:55 +02:00
|
|
|
texty = obj.entities[i].yp - 16 - (txt.size()*8);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (j == 0) //Right
|
|
|
|
{
|
|
|
|
textx = obj.entities[i].xp - 16;
|
2020-04-04 03:04:55 +02:00
|
|
|
texty = obj.entities[i].yp - 18 - (txt.size() * 8);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
else if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (j == 1) //left
|
|
|
|
{
|
|
|
|
textx = obj.entities[i].xp -10000; //tells the box to be oriented correctly later
|
|
|
|
texty = obj.entities[i].yp + 26;
|
|
|
|
}
|
|
|
|
else if (j == 0) //Right
|
|
|
|
{
|
|
|
|
textx = obj.entities[i].xp - 16;
|
|
|
|
texty = obj.entities[i].yp + 26;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "customposition")
|
|
|
|
{
|
|
|
|
//are we facing left or right? for some objects we don't care, default at 0.
|
|
|
|
j = 0;
|
|
|
|
|
|
|
|
//the first word is the object to position relative to
|
|
|
|
if (words[1] == "player")
|
|
|
|
{
|
|
|
|
i = obj.getcustomcrewman(0);
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
|
|
|
else if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
i = obj.getcustomcrewman(0);
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
i = obj.getcustomcrewman(1);
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
i = obj.getcustomcrewman(2);
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
|
|
|
i = obj.getcustomcrewman(3);
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
i = obj.getcustomcrewman(4);
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
i = obj.getcustomcrewman(5);
|
|
|
|
j = obj.entities[i].dir;
|
|
|
|
}
|
|
|
|
else if (words[1] == "centerx")
|
|
|
|
{
|
|
|
|
words[2] = "donothing";
|
|
|
|
j = -1;
|
|
|
|
textx = -500;
|
|
|
|
}
|
|
|
|
else if (words[1] == "centery")
|
|
|
|
{
|
|
|
|
words[2] = "donothing";
|
|
|
|
j = -1;
|
|
|
|
texty = -500;
|
|
|
|
}
|
|
|
|
else if (words[1] == "center")
|
|
|
|
{
|
|
|
|
words[2] = "donothing";
|
|
|
|
j = -1;
|
|
|
|
textx = -500;
|
|
|
|
texty = -500;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(i==0 && words[1]!="player" && words[1]!="cyan"){
|
2020-04-02 22:26:22 +02:00
|
|
|
//Requested crewmate is not actually on screen
|
|
|
|
words[2] = "donothing";
|
2020-01-01 21:29:24 +01:00
|
|
|
j = -1;
|
|
|
|
textx = -500;
|
|
|
|
texty = -500;
|
|
|
|
}
|
|
|
|
|
|
|
|
//next is whether to position above or below
|
Bounds check all entity getters that can return 0
The entity getters I'm referring to are entityclass::getscm(),
entityclass::getlineat(), entityclass::getcrewman(), and
entityclass::getcustomcrewman().
Even though the player should always exist, and the player should always
be indice 0, I wouldn't want to make that assumption. I've been wrong
before.
Also, these functions returning 0 lull you into a false sense of
security. If you assume that commands using these functions are fine,
you'll forget about the fact that `i` in those commands could be
potentially anything, given an invalid argument. In fact, it's possible
to index createactivityzone(), flipgravity(), and customposition()
out-of-bounds by setting `i` to anything! Well, WAS possible. I fixed it
so now they can't.
Furthermore, in the game.scmmoveme block in gamelogic(), obj.getplayer()
wasn't even checked, even though it's been checked in all other places.
I only caught it just now because I wanted to bounds-check all usages of
obj.getscm(), too, and that game.scmmove block also used obj.getscm()
without bounds-checking it as well.
2020-09-10 07:31:09 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities) && words[2] == "above")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (j == 1) //left
|
|
|
|
{
|
|
|
|
textx = obj.entities[i].xp -10000; //tells the box to be oriented correctly later
|
2020-04-04 03:04:55 +02:00
|
|
|
texty = obj.entities[i].yp - 16 - (txt.size()*8);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (j == 0) //Right
|
|
|
|
{
|
|
|
|
textx = obj.entities[i].xp - 16;
|
2020-04-04 03:04:55 +02:00
|
|
|
texty = obj.entities[i].yp - 18 - (txt.size() * 8);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
Bounds check all entity getters that can return 0
The entity getters I'm referring to are entityclass::getscm(),
entityclass::getlineat(), entityclass::getcrewman(), and
entityclass::getcustomcrewman().
Even though the player should always exist, and the player should always
be indice 0, I wouldn't want to make that assumption. I've been wrong
before.
Also, these functions returning 0 lull you into a false sense of
security. If you assume that commands using these functions are fine,
you'll forget about the fact that `i` in those commands could be
potentially anything, given an invalid argument. In fact, it's possible
to index createactivityzone(), flipgravity(), and customposition()
out-of-bounds by setting `i` to anything! Well, WAS possible. I fixed it
so now they can't.
Furthermore, in the game.scmmoveme block in gamelogic(), obj.getplayer()
wasn't even checked, even though it's been checked in all other places.
I only caught it just now because I wanted to bounds-check all usages of
obj.getscm(), too, and that game.scmmove block also used obj.getscm()
without bounds-checking it as well.
2020-09-10 07:31:09 +02:00
|
|
|
else if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (j == 1) //left
|
|
|
|
{
|
|
|
|
textx = obj.entities[i].xp -10000; //tells the box to be oriented correctly later
|
|
|
|
texty = obj.entities[i].yp + 26;
|
|
|
|
}
|
|
|
|
else if (j == 0) //Right
|
|
|
|
{
|
|
|
|
textx = obj.entities[i].xp - 16;
|
|
|
|
texty = obj.entities[i].yp + 26;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "backgroundtext")
|
|
|
|
{
|
|
|
|
game.backgroundtext = true;
|
|
|
|
}
|
|
|
|
else if (words[0] == "flipme")
|
|
|
|
{
|
2021-03-20 04:08:41 +01:00
|
|
|
textflipme = !textflipme;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-05-23 21:24:54 +02:00
|
|
|
else if (words[0] == "speak_active" || words[0] == "speak")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Ok, actually display the textbox we've initilised now!
|
2020-05-23 21:24:54 +02:00
|
|
|
//If using "speak", don't make the textbox active (so we can use multiple textboxes)
|
2020-05-23 21:47:56 +02:00
|
|
|
if (txt.empty())
|
|
|
|
{
|
|
|
|
txt.resize(1);
|
|
|
|
}
|
2021-03-20 04:08:41 +01:00
|
|
|
graphics.createtextboxreal(txt[0], textx, texty, r, g, b, textflipme);
|
|
|
|
textflipme = false;
|
2020-04-04 03:04:55 +02:00
|
|
|
if ((int) txt.size() > 1)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-04 03:04:55 +02:00
|
|
|
for (i = 1; i < (int) txt.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.addline(txt[i]);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//the textbox cannot be outside the screen. Fix if it is.
|
|
|
|
if (textx <= -1000)
|
|
|
|
{
|
|
|
|
//position to the left of the player
|
|
|
|
textx += 10000;
|
2020-03-31 21:38:52 +02:00
|
|
|
textx -= graphics.textboxwidth();
|
2020-01-01 21:29:24 +01:00
|
|
|
textx += 16;
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.textboxmoveto(textx);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-01-31 22:36:36 +01:00
|
|
|
if (textx == -500 || textx == -1)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (texty == -500)
|
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.textboxcentery();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.textboxadjust();
|
2020-05-23 21:24:54 +02:00
|
|
|
if (words[0] == "speak_active")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-05-23 21:24:54 +02:00
|
|
|
graphics.textboxactive();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!game.backgroundtext)
|
|
|
|
{
|
|
|
|
game.advancetext = true;
|
|
|
|
game.hascontrol = false;
|
|
|
|
game.pausescript = true;
|
|
|
|
if (key.isDown(90) || key.isDown(32) || key.isDown(86)
|
|
|
|
|| key.isDown(KEYBOARD_UP) || key.isDown(KEYBOARD_DOWN)) game.jumpheld = true;
|
|
|
|
}
|
|
|
|
game.backgroundtext = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "endtext")
|
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.hascontrol = true;
|
|
|
|
game.advancetext = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "endtextfast")
|
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.textboxremovefast();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.hascontrol = true;
|
|
|
|
game.advancetext = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "do")
|
|
|
|
{
|
|
|
|
//right, loop from this point
|
|
|
|
looppoint = position;
|
|
|
|
loopcount = ss_toi(words[1]);
|
|
|
|
}
|
|
|
|
else if (words[0] == "loop")
|
|
|
|
{
|
|
|
|
//right, loop from this point
|
|
|
|
loopcount--;
|
|
|
|
if (loopcount > 0)
|
|
|
|
{
|
|
|
|
position = looppoint;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "vvvvvvman")
|
|
|
|
{
|
|
|
|
//Create the super VVVVVV combo!
|
|
|
|
i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp = 30;
|
|
|
|
obj.entities[i].yp = 46;
|
|
|
|
obj.entities[i].size = 13;
|
|
|
|
obj.entities[i].colour = 23;
|
|
|
|
obj.entities[i].cx = 36;// 6;
|
|
|
|
obj.entities[i].cy = 12+80;// 2;
|
|
|
|
obj.entities[i].h = 126-80;// 21;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "undovvvvvvman")
|
|
|
|
{
|
|
|
|
//Create the super VVVVVV combo!
|
|
|
|
i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp = 100;
|
|
|
|
obj.entities[i].size = 0;
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
obj.entities[i].cx = 6;
|
|
|
|
obj.entities[i].cy = 2;
|
|
|
|
obj.entities[i].h = 21;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "createentity")
|
|
|
|
{
|
2020-07-02 20:34:21 +02:00
|
|
|
std::string word6 = words[6];
|
|
|
|
std::string word7 = words[7];
|
|
|
|
std::string word8 = words[8];
|
|
|
|
std::string word9 = words[9];
|
Expose all 9 arguments of createentity()
For whatever reason, not all arguments of createentity() are exposed in
the command.
We have to keep in mind that (1) unspecified arguments default to 0
(instead of the 320 and 240 for argument 8 and 9 that createentity()
usually defaults to), and that (2) arguments persist across commands.
(Why not get rid of argument persistence, you say? Unfortunately, some
levels rely on argument persistence to call gotoposition() without
specifying the third argument, even though you're supposed to specify
all three arguments.)
To add these arguments without breaking levels, I re-added the
createentity() defaults of 320 and 240 for args 8 and 9, and then I
reset the new arguments afterwards when I'm done. Technically this could
be bad if other commands used those higher arguments, but none of them
really do. (Except createcrewman(), but it only sets argument 6 to 0
sometimes anyway, but argument 6 is already supposed to default to 0.)
2020-06-30 22:35:02 +02:00
|
|
|
if (words[6] == "") words[6] = "0";
|
|
|
|
if (words[7] == "") words[7] = "0";
|
|
|
|
if (words[8] == "") words[8] = "320";
|
|
|
|
if (words[9] == "") words[9] = "240";
|
2020-07-02 20:34:21 +02:00
|
|
|
obj.createentity(
|
|
|
|
ss_toi(words[1]),
|
|
|
|
ss_toi(words[2]),
|
|
|
|
ss_toi(words[3]),
|
|
|
|
ss_toi(words[4]),
|
|
|
|
ss_toi(words[5]),
|
|
|
|
ss_toi(words[6]),
|
|
|
|
ss_toi(words[7]),
|
|
|
|
ss_toi(words[8]),
|
|
|
|
ss_toi(words[9])
|
|
|
|
);
|
|
|
|
words[6] = word6;
|
|
|
|
words[7] = word7;
|
|
|
|
words[8] = word8;
|
|
|
|
words[9] = word9;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "createcrewman")
|
|
|
|
{
|
|
|
|
if (words[3] == "cyan")
|
|
|
|
{
|
|
|
|
r=0;
|
|
|
|
}
|
|
|
|
else if (words[3] == "red")
|
|
|
|
{
|
|
|
|
r=15;
|
|
|
|
}
|
|
|
|
else if (words[3] == "green")
|
|
|
|
{
|
|
|
|
r=13;
|
|
|
|
}
|
|
|
|
else if (words[3] == "yellow")
|
|
|
|
{
|
|
|
|
r=14;
|
|
|
|
}
|
|
|
|
else if (words[3] == "blue")
|
|
|
|
{
|
|
|
|
r=16;
|
|
|
|
}
|
|
|
|
else if (words[3] == "purple")
|
|
|
|
{
|
|
|
|
r=20;
|
|
|
|
}
|
|
|
|
else if (words[3] == "gray")
|
|
|
|
{
|
|
|
|
r=19;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
r = 19;
|
|
|
|
}
|
|
|
|
|
|
|
|
//convert the command to the right index
|
|
|
|
if (words[5] == "followplayer") words[5] = "10";
|
|
|
|
if (words[5] == "followpurple") words[5] = "11";
|
|
|
|
if (words[5] == "followyellow") words[5] = "12";
|
|
|
|
if (words[5] == "followred") words[5] = "13";
|
|
|
|
if (words[5] == "followgreen") words[5] = "14";
|
|
|
|
if (words[5] == "followblue") words[5] = "15";
|
|
|
|
|
|
|
|
if (words[5] == "followposition") words[5] = "16";
|
|
|
|
if (words[5] == "faceleft")
|
|
|
|
{
|
|
|
|
words[5] = "17";
|
|
|
|
words[6] = "0";
|
|
|
|
}
|
|
|
|
if (words[5] == "faceright")
|
|
|
|
{
|
|
|
|
words[5] = "17";
|
|
|
|
words[6] = "1";
|
|
|
|
}
|
|
|
|
if (words[5] == "faceplayer")
|
|
|
|
{
|
|
|
|
words[5] = "18";
|
|
|
|
words[6] = "0";
|
|
|
|
}
|
|
|
|
if (words[5] == "panic")
|
|
|
|
{
|
|
|
|
words[5] = "20";
|
|
|
|
words[6] = "0";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ss_toi(words[5]) >= 16)
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(ss_toi(words[1]), ss_toi(words[2]), 18, r, ss_toi(words[4]), ss_toi(words[5]), ss_toi(words[6]));
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(ss_toi(words[1]), ss_toi(words[2]), 18, r, ss_toi(words[4]), ss_toi(words[5]));
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "changemood")
|
|
|
|
{
|
|
|
|
if (words[1] == "player")
|
|
|
|
{
|
|
|
|
i=obj.getplayer();
|
|
|
|
}
|
|
|
|
else if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(0);
|
|
|
|
}
|
|
|
|
else if (words[1] == "customcyan")
|
|
|
|
{
|
|
|
|
i=obj.getcustomcrewman(0);
|
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(3);
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(4);
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(2);
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(5);
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(1);
|
|
|
|
}
|
|
|
|
else if (words[1] == "pink")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(1);
|
|
|
|
}
|
|
|
|
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities) && ss_toi(words[2]) == 0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 0;
|
|
|
|
}
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
else if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 144;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "changecustommood")
|
|
|
|
{
|
|
|
|
if (words[1] == "player")
|
|
|
|
{
|
|
|
|
i=obj.getcustomcrewman(0);
|
|
|
|
obj.customcrewmoods[0]=ss_toi(words[2]);
|
|
|
|
}
|
|
|
|
else if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
i=obj.getcustomcrewman(0);
|
|
|
|
obj.customcrewmoods[0]=ss_toi(words[2]);
|
|
|
|
}
|
|
|
|
else if (words[1] == "customcyan")
|
|
|
|
{
|
|
|
|
i=obj.getcustomcrewman(0);
|
|
|
|
obj.customcrewmoods[0]=ss_toi(words[2]);
|
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
|
|
|
i=obj.getcustomcrewman(3);
|
|
|
|
obj.customcrewmoods[3]=ss_toi(words[2]);
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
i=obj.getcustomcrewman(4);
|
|
|
|
obj.customcrewmoods[4]=ss_toi(words[2]);
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
i=obj.getcustomcrewman(2);
|
|
|
|
obj.customcrewmoods[2]=ss_toi(words[2]);
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
i=obj.getcustomcrewman(5);
|
|
|
|
obj.customcrewmoods[5]=ss_toi(words[2]);
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
i=obj.getcustomcrewman(1);
|
|
|
|
obj.customcrewmoods[1]=ss_toi(words[2]);
|
|
|
|
}
|
|
|
|
else if (words[1] == "pink")
|
|
|
|
{
|
|
|
|
i=obj.getcustomcrewman(1);
|
|
|
|
obj.customcrewmoods[1]=ss_toi(words[2]);
|
|
|
|
}
|
|
|
|
|
Bounds check all entity getters that can return 0
The entity getters I'm referring to are entityclass::getscm(),
entityclass::getlineat(), entityclass::getcrewman(), and
entityclass::getcustomcrewman().
Even though the player should always exist, and the player should always
be indice 0, I wouldn't want to make that assumption. I've been wrong
before.
Also, these functions returning 0 lull you into a false sense of
security. If you assume that commands using these functions are fine,
you'll forget about the fact that `i` in those commands could be
potentially anything, given an invalid argument. In fact, it's possible
to index createactivityzone(), flipgravity(), and customposition()
out-of-bounds by setting `i` to anything! Well, WAS possible. I fixed it
so now they can't.
Furthermore, in the game.scmmoveme block in gamelogic(), obj.getplayer()
wasn't even checked, even though it's been checked in all other places.
I only caught it just now because I wanted to bounds-check all usages of
obj.getscm(), too, and that game.scmmove block also used obj.getscm()
without bounds-checking it as well.
2020-09-10 07:31:09 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities) && ss_toi(words[2]) == 0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 0;
|
|
|
|
}
|
Bounds check all entity getters that can return 0
The entity getters I'm referring to are entityclass::getscm(),
entityclass::getlineat(), entityclass::getcrewman(), and
entityclass::getcustomcrewman().
Even though the player should always exist, and the player should always
be indice 0, I wouldn't want to make that assumption. I've been wrong
before.
Also, these functions returning 0 lull you into a false sense of
security. If you assume that commands using these functions are fine,
you'll forget about the fact that `i` in those commands could be
potentially anything, given an invalid argument. In fact, it's possible
to index createactivityzone(), flipgravity(), and customposition()
out-of-bounds by setting `i` to anything! Well, WAS possible. I fixed it
so now they can't.
Furthermore, in the game.scmmoveme block in gamelogic(), obj.getplayer()
wasn't even checked, even though it's been checked in all other places.
I only caught it just now because I wanted to bounds-check all usages of
obj.getscm(), too, and that game.scmmove block also used obj.getscm()
without bounds-checking it as well.
2020-09-10 07:31:09 +02:00
|
|
|
else if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 144;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "changetile")
|
|
|
|
{
|
|
|
|
if (words[1] == "player")
|
|
|
|
{
|
|
|
|
i=obj.getplayer();
|
|
|
|
}
|
|
|
|
else if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(0);
|
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(3);
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(4);
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(2);
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(5);
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(1);
|
|
|
|
}
|
|
|
|
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = ss_toi(words[2]);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "flipgravity")
|
|
|
|
{
|
|
|
|
//not something I'll use a lot, I think. Doesn't need to be very robust!
|
|
|
|
if (words[1] == "player")
|
|
|
|
{
|
2020-01-14 14:51:33 +01:00
|
|
|
game.gravitycontrol = !game.gravitycontrol;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-01-14 14:51:33 +01:00
|
|
|
else
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-01-14 14:51:33 +01:00
|
|
|
if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(0);
|
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(3);
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(4);
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(2);
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(5);
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(1);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Bounds check all entity getters that can return 0
The entity getters I'm referring to are entityclass::getscm(),
entityclass::getlineat(), entityclass::getcrewman(), and
entityclass::getcustomcrewman().
Even though the player should always exist, and the player should always
be indice 0, I wouldn't want to make that assumption. I've been wrong
before.
Also, these functions returning 0 lull you into a false sense of
security. If you assume that commands using these functions are fine,
you'll forget about the fact that `i` in those commands could be
potentially anything, given an invalid argument. In fact, it's possible
to index createactivityzone(), flipgravity(), and customposition()
out-of-bounds by setting `i` to anything! Well, WAS possible. I fixed it
so now they can't.
Furthermore, in the game.scmmoveme block in gamelogic(), obj.getplayer()
wasn't even checked, even though it's been checked in all other places.
I only caught it just now because I wanted to bounds-check all usages of
obj.getscm(), too, and that game.scmmove block also used obj.getscm()
without bounds-checking it as well.
2020-09-10 07:31:09 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities) && obj.entities[i].rule == 7)
|
2020-01-14 14:51:33 +01:00
|
|
|
{
|
Fix flipgravity() rule conversion being inverted
In 2.0, 2.1, and 2.2, calling flipgravity() on an entity that wasn't
rule 6 would change it to rule 7. In 2.3 currently, doing this will only
change it to rule 7 if it's already rule 6, starting with the
introduction of the change where if an entity was rule 7 it would be
changed to rule 6.
The crewmate conversion trick has been restored, but converting an
entity to a crewmate will change its rule to 6, not 7 like in pre-2.3.
If you want it to be changed to rule 7 instead of 6, you'd have to call
flipgravity() twice in 2.3 and only once in pre-2.3, which would make
maintaining compatibility between versions a bit harder.
So to fix this, I'm inverting it so that if you call flipgravity() on an
entity that isn't rule 7, it will be converted to rule 7, and only if
it's rule 7 will it be converted to rule 6.
2020-09-24 08:40:16 +02:00
|
|
|
obj.entities[i].rule = 6;
|
|
|
|
obj.entities[i].tile = 0;
|
2020-01-14 14:51:33 +01:00
|
|
|
}
|
Bounds check all entity getters that can return 0
The entity getters I'm referring to are entityclass::getscm(),
entityclass::getlineat(), entityclass::getcrewman(), and
entityclass::getcustomcrewman().
Even though the player should always exist, and the player should always
be indice 0, I wouldn't want to make that assumption. I've been wrong
before.
Also, these functions returning 0 lull you into a false sense of
security. If you assume that commands using these functions are fine,
you'll forget about the fact that `i` in those commands could be
potentially anything, given an invalid argument. In fact, it's possible
to index createactivityzone(), flipgravity(), and customposition()
out-of-bounds by setting `i` to anything! Well, WAS possible. I fixed it
so now they can't.
Furthermore, in the game.scmmoveme block in gamelogic(), obj.getplayer()
wasn't even checked, even though it's been checked in all other places.
I only caught it just now because I wanted to bounds-check all usages of
obj.getscm(), too, and that game.scmmove block also used obj.getscm()
without bounds-checking it as well.
2020-09-10 07:31:09 +02:00
|
|
|
else if (INBOUNDS_VEC(i, obj.entities) && obj.getplayer() != i) // Don't destroy player entity
|
2020-01-14 14:51:33 +01:00
|
|
|
{
|
Fix flipgravity() rule conversion being inverted
In 2.0, 2.1, and 2.2, calling flipgravity() on an entity that wasn't
rule 6 would change it to rule 7. In 2.3 currently, doing this will only
change it to rule 7 if it's already rule 6, starting with the
introduction of the change where if an entity was rule 7 it would be
changed to rule 6.
The crewmate conversion trick has been restored, but converting an
entity to a crewmate will change its rule to 6, not 7 like in pre-2.3.
If you want it to be changed to rule 7 instead of 6, you'd have to call
flipgravity() twice in 2.3 and only once in pre-2.3, which would make
maintaining compatibility between versions a bit harder.
So to fix this, I'm inverting it so that if you call flipgravity() on an
entity that isn't rule 7, it will be converted to rule 7, and only if
it's rule 7 will it be converted to rule 6.
2020-09-24 08:40:16 +02:00
|
|
|
obj.entities[i].rule = 7;
|
|
|
|
obj.entities[i].tile = 6;
|
2020-01-14 14:51:33 +01:00
|
|
|
}
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "changegravity")
|
|
|
|
{
|
|
|
|
//not something I'll use a lot, I think. Doesn't need to be very robust!
|
|
|
|
if (words[1] == "player")
|
|
|
|
{
|
|
|
|
i=obj.getplayer();
|
|
|
|
}
|
|
|
|
else if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(0);
|
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(3);
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(4);
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(2);
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(5);
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(1);
|
|
|
|
}
|
|
|
|
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].tile +=12;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "changedir")
|
|
|
|
{
|
|
|
|
if (words[1] == "player")
|
|
|
|
{
|
|
|
|
i=obj.getplayer();
|
|
|
|
}
|
|
|
|
else if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(0);
|
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(3);
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(4);
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(2);
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(5);
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(1);
|
|
|
|
}
|
|
|
|
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities) && ss_toi(words[2]) == 0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
obj.entities[i].dir = 0;
|
|
|
|
}
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
else if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
obj.entities[i].dir = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "alarmon")
|
|
|
|
{
|
|
|
|
game.alarmon = true;
|
|
|
|
game.alarmdelay = 0;
|
|
|
|
}
|
|
|
|
else if (words[0] == "alarmoff")
|
|
|
|
{
|
|
|
|
game.alarmon = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "changeai")
|
|
|
|
{
|
|
|
|
if (words[1] == "player")
|
|
|
|
{
|
|
|
|
i=obj.getplayer();
|
|
|
|
}
|
|
|
|
else if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(0);
|
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(3);
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(4);
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(2);
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(5);
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (words[2] == "followplayer") words[2] = "10";
|
|
|
|
if (words[2] == "followpurple") words[2] = "11";
|
|
|
|
if (words[2] == "followyellow") words[2] = "12";
|
|
|
|
if (words[2] == "followred") words[2] = "13";
|
|
|
|
if (words[2] == "followgreen") words[2] = "14";
|
|
|
|
if (words[2] == "followblue") words[2] = "15";
|
|
|
|
|
|
|
|
if (words[2] == "followposition") words[2] = "16";
|
|
|
|
if (words[2] == "faceleft")
|
|
|
|
{
|
|
|
|
words[2] = "17";
|
|
|
|
words[3] = "0";
|
|
|
|
}
|
|
|
|
if (words[2] == "faceright")
|
|
|
|
{
|
|
|
|
words[2] = "17";
|
|
|
|
words[3] = "1";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[i].state = ss_toi(words[2]);
|
|
|
|
if (obj.entities[i].state == 16)
|
|
|
|
{
|
|
|
|
obj.entities[i].para=ss_toi(words[3]);
|
|
|
|
}
|
|
|
|
else if (obj.entities[i].state == 17)
|
|
|
|
{
|
|
|
|
obj.entities[i].dir=ss_toi(words[3]);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "activateteleporter")
|
|
|
|
{
|
|
|
|
i = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 04:31:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 6;
|
|
|
|
obj.entities[i].colour = 102;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "changecolour")
|
|
|
|
{
|
|
|
|
if (words[1] == "player")
|
|
|
|
{
|
|
|
|
i=obj.getplayer();
|
|
|
|
}
|
|
|
|
else if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(0);
|
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(3);
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(4);
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(2);
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(5);
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(1);
|
|
|
|
}
|
|
|
|
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-13 05:36:08 +02:00
|
|
|
if (words[2] == "cyan")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
}
|
|
|
|
else if (words[2] == "red")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 15;
|
|
|
|
}
|
|
|
|
else if (words[2] == "green")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 13;
|
|
|
|
}
|
|
|
|
else if (words[2] == "yellow")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 14;
|
|
|
|
}
|
|
|
|
else if (words[2] == "blue")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 16;
|
|
|
|
}
|
|
|
|
else if (words[2] == "purple")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 20;
|
|
|
|
}
|
|
|
|
else if (words[2] == "teleporter")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 102;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "squeak")
|
|
|
|
{
|
|
|
|
if (words[1] == "player")
|
|
|
|
{
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(11);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[1] == "cyan")
|
|
|
|
{
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(11);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(16);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(12);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(14);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(13);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(15);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[1] == "cry")
|
|
|
|
{
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(2);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[1] == "terminal")
|
|
|
|
{
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(20);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "blackout")
|
|
|
|
{
|
|
|
|
game.blackout = true;
|
|
|
|
}
|
|
|
|
else if (words[0] == "blackon")
|
|
|
|
{
|
|
|
|
game.blackout = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "setcheckpoint")
|
|
|
|
{
|
|
|
|
i = obj.getplayer();
|
|
|
|
game.savepoint = 0;
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
game.savex = obj.entities[i].xp ;
|
|
|
|
game.savey = obj.entities[i].yp;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
game.savegc = game.gravitycontrol;
|
|
|
|
game.saverx = game.roomx;
|
|
|
|
game.savery = game.roomy;
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
game.savedir = obj.entities[i].dir;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "gamestate")
|
|
|
|
{
|
|
|
|
game.state = ss_toi(words[1]);
|
|
|
|
game.statedelay = 0;
|
|
|
|
}
|
|
|
|
else if (words[0] == "textboxactive")
|
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.textboxactive();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "gamemode")
|
|
|
|
{
|
|
|
|
if (words[1] == "teleporter")
|
|
|
|
{
|
De-duplicate menu animation code when bringing up map screen
When bringing up the map screen, the game does a small menu animation
where the menu comes in from the bottom. The code to calculate the menu
offset is copy-pasted everywhere, so I thought I'd de-duplicate it to
make my life easier when working with it. I also included the
game.gamestate assignment in the de-duplicated function, so it would be
easier for a future bugfix.
At the same time, I'm also removing all the BlitSurfaceStandard()s that
copied menubuffer to backBuffer. The red flag is that this blit happened
for every single entry point to MAPMODE and TELEPORTERMODE, except for
the script command gamemode(teleporter). Pressing Enter to bring up the
map screen, pressing Enter to quit the Super Gravitron, pressing Esc to
bring up the pause screen, and pressing Enter to bring up the teleporter
screen all do this blit, so if this blit was there to fix a bug, then
there's a bug with using the script command gamemode(teleporter)... but,
as far as I can tell, there isn't.
That's because the blit basically does nothing. All the blit does is
copy menubuffer onto backBuffer. Then the next thing that happens is
that either maprender() or teleporterrender() will be called, and the
first thing that those functions will always do is fill backBuffer with
solid black, completely overriding the previous blit. So that's why
removing this blit won't have any effect, and it can be safely removed
for code clarity.
2020-12-28 23:23:35 +01:00
|
|
|
game.mapmenuchange(TELEPORTERMODE);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
game.useteleporter = false; //good heavens don't actually use it
|
|
|
|
}
|
|
|
|
else if (words[1] == "game")
|
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.resumegamemode = true;
|
Fix bringing up map menu during gamemode(teleporter)
When gamemode(teleporter) gets run in a script, it brings up a read-only
version of the teleporter screen, intended only for displaying rooms on
the minimap.
However, ever since 2.3 allowed bringing up the map screen during
cutscenes (in order to prevent softlocks), bringing up the map screen
during this mode would (1) do an unnecessary animation of suddenly
switching back to the game and bringing up the menu screen again (even
though the menu screen has already been brought up), and (2) would let
you close the menu entirely and go back to GAMEMODE, thus
unintentionally closing the teleporter screen and kind of ruining the
cutscene.
To fix this, when you bring up the map screen, it will instead instantly
transition to the map screen. And when you bring it down, it will also
instantly transition back to the teleporter screen.
But that's not all. The previous behavior was actually kind of a nice
failsafe, in that if you somehow got stuck in a state where a script ran
gamemode(teleporter), but stopped running before it could take you out
of that mode by running gamemode(game), then you could return to
GAMEMODE yourself by bringing up the map screen and then bringing it
back down. So I've made sure to keep that failsafe behavior, only as
long as there isn't a script running.
2020-12-29 00:36:32 +01:00
|
|
|
game.prevgamestate = GAMEMODE;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "ifexplored")
|
|
|
|
{
|
Add bounds checks to script commands that didn't have them
Continuing from #280, another potential source of out-of-bounds indexing
(and thus, Undefined Behavior badness) comes from script commands. A
majority of them don't do any input validation at all, which means the
potential for out-of-bounds indexing and segfaulting in custom levels.
So it's always good to add bounds checks to them.
Interesting note, the only existing command that has bounds checks is
the flag() command. That means you can't turn out-of-bounds flags on or
off. But there's no bounds checks for ifflag(), or customifflag(), which
means you CAN index out-of-bounds with those commands! That's a bit bad
to do, so.
Also, I decided to add the bounds checks for playef() at the
musicclass::playef() level, instead of just the level of the playef()
command. I don't know of any other cases outside of the command where
musicclass::playef() will index out of bounds, but musicclass is the one
containing the indexed vector anyway, I wanted to cover more cases, and
it's better to be safe than sorry.
2020-06-13 06:55:41 +02:00
|
|
|
int room = ss_toi(words[1]) + (20 * ss_toi(words[2]));
|
2020-07-03 06:01:09 +02:00
|
|
|
if (INBOUNDS_ARR(room, map.explored) && map.explored[room] == 1)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
load(words[3]);
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "iflast")
|
|
|
|
{
|
|
|
|
if (game.lastsaved==ss_toi(words[1]))
|
|
|
|
{
|
|
|
|
load(words[2]);
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "ifskip")
|
|
|
|
{
|
|
|
|
if (game.nocutscenes)
|
|
|
|
{
|
|
|
|
load(words[1]);
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "ifflag")
|
|
|
|
{
|
2020-11-02 19:09:13 +01:00
|
|
|
int flag = ss_toi(words[1]);
|
|
|
|
if (INBOUNDS_ARR(flag, obj.flags) && obj.flags[flag])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
load(words[2]);
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "ifcrewlost")
|
|
|
|
{
|
2020-11-02 19:09:13 +01:00
|
|
|
int crewmate = ss_toi(words[1]);
|
|
|
|
if (INBOUNDS_ARR(crewmate, game.crewstats) && !game.crewstats[crewmate])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
load(words[2]);
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "iftrinkets")
|
|
|
|
{
|
2020-04-07 08:46:27 +02:00
|
|
|
if (game.trinkets() >= ss_toi(words[1]))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
load(words[2]);
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "iftrinketsless")
|
|
|
|
{
|
|
|
|
if (game.stat_trinkets < ss_toi(words[1]))
|
|
|
|
{
|
|
|
|
load(words[2]);
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "hidecoordinates")
|
|
|
|
{
|
Add bounds checks to script commands that didn't have them
Continuing from #280, another potential source of out-of-bounds indexing
(and thus, Undefined Behavior badness) comes from script commands. A
majority of them don't do any input validation at all, which means the
potential for out-of-bounds indexing and segfaulting in custom levels.
So it's always good to add bounds checks to them.
Interesting note, the only existing command that has bounds checks is
the flag() command. That means you can't turn out-of-bounds flags on or
off. But there's no bounds checks for ifflag(), or customifflag(), which
means you CAN index out-of-bounds with those commands! That's a bit bad
to do, so.
Also, I decided to add the bounds checks for playef() at the
musicclass::playef() level, instead of just the level of the playef()
command. I don't know of any other cases outside of the command where
musicclass::playef() will index out of bounds, but musicclass is the one
containing the indexed vector anyway, I wanted to cover more cases, and
it's better to be safe than sorry.
2020-06-13 06:55:41 +02:00
|
|
|
int room = ss_toi(words[1]) + (20 * ss_toi(words[2]));
|
2020-07-03 06:01:09 +02:00
|
|
|
if (INBOUNDS_ARR(room, map.explored))
|
Add bounds checks to script commands that didn't have them
Continuing from #280, another potential source of out-of-bounds indexing
(and thus, Undefined Behavior badness) comes from script commands. A
majority of them don't do any input validation at all, which means the
potential for out-of-bounds indexing and segfaulting in custom levels.
So it's always good to add bounds checks to them.
Interesting note, the only existing command that has bounds checks is
the flag() command. That means you can't turn out-of-bounds flags on or
off. But there's no bounds checks for ifflag(), or customifflag(), which
means you CAN index out-of-bounds with those commands! That's a bit bad
to do, so.
Also, I decided to add the bounds checks for playef() at the
musicclass::playef() level, instead of just the level of the playef()
command. I don't know of any other cases outside of the command where
musicclass::playef() will index out of bounds, but musicclass is the one
containing the indexed vector anyway, I wanted to cover more cases, and
it's better to be safe than sorry.
2020-06-13 06:55:41 +02:00
|
|
|
{
|
2020-07-03 06:01:09 +02:00
|
|
|
map.explored[room] = false;
|
Add bounds checks to script commands that didn't have them
Continuing from #280, another potential source of out-of-bounds indexing
(and thus, Undefined Behavior badness) comes from script commands. A
majority of them don't do any input validation at all, which means the
potential for out-of-bounds indexing and segfaulting in custom levels.
So it's always good to add bounds checks to them.
Interesting note, the only existing command that has bounds checks is
the flag() command. That means you can't turn out-of-bounds flags on or
off. But there's no bounds checks for ifflag(), or customifflag(), which
means you CAN index out-of-bounds with those commands! That's a bit bad
to do, so.
Also, I decided to add the bounds checks for playef() at the
musicclass::playef() level, instead of just the level of the playef()
command. I don't know of any other cases outside of the command where
musicclass::playef() will index out of bounds, but musicclass is the one
containing the indexed vector anyway, I wanted to cover more cases, and
it's better to be safe than sorry.
2020-06-13 06:55:41 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "showcoordinates")
|
|
|
|
{
|
Add bounds checks to script commands that didn't have them
Continuing from #280, another potential source of out-of-bounds indexing
(and thus, Undefined Behavior badness) comes from script commands. A
majority of them don't do any input validation at all, which means the
potential for out-of-bounds indexing and segfaulting in custom levels.
So it's always good to add bounds checks to them.
Interesting note, the only existing command that has bounds checks is
the flag() command. That means you can't turn out-of-bounds flags on or
off. But there's no bounds checks for ifflag(), or customifflag(), which
means you CAN index out-of-bounds with those commands! That's a bit bad
to do, so.
Also, I decided to add the bounds checks for playef() at the
musicclass::playef() level, instead of just the level of the playef()
command. I don't know of any other cases outside of the command where
musicclass::playef() will index out of bounds, but musicclass is the one
containing the indexed vector anyway, I wanted to cover more cases, and
it's better to be safe than sorry.
2020-06-13 06:55:41 +02:00
|
|
|
int room = ss_toi(words[1]) + (20 * ss_toi(words[2]));
|
2020-07-03 06:01:09 +02:00
|
|
|
if (INBOUNDS_ARR(room, map.explored))
|
Add bounds checks to script commands that didn't have them
Continuing from #280, another potential source of out-of-bounds indexing
(and thus, Undefined Behavior badness) comes from script commands. A
majority of them don't do any input validation at all, which means the
potential for out-of-bounds indexing and segfaulting in custom levels.
So it's always good to add bounds checks to them.
Interesting note, the only existing command that has bounds checks is
the flag() command. That means you can't turn out-of-bounds flags on or
off. But there's no bounds checks for ifflag(), or customifflag(), which
means you CAN index out-of-bounds with those commands! That's a bit bad
to do, so.
Also, I decided to add the bounds checks for playef() at the
musicclass::playef() level, instead of just the level of the playef()
command. I don't know of any other cases outside of the command where
musicclass::playef() will index out of bounds, but musicclass is the one
containing the indexed vector anyway, I wanted to cover more cases, and
it's better to be safe than sorry.
2020-06-13 06:55:41 +02:00
|
|
|
{
|
2020-07-03 06:01:09 +02:00
|
|
|
map.explored[room] = true;
|
Add bounds checks to script commands that didn't have them
Continuing from #280, another potential source of out-of-bounds indexing
(and thus, Undefined Behavior badness) comes from script commands. A
majority of them don't do any input validation at all, which means the
potential for out-of-bounds indexing and segfaulting in custom levels.
So it's always good to add bounds checks to them.
Interesting note, the only existing command that has bounds checks is
the flag() command. That means you can't turn out-of-bounds flags on or
off. But there's no bounds checks for ifflag(), or customifflag(), which
means you CAN index out-of-bounds with those commands! That's a bit bad
to do, so.
Also, I decided to add the bounds checks for playef() at the
musicclass::playef() level, instead of just the level of the playef()
command. I don't know of any other cases outside of the command where
musicclass::playef() will index out of bounds, but musicclass is the one
containing the indexed vector anyway, I wanted to cover more cases, and
it's better to be safe than sorry.
2020-06-13 06:55:41 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "hideship")
|
|
|
|
{
|
|
|
|
map.hideship();
|
|
|
|
}
|
|
|
|
else if (words[0] == "showship")
|
|
|
|
{
|
|
|
|
map.showship();
|
|
|
|
}
|
|
|
|
else if (words[0] == "showsecretlab")
|
|
|
|
{
|
|
|
|
map.explored[16 + (20 * 5)] = 1;
|
|
|
|
map.explored[17 + (20 * 5)] = 1;
|
|
|
|
map.explored[18 + (20 * 5)] = 1;
|
|
|
|
map.explored[17 + (20 * 6)] = 1;
|
|
|
|
map.explored[18 + (20 * 6)] = 1;
|
|
|
|
map.explored[19 + (20 * 6)] = 1;
|
|
|
|
map.explored[19 + (20 * 7)] = 1;
|
|
|
|
map.explored[19 + (20 * 8)] = 1;
|
|
|
|
}
|
|
|
|
else if (words[0] == "hidesecretlab")
|
|
|
|
{
|
|
|
|
map.explored[16 + (20 * 5)] = 0;
|
|
|
|
map.explored[17 + (20 * 5)] = 0;
|
|
|
|
map.explored[18 + (20 * 5)] = 0;
|
|
|
|
map.explored[17 + (20 * 6)] = 0;
|
|
|
|
map.explored[18 + (20 * 6)] = 0;
|
|
|
|
map.explored[19 + (20 * 6)] = 0;
|
|
|
|
map.explored[19 + (20 * 7)] = 0;
|
|
|
|
map.explored[19 + (20 * 8)] = 0;
|
|
|
|
}
|
|
|
|
else if (words[0] == "showteleporters")
|
|
|
|
{
|
|
|
|
map.showteleporters = true;
|
|
|
|
}
|
|
|
|
else if (words[0] == "showtargets")
|
|
|
|
{
|
|
|
|
map.showtargets = true;
|
|
|
|
}
|
|
|
|
else if (words[0] == "showtrinkets")
|
|
|
|
{
|
|
|
|
map.showtrinkets = true;
|
|
|
|
}
|
|
|
|
else if (words[0] == "hideteleporters")
|
|
|
|
{
|
|
|
|
map.showteleporters = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "hidetargets")
|
|
|
|
{
|
|
|
|
map.showtargets = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "hidetrinkets")
|
|
|
|
{
|
|
|
|
map.showtrinkets = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "hideplayer")
|
|
|
|
{
|
2020-06-13 05:36:08 +02:00
|
|
|
int player = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(player, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[player].invis = true;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "showplayer")
|
|
|
|
{
|
2020-06-13 05:36:08 +02:00
|
|
|
int player = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(player, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[player].invis = false;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "teleportscript")
|
|
|
|
{
|
|
|
|
game.teleportscript = words[1];
|
|
|
|
}
|
|
|
|
else if (words[0] == "clearteleportscript")
|
|
|
|
{
|
|
|
|
game.teleportscript = "";
|
|
|
|
}
|
|
|
|
else if (words[0] == "nocontrol")
|
|
|
|
{
|
|
|
|
game.hascontrol = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "hascontrol")
|
|
|
|
{
|
|
|
|
game.hascontrol = true;
|
|
|
|
}
|
|
|
|
else if (words[0] == "companion")
|
|
|
|
{
|
|
|
|
game.companion = ss_toi(words[1]);
|
|
|
|
}
|
|
|
|
else if (words[0] == "befadein")
|
|
|
|
{
|
2021-03-20 07:09:11 +01:00
|
|
|
graphics.setfade(0);
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode= 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "fadein")
|
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "fadeout")
|
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 2;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "untilfade")
|
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.fademode>1)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
scriptdelay = 1;
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "entersecretlab")
|
|
|
|
{
|
2020-07-03 04:56:26 +02:00
|
|
|
game.unlocknum(8);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.insecretlab = true;
|
|
|
|
}
|
|
|
|
else if (words[0] == "leavesecretlab")
|
|
|
|
{
|
|
|
|
game.insecretlab = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "resetgame")
|
|
|
|
{
|
|
|
|
map.resetnames();
|
|
|
|
map.resetmap();
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.tdrawback = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
obj.resetallflags();
|
|
|
|
i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 0;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
for (i = 0; i < 100; i++)
|
|
|
|
{
|
2020-04-09 08:56:36 +02:00
|
|
|
obj.collect[i] = false;
|
2020-04-09 08:58:03 +02:00
|
|
|
obj.customcollect[i] = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
game.deathcounts = 0;
|
|
|
|
game.advancetext = false;
|
|
|
|
game.hascontrol = true;
|
|
|
|
game.frames = 0;
|
|
|
|
game.seconds = 0;
|
|
|
|
game.minutes = 0;
|
|
|
|
game.hours = 0;
|
|
|
|
game.gravitycontrol = 0;
|
|
|
|
game.teleport = false;
|
|
|
|
game.companion = 0;
|
|
|
|
game.roomchange = false;
|
|
|
|
game.teleport_to_new_area = false;
|
|
|
|
game.teleport_to_x = 0;
|
|
|
|
game.teleport_to_y = 0;
|
|
|
|
|
|
|
|
game.teleportscript = "";
|
|
|
|
|
|
|
|
//get out of final level mode!
|
|
|
|
map.finalmode = false;
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 0;
|
|
|
|
map.finalstretch = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "loadscript")
|
|
|
|
{
|
|
|
|
load(words[1]);
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else if (words[0] == "rollcredits")
|
|
|
|
{
|
2020-04-17 05:15:53 +02:00
|
|
|
game.gamestate = GAMECOMPLETE;
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-01-01 21:29:24 +01:00
|
|
|
game.creditposition = 0;
|
|
|
|
}
|
|
|
|
else if (words[0] == "finalmode")
|
|
|
|
{
|
|
|
|
map.finalmode = true;
|
2021-01-11 04:58:36 +01:00
|
|
|
map.gotoroom(ss_toi(words[1]), ss_toi(words[2]));
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "rescued")
|
|
|
|
{
|
|
|
|
if (words[1] == "red")
|
|
|
|
{
|
|
|
|
game.crewstats[3] = true;
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
game.crewstats[4] = true;
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
game.crewstats[2] = true;
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
game.crewstats[5] = true;
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
game.crewstats[1] = true;
|
|
|
|
}
|
|
|
|
else if (words[1] == "player")
|
|
|
|
{
|
|
|
|
game.crewstats[0] = true;
|
|
|
|
}
|
|
|
|
else if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
game.crewstats[0] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "missing")
|
|
|
|
{
|
|
|
|
if (words[1] == "red")
|
|
|
|
{
|
|
|
|
game.crewstats[3] = false;
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
game.crewstats[4] = false;
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
game.crewstats[2] = false;
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
game.crewstats[5] = false;
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
game.crewstats[1] = false;
|
|
|
|
}
|
|
|
|
else if (words[1] == "player")
|
|
|
|
{
|
|
|
|
game.crewstats[0] = false;
|
|
|
|
}
|
|
|
|
else if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
game.crewstats[0] = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "face")
|
|
|
|
{
|
|
|
|
if (words[1] == "player")
|
|
|
|
{
|
|
|
|
i=obj.getplayer();
|
|
|
|
}
|
|
|
|
else if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(0);
|
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(3);
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(4);
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(2);
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(5);
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
i=obj.getcrewman(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (words[2] == "player")
|
|
|
|
{
|
|
|
|
j=obj.getplayer();
|
|
|
|
}
|
|
|
|
else if (words[2] == "cyan")
|
|
|
|
{
|
|
|
|
j=obj.getcrewman(0);
|
|
|
|
}
|
|
|
|
else if (words[2] == "red")
|
|
|
|
{
|
|
|
|
j=obj.getcrewman(3);
|
|
|
|
}
|
|
|
|
else if (words[2] == "green")
|
|
|
|
{
|
|
|
|
j=obj.getcrewman(4);
|
|
|
|
}
|
|
|
|
else if (words[2] == "yellow")
|
|
|
|
{
|
|
|
|
j=obj.getcrewman(2);
|
|
|
|
}
|
|
|
|
else if (words[2] == "blue")
|
|
|
|
{
|
|
|
|
j=obj.getcrewman(5);
|
|
|
|
}
|
|
|
|
else if (words[2] == "purple")
|
|
|
|
{
|
|
|
|
j=obj.getcrewman(1);
|
|
|
|
}
|
|
|
|
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities) && INBOUNDS_VEC(j, obj.entities) && obj.entities[j].xp > obj.entities[i].xp + 5)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
obj.entities[i].dir = 1;
|
|
|
|
}
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
else if (INBOUNDS_VEC(i, obj.entities) && INBOUNDS_VEC(j, obj.entities) && obj.entities[j].xp < obj.entities[i].xp - 5)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
obj.entities[i].dir = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "jukebox")
|
|
|
|
{
|
2020-04-03 22:50:16 +02:00
|
|
|
for (j = 0; j < (int) obj.entities.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-03 22:50:16 +02:00
|
|
|
if (obj.entities[j].type == 13)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
obj.entities[j].colour = 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ss_toi(words[1]) == 1)
|
|
|
|
{
|
|
|
|
obj.createblock(5, 88 - 4, 80, 20, 16, 25);
|
2020-04-03 22:50:16 +02:00
|
|
|
for (j = 0; j < (int) obj.entities.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (obj.entities[j].xp == 88 && obj.entities[j].yp==80)
|
|
|
|
{
|
|
|
|
obj.entities[j].colour = 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ss_toi(words[1]) == 2)
|
|
|
|
{
|
|
|
|
obj.createblock(5, 128 - 4, 80, 20, 16, 26);
|
2020-04-03 22:50:16 +02:00
|
|
|
for (j = 0; j < (int) obj.entities.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (obj.entities[j].xp == 128 && obj.entities[j].yp==80)
|
|
|
|
{
|
|
|
|
obj.entities[j].colour = 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ss_toi(words[1]) == 3)
|
|
|
|
{
|
|
|
|
obj.createblock(5, 176 - 4, 80, 20, 16, 27);
|
2020-04-03 22:50:16 +02:00
|
|
|
for (j = 0; j < (int) obj.entities.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (obj.entities[j].xp == 176 && obj.entities[j].yp==80)
|
|
|
|
{
|
|
|
|
obj.entities[j].colour = 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ss_toi(words[1]) == 4)
|
|
|
|
{
|
|
|
|
obj.createblock(5, 216 - 4, 80, 20, 16, 28);
|
2020-04-03 22:50:16 +02:00
|
|
|
for (j = 0; j < (int) obj.entities.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (obj.entities[j].xp == 216 && obj.entities[j].yp==80)
|
|
|
|
{
|
|
|
|
obj.entities[j].colour = 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ss_toi(words[1]) == 5)
|
|
|
|
{
|
|
|
|
obj.createblock(5, 88 - 4, 128, 20, 16, 29);
|
2020-04-03 22:50:16 +02:00
|
|
|
for (j = 0; j < (int) obj.entities.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (obj.entities[j].xp == 88 && obj.entities[j].yp==128)
|
|
|
|
{
|
|
|
|
obj.entities[j].colour = 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ss_toi(words[1]) == 6)
|
|
|
|
{
|
|
|
|
obj.createblock(5, 176 - 4, 128, 20, 16, 30);
|
2020-04-03 22:50:16 +02:00
|
|
|
for (j = 0; j < (int) obj.entities.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (obj.entities[j].xp == 176 && obj.entities[j].yp==128)
|
|
|
|
{
|
|
|
|
obj.entities[j].colour = 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ss_toi(words[1]) == 7)
|
|
|
|
{
|
|
|
|
obj.createblock(5, 40 - 4, 40, 20, 16, 31);
|
2020-04-03 22:50:16 +02:00
|
|
|
for (j = 0; j < (int) obj.entities.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (obj.entities[j].xp == 40 && obj.entities[j].yp==40)
|
|
|
|
{
|
|
|
|
obj.entities[j].colour = 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ss_toi(words[1]) == 8)
|
|
|
|
{
|
|
|
|
obj.createblock(5, 216 - 4, 128, 20, 16, 32);
|
2020-04-03 22:50:16 +02:00
|
|
|
for (j = 0; j < (int) obj.entities.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (obj.entities[j].xp == 216 && obj.entities[j].yp==128)
|
|
|
|
{
|
|
|
|
obj.entities[j].colour = 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ss_toi(words[1]) == 9)
|
|
|
|
{
|
|
|
|
obj.createblock(5, 128 - 4, 128, 20, 16, 33);
|
2020-04-03 22:50:16 +02:00
|
|
|
for (j = 0; j < (int) obj.entities.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (obj.entities[j].xp == 128 && obj.entities[j].yp==128)
|
|
|
|
{
|
|
|
|
obj.entities[j].colour = 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ss_toi(words[1]) == 10)
|
|
|
|
{
|
|
|
|
obj.createblock(5, 264 - 4, 40, 20, 16, 34);
|
2020-04-03 22:50:16 +02:00
|
|
|
for (j = 0; j < (int) obj.entities.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (obj.entities[j].xp == 264 && obj.entities[j].yp==40)
|
|
|
|
{
|
|
|
|
obj.entities[j].colour = 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "createactivityzone")
|
|
|
|
{
|
|
|
|
if (words[1] == "red")
|
|
|
|
{
|
|
|
|
i=3;
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
i=4;
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
i=2;
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
i=5;
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
i=1;
|
|
|
|
}
|
|
|
|
|
Bounds check all entity getters that can return 0
The entity getters I'm referring to are entityclass::getscm(),
entityclass::getlineat(), entityclass::getcrewman(), and
entityclass::getcustomcrewman().
Even though the player should always exist, and the player should always
be indice 0, I wouldn't want to make that assumption. I've been wrong
before.
Also, these functions returning 0 lull you into a false sense of
security. If you assume that commands using these functions are fine,
you'll forget about the fact that `i` in those commands could be
potentially anything, given an invalid argument. In fact, it's possible
to index createactivityzone(), flipgravity(), and customposition()
out-of-bounds by setting `i` to anything! Well, WAS possible. I fixed it
so now they can't.
Furthermore, in the game.scmmoveme block in gamelogic(), obj.getplayer()
wasn't even checked, even though it's been checked in all other places.
I only caught it just now because I wanted to bounds-check all usages of
obj.getscm(), too, and that game.scmmove block also used obj.getscm()
without bounds-checking it as well.
2020-09-10 07:31:09 +02:00
|
|
|
int crewman = obj.getcrewman(i);
|
|
|
|
if (INBOUNDS_VEC(crewman, obj.entities) && i == 4)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Bounds check all entity getters that can return 0
The entity getters I'm referring to are entityclass::getscm(),
entityclass::getlineat(), entityclass::getcrewman(), and
entityclass::getcustomcrewman().
Even though the player should always exist, and the player should always
be indice 0, I wouldn't want to make that assumption. I've been wrong
before.
Also, these functions returning 0 lull you into a false sense of
security. If you assume that commands using these functions are fine,
you'll forget about the fact that `i` in those commands could be
potentially anything, given an invalid argument. In fact, it's possible
to index createactivityzone(), flipgravity(), and customposition()
out-of-bounds by setting `i` to anything! Well, WAS possible. I fixed it
so now they can't.
Furthermore, in the game.scmmoveme block in gamelogic(), obj.getplayer()
wasn't even checked, even though it's been checked in all other places.
I only caught it just now because I wanted to bounds-check all usages of
obj.getscm(), too, and that game.scmmove block also used obj.getscm()
without bounds-checking it as well.
2020-09-10 07:31:09 +02:00
|
|
|
obj.createblock(5, obj.entities[crewman].xp - 32, obj.entities[crewman].yp-20, 96, 60, i);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
Bounds check all entity getters that can return 0
The entity getters I'm referring to are entityclass::getscm(),
entityclass::getlineat(), entityclass::getcrewman(), and
entityclass::getcustomcrewman().
Even though the player should always exist, and the player should always
be indice 0, I wouldn't want to make that assumption. I've been wrong
before.
Also, these functions returning 0 lull you into a false sense of
security. If you assume that commands using these functions are fine,
you'll forget about the fact that `i` in those commands could be
potentially anything, given an invalid argument. In fact, it's possible
to index createactivityzone(), flipgravity(), and customposition()
out-of-bounds by setting `i` to anything! Well, WAS possible. I fixed it
so now they can't.
Furthermore, in the game.scmmoveme block in gamelogic(), obj.getplayer()
wasn't even checked, even though it's been checked in all other places.
I only caught it just now because I wanted to bounds-check all usages of
obj.getscm(), too, and that game.scmmove block also used obj.getscm()
without bounds-checking it as well.
2020-09-10 07:31:09 +02:00
|
|
|
else if (INBOUNDS_VEC(crewman, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Bounds check all entity getters that can return 0
The entity getters I'm referring to are entityclass::getscm(),
entityclass::getlineat(), entityclass::getcrewman(), and
entityclass::getcustomcrewman().
Even though the player should always exist, and the player should always
be indice 0, I wouldn't want to make that assumption. I've been wrong
before.
Also, these functions returning 0 lull you into a false sense of
security. If you assume that commands using these functions are fine,
you'll forget about the fact that `i` in those commands could be
potentially anything, given an invalid argument. In fact, it's possible
to index createactivityzone(), flipgravity(), and customposition()
out-of-bounds by setting `i` to anything! Well, WAS possible. I fixed it
so now they can't.
Furthermore, in the game.scmmoveme block in gamelogic(), obj.getplayer()
wasn't even checked, even though it's been checked in all other places.
I only caught it just now because I wanted to bounds-check all usages of
obj.getscm(), too, and that game.scmmove block also used obj.getscm()
without bounds-checking it as well.
2020-09-10 07:31:09 +02:00
|
|
|
obj.createblock(5, obj.entities[crewman].xp - 32, 0, 96, 240, i);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "createrescuedcrew")
|
|
|
|
{
|
|
|
|
//special for final level cutscene
|
|
|
|
//starting at 180, create the rescued crewmembers (ingoring violet, who's at 155)
|
|
|
|
i = 215;
|
|
|
|
if (game.crewstats[2] && game.lastsaved!=2)
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(i, 153, 18, 14, 0, 17, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
i += 25;
|
|
|
|
}
|
|
|
|
if (game.crewstats[3] && game.lastsaved!=3)
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(i, 153, 18, 15, 0, 17, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
i += 25;
|
|
|
|
}
|
|
|
|
if (game.crewstats[4] && game.lastsaved!=4)
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(i, 153, 18, 13, 0, 17, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
i += 25;
|
|
|
|
}
|
|
|
|
if (game.crewstats[5] && game.lastsaved!=5)
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(i, 153, 18, 16, 0, 17, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
i += 25;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "restoreplayercolour")
|
|
|
|
{
|
|
|
|
i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "changeplayercolour")
|
|
|
|
{
|
|
|
|
i = obj.getplayer();
|
|
|
|
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-13 05:36:08 +02:00
|
|
|
if (words[1] == "cyan")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
}
|
|
|
|
else if (words[1] == "red")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 15;
|
|
|
|
}
|
|
|
|
else if (words[1] == "green")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 13;
|
|
|
|
}
|
|
|
|
else if (words[1] == "yellow")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 14;
|
|
|
|
}
|
|
|
|
else if (words[1] == "blue")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 16;
|
|
|
|
}
|
|
|
|
else if (words[1] == "purple")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 20;
|
|
|
|
}
|
|
|
|
else if (words[1] == "teleporter")
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = 102;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "altstates")
|
|
|
|
{
|
|
|
|
obj.altstates = ss_toi(words[1]);
|
|
|
|
}
|
|
|
|
else if (words[0] == "activeteleporter")
|
|
|
|
{
|
|
|
|
i = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 04:31:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].colour = 101;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "foundtrinket")
|
|
|
|
{
|
|
|
|
music.haltdasmusik();
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(3);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 04:17:32 +02:00
|
|
|
size_t trinket = ss_toi(words[1]);
|
|
|
|
if (trinket < SDL_arraysize(obj.collect))
|
Add bounds checks to script commands that didn't have them
Continuing from #280, another potential source of out-of-bounds indexing
(and thus, Undefined Behavior badness) comes from script commands. A
majority of them don't do any input validation at all, which means the
potential for out-of-bounds indexing and segfaulting in custom levels.
So it's always good to add bounds checks to them.
Interesting note, the only existing command that has bounds checks is
the flag() command. That means you can't turn out-of-bounds flags on or
off. But there's no bounds checks for ifflag(), or customifflag(), which
means you CAN index out-of-bounds with those commands! That's a bit bad
to do, so.
Also, I decided to add the bounds checks for playef() at the
musicclass::playef() level, instead of just the level of the playef()
command. I don't know of any other cases outside of the command where
musicclass::playef() will index out of bounds, but musicclass is the one
containing the indexed vector anyway, I wanted to cover more cases, and
it's better to be safe than sorry.
2020-06-13 06:55:41 +02:00
|
|
|
{
|
|
|
|
obj.collect[trinket] = true;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.textboxremovefast();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2021-03-20 04:12:42 +01:00
|
|
|
graphics.createtextboxflipme(" Congratulations! ", 50, 85, 174, 174, 174);
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("You have found a shiny trinket!");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-01-23 15:50:09 +01:00
|
|
|
std::string usethisnum;
|
2020-04-09 06:56:47 +02:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
2020-01-23 15:50:09 +01:00
|
|
|
if (map.custommode)
|
|
|
|
{
|
2020-04-09 07:09:11 +02:00
|
|
|
usethisnum = help.number(ed.numtrinkets());
|
2020-01-23 15:50:09 +01:00
|
|
|
}
|
|
|
|
else
|
2020-04-09 06:56:47 +02:00
|
|
|
#endif
|
2020-01-23 15:50:09 +01:00
|
|
|
{
|
|
|
|
usethisnum = "Twenty";
|
|
|
|
}
|
2021-03-20 04:12:42 +01:00
|
|
|
graphics.createtextboxflipme(" " + help.number(game.trinkets()) + " out of " + usethisnum + " ", 50, 135, 174, 174, 174);
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
if (!game.backgroundtext)
|
|
|
|
{
|
|
|
|
game.advancetext = true;
|
|
|
|
game.hascontrol = false;
|
|
|
|
game.pausescript = true;
|
|
|
|
if (key.isDown(90) || key.isDown(32) || key.isDown(86)
|
|
|
|
|| key.isDown(KEYBOARD_UP) || key.isDown(KEYBOARD_DOWN)) game.jumpheld = true;
|
|
|
|
}
|
|
|
|
game.backgroundtext = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "foundlab")
|
|
|
|
{
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(3);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.textboxremovefast();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.createtextbox(" Congratulations! ", 50, 85, 174, 174, 174);
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("You have found the secret lab!");
|
|
|
|
graphics.textboxcenterx();
|
|
|
|
graphics.textboxcentery();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
if (!game.backgroundtext)
|
|
|
|
{
|
|
|
|
game.advancetext = true;
|
|
|
|
game.hascontrol = false;
|
|
|
|
game.pausescript = true;
|
|
|
|
if (key.isDown(90) || key.isDown(32) || key.isDown(86)
|
|
|
|
|| key.isDown(KEYBOARD_UP) || key.isDown(KEYBOARD_DOWN)) game.jumpheld = true;
|
|
|
|
}
|
|
|
|
game.backgroundtext = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "foundlab2")
|
|
|
|
{
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.textboxremovefast();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.createtextbox("The secret lab is separate from", 50, 85, 174, 174, 174);
|
|
|
|
graphics.addline("the rest of the game. You can");
|
|
|
|
graphics.addline("now come back here at any time");
|
|
|
|
graphics.addline("by selecting the new SECRET LAB");
|
|
|
|
graphics.addline("option in the play menu.");
|
|
|
|
graphics.textboxcenterx();
|
|
|
|
graphics.textboxcentery();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
if (!game.backgroundtext)
|
|
|
|
{
|
|
|
|
game.advancetext = true;
|
|
|
|
game.hascontrol = false;
|
|
|
|
game.pausescript = true;
|
|
|
|
if (key.isDown(90) || key.isDown(32) || key.isDown(86)
|
|
|
|
|| key.isDown(KEYBOARD_UP) || key.isDown(KEYBOARD_DOWN)) game.jumpheld = true;
|
|
|
|
}
|
|
|
|
game.backgroundtext = false;
|
|
|
|
}
|
|
|
|
else if (words[0] == "everybodysad")
|
|
|
|
{
|
2020-04-03 22:50:16 +02:00
|
|
|
for (i = 0; i < (int) obj.entities.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (obj.entities[i].rule == 6 || obj.entities[i].rule == 0)
|
|
|
|
{
|
|
|
|
obj.entities[i].tile = 144;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "startintermission2")
|
|
|
|
{
|
|
|
|
map.finalmode = true; //Enable final level mode
|
|
|
|
|
|
|
|
game.savex = 228;
|
|
|
|
game.savey = 129;
|
|
|
|
game.saverx = 53;
|
|
|
|
game.savery = 49;
|
|
|
|
game.savegc = 0;
|
|
|
|
game.savedir = 0; //Intermission level 2
|
|
|
|
game.savepoint = 0;
|
|
|
|
game.gravitycontrol = 0;
|
|
|
|
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(46, 54);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "telesave")
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
if (!game.intimetrial && !game.nodeathmode && !game.inintermission) game.savetele();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "createlastrescued")
|
|
|
|
{
|
|
|
|
if (game.lastsaved==2)
|
|
|
|
{
|
|
|
|
r=14;
|
|
|
|
}
|
|
|
|
else if (game.lastsaved==3)
|
|
|
|
{
|
|
|
|
r=15;
|
|
|
|
}
|
|
|
|
else if (game.lastsaved==4)
|
|
|
|
{
|
|
|
|
r=13;
|
|
|
|
}
|
|
|
|
else if (game.lastsaved==5)
|
|
|
|
{
|
|
|
|
r=16;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
r = 19;
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(200, 153, 18, r, 0, 19, 30);
|
2020-01-01 21:29:24 +01:00
|
|
|
i = obj.getcrewman(game.lastsaved);
|
Bounds check all entity getters that can return 0
The entity getters I'm referring to are entityclass::getscm(),
entityclass::getlineat(), entityclass::getcrewman(), and
entityclass::getcustomcrewman().
Even though the player should always exist, and the player should always
be indice 0, I wouldn't want to make that assumption. I've been wrong
before.
Also, these functions returning 0 lull you into a false sense of
security. If you assume that commands using these functions are fine,
you'll forget about the fact that `i` in those commands could be
potentially anything, given an invalid argument. In fact, it's possible
to index createactivityzone(), flipgravity(), and customposition()
out-of-bounds by setting `i` to anything! Well, WAS possible. I fixed it
so now they can't.
Furthermore, in the game.scmmoveme block in gamelogic(), obj.getplayer()
wasn't even checked, even though it's been checked in all other places.
I only caught it just now because I wanted to bounds-check all usages of
obj.getscm(), too, and that game.scmmove block also used obj.getscm()
without bounds-checking it as well.
2020-09-10 07:31:09 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
|
|
|
{
|
|
|
|
obj.entities[i].dir = 1;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (words[0] == "specialline")
|
|
|
|
{
|
|
|
|
switch(ss_toi(words[1]))
|
|
|
|
{
|
|
|
|
case 1:
|
2020-04-04 03:04:55 +02:00
|
|
|
txt.resize(1);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
txt[0] = "I'm worried about " + game.unrescued() + ", Doctor!";
|
|
|
|
break;
|
|
|
|
case 2:
|
2020-04-04 03:04:55 +02:00
|
|
|
txt.resize(3);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
if (game.crewrescued() < 5)
|
|
|
|
{
|
|
|
|
txt[1] = "to helping you find the";
|
|
|
|
txt[2] = "rest of the crew!";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-04-04 03:04:55 +02:00
|
|
|
txt.resize(2);
|
2020-01-01 21:29:24 +01:00
|
|
|
txt[1] = "to helping you find " + game.unrescued() + "!";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "trinketbluecontrol")
|
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
if (game.trinkets() == 20 && obj.flags[67])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
load("talkblue_trinket6");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (game.trinkets() >= 19 && !obj.flags[67])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
load("talkblue_trinket5");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
load("talkblue_trinket4");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "trinketyellowcontrol")
|
|
|
|
{
|
2020-04-07 08:46:27 +02:00
|
|
|
if (game.trinkets() >= 19)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
load("talkyellow_trinket3");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
load("talkyellow_trinket2");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "redcontrol")
|
|
|
|
{
|
|
|
|
if (game.insecretlab)
|
|
|
|
{
|
|
|
|
load("talkred_14");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else if (game.roomx != 104)
|
|
|
|
{
|
|
|
|
if (game.roomx == 100)
|
|
|
|
{
|
|
|
|
load("talkred_10");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else if (game.roomx == 107)
|
|
|
|
{
|
|
|
|
load("talkred_11");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else if (game.roomx == 114)
|
|
|
|
{
|
|
|
|
load("talkred_12");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (obj.flags[67])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//game complete
|
|
|
|
load("talkred_13");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (obj.flags[35] && !obj.flags[52])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Intermission level
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[52] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkred_9");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[51])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//We're back home!
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[51] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkred_5");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[48] && game.crewstats[5])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Victoria's back
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[48] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkred_6");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[49] && game.crewstats[4])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Verdigris' back
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[49] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkred_7");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[50] && game.crewstats[2])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Vitellary's back
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[50] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkred_8");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[45] && !game.crewstats[5])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[45] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkred_2");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[46] && !game.crewstats[4])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[46] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkred_3");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[47] && !game.crewstats[2])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[47] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkred_4");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[45] = false;
|
|
|
|
obj.flags[46] = false;
|
|
|
|
obj.flags[47] = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkred_1");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//TODO: Non Urgent fix compiler nesting errors without adding complexity
|
|
|
|
if (words[0] == "greencontrol")
|
|
|
|
{
|
|
|
|
if (game.insecretlab)
|
|
|
|
{
|
|
|
|
load("talkgreen_11");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else if (game.roomx == 103 && game.roomy == 109)
|
|
|
|
{
|
|
|
|
load("talkgreen_8");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else if (game.roomx == 101 && game.roomy == 109)
|
|
|
|
{
|
|
|
|
load("talkgreen_9");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (obj.flags[67])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//game complete
|
|
|
|
load("talkgreen_10");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (obj.flags[34] && !obj.flags[57])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Intermission level
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[57] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkgreen_7");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[53])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Home!
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[53] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkgreen_6");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[54] && game.crewstats[2])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[54] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkgreen_5");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[55] && game.crewstats[3])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[55] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkgreen_4");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[56] && game.crewstats[5])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[56] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkgreen_3");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[58])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[58] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkgreen_2");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
load("talkgreen_1");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "bluecontrol")
|
|
|
|
{
|
|
|
|
if (game.insecretlab)
|
|
|
|
{
|
|
|
|
load("talkblue_9");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (obj.flags[67])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//game complete, everything changes for victoria
|
2020-04-09 08:34:26 +02:00
|
|
|
if (obj.flags[41] && !obj.flags[42])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//second trinket conversation
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[42] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkblue_trinket2");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[41] && !obj.flags[42])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Third trinket conversation
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[42] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkblue_trinket3");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//Ok, we've already dealt with the trinket thing; so either you have them all, or you don't. If you do:
|
2020-04-07 08:46:27 +02:00
|
|
|
if (game.trinkets() >= 20)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
load("startepilogue");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
load("talkblue_8");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (obj.flags[33] && !obj.flags[40])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Intermission level
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[40] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkblue_7");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[36] && game.crewstats[5])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Back on the ship!
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[36] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkblue_3");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[41] && game.crewrescued() <= 4)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//First trinket conversation
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[41] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkblue_trinket1");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (obj.flags[41] && !obj.flags[42] && game.crewrescued() == 5)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//second trinket conversation
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[42] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkblue_trinket2");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[41] && !obj.flags[42] && game.crewrescued() == 5)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Third trinket conversation
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[42] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkblue_trinket3");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[37] && game.crewstats[2])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[37] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkblue_4");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[38] && game.crewstats[3])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[38] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkblue_5");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[39] && game.crewstats[4])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[39] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkblue_6");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//if all else fails:
|
|
|
|
//if yellow is found
|
|
|
|
if (game.crewstats[2])
|
|
|
|
{
|
|
|
|
load("talkblue_2");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
load("talkblue_1");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "yellowcontrol")
|
|
|
|
{
|
|
|
|
if (game.insecretlab)
|
|
|
|
{
|
|
|
|
load("talkyellow_12");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (obj.flags[67])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//game complete
|
|
|
|
load("talkyellow_11");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (obj.flags[32] && !obj.flags[31])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Intermission level
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[31] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkyellow_6");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[27] && game.crewstats[2])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Back on the ship!
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[27] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkyellow_10");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[43] && game.crewrescued() == 5 && !game.crewstats[5])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//If by chance we've rescued everyone except Victoria by the end, Vitellary provides you with
|
|
|
|
//the trinket information instead.
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[43] = true;
|
|
|
|
obj.flags[42] = true;
|
|
|
|
obj.flags[41] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkyellow_trinket1");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[24] && game.crewstats[5])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[24] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkyellow_8");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[26] && game.crewstats[4])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[26] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkyellow_7");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[25] && game.crewstats[3])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[25] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkyellow_9");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[28])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[28] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkyellow_3");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[29])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[29] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkyellow_4");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[30])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[30] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkyellow_5");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[23])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[23] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkyellow_2");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
load("talkyellow_1");
|
|
|
|
position--;
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[23] = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (words[0] == "purplecontrol")
|
|
|
|
{
|
|
|
|
//Controls Purple's conversion
|
|
|
|
//Crew rescued:
|
|
|
|
if (game.insecretlab)
|
|
|
|
{
|
|
|
|
load("talkpurple_9");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (obj.flags[67])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//game complete
|
|
|
|
load("talkpurple_8");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[17] && game.crewstats[4])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[17] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkpurple_6");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[15] && game.crewstats[5])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[15] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkpurple_4");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[16] && game.crewstats[3])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[16] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkpurple_5");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[18] && game.crewstats[2])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[18] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkpurple_7");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (obj.flags[19] && !obj.flags[20] && !obj.flags[21])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//intermission one: if played one / not had first conversation / not played two [conversation one]
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[21] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkpurple_intermission1");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (obj.flags[20] && obj.flags[21] && !obj.flags[22])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//intermission two: if played two / had first conversation / not had second conversation [conversation two]
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[22] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkpurple_intermission2");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (obj.flags[20] && !obj.flags[21] && !obj.flags[22])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//intermission two: if played two / not had first conversation / not had second conversation [conversation three]
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[22] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkpurple_intermission3");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[12])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Intro conversation
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[12] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkpurple_intro");
|
|
|
|
position--;
|
|
|
|
}
|
2020-04-09 08:34:26 +02:00
|
|
|
else if (!obj.flags[14])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Shorter intro conversation
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[14] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
load("talkpurple_3");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//if all else fails:
|
|
|
|
//if green is found
|
|
|
|
if (game.crewstats[4])
|
|
|
|
{
|
|
|
|
load("talkpurple_2");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
load("talkpurple_1");
|
|
|
|
position--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
position++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
running = false;
|
|
|
|
}
|
2020-09-08 05:58:32 +02:00
|
|
|
// Don't increment if we're at the max, signed int overflow is UB
|
|
|
|
if (execution_counter == SHRT_MAX)
|
|
|
|
{
|
|
|
|
// We must be in an infinite loop
|
|
|
|
printf("Warning: execution counter got to %i, stopping script\n", SHRT_MAX);
|
|
|
|
running = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
execution_counter++;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if(scriptdelay>0)
|
|
|
|
{
|
|
|
|
scriptdelay--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 scriptclass::resetgametomenu(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-03 22:50:16 +02:00
|
|
|
obj.entities.clear();
|
Clean up all exit paths to the menu to use common code
There are multiple different exit paths to the main menu. In 2.2, they
all had a bunch of copy-pasted code. In 2.3 currently, most of them use
game.quittomenu(), but there are some stragglers that still use
hand-copied code.
This is a bit of a problem, because all exit paths should consistently
have FILESYSTEM_unmountassets(), as part of the 2.3 feature of per-level
custom assets. Furthermore, most (but not all) of the paths call
script.hardreset() too, and some of the stragglers don't. So there could
be something persisting through to the title screen (like a really long
flash/shake timer) that could only persist if exiting to the title
screen through those paths.
But, actually, it seems like there's a good reason for some of those to
not call script.hardreset() - namely, dying or completing No Death Mode
and completing a Time Trial presents some information onscreen that
would get reset by script.hardreset(), so I'll fix that in a later
commit.
So what I've done for this commit is found every exit path that didn't
already use game.quittomenu(), and made them use game.quittomenu(). As
well, some of them had special handling that existed on top of them
already having a corresponding entry in game.quittomenu() (but the path
would take the special handling because it never did game.quittomenu()),
so I removed that special handling as well (e.g. exiting from a custom
level used returntomenu(Menu::levellist) when quittomenu() already had
that same returntomenu()).
The menu that exiting from the level editor returns to is now handled in
game.quittomenu() as well, where the map.custommode branch now also
checks for map.custommodeforreal. Unfortunately, it seems like entering
the level editor doesn't properly initialize map.custommode, so entering
the level editor now initializes map.custommode, too.
I've also taken the music.play(6) out of game.quittomenu(), because not
all exit paths immediately play Presenting VVVVVV, so all exit paths
that DO immediately play Presenting VVVVVV now have music.play(6)
special-cased for them, which is fine enough for me.
Here is the list of all exit paths to the menu:
- Exiting through the pause menu (without glitchrunner mode)
- Exiting through the pause menu (with glitchrunner mode)
- Completing a custom level
- Completing a Time Trial
- Dying in No Death Mode
- Completing No Death Mode
- Completing an Intermission replay
- Exiting from the level editor
- Completing the main game
2021-01-07 23:20:37 +01:00
|
|
|
game.quittomenu();
|
2020-04-16 06:53:36 +02:00
|
|
|
game.createmenu(Menu::gameover);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-03-31 21:38:52 +02:00
|
|
|
void scriptclass::startgamemode( int t )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
switch(t)
|
|
|
|
{
|
|
|
|
case 0: //Normal new game
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-03-31 02:16:02 +02:00
|
|
|
game.start();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.showcutscenebars = true;
|
2021-03-20 07:08:35 +01:00
|
|
|
graphics.setbars(320);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
Fix being able to circumvent not-in-Flip-Mode detection
So you get a trophy and achievement for completing the game in Flip
Mode. Which begs the question, how does the game know that you've played
through the game in Flip Mode the entire way, and haven't switched it
off at any point? It looks like if you play normally all the way up
until the checkpoint in V, and then turn on Flip Mode, the game won't
give you the trophy. What gives?
Well, actually, what happens is that every time you press Enter on a
teleporter, the game will set flag 73 to true if you're NOT in Flip
Mode. Then when Game Complete runs, the game will check if flag 73 is
off, and then give you the achievement and trophy accordingly.
However, what this means is that you could just save your game before
pressing Enter on a teleporter, then quit and go into options, turn on
Flip Mode, use the teleporter, then save your game (it's automatically
saved since you just used a teleporter), quit and go into options, and
turn it off. Then you'd get the Flip Mode trophy even though you haven't
actually played the entire game in Flip Mode.
Furthermore, in 2.3 you can bring up the pause menu to toggle Flip Mode,
so you don't even have to quit to circumvent this detection.
To fix both of these exploits, I moved the turning on of flag 73 to
starting a new game, loading a quicksave, and loading a telesave (cases
0, 1, and 2 respectively in scriptclass::startgamemode()). I also added
a Flip Mode check to the routine that runs whenever you exit an options
menu back to the pause menu, so you can't circumvent the detection that
way, either.
2020-07-11 01:30:28 +02:00
|
|
|
else obj.flags[73] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
load("intro");
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-03-31 02:16:02 +02:00
|
|
|
game.start();
|
|
|
|
game.loadtele();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.gravitycontrol = game.savegc;
|
|
|
|
game.jumpheld = true;
|
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
Fix being able to circumvent not-in-Flip-Mode detection
So you get a trophy and achievement for completing the game in Flip
Mode. Which begs the question, how does the game know that you've played
through the game in Flip Mode the entire way, and haven't switched it
off at any point? It looks like if you play normally all the way up
until the checkpoint in V, and then turn on Flip Mode, the game won't
give you the trophy. What gives?
Well, actually, what happens is that every time you press Enter on a
teleporter, the game will set flag 73 to true if you're NOT in Flip
Mode. Then when Game Complete runs, the game will check if flag 73 is
off, and then give you the achievement and trophy accordingly.
However, what this means is that you could just save your game before
pressing Enter on a teleporter, then quit and go into options, turn on
Flip Mode, use the teleporter, then save your game (it's automatically
saved since you just used a teleporter), quit and go into options, and
turn it off. Then you'd get the Flip Mode trophy even though you haven't
actually played the entire game in Flip Mode.
Furthermore, in 2.3 you can bring up the pause menu to toggle Flip Mode,
so you don't even have to quit to circumvent this detection.
To fix both of these exploits, I moved the turning on of flag 73 to
starting a new game, loading a quicksave, and loading a telesave (cases
0, 1, and 2 respectively in scriptclass::startgamemode()). I also added
a Flip Mode check to the routine that runs whenever you exit an options
menu back to the pause menu, so you can't circumvent the detection that
way, either.
2020-07-11 01:30:28 +02:00
|
|
|
else obj.flags[73] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 2: //Load Quicksave
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-03-31 02:16:02 +02:00
|
|
|
game.start();
|
|
|
|
game.loadquick();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.gravitycontrol = game.savegc;
|
|
|
|
game.jumpheld = true;
|
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
Fix being able to circumvent not-in-Flip-Mode detection
So you get a trophy and achievement for completing the game in Flip
Mode. Which begs the question, how does the game know that you've played
through the game in Flip Mode the entire way, and haven't switched it
off at any point? It looks like if you play normally all the way up
until the checkpoint in V, and then turn on Flip Mode, the game won't
give you the trophy. What gives?
Well, actually, what happens is that every time you press Enter on a
teleporter, the game will set flag 73 to true if you're NOT in Flip
Mode. Then when Game Complete runs, the game will check if flag 73 is
off, and then give you the achievement and trophy accordingly.
However, what this means is that you could just save your game before
pressing Enter on a teleporter, then quit and go into options, turn on
Flip Mode, use the teleporter, then save your game (it's automatically
saved since you just used a teleporter), quit and go into options, and
turn it off. Then you'd get the Flip Mode trophy even though you haven't
actually played the entire game in Flip Mode.
Furthermore, in 2.3 you can bring up the pause menu to toggle Flip Mode,
so you don't even have to quit to circumvent this detection.
To fix both of these exploits, I moved the turning on of flag 73 to
starting a new game, loading a quicksave, and loading a telesave (cases
0, 1, and 2 respectively in scriptclass::startgamemode()). I also added
a Flip Mode check to the routine that runs whenever you exit an options
menu back to the pause menu, so you can't circumvent the detection that
way, either.
2020-07-11 01:30:28 +02:00
|
|
|
else obj.flags[73] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
//a very special case for here needs to ensure that the tower is set correctly
|
|
|
|
if (map.towermode)
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
map.ypos = obj.entities[i].yp - 120;
|
|
|
|
}
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.bypos = map.ypos / 2;
|
2020-01-01 21:29:24 +01:00
|
|
|
map.cameramode = 0;
|
|
|
|
map.colsuperstate = 0;
|
|
|
|
}
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
//Start Time Trial 1
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.nocutscenes = true;
|
|
|
|
game.intimetrial = true;
|
|
|
|
game.timetrialcountdown = 150;
|
|
|
|
game.timetrialparlost = false;
|
|
|
|
game.timetriallevel = 0;
|
|
|
|
game.timetrialpar = 75;
|
|
|
|
game.timetrialshinytarget = 2;
|
|
|
|
|
|
|
|
music.fadeout();
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.starttrial(game.timetriallevel);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;//set flipmode
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
//Start Time Trial 2
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.nocutscenes = true;
|
|
|
|
game.intimetrial = true;
|
|
|
|
game.timetrialcountdown = 150;
|
|
|
|
game.timetrialparlost = false;
|
|
|
|
game.timetriallevel = 1;
|
|
|
|
game.timetrialpar = 165;
|
|
|
|
game.timetrialshinytarget = 4;
|
|
|
|
|
|
|
|
music.fadeout();
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.starttrial(game.timetriallevel);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;//set flipmode
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
//Start Time Trial 3 tow
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.nocutscenes = true;
|
|
|
|
game.intimetrial = true;
|
|
|
|
game.timetrialcountdown = 150;
|
|
|
|
game.timetrialparlost = false;
|
|
|
|
game.timetriallevel = 2;
|
|
|
|
game.timetrialpar = 105;
|
|
|
|
game.timetrialshinytarget = 2;
|
|
|
|
|
|
|
|
music.fadeout();
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.starttrial(game.timetriallevel);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;//set flipmode
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
//Start Time Trial 4 station
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.nocutscenes = true;
|
|
|
|
game.intimetrial = true;
|
|
|
|
game.timetrialcountdown = 150;
|
|
|
|
game.timetrialparlost = false;
|
|
|
|
game.timetriallevel = 3;
|
|
|
|
game.timetrialpar = 200;
|
|
|
|
game.timetrialshinytarget = 5;
|
|
|
|
|
|
|
|
music.fadeout();
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.starttrial(game.timetriallevel);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;//set flipmode
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
//Start Time Trial 5 warp
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.nocutscenes = true;
|
|
|
|
game.intimetrial = true;
|
|
|
|
game.timetrialcountdown = 150;
|
|
|
|
game.timetrialparlost = false;
|
|
|
|
game.timetriallevel = 4;
|
|
|
|
game.timetrialpar = 120;
|
|
|
|
game.timetrialshinytarget = 1;
|
|
|
|
|
|
|
|
music.fadeout();
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.starttrial(game.timetriallevel);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;//set flipmode
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
//Start Time Trial 6// final level!
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.nocutscenes = true;
|
|
|
|
game.intimetrial = true;
|
|
|
|
game.timetrialcountdown = 150;
|
|
|
|
game.timetrialparlost = false;
|
|
|
|
game.timetriallevel = 5;
|
|
|
|
game.timetrialpar = 135;
|
|
|
|
game.timetrialshinytarget = 1;
|
|
|
|
|
|
|
|
music.fadeout();
|
|
|
|
map.finalmode = true; //Enable final level mode
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 0;
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.starttrial(game.timetriallevel);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;//set flipmode
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 9:
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.nodeathmode = true;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.start();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.showcutscenebars = true;
|
2021-03-20 07:08:35 +01:00
|
|
|
graphics.setbars(320);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
load("intro");
|
|
|
|
break;
|
|
|
|
case 10:
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.nodeathmode = true;
|
|
|
|
game.nocutscenes = true;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
game.start();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.showcutscenebars = true;
|
2021-03-20 07:08:35 +01:00
|
|
|
graphics.setbars(320);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
load("intro");
|
|
|
|
break;
|
|
|
|
case 11:
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
game.startspecial(0);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
|
|
|
//Secret lab, so reveal the map, give them all 20 trinkets
|
|
|
|
for (int j = 0; j < 20; j++)
|
|
|
|
{
|
|
|
|
obj.collect[j] = true;
|
|
|
|
for (i = 0; i < 20; i++)
|
|
|
|
{
|
|
|
|
map.explored[i + (j * 20)] = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
game.insecretlab = true;
|
|
|
|
map.showteleporters = true;
|
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
music.play(11);
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 12:
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
music.fadeout();
|
|
|
|
|
|
|
|
game.lastsaved = 2;
|
|
|
|
|
|
|
|
game.crewstats[game.lastsaved] = true;
|
|
|
|
game.inintermission = true;
|
|
|
|
game.companion = 11;
|
|
|
|
game.supercrewmate = true;
|
|
|
|
game.scmprogress = 0;
|
|
|
|
map.finalmode = true;
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 0;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.startspecial(1);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
load("intermission_1");
|
|
|
|
break;
|
|
|
|
case 13:
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
music.fadeout();
|
|
|
|
|
|
|
|
game.lastsaved = 3;
|
|
|
|
|
|
|
|
game.crewstats[game.lastsaved] = true;
|
|
|
|
game.inintermission = true;
|
|
|
|
game.companion = 11;
|
|
|
|
game.supercrewmate = true;
|
|
|
|
game.scmprogress = 0;
|
|
|
|
map.finalmode = true;
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 0;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.startspecial(1);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
load("intermission_1");
|
|
|
|
break;
|
|
|
|
case 14:
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
music.fadeout();
|
|
|
|
|
|
|
|
game.lastsaved = 4;
|
|
|
|
|
|
|
|
game.crewstats[game.lastsaved] = true;
|
|
|
|
game.inintermission = true;
|
|
|
|
game.companion = 11;
|
|
|
|
game.supercrewmate = true;
|
|
|
|
game.scmprogress = 0;
|
|
|
|
map.finalmode = true;
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 0;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.startspecial(1);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
load("intermission_1");
|
|
|
|
break;
|
|
|
|
case 15:
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
music.fadeout();
|
|
|
|
|
|
|
|
game.lastsaved = 5;
|
|
|
|
|
|
|
|
game.crewstats[game.lastsaved] = true;
|
|
|
|
game.inintermission = true;
|
|
|
|
game.companion = 11;
|
|
|
|
game.supercrewmate = true;
|
|
|
|
game.scmprogress = 0;
|
|
|
|
map.finalmode = true;
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 0;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.startspecial(1);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
load("intermission_1");
|
|
|
|
break;
|
|
|
|
case 16:
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
music.fadeout();
|
|
|
|
|
|
|
|
game.lastsaved = 2;
|
|
|
|
|
|
|
|
game.crewstats[game.lastsaved] = true;
|
|
|
|
game.inintermission = true;
|
|
|
|
map.finalmode = true;
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 0;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.startspecial(1);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
load("intermission_2");
|
|
|
|
break;
|
|
|
|
case 17:
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
music.fadeout();
|
|
|
|
|
|
|
|
game.lastsaved = 3;
|
|
|
|
|
|
|
|
game.crewstats[game.lastsaved] = true;
|
|
|
|
game.inintermission = true;
|
|
|
|
map.finalmode = true;
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 0;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.startspecial(1);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
load("intermission_2");
|
|
|
|
break;
|
|
|
|
case 18:
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
music.fadeout();
|
|
|
|
|
|
|
|
game.lastsaved = 4;
|
|
|
|
|
|
|
|
game.crewstats[game.lastsaved] = true;
|
|
|
|
game.inintermission = true;
|
|
|
|
map.finalmode = true;
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 0;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.startspecial(1);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
load("intermission_2");
|
|
|
|
break;
|
|
|
|
case 19:
|
|
|
|
game.gamestate = GAMEMODE;
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
music.fadeout();
|
|
|
|
|
|
|
|
game.lastsaved = 5;
|
|
|
|
|
|
|
|
game.crewstats[game.lastsaved] = true;
|
|
|
|
game.inintermission = true;
|
|
|
|
map.finalmode = true;
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 0;
|
2020-03-31 02:16:02 +02:00
|
|
|
game.startspecial(1);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
load("intermission_2");
|
|
|
|
break;
|
2020-02-10 03:21:19 +01:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
2020-01-01 21:29:24 +01:00
|
|
|
case 20:
|
|
|
|
//Level editor
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
ed.reset();
|
|
|
|
music.fadeout();
|
Clean up all exit paths to the menu to use common code
There are multiple different exit paths to the main menu. In 2.2, they
all had a bunch of copy-pasted code. In 2.3 currently, most of them use
game.quittomenu(), but there are some stragglers that still use
hand-copied code.
This is a bit of a problem, because all exit paths should consistently
have FILESYSTEM_unmountassets(), as part of the 2.3 feature of per-level
custom assets. Furthermore, most (but not all) of the paths call
script.hardreset() too, and some of the stragglers don't. So there could
be something persisting through to the title screen (like a really long
flash/shake timer) that could only persist if exiting to the title
screen through those paths.
But, actually, it seems like there's a good reason for some of those to
not call script.hardreset() - namely, dying or completing No Death Mode
and completing a Time Trial presents some information onscreen that
would get reset by script.hardreset(), so I'll fix that in a later
commit.
So what I've done for this commit is found every exit path that didn't
already use game.quittomenu(), and made them use game.quittomenu(). As
well, some of them had special handling that existed on top of them
already having a corresponding entry in game.quittomenu() (but the path
would take the special handling because it never did game.quittomenu()),
so I removed that special handling as well (e.g. exiting from a custom
level used returntomenu(Menu::levellist) when quittomenu() already had
that same returntomenu()).
The menu that exiting from the level editor returns to is now handled in
game.quittomenu() as well, where the map.custommode branch now also
checks for map.custommodeforreal. Unfortunately, it seems like entering
the level editor doesn't properly initialize map.custommode, so entering
the level editor now initializes map.custommode, too.
I've also taken the music.play(6) out of game.quittomenu(), because not
all exit paths immediately play Presenting VVVVVV, so all exit paths
that DO immediately play Presenting VVVVVV now have music.play(6)
special-cased for them, which is fine enough for me.
Here is the list of all exit paths to the menu:
- Exiting through the pause menu (without glitchrunner mode)
- Exiting through the pause menu (with glitchrunner mode)
- Completing a custom level
- Completing a Time Trial
- Dying in No Death Mode
- Completing No Death Mode
- Completing an Intermission replay
- Exiting from the level editor
- Completing the main game
2021-01-07 23:20:37 +01:00
|
|
|
map.custommode = true;
|
|
|
|
map.custommodeforreal = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
game.gamestate = EDITORMODE;
|
|
|
|
game.jumpheld = true;
|
|
|
|
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;//set flipmode
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 21: //play custom level (in editor)
|
|
|
|
game.gamestate = GAMEMODE;
|
|
|
|
music.fadeout();
|
2020-03-31 21:38:52 +02:00
|
|
|
hardreset();
|
2020-01-31 04:06:16 +01:00
|
|
|
//If warpdir() is used during playtesting, we need to set it back after!
|
|
|
|
for (int j = 0; j < ed.maxheight; j++)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < ed.maxwidth; i++)
|
|
|
|
{
|
|
|
|
ed.kludgewarpdir[i+(j*ed.maxwidth)]=ed.level[i+(j*ed.maxwidth)].warpdir;
|
|
|
|
}
|
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
game.customstart();
|
2020-01-01 21:29:24 +01:00
|
|
|
game.jumpheld = true;
|
|
|
|
|
2020-06-13 00:20:39 +02:00
|
|
|
ed.ghosts.clear();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
map.custommode = true;
|
|
|
|
|
|
|
|
//set flipmode
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
map.resetplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
if(ed.levmusic>0){
|
2020-04-02 22:26:22 +02:00
|
|
|
music.play(ed.levmusic);
|
2020-01-01 21:29:24 +01:00
|
|
|
}else{
|
2020-04-02 22:26:22 +02:00
|
|
|
music.currentsong=-1;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
2020-04-02 22:26:22 +02:00
|
|
|
case 22: //play custom level (in game)
|
|
|
|
{
|
|
|
|
//Initilise the level
|
|
|
|
//First up, find the start point
|
|
|
|
std::string filename = std::string(ed.ListOfMetaData[game.playcustomlevel].filename);
|
|
|
|
ed.load(filename);
|
|
|
|
ed.findstartpoint();
|
|
|
|
|
|
|
|
game.gamestate = GAMEMODE;
|
|
|
|
music.fadeout();
|
|
|
|
hardreset();
|
|
|
|
game.customstart();
|
|
|
|
game.jumpheld = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
map.custommodeforreal = true;
|
2020-04-02 22:26:22 +02:00
|
|
|
map.custommode = true;
|
|
|
|
|
|
|
|
//set flipmode
|
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
|
|
|
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-04-02 22:26:22 +02:00
|
|
|
{
|
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
map.resetplayer();
|
|
|
|
}
|
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-03-31 21:52:10 +02:00
|
|
|
ed.generatecustomminimap();
|
2020-01-01 21:29:24 +01:00
|
|
|
map.customshowmm=true;
|
2020-04-02 22:26:22 +02:00
|
|
|
if(ed.levmusic>0){
|
|
|
|
music.play(ed.levmusic);
|
|
|
|
}else{
|
|
|
|
music.currentsong=-1;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-04-02 22:26:22 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 23: //Continue in custom level
|
|
|
|
{
|
|
|
|
//Initilise the level
|
|
|
|
//First up, find the start point
|
|
|
|
std::string filename = std::string(ed.ListOfMetaData[game.playcustomlevel].filename);
|
|
|
|
ed.load(filename);
|
|
|
|
ed.findstartpoint();
|
|
|
|
|
|
|
|
game.gamestate = GAMEMODE;
|
|
|
|
music.fadeout();
|
|
|
|
hardreset();
|
2020-01-01 21:29:24 +01:00
|
|
|
map.custommodeforreal = true;
|
2020-04-02 22:26:22 +02:00
|
|
|
map.custommode = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-02 22:26:22 +02:00
|
|
|
game.customstart();
|
2020-03-31 02:16:02 +02:00
|
|
|
game.customloadquick(ed.ListOfMetaData[game.playcustomlevel].filename);
|
2020-04-02 22:26:22 +02:00
|
|
|
game.jumpheld = true;
|
|
|
|
game.gravitycontrol = game.savegc;
|
|
|
|
|
|
|
|
|
|
|
|
//set flipmode
|
|
|
|
if (graphics.setflipmode) graphics.flipmode = true;
|
|
|
|
|
2020-04-03 22:50:16 +02:00
|
|
|
if(obj.entities.empty())
|
2020-04-02 22:26:22 +02:00
|
|
|
{
|
|
|
|
obj.createentity(game.savex, game.savey, 0, 0); //In this game, constant, never destroyed
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
map.resetplayer();
|
|
|
|
}
|
|
|
|
map.gotoroom(game.saverx, game.savery);
|
2020-06-30 21:30:19 +02:00
|
|
|
map.initmapdata();
|
2020-03-31 21:52:10 +02:00
|
|
|
ed.generatecustomminimap();
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.fademode = 4;
|
2020-04-02 22:26:22 +02:00
|
|
|
break;
|
|
|
|
}
|
2020-02-10 01:53:01 +01:00
|
|
|
#endif
|
2020-01-01 21:29:24 +01:00
|
|
|
case 100:
|
2021-02-16 03:53:17 +01:00
|
|
|
VVV_exit(0);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 scriptclass::teleport(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//er, ok! Teleport to a new area, so!
|
|
|
|
//A general rule of thumb: if you teleport with a companion, get rid of them!
|
|
|
|
game.companion = 0;
|
|
|
|
|
|
|
|
i = obj.getplayer(); //less likely to have a serious collision error if the player is centered
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp = 150;
|
|
|
|
obj.entities[i].yp = 110;
|
|
|
|
if(game.teleport_to_x==17 && game.teleport_to_y==17) obj.entities[i].xp = 88; //prevent falling!
|
Restore previous oldxp/oldyp variables in favor of lerpoldxp/lerpoldyp
I was investigating a desync in my Nova TAS, and it turns out that
the gravity line collision functions check for the `oldxp` and `oldyp`
of the player, i.e. their position on the previous frame, along with
their position on the current frame. So, if the player either collided
with the gravity line last frame or this frame, then the player collided
with the gravity line this frame.
Except, that's not actually true. It turns out that `oldxp` and `oldyp`
don't necessarily always correspond to the `xp` and `yp` of the player
on the previous frame. It turns out that your `oldyp` will be updated if
you stand on a vertically moving platform, before the gravity line
collision function gets ran. So, if you were colliding with a gravity
line on the previous frame, but you got moved out of there by a
vertically moving platform, then you just don't collide with the gravity
line at all.
However, this behavior changed in 2.3 after my over-30-FPS patch got
merged (#220). That patch took advantage of the existing `oldxp` and
`oldyp` entity attributes, and uses them to interpolate their positions
during rendering to make everything look real smooth.
Previously, `oldxp` and `oldyp` would both be updated in
`entityclass::updateentitylogic()`. However, I moved it in that patch to
update right before `gameinput()` in `main.cpp`.
As a result, `oldyp` no longer gets updated whenever the player stands
on a vertically moving platform. This ends up desyncing my TAS.
As expected, updating `oldyp` in `entityclass::movingplatformfix()` (the
function responsible for moving the player whenever they stand on a
vertically moving platform) makes it so that my TAS syncs, but the
visuals are glitchy when standing on a vertically moving platform. And
as much as I'd like to get rid of gravity lines checking for whether
you've collided with them on the previous frame, doing that desyncs my
TAS, too.
In the end, it seems like I should just leave `oldxp` and `oldyp` alone,
and switch to using dedicated variables that are never used in the
physics of the game. So I'm introducing `lerpoldxp` and `lerpoldyp`, and
replacing all instances of using `oldxp` and `oldyp` that my over-30-FPS
patch added, with `lerpoldxp` and `lerpoldyp` instead.
After doing this, and applying #503 as well, my Nova TAS syncs after
some minor but acceptable fixes with Viridian's walkingframe.
2020-10-10 05:58:58 +02:00
|
|
|
obj.entities[i].lerpoldxp = obj.entities[i].xp;
|
|
|
|
obj.entities[i].lerpoldyp = obj.entities[i].yp;
|
2020-06-13 05:36:08 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
if (game.teleportscript == "levelonecomplete")
|
|
|
|
{
|
|
|
|
game.teleport_to_x = 2;
|
|
|
|
game.teleport_to_y = 11;
|
|
|
|
}
|
|
|
|
else if (game.teleportscript == "gamecomplete")
|
|
|
|
{
|
|
|
|
game.teleport_to_x = 2;
|
|
|
|
game.teleport_to_y = 11;
|
|
|
|
}
|
|
|
|
|
|
|
|
game.gravitycontrol = 0;
|
2020-03-31 10:09:42 +02:00
|
|
|
map.gotoroom(100+game.teleport_to_x, 100+game.teleport_to_y);
|
2020-01-01 21:29:24 +01:00
|
|
|
j = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(j, obj.entities))
|
2020-06-13 04:31:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[j].state = 2;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
game.teleport_to_new_area = false;
|
|
|
|
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(j, obj.entities))
|
2020-06-13 04:31:08 +02:00
|
|
|
{
|
|
|
|
game.savepoint = obj.entities[j].para;
|
|
|
|
game.savex = obj.entities[j].xp + 44;
|
|
|
|
game.savey = obj.entities[j].yp + 44;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
game.savegc = 0;
|
|
|
|
|
|
|
|
game.saverx = game.roomx;
|
|
|
|
game.savery = game.roomy;
|
2020-06-13 05:36:08 +02:00
|
|
|
int player = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(player, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
game.savedir = obj.entities[player].dir;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
if(game.teleport_to_x==0 && game.teleport_to_y==0)
|
|
|
|
{
|
|
|
|
game.state = 4020;
|
|
|
|
}
|
|
|
|
else if(game.teleport_to_x==0 && game.teleport_to_y==16)
|
|
|
|
{
|
|
|
|
game.state = 4030;
|
|
|
|
}
|
|
|
|
else if(game.teleport_to_x==7 && game.teleport_to_y==9)
|
|
|
|
{
|
|
|
|
game.state = 4040;
|
|
|
|
}
|
|
|
|
else if(game.teleport_to_x==8 && game.teleport_to_y==11)
|
|
|
|
{
|
|
|
|
game.state = 4050;
|
|
|
|
}
|
|
|
|
else if(game.teleport_to_x==14 && game.teleport_to_y==19)
|
|
|
|
{
|
|
|
|
game.state = 4030;
|
|
|
|
}
|
|
|
|
else if(game.teleport_to_x==17 && game.teleport_to_y==12)
|
|
|
|
{
|
|
|
|
game.state = 4020;
|
|
|
|
}
|
|
|
|
else if(game.teleport_to_x==17 && game.teleport_to_y==17)
|
|
|
|
{
|
|
|
|
game.state = 4020;
|
|
|
|
}
|
|
|
|
else if(game.teleport_to_x==18 && game.teleport_to_y==7)
|
|
|
|
{
|
|
|
|
game.state = 4060;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
game.state = 4010;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (game.teleportscript != "")
|
|
|
|
{
|
|
|
|
game.state = 0;
|
|
|
|
load(game.teleportscript);
|
|
|
|
game.teleportscript = "";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//change music based on location
|
2020-03-31 21:38:52 +02:00
|
|
|
if (graphics.setflipmode && game.teleport_to_x == 11 && game.teleport_to_y == 4)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
music.niceplay(9);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
music.changemusicarea(game.teleport_to_x, game.teleport_to_y);
|
|
|
|
}
|
2021-03-20 03:40:16 +01:00
|
|
|
game.savetele_textbox();
|
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 scriptclass::hardreset(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Game:
|
|
|
|
game.hascontrol = true;
|
|
|
|
game.gravitycontrol = 0;
|
|
|
|
game.teleport = false;
|
|
|
|
game.companion = 0;
|
|
|
|
game.roomchange = false;
|
2020-06-25 23:49:54 +02:00
|
|
|
if (!game.glitchrunnermode)
|
|
|
|
{
|
|
|
|
// Ironically, resetting more variables makes the janky fadeout system in glitchrunnermode even more glitchy
|
|
|
|
game.roomx = 0;
|
|
|
|
game.roomy = 0;
|
|
|
|
}
|
2020-01-27 23:46:11 +01:00
|
|
|
game.prevroomx = 0;
|
|
|
|
game.prevroomy = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
game.teleport_to_new_area = false;
|
|
|
|
game.teleport_to_x = 0;
|
|
|
|
game.teleport_to_y = 0;
|
|
|
|
game.teleportscript = "";
|
|
|
|
|
|
|
|
game.tapleft = 0;
|
|
|
|
game.tapright = 0;
|
|
|
|
game.startscript = false;
|
|
|
|
game.newscript = "";
|
|
|
|
game.alarmon = false;
|
|
|
|
game.alarmdelay = 0;
|
|
|
|
game.blackout = false;
|
|
|
|
game.useteleporter = false;
|
|
|
|
game.teleport_to_teleporter = 0;
|
|
|
|
|
|
|
|
game.nodeathmode = false;
|
|
|
|
game.nocutscenes = false;
|
|
|
|
|
2020-07-03 03:10:52 +02:00
|
|
|
for (i = 0; i < (int) SDL_arraysize(game.crewstats); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
game.crewstats[i] = false;
|
|
|
|
}
|
|
|
|
game.crewstats[0] = true;
|
|
|
|
game.lastsaved = 0;
|
|
|
|
|
|
|
|
game.deathcounts = 0;
|
|
|
|
game.gameoverdelay = 0;
|
|
|
|
game.frames = 0;
|
|
|
|
game.seconds = 0;
|
|
|
|
game.minutes = 0;
|
|
|
|
game.hours = 0;
|
|
|
|
game.gamesaved = false;
|
2020-11-04 03:45:33 +01:00
|
|
|
game.gamesavefailed = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
game.savetime = "00:00";
|
|
|
|
game.savearea = "nowhere";
|
|
|
|
game.savetrinkets = 0;
|
2020-06-25 23:49:54 +02:00
|
|
|
if (!game.glitchrunnermode)
|
|
|
|
{
|
|
|
|
// Ironically, resetting more variables makes the janky fadeout system in glitchrunnermode even more glitchy
|
|
|
|
game.saverx = 0;
|
|
|
|
game.savery = 0;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
game.intimetrial = false;
|
|
|
|
game.timetrialcountdown = 0;
|
|
|
|
game.timetrialshinytarget = 0;
|
|
|
|
game.timetrialparlost = false;
|
|
|
|
game.timetrialpar = 0;
|
|
|
|
|
|
|
|
game.totalflips = 0;
|
|
|
|
game.hardestroom = "Welcome Aboard";
|
|
|
|
game.hardestroomdeaths = 0;
|
|
|
|
game.currentroomdeaths=0;
|
|
|
|
|
|
|
|
game.swnmode = false;
|
|
|
|
game.swntimer = 0;
|
|
|
|
game.swngame = 0;//Not playing sine wave ninja!
|
|
|
|
game.swnstate = 0;
|
|
|
|
game.swnstate2 = 0;
|
|
|
|
game.swnstate3 = 0;
|
|
|
|
game.swnstate4 = 0;
|
|
|
|
game.swndelay = 0;
|
|
|
|
game.swndeaths = 0;
|
|
|
|
game.supercrewmate = false;
|
|
|
|
game.scmhurt = false;
|
|
|
|
game.scmprogress = 0;
|
|
|
|
game.scmmoveme = false;
|
|
|
|
game.swncolstate = 0;
|
|
|
|
game.swncoldelay = 0;
|
|
|
|
game.swnrank = 0;
|
|
|
|
game.swnmessage = 0;
|
|
|
|
game.creditposx = 0;
|
|
|
|
game.creditposy = 0;
|
|
|
|
game.creditposdelay = 0;
|
|
|
|
|
|
|
|
game.inintermission = false;
|
|
|
|
game.insecretlab = false;
|
|
|
|
|
|
|
|
game.state = 0;
|
|
|
|
game.statedelay = 0;
|
|
|
|
|
2020-04-02 22:26:22 +02:00
|
|
|
game.hascontrol = true;
|
2020-06-25 23:39:51 +02:00
|
|
|
if (!game.glitchrunnermode)
|
|
|
|
{
|
|
|
|
// Keep the "- Press ACTION to advance text -" prompt around,
|
|
|
|
// apparently the speedrunners call it the "text storage" glitch
|
|
|
|
game.advancetext = false;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-01-23 01:16:27 +01:00
|
|
|
game.pausescript = false;
|
|
|
|
|
2020-04-27 04:33:07 +02:00
|
|
|
game.flashlight = 0;
|
|
|
|
game.screenshake = 0;
|
|
|
|
|
2020-05-20 06:32:53 +02:00
|
|
|
game.activeactivity = -1;
|
|
|
|
game.act_fade = 5;
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
//dwgraphicsclass
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.backgrounddrawn = false;
|
2020-07-11 09:10:06 +02:00
|
|
|
graphics.textbox.clear();
|
2020-03-31 21:38:52 +02:00
|
|
|
graphics.flipmode = false; //This will be reset if needs be elsewhere
|
|
|
|
graphics.showcutscenebars = false;
|
2021-03-20 07:08:35 +01:00
|
|
|
graphics.setbars(0);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-02 22:26:22 +02:00
|
|
|
//mapclass
|
2020-01-01 21:29:24 +01:00
|
|
|
map.warpx = false;
|
|
|
|
map.warpy = false;
|
|
|
|
map.showteleporters = false;
|
|
|
|
map.showtargets = false;
|
|
|
|
map.showtrinkets = false;
|
|
|
|
map.finalmode = false;
|
|
|
|
map.finalstretch = false;
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_colorframe = 0;
|
|
|
|
map.final_colorframedelay = 0;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_aniframe = 0;
|
|
|
|
map.final_aniframedelay = 0;
|
|
|
|
map.rcol = 0;
|
|
|
|
map.resetnames();
|
|
|
|
map.custommode=false;
|
|
|
|
map.custommodeforreal=false;
|
2020-06-25 23:49:54 +02:00
|
|
|
if (!game.glitchrunnermode)
|
|
|
|
{
|
|
|
|
// Ironically, resetting more variables makes the janky fadeout system even more glitchy
|
|
|
|
map.towermode=false;
|
|
|
|
}
|
2020-01-25 00:18:15 +01:00
|
|
|
map.cameraseekframe = 0;
|
|
|
|
map.resumedelay = 0;
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.scrolldir = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
map.customshowmm=true;
|
|
|
|
|
2020-07-03 06:01:09 +02:00
|
|
|
SDL_memset(map.roomdeaths, 0, sizeof(map.roomdeaths));
|
|
|
|
SDL_memset(map.roomdeathsfinal, 0, sizeof(map.roomdeathsfinal));
|
|
|
|
map.resetmap();
|
2020-01-01 21:29:24 +01:00
|
|
|
//entityclass
|
|
|
|
obj.nearelephant = false;
|
|
|
|
obj.upsetmode = false;
|
|
|
|
obj.upset = 0;
|
|
|
|
|
|
|
|
obj.trophytext = 0 ;
|
|
|
|
obj.trophytype = 0;
|
|
|
|
obj.altstates = 0;
|
|
|
|
|
2020-07-03 03:22:19 +02:00
|
|
|
obj.resetallflags();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 03:10:52 +02:00
|
|
|
for (i = 0; i < (int) SDL_arraysize(obj.customcrewmoods); i++){
|
|
|
|
obj.customcrewmoods[i]=true;
|
2020-04-02 22:26:22 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 04:17:32 +02:00
|
|
|
SDL_memset(obj.collect, false, sizeof(obj.collect));
|
|
|
|
SDL_memset(obj.customcollect, false, sizeof(obj.customcollect));
|
2020-05-20 01:41:29 +02:00
|
|
|
i = 100; //previously a for-loop iterating over collect/customcollect set this to 100
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-16 01:19:25 +02:00
|
|
|
int theplayer = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(theplayer, obj.entities)){
|
2020-06-16 01:19:25 +02:00
|
|
|
obj.entities[theplayer].tile = 0;
|
|
|
|
}
|
|
|
|
|
Fix entity and block indices after destroying them
This patch restores some 2.2 behavior, fixing a regression caused by the
refactor of properly using std::vectors.
In 2.2, the game allocated 200 items in obj.entities, but used a system
where each entity had an `active` attribute to signify if the entity
actually existed or not. When dealing with entities, you would have to
check this `active` flag, or else you'd be dealing with an entity that
didn't actually exist. (By the way, what I'm saying applies to blocks
and obj.blocks as well, except for some small differing details like the
game allocating 500 block slots versus obj.entities's 200.)
As a consequence, the game had to use a separate tracking variable,
obj.nentity, because obj.entities.size() would just report 200, instead
of the actual amount of entities. Needless to say, having to check for
`active` and use `obj.nentity` is a bit error-prone, and it's messier
than simply using the std::vector the way it was intended. Also, this
resulted in a hard limit of 200 entities, which custom level makers ran
into surprisingly quite often.
2.3 comes along, and removes the whole system. Now, std::vectors are
properly being used, and obj.entities.size() reports the actual number
of entities in the vector; you no longer have to check for `active` when
dealing with entities of any sort.
But there was one previous behavior of 2.2 that this system kind of
forgets about - namely, the ability to have holes in between entities.
You see, when an entity got disabled in 2.2 (which just meant turning
its `active` off), the indices of all other entities stayed the same;
the indice of the entity that got disabled stays there as a hole in the
array. But when an entity gets removed in 2.3 (previous to this patch),
the indices of every entity afterwards in the array get shifted down by
one. std::vector isn't really meant to be able to contain holes.
Do the indices of entities and blocks matter? Yes; they determine the
order in which entities and blocks get evaluated (the highest indice
gets evaluated first), and I had to fix some block evaluation order
stuff in previous PRs.
And in the case of entities, they matter hugely when using the
recently-discovered Arbitrary Entity Manipulation glitch (where crewmate
script commands are used on arbitrary entities by setting the `i`
attribute of `scriptclass` and passing invalid crewmate identifiers to
the commands). If you use Arbitrary Entity Manipulation after destroying
some entities, there is a chance that your script won't work between 2.2
and 2.3.
The indices also still determine the rendering order of entities
(highest indice gets drawn first, which means lowest indice gets drawn
in front of other entities). As an example: let's say we have the player
at 0, a gravity line at 1, and a checkpoint at 2; then we destroy the
gravity line and create a crewmate (let's do Violet).
If we're able to have holes, then after removing the gravity line, none
of the other indices shift. Then Violet will be created at indice 1, and
will be drawn in front of the checkpoint.
But if we can't have holes, then removing the gravity line results in
the indice of the checkpoint shifting down to indice 1. Then Violet is
created at indice 2, and gets drawn behind the checkpoint! This is a
clear illustration of changing the behavior that existed in 2.2.
However, I also don't want to go back to the `active` system of having
to check an attribute before operating on an entity. So... what do we
do to restore the holes?
Well, we don't need to have an `active` attribute, or modify any
existing code that operates on entities. Instead, we can just set the
attributes of the entities so that they naturally get ignored by
everything that comes into contact with it. For entities, we set their
invis to true, and their size, type, and rule to -1 (the game never uses
a size, type, or rule of -1 anywhere); for blocks, we set their type to
-1, and their width and height to 0.
obj.entities.size() will no longer necessarily equal the amount of
entities in the room; rather, it will be the amount of entity SLOTS that
have been allocated. But nothing that uses obj.entities.size() needs to
actually know the amount of entities; it's mostly used for iterating
over every entity in the vector.
Excess entity slots get cleaned up upon every call of
mapclass::gotoroom(), which will now deallocate entity slots starting
from the end until it hits a player, at which point it will switch to
disabling entity slots instead of removing them entirely.
The entclass::clear() and blockclass::clear() functions have been
restored because we need to call their initialization functions when
reusing a block/entity slot; it's possible to create an entity with an
invalid type number (it creates a glitchy Viridian), and without calling
the initialization function again, it would simply not create anything.
After this patch is applied, entity and block indices will be restored
to how they behaved in 2.2.
2020-12-27 07:11:34 +01:00
|
|
|
/* Disable duplicate player entities */
|
2020-06-16 01:19:25 +02:00
|
|
|
for (int i = 0; i < (int) obj.entities.size(); i++)
|
|
|
|
{
|
2020-07-11 21:21:24 +02:00
|
|
|
if (obj.entities[i].rule == 0 && i != theplayer)
|
2020-06-16 01:19:25 +02:00
|
|
|
{
|
Fix entity and block indices after destroying them
This patch restores some 2.2 behavior, fixing a regression caused by the
refactor of properly using std::vectors.
In 2.2, the game allocated 200 items in obj.entities, but used a system
where each entity had an `active` attribute to signify if the entity
actually existed or not. When dealing with entities, you would have to
check this `active` flag, or else you'd be dealing with an entity that
didn't actually exist. (By the way, what I'm saying applies to blocks
and obj.blocks as well, except for some small differing details like the
game allocating 500 block slots versus obj.entities's 200.)
As a consequence, the game had to use a separate tracking variable,
obj.nentity, because obj.entities.size() would just report 200, instead
of the actual amount of entities. Needless to say, having to check for
`active` and use `obj.nentity` is a bit error-prone, and it's messier
than simply using the std::vector the way it was intended. Also, this
resulted in a hard limit of 200 entities, which custom level makers ran
into surprisingly quite often.
2.3 comes along, and removes the whole system. Now, std::vectors are
properly being used, and obj.entities.size() reports the actual number
of entities in the vector; you no longer have to check for `active` when
dealing with entities of any sort.
But there was one previous behavior of 2.2 that this system kind of
forgets about - namely, the ability to have holes in between entities.
You see, when an entity got disabled in 2.2 (which just meant turning
its `active` off), the indices of all other entities stayed the same;
the indice of the entity that got disabled stays there as a hole in the
array. But when an entity gets removed in 2.3 (previous to this patch),
the indices of every entity afterwards in the array get shifted down by
one. std::vector isn't really meant to be able to contain holes.
Do the indices of entities and blocks matter? Yes; they determine the
order in which entities and blocks get evaluated (the highest indice
gets evaluated first), and I had to fix some block evaluation order
stuff in previous PRs.
And in the case of entities, they matter hugely when using the
recently-discovered Arbitrary Entity Manipulation glitch (where crewmate
script commands are used on arbitrary entities by setting the `i`
attribute of `scriptclass` and passing invalid crewmate identifiers to
the commands). If you use Arbitrary Entity Manipulation after destroying
some entities, there is a chance that your script won't work between 2.2
and 2.3.
The indices also still determine the rendering order of entities
(highest indice gets drawn first, which means lowest indice gets drawn
in front of other entities). As an example: let's say we have the player
at 0, a gravity line at 1, and a checkpoint at 2; then we destroy the
gravity line and create a crewmate (let's do Violet).
If we're able to have holes, then after removing the gravity line, none
of the other indices shift. Then Violet will be created at indice 1, and
will be drawn in front of the checkpoint.
But if we can't have holes, then removing the gravity line results in
the indice of the checkpoint shifting down to indice 1. Then Violet is
created at indice 2, and gets drawn behind the checkpoint! This is a
clear illustration of changing the behavior that existed in 2.2.
However, I also don't want to go back to the `active` system of having
to check an attribute before operating on an entity. So... what do we
do to restore the holes?
Well, we don't need to have an `active` attribute, or modify any
existing code that operates on entities. Instead, we can just set the
attributes of the entities so that they naturally get ignored by
everything that comes into contact with it. For entities, we set their
invis to true, and their size, type, and rule to -1 (the game never uses
a size, type, or rule of -1 anywhere); for blocks, we set their type to
-1, and their width and height to 0.
obj.entities.size() will no longer necessarily equal the amount of
entities in the room; rather, it will be the amount of entity SLOTS that
have been allocated. But nothing that uses obj.entities.size() needs to
actually know the amount of entities; it's mostly used for iterating
over every entity in the vector.
Excess entity slots get cleaned up upon every call of
mapclass::gotoroom(), which will now deallocate entity slots starting
from the end until it hits a player, at which point it will switch to
disabling entity slots instead of removing them entirely.
The entclass::clear() and blockclass::clear() functions have been
restored because we need to call their initialization functions when
reusing a block/entity slot; it's possible to create an entity with an
invalid type number (it creates a glitchy Viridian), and without calling
the initialization function again, it would simply not create anything.
After this patch is applied, entity and block indices will be restored
to how they behaved in 2.2.
2020-12-27 07:11:34 +01:00
|
|
|
obj.disableentity(i);
|
2020-06-16 01:19:25 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//Script Stuff
|
|
|
|
position = 0;
|
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.clear();
|
2020-01-01 21:29:24 +01:00
|
|
|
scriptdelay = 0;
|
|
|
|
scriptname = "null";
|
|
|
|
running = false;
|
|
|
|
}
|
2020-05-18 19:08:34 +02:00
|
|
|
|
2020-07-05 01:01:35 +02:00
|
|
|
void scriptclass::loadcustom(const std::string& t)
|
2020-05-18 19:08:34 +02:00
|
|
|
{
|
2020-05-18 19:09:57 +02:00
|
|
|
//this magic function breaks down the custom script and turns into real scripting!
|
|
|
|
std::string cscriptname="";
|
|
|
|
for(size_t i=0; i<t.length(); i++){
|
|
|
|
if(i>=7) cscriptname+=t[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string tstring;
|
|
|
|
|
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<std::string>* contents = NULL;
|
|
|
|
for(size_t i = 0; i < customscripts.size(); i++){
|
|
|
|
Script& script_ = customscripts[i];
|
|
|
|
|
|
|
|
if(script_.name == cscriptname){
|
|
|
|
contents = &script_.contents;
|
|
|
|
break;
|
2020-05-18 19:09:57 +02:00
|
|
|
}
|
|
|
|
}
|
2020-06-12 02:37:46 +02:00
|
|
|
if(contents == NULL){
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-12 02:38:43 +02:00
|
|
|
std::vector<std::string>& lines = *contents;
|
|
|
|
|
|
|
|
//Ok, we've got the relavent script segment, we do a pass to assess it, then run it!
|
|
|
|
int customcutscenemode=0;
|
|
|
|
for(size_t i=0; i<lines.size(); i++){
|
|
|
|
tokenize(lines[i]);
|
|
|
|
if(words[0] == "say"){
|
|
|
|
customcutscenemode=1;
|
|
|
|
}else if(words[0] == "reply"){
|
|
|
|
customcutscenemode=1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(customcutscenemode==1){
|
|
|
|
add("cutscene()");
|
|
|
|
add("untilbars()");
|
|
|
|
}
|
|
|
|
int customtextmode=0;
|
|
|
|
int speakermode=0; //0, terminal, numbers for crew
|
|
|
|
int squeakmode=0;//default on
|
|
|
|
//Now run the script
|
|
|
|
for(size_t i=0; i<lines.size(); i++){
|
|
|
|
words[0]="nothing"; //Default!
|
|
|
|
words[1]="1"; //Default!
|
|
|
|
tokenize(lines[i]);
|
2020-07-03 23:54:23 +02:00
|
|
|
for (size_t ii = 0; ii < words[0].length(); ii++)
|
|
|
|
{
|
|
|
|
words[0][ii] = SDL_tolower(words[0][ii]);
|
|
|
|
}
|
2020-06-12 02:38:43 +02:00
|
|
|
if(words[0] == "music"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
if(words[1]=="0"){
|
|
|
|
tstring="stopmusic()";
|
|
|
|
}else{
|
|
|
|
if(words[1]=="11"){ tstring="play(14)";
|
|
|
|
}else if(words[1]=="10"){ tstring="play(13)";
|
|
|
|
}else if(words[1]=="9"){ tstring="play(12)";
|
|
|
|
}else if(words[1]=="8"){ tstring="play(11)";
|
|
|
|
}else if(words[1]=="7"){ tstring="play(10)";
|
|
|
|
}else if(words[1]=="6"){ tstring="play(8)";
|
|
|
|
}else if(words[1]=="5"){ tstring="play(6)";
|
|
|
|
}else { tstring="play("+words[1]+")"; }
|
|
|
|
}
|
|
|
|
add(tstring);
|
|
|
|
}else if(words[0] == "playremix"){
|
|
|
|
add("play(15)");
|
|
|
|
}else if(words[0] == "flash"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
add("flash(5)");
|
|
|
|
add("shake(20)");
|
|
|
|
add("playef(9)");
|
|
|
|
}else if(words[0] == "sad" || words[0] == "cry"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
if(words[1]=="player"){
|
|
|
|
add("changemood(player,1)");
|
|
|
|
}else if(words[1]=="cyan" || words[1]=="viridian" || words[1]=="1"){
|
|
|
|
add("changecustommood(customcyan,1)");
|
|
|
|
}else if(words[1]=="purple" || words[1]=="violet" || words[1]=="pink" || words[1]=="2"){
|
|
|
|
add("changecustommood(purple,1)");
|
|
|
|
}else if(words[1]=="yellow" || words[1]=="vitellary" || words[1]=="3"){
|
|
|
|
add("changecustommood(yellow,1)");
|
|
|
|
}else if(words[1]=="red" || words[1]=="vermilion" || words[1]=="4"){
|
|
|
|
add("changecustommood(red,1)");
|
|
|
|
}else if(words[1]=="green" || words[1]=="verdigris" || words[1]=="5"){
|
|
|
|
add("changecustommood(green,1)");
|
|
|
|
}else if(words[1]=="blue" || words[1]=="victoria" || words[1]=="6"){
|
|
|
|
add("changecustommood(blue,1)");
|
|
|
|
}else if(words[1]=="all" || words[1]=="everybody" || words[1]=="everyone"){
|
|
|
|
add("changemood(player,1)");
|
|
|
|
add("changecustommood(customcyan,1)");
|
|
|
|
add("changecustommood(purple,1)");
|
|
|
|
add("changecustommood(yellow,1)");
|
|
|
|
add("changecustommood(red,1)");
|
|
|
|
add("changecustommood(green,1)");
|
|
|
|
add("changecustommood(blue,1)");
|
|
|
|
}else{
|
|
|
|
add("changemood(player,1)");
|
|
|
|
}
|
|
|
|
if(squeakmode==0) add("squeak(cry)");
|
|
|
|
}else if(words[0] == "happy"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
if(words[1]=="player"){
|
|
|
|
add("changemood(player,0)");
|
|
|
|
if(squeakmode==0) add("squeak(player)");
|
|
|
|
}else if(words[1]=="cyan" || words[1]=="viridian" || words[1]=="1"){
|
|
|
|
add("changecustommood(customcyan,0)");
|
|
|
|
if(squeakmode==0) add("squeak(player)");
|
|
|
|
}else if(words[1]=="purple" || words[1]=="violet" || words[1]=="pink" || words[1]=="2"){
|
|
|
|
add("changecustommood(purple,0)");
|
|
|
|
if(squeakmode==0) add("squeak(purple)");
|
|
|
|
}else if(words[1]=="yellow" || words[1]=="vitellary" || words[1]=="3"){
|
|
|
|
add("changecustommood(yellow,0)");
|
|
|
|
if(squeakmode==0) add("squeak(yellow)");
|
|
|
|
}else if(words[1]=="red" || words[1]=="vermilion" || words[1]=="4"){
|
|
|
|
add("changecustommood(red,0)");
|
|
|
|
if(squeakmode==0) add("squeak(red)");
|
|
|
|
}else if(words[1]=="green" || words[1]=="verdigris" || words[1]=="5"){
|
|
|
|
add("changecustommood(green,0)");
|
|
|
|
if(squeakmode==0) add("squeak(green)");
|
|
|
|
}else if(words[1]=="blue" || words[1]=="victoria" || words[1]=="6"){
|
|
|
|
add("changecustommood(blue,0)");
|
|
|
|
if(squeakmode==0) add("squeak(blue)");
|
|
|
|
}else if(words[1]=="all" || words[1]=="everybody" || words[1]=="everyone"){
|
|
|
|
add("changemood(player,0)");
|
|
|
|
add("changecustommood(customcyan,0)");
|
|
|
|
add("changecustommood(purple,0)");
|
|
|
|
add("changecustommood(yellow,0)");
|
|
|
|
add("changecustommood(red,0)");
|
|
|
|
add("changecustommood(green,0)");
|
|
|
|
add("changecustommood(blue,0)");
|
|
|
|
}else{
|
|
|
|
add("changemood(player,0)");
|
|
|
|
if(squeakmode==0) add("squeak(player)");
|
|
|
|
}
|
|
|
|
}else if(words[0] == "squeak"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
if(words[1]=="player"){
|
|
|
|
add("squeak(player)");
|
|
|
|
}else if(words[1]=="cyan" || words[1]=="viridian" || words[1]=="1"){
|
|
|
|
add("squeak(player)");
|
|
|
|
}else if(words[1]=="purple" || words[1]=="violet" || words[1]=="pink" || words[1]=="2"){
|
|
|
|
add("squeak(purple)");
|
|
|
|
}else if(words[1]=="yellow" || words[1]=="vitellary" || words[1]=="3"){
|
|
|
|
add("squeak(yellow)");
|
|
|
|
}else if(words[1]=="red" || words[1]=="vermilion" || words[1]=="4"){
|
|
|
|
add("squeak(red)");
|
|
|
|
}else if(words[1]=="green" || words[1]=="verdigris" || words[1]=="5"){
|
|
|
|
add("squeak(green)");
|
|
|
|
}else if(words[1]=="blue" || words[1]=="victoria" || words[1]=="6"){
|
|
|
|
add("squeak(blue)");
|
|
|
|
}else if(words[1]=="cry" || words[1]=="sad"){
|
|
|
|
add("squeak(cry)");
|
|
|
|
}else if(words[1]=="on"){
|
|
|
|
squeakmode=0;
|
|
|
|
}else if(words[1]=="off"){
|
|
|
|
squeakmode=1;
|
|
|
|
}
|
|
|
|
}else if(words[0] == "delay"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
add(lines[i]);
|
|
|
|
}else if(words[0] == "flag"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
add(lines[i]);
|
|
|
|
}else if(words[0] == "map"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
add("custom"+lines[i]);
|
|
|
|
}else if(words[0] == "warpdir"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
add(lines[i]);
|
|
|
|
}else if(words[0] == "ifwarp"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
add(lines[i]);
|
|
|
|
}else if(words[0] == "iftrinkets"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
add("custom"+lines[i]);
|
|
|
|
}else if(words[0] == "ifflag"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
add("custom"+lines[i]);
|
|
|
|
}else if(words[0] == "iftrinketsless"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
add("custom"+lines[i]);
|
|
|
|
}else if(words[0] == "destroy"){
|
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
if(words[1]=="gravitylines"){
|
|
|
|
add("destroy(gravitylines)");
|
|
|
|
}else if(words[1]=="warptokens"){
|
|
|
|
add("destroy(warptokens)");
|
|
|
|
}else if(words[1]=="platforms"){
|
|
|
|
add("destroy(platforms)");
|
|
|
|
}
|
|
|
|
}else if(words[0] == "speaker"){
|
|
|
|
speakermode=0;
|
|
|
|
if(words[1]=="gray" || words[1]=="grey" || words[1]=="terminal" || words[1]=="0") speakermode=0;
|
|
|
|
if(words[1]=="cyan" || words[1]=="viridian" || words[1]=="player" || words[1]=="1") speakermode=1;
|
|
|
|
if(words[1]=="purple" || words[1]=="violet" || words[1]=="pink" || words[1]=="2") speakermode=2;
|
|
|
|
if(words[1]=="yellow" || words[1]=="vitellary" || words[1]=="3") speakermode=3;
|
|
|
|
if(words[1]=="red" || words[1]=="vermilion" || words[1]=="4") speakermode=4;
|
|
|
|
if(words[1]=="green" || words[1]=="verdigris" || words[1]=="5") speakermode=5;
|
|
|
|
if(words[1]=="blue" || words[1]=="victoria" || words[1]=="6") speakermode=6;
|
|
|
|
}else if(words[0] == "say"){
|
|
|
|
//Speakers!
|
|
|
|
if(words[2]=="terminal" || words[2]=="gray" || words[2]=="grey" || words[2]=="0") speakermode=0;
|
|
|
|
if(words[2]=="cyan" || words[2]=="viridian" || words[2]=="player" || words[2]=="1") speakermode=1;
|
|
|
|
if(words[2]=="purple" || words[2]=="violet" || words[2]=="pink" || words[2]=="2") speakermode=2;
|
|
|
|
if(words[2]=="yellow" || words[2]=="vitellary" || words[2]=="3") speakermode=3;
|
|
|
|
if(words[2]=="red" || words[2]=="vermilion" || words[2]=="4") speakermode=4;
|
|
|
|
if(words[2]=="green" || words[2]=="verdigris" || words[2]=="5") speakermode=5;
|
|
|
|
if(words[2]=="blue" || words[2]=="victoria" || words[2]=="6") speakermode=6;
|
|
|
|
switch(speakermode){
|
|
|
|
case 0:
|
|
|
|
if(squeakmode==0) add("squeak(terminal)");
|
|
|
|
add("text(gray,0,114,"+words[1]+")");
|
|
|
|
break;
|
|
|
|
case 1: //NOT THE PLAYER
|
|
|
|
if(squeakmode==0) add("squeak(cyan)");
|
|
|
|
add("text(cyan,0,0,"+words[1]+")");
|
|
|
|
break;
|
|
|
|
case 2:
|
2020-05-18 19:09:57 +02:00
|
|
|
if(squeakmode==0) add("squeak(purple)");
|
2020-06-12 02:38:43 +02:00
|
|
|
add("text(purple,0,0,"+words[1]+")");
|
|
|
|
break;
|
|
|
|
case 3:
|
2020-05-18 19:09:57 +02:00
|
|
|
if(squeakmode==0) add("squeak(yellow)");
|
2020-06-12 02:38:43 +02:00
|
|
|
add("text(yellow,0,0,"+words[1]+")");
|
|
|
|
break;
|
|
|
|
case 4:
|
2020-05-18 19:09:57 +02:00
|
|
|
if(squeakmode==0) add("squeak(red)");
|
2020-06-12 02:38:43 +02:00
|
|
|
add("text(red,0,0,"+words[1]+")");
|
|
|
|
break;
|
|
|
|
case 5:
|
2020-05-18 19:09:57 +02:00
|
|
|
if(squeakmode==0) add("squeak(green)");
|
2020-06-12 02:38:43 +02:00
|
|
|
add("text(green,0,0,"+words[1]+")");
|
|
|
|
break;
|
|
|
|
case 6:
|
2020-05-18 19:09:57 +02:00
|
|
|
if(squeakmode==0) add("squeak(blue)");
|
2020-06-12 02:38:43 +02:00
|
|
|
add("text(blue,0,0,"+words[1]+")");
|
|
|
|
break;
|
|
|
|
}
|
2020-08-07 06:31:29 +02:00
|
|
|
int ti=help.Int(words[1].c_str());
|
2020-06-12 02:38:43 +02:00
|
|
|
int nti = ti>=0 && ti<=50 ? ti : 1;
|
|
|
|
for(int ti2=0; ti2<nti; ti2++){
|
|
|
|
i++;
|
2020-09-10 06:58:59 +02:00
|
|
|
if(INBOUNDS_VEC(i, lines)){
|
2020-06-12 02:38:43 +02:00
|
|
|
add(lines[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(speakermode){
|
|
|
|
case 0: add("customposition(center)"); break;
|
|
|
|
case 1: add("customposition(cyan,above)"); break;
|
|
|
|
case 2: add("customposition(purple,above)"); break;
|
|
|
|
case 3: add("customposition(yellow,above)"); break;
|
|
|
|
case 4: add("customposition(red,above)"); break;
|
|
|
|
case 5: add("customposition(green,above)"); break;
|
|
|
|
case 6: add("customposition(blue,above)"); break;
|
|
|
|
}
|
|
|
|
add("speak_active");
|
|
|
|
customtextmode=1;
|
|
|
|
}else if(words[0] == "reply"){
|
|
|
|
//For this version, terminal only
|
|
|
|
if(squeakmode==0) add("squeak(player)");
|
|
|
|
add("text(cyan,0,0,"+words[1]+")");
|
|
|
|
|
2020-08-07 06:31:29 +02:00
|
|
|
int ti=help.Int(words[1].c_str());
|
2020-06-12 02:38:43 +02:00
|
|
|
int nti = ti>=0 && ti<=50 ? ti : 1;
|
|
|
|
for(int ti2=0; ti2<nti; ti2++){
|
|
|
|
i++;
|
2020-09-10 06:58:59 +02:00
|
|
|
if(INBOUNDS_VEC(i, lines)){
|
2020-06-12 02:38:43 +02:00
|
|
|
add(lines[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
add("position(player,above)");
|
|
|
|
add("speak_active");
|
|
|
|
customtextmode=1;
|
2020-05-18 19:09:57 +02:00
|
|
|
}
|
2020-06-12 02:38:43 +02:00
|
|
|
}
|
2020-05-18 19:09:57 +02:00
|
|
|
|
2020-06-12 02:38:43 +02:00
|
|
|
if(customtextmode==1){ add("endtext"); customtextmode=0;}
|
|
|
|
if(customcutscenemode==1){
|
|
|
|
add("endcutscene()");
|
|
|
|
add("untilbars()");
|
|
|
|
}
|
2020-05-18 19:08:34 +02:00
|
|
|
}
|