2020-09-28 04:15:06 +02:00
|
|
|
#define MAP_DEFINITION
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "Map.h"
|
|
|
|
|
2020-07-19 21:05:41 +02:00
|
|
|
#include "editor.h"
|
|
|
|
#include "Entity.h"
|
|
|
|
#include "Game.h"
|
Split glitchrunner mode into multiple versions
Previously, turning glitchrunner mode on essentially locked you to
emulating 2.0, and turning it off just meant normal 2.3 behavior. But
what if you wanted 2.2 behavior instead? Well, that's what I had to ask
when a TAS of mine would desync in 2.3 because of the two-frame delay
fix (glitchrunner off), but would also desync because of 2.0 warp lines
(glitchrunner on).
What I've done is made it so there are three states to glitchrunner mode
now: 2.0 (previously just the "on" state), 2.2 (previously a state you
couldn't use), and "off". Furthermore, I made it an enum, so in case
future versions of the game patch out more glitches, we can add them to
the enum (and the only other thing we have to update is a lookup table
in GlitchrunnerMode.c). Also, 2.2 glitches exist in 2.0, so you'll want
to use GlitchrunnerMode_less_than_or_equal() to check glitchrunner
version.
2021-08-05 02:09:49 +02:00
|
|
|
#include "GlitchrunnerMode.h"
|
2020-07-19 21:05:41 +02:00
|
|
|
#include "Graphics.h"
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "MakeAndPlay.h"
|
2020-07-19 21:05:41 +02:00
|
|
|
#include "Music.h"
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "Script.h"
|
2020-07-19 21:05:41 +02:00
|
|
|
#include "UtilityClass.h"
|
|
|
|
|
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
|
|
|
mapclass::mapclass(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Start here!
|
|
|
|
colstatedelay = 0;
|
|
|
|
colsuperstate = 0;
|
|
|
|
spikeleveltop = 0;
|
|
|
|
spikelevelbottom = 0;
|
2020-04-30 21:58:08 +02:00
|
|
|
oldspikeleveltop = 0;
|
|
|
|
oldspikelevelbottom = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
warpx = false;
|
|
|
|
warpy = false;
|
|
|
|
extrarow = 0;
|
|
|
|
|
|
|
|
showteleporters = false;
|
|
|
|
showtargets = false;
|
|
|
|
showtrinkets = false;
|
|
|
|
|
|
|
|
finalmode = false;
|
|
|
|
finalstretch = false;
|
|
|
|
|
|
|
|
cursorstate = 0;
|
|
|
|
cursordelay = 0;
|
|
|
|
|
2020-06-25 23:49:54 +02:00
|
|
|
towermode = false;
|
|
|
|
cameraseekframe = 0;
|
|
|
|
resumedelay = 0;
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
final_colormode = false;
|
|
|
|
final_colorframe = 0;
|
|
|
|
final_colorframedelay = 0;
|
|
|
|
final_mapcol = 0;
|
|
|
|
final_aniframe = 0;
|
|
|
|
final_aniframedelay = 0;
|
|
|
|
|
|
|
|
custommode=false;
|
|
|
|
custommodeforreal=false;
|
|
|
|
customwidth=20; customheight=20;
|
|
|
|
custommmxoff=0; custommmyoff=0; custommmxsize=0; custommmysize=0;
|
|
|
|
customzoom=0;
|
|
|
|
customshowmm=true;
|
|
|
|
|
|
|
|
rcol = 0;
|
|
|
|
|
|
|
|
//This needs to be in map instead!
|
|
|
|
invincibility = false;
|
|
|
|
|
|
|
|
//We init the lookup table:
|
2020-07-03 11:06:46 +02:00
|
|
|
for (size_t i = 0; i < SDL_arraysize(vmult); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-03 11:06:46 +02:00
|
|
|
vmult[i] = i * 40;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
//We create a blank map
|
2020-07-03 11:06:46 +02:00
|
|
|
SDL_memset(contents, 0, sizeof(contents));
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 06:01:09 +02:00
|
|
|
SDL_memset(roomdeaths, 0, sizeof(roomdeaths));
|
|
|
|
SDL_memset(roomdeathsfinal, 0, sizeof(roomdeathsfinal));
|
|
|
|
resetmap();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
tileset = 0;
|
|
|
|
initmapdata();
|
|
|
|
|
|
|
|
resetnames();
|
|
|
|
|
2020-07-03 06:13:23 +02:00
|
|
|
ypos = 0;
|
|
|
|
oldypos = 0;
|
2020-07-06 22:04:34 +02:00
|
|
|
|
|
|
|
background = 0;
|
|
|
|
cameramode = 0;
|
|
|
|
cameraseek = 0;
|
|
|
|
minitowermode = false;
|
|
|
|
roomtexton = false;
|
2020-07-03 06:13:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//Areamap starts at 100,100 and extends 20x20
|
|
|
|
const int mapclass::areamap[] = {
|
2020-05-18 02:05:07 +02:00
|
|
|
1,2,2,2,2,2,2,2,0,3,0,0,0,4,4,4,4,4,4,4,
|
|
|
|
1,2,2,2,2,2,2,0,0,3,0,0,0,0,4,4,4,4,4,4,
|
|
|
|
0,1,0,0,2,0,0,0,0,3,0,0,0,0,4,4,4,4,4,4,
|
|
|
|
0,0,0,0,2,0,0,0,0,3,0,0,5,5,5,5,4,4,4,4,
|
|
|
|
0,0,2,2,2,0,0,0,0,3,11,11,5,5,5,5,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,3,5,5,5,5,5,5,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,3,5,5,5,5,5,5,5,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,3,5,5,5,5,5,5,5,5,5,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,3,0,0,0,5,5,5,5,5,5,0,
|
|
|
|
0,0,0,0,0,0,0,0,11,3,0,0,0,5,5,5,5,5,5,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,3,0,0,0,5,5,5,5,5,5,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,3,0,5,5,5,5,5,5,5,5,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,3,0,5,5,5,5,5,5,0,5,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,3,0,5,5,5,5,5,5,0,5,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,3,0,5,5,0,0,0,0,0,5,0,
|
|
|
|
0,0,0,0,0,0,0,2,0,3,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,2,2,2,2,2,2,0,3,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,2,2,2,2,2,2,2,0,3,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
2,2,2,2,2,0,0,2,0,3,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
2,2,2,2,2,0,0,2,0,3,0,0,0,0,0,0,0,0,0,0,
|
2020-07-03 06:13:23 +02:00
|
|
|
};
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
int mapclass::intpol(int a, int b, float c)
|
|
|
|
{
|
|
|
|
return static_cast<int>(a + ((b - a) * c));
|
|
|
|
}
|
|
|
|
|
2020-04-15 04:29:19 +02:00
|
|
|
void mapclass::setteleporter(int x, int y)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-15 04:25:12 +02:00
|
|
|
point temp;
|
|
|
|
temp.x = x;
|
|
|
|
temp.y = y;
|
|
|
|
teleporters.push_back(temp);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-04-15 04:37:00 +02:00
|
|
|
void mapclass::settrinket(int x, int y)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-15 04:34:10 +02:00
|
|
|
point temp;
|
|
|
|
temp.x = x;
|
|
|
|
temp.y = y;
|
|
|
|
shinytrinkets.push_back(temp);
|
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 mapclass::resetmap(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//clear the explored area of the map
|
2020-07-03 06:01:09 +02:00
|
|
|
SDL_memset(explored, 0, sizeof(explored));
|
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 mapclass::resetnames(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Reset all the special names
|
|
|
|
specialnames[0] = "Rear Window";
|
|
|
|
specialnames[1] = "On the Waterfront";
|
|
|
|
specialnames[2] = "The Untouchables";
|
|
|
|
specialnames[3] = "Television Newsveel";
|
|
|
|
specialnames[4] = "Vwitched";
|
|
|
|
specialnames[5] = "Gvnsmoke";
|
|
|
|
specialnames[6] = "Please enjoy these repeats";
|
|
|
|
specialnames[7] = "Try Jiggling the Antenna";
|
|
|
|
|
|
|
|
glitchmode = 0;
|
|
|
|
glitchdelay = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void mapclass::transformname(int t)
|
|
|
|
{
|
|
|
|
//transform special names into new ones, one step at a time
|
|
|
|
|
|
|
|
glitchdelay--;
|
|
|
|
if(glitchdelay<=0)
|
|
|
|
{
|
|
|
|
switch(t)
|
|
|
|
{
|
|
|
|
case 3:
|
|
|
|
//Television Newsveel -> The 9 O'Clock News
|
|
|
|
if (specialnames[3] == "Television Newsveel")
|
|
|
|
{
|
|
|
|
specialnames[3] = "Television Newsvel";
|
|
|
|
}
|
|
|
|
else if (specialnames[3] == "Television Newsvel")
|
|
|
|
{
|
|
|
|
specialnames[3] = "TelevisvonvNewsvel";
|
|
|
|
}
|
|
|
|
else if (specialnames[3] == "TelevisvonvNewsvel")
|
|
|
|
{
|
|
|
|
specialnames[3] = "TvlvvvsvonvNevsvel";
|
|
|
|
}
|
|
|
|
else if (specialnames[3] == "TvlvvvsvonvNevsvel")
|
|
|
|
{
|
|
|
|
specialnames[3] = "vvvvvvsvovvNe svel";
|
|
|
|
}
|
|
|
|
else if (specialnames[3] == "vvvvvvsvovvNe svel")
|
|
|
|
{
|
|
|
|
specialnames[3] = "vhv vvv'vvovv vevl";
|
|
|
|
}
|
|
|
|
else if (specialnames[3] == "vhv vvv'vvovv vevl")
|
|
|
|
{
|
|
|
|
specialnames[3] = "vhv V v'Cvovv vewv";
|
|
|
|
}
|
|
|
|
else if (specialnames[3] == "vhv V v'Cvovv vewv")
|
|
|
|
{
|
|
|
|
specialnames[3] = "vhe 9 v'Cvovv vewv";
|
|
|
|
}
|
|
|
|
else if (specialnames[3] == "vhe 9 v'Cvovv vewv")
|
|
|
|
{
|
|
|
|
specialnames[3] = "vhe 9 v'Cvovv Newv";
|
|
|
|
}
|
|
|
|
else if (specialnames[3] == "vhe 9 v'Cvovv Newv")
|
|
|
|
{
|
|
|
|
specialnames[3] = "The 9 O'Cvovk Newv";
|
|
|
|
}
|
|
|
|
else if (specialnames[3] == "The 9 O'Cvovk Newv")
|
|
|
|
{
|
|
|
|
specialnames[3] = "The 9 O'Clock News";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
//Vwitched -> Dial M for Murder
|
|
|
|
if (specialnames[4] == "Vwitched")
|
|
|
|
{
|
|
|
|
specialnames[4] = "Vwitvhed";
|
|
|
|
}
|
|
|
|
else if (specialnames[4] == "Vwitvhed")
|
|
|
|
{
|
|
|
|
specialnames[4] = "vVwivcvedv";
|
|
|
|
}
|
|
|
|
else if (specialnames[4] == "vVwivcvedv")
|
|
|
|
{
|
|
|
|
specialnames[4] = "vvvwMvcvMdvv";
|
|
|
|
}
|
|
|
|
else if (specialnames[4] == "vvvwMvcvMdvv")
|
|
|
|
{
|
|
|
|
specialnames[4] = "DvvvwMvfvvMdvvv";
|
|
|
|
}
|
|
|
|
else if (specialnames[4] == "DvvvwMvfvvMdvvv")
|
|
|
|
{
|
|
|
|
specialnames[4] = "Dvav Mvfvr Mdvvvv";
|
|
|
|
}
|
|
|
|
else if (specialnames[4] == "Dvav Mvfvr Mdvvvv")
|
|
|
|
{
|
|
|
|
specialnames[4] = "Diav M for Mdrver";
|
|
|
|
}
|
|
|
|
else if (specialnames[4] == "Diav M for Mdrver")
|
|
|
|
{
|
|
|
|
specialnames[4] = "Dial M for Murder";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
//Gvnsmoke -> Gunsmoke 1966
|
|
|
|
if (specialnames[5] == "Gvnsmoke")
|
|
|
|
{
|
|
|
|
specialnames[5] = "Gvnsmove";
|
|
|
|
}
|
|
|
|
else if (specialnames[5] == "Gvnsmove")
|
|
|
|
{
|
|
|
|
specialnames[5] = "Gvnvmovevv";
|
|
|
|
}
|
|
|
|
else if (specialnames[5] == "Gvnvmovevv")
|
|
|
|
{
|
|
|
|
specialnames[5] = "Gunvmove1vv6";
|
|
|
|
}
|
|
|
|
else if (specialnames[5] == "Gunvmove1vv6")
|
|
|
|
{
|
|
|
|
specialnames[5] = "Vunsmoke 19v6";
|
|
|
|
}
|
|
|
|
else if (specialnames[5] == "Vunsmoke 19v6")
|
|
|
|
{
|
|
|
|
specialnames[5] = "Gunsmoke 1966";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
//Please enjoy these repeats -> In the Margins
|
|
|
|
if (specialnames[6] == "Please enjoy these repeats")
|
|
|
|
{
|
|
|
|
specialnames[6] = "Please envoy theve repeats";
|
|
|
|
}
|
|
|
|
else if (specialnames[6] == "Please envoy theve repeats")
|
|
|
|
{
|
|
|
|
specialnames[6] = "Plse envoy tse rvpvas";
|
|
|
|
}
|
|
|
|
else if (specialnames[6] == "Plase envoy these rvpeas")
|
|
|
|
{
|
|
|
|
specialnames[6] = "Plse envoy tse rvpvas";
|
|
|
|
}
|
|
|
|
else if (specialnames[6] == "Plse envoy tse rvpvas")
|
|
|
|
{
|
|
|
|
specialnames[6] = "Vl envoy te rvevs";
|
|
|
|
}
|
|
|
|
else if (specialnames[6] == "Vl envoy te rvevs")
|
|
|
|
{
|
|
|
|
specialnames[6] = "Vv evo tv vevs";
|
|
|
|
}
|
|
|
|
else if (specialnames[6] == "Vv evo tv vevs")
|
|
|
|
{
|
|
|
|
specialnames[6] = "Iv vhv Mvrvivs";
|
|
|
|
}
|
|
|
|
else if (specialnames[6] == "Iv vhv Mvrvivs")
|
|
|
|
{
|
|
|
|
specialnames[6] = "In the Margins";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
//Try Jiggling the Antenna -> Heaven's Gate
|
|
|
|
if (specialnames[7] == "Try Jiggling the Antenna")
|
|
|
|
{
|
|
|
|
specialnames[7] = "Try Viggling the Antenna";
|
|
|
|
}
|
|
|
|
else if (specialnames[7] == "Try Viggling the Antenna")
|
|
|
|
{
|
|
|
|
specialnames[7] = "TryJivglvng theAvtevna";
|
|
|
|
}
|
|
|
|
else if (specialnames[7] == "TryJivglvng theAvtevna")
|
|
|
|
{
|
|
|
|
specialnames[7] = "Tvvivglvng thAvtvvv";
|
|
|
|
}
|
|
|
|
else if (specialnames[7] == "Tvvivglvng thAvtvvv")
|
|
|
|
{
|
|
|
|
specialnames[7] = "Vvvgglvnv tvnvva";
|
|
|
|
}
|
|
|
|
else if (specialnames[7] == "Vvvgglvnv tvnvva")
|
|
|
|
{
|
|
|
|
specialnames[7] = "Vvavvnvs vvtv";
|
|
|
|
}
|
|
|
|
else if (specialnames[7] == "Vvavvnvs vvtv")
|
|
|
|
{
|
|
|
|
specialnames[7] = "Veavvn's Gvte";
|
|
|
|
}
|
|
|
|
else if (specialnames[7] == "Veavvn's Gvte")
|
|
|
|
{
|
|
|
|
specialnames[7] = "Heaven's Gate";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
glitchdelay = 5;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
glitchdelay--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string mapclass::getglitchname(int x, int y)
|
|
|
|
{
|
|
|
|
//Returns the name in the final area.
|
|
|
|
if (roomname == "glitch")
|
|
|
|
{
|
|
|
|
//8 Cases!
|
|
|
|
//First, the three "glitches"
|
|
|
|
glitchdelay--;
|
|
|
|
if (glitchdelay <= -5)
|
|
|
|
{
|
|
|
|
glitchmode = (glitchmode + 1) % 2;
|
|
|
|
glitchdelay = 0;
|
|
|
|
if (glitchmode == 0) glitchdelay = 20 +int(fRandom() * 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x == 42 && y == 51)
|
|
|
|
{
|
|
|
|
if (glitchmode == 0)
|
|
|
|
{
|
|
|
|
return specialnames[0];
|
|
|
|
}
|
|
|
|
else return "Rear Vindow";
|
|
|
|
}
|
|
|
|
else if (x == 48 && y == 51)
|
|
|
|
{
|
|
|
|
if (glitchmode == 0)
|
|
|
|
{
|
|
|
|
return specialnames[1];
|
|
|
|
}
|
|
|
|
else return "On the Vaterfront";
|
|
|
|
}
|
|
|
|
else if (x == 49 && y == 51)
|
|
|
|
{
|
|
|
|
if (glitchmode == 0)
|
|
|
|
{
|
|
|
|
return specialnames[2];
|
|
|
|
}
|
|
|
|
else return "The Untouchavles";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (roomname == "change")
|
|
|
|
{
|
|
|
|
if (finalstretch)
|
|
|
|
{
|
|
|
|
if (x == 45 && y == 51) transformname(3);
|
|
|
|
if (x == 46 && y == 51) transformname(4);
|
|
|
|
if (x == 47 && y == 51) transformname(5);
|
|
|
|
if (x == 50 && y == 53) transformname(6);
|
|
|
|
if (x == 50 && y == 54) transformname(7);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x == 45 && y == 51) return specialnames[3];
|
|
|
|
if (x == 46 && y == 51) return specialnames[4];
|
|
|
|
if (x == 47 && y == 51) return specialnames[5];
|
|
|
|
if (x == 50 && y == 53) return specialnames[6];
|
|
|
|
if (x == 50 && y == 54) return specialnames[7];
|
|
|
|
return roomname;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return roomname;
|
|
|
|
}
|
|
|
|
return roomname;
|
|
|
|
}
|
|
|
|
|
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 mapclass::initmapdata(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-30 21:30:19 +02:00
|
|
|
if (custommode)
|
|
|
|
{
|
|
|
|
initcustommapdata();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
teleporters.clear();
|
|
|
|
shinytrinkets.clear();
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
//Set up static map information like teleporters and shiny trinkets.
|
2020-04-15 04:29:19 +02:00
|
|
|
setteleporter(0, 0);
|
|
|
|
setteleporter(0, 16);
|
|
|
|
setteleporter(2, 4);
|
|
|
|
setteleporter(2, 11);
|
|
|
|
setteleporter(7, 9);
|
|
|
|
setteleporter(7, 15);
|
|
|
|
setteleporter(8, 11);
|
|
|
|
setteleporter(10, 5);
|
|
|
|
setteleporter(11, 4);
|
|
|
|
setteleporter(13, 2);
|
|
|
|
setteleporter(13, 8);
|
|
|
|
setteleporter(14, 19);
|
|
|
|
setteleporter(15, 0);
|
|
|
|
setteleporter(17, 12);
|
|
|
|
setteleporter(17, 17);
|
|
|
|
setteleporter(18, 1);
|
|
|
|
setteleporter(18, 7);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-15 04:37:00 +02:00
|
|
|
settrinket(14, 4);
|
|
|
|
settrinket(13, 6);
|
|
|
|
settrinket(11, 12);
|
|
|
|
settrinket(15, 12);
|
|
|
|
settrinket(14, 11);
|
|
|
|
settrinket(18, 14);
|
|
|
|
settrinket(11, 7);
|
|
|
|
settrinket(9, 2);
|
|
|
|
settrinket(9, 16);
|
|
|
|
settrinket(2, 18);
|
|
|
|
settrinket(7, 18);
|
|
|
|
settrinket(6, 1);
|
|
|
|
settrinket(17, 3);
|
|
|
|
settrinket(10, 19);
|
|
|
|
settrinket(5, 15);
|
|
|
|
settrinket(1, 10);
|
|
|
|
settrinket(3, 2);
|
|
|
|
settrinket(10, 8);
|
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 mapclass::initcustommapdata(void)
|
2020-06-30 21:30:19 +02:00
|
|
|
{
|
|
|
|
shinytrinkets.clear();
|
|
|
|
|
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
|
|
|
for (size_t i = 0; i < edentity.size(); i++)
|
|
|
|
{
|
|
|
|
const edentities& ent = edentity[i];
|
|
|
|
if (ent.t != 9)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const int rx = ent.x / 40;
|
|
|
|
const int ry = ent.y / 30;
|
|
|
|
|
|
|
|
settrinket(rx, ry);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
int mapclass::finalat(int x, int y)
|
|
|
|
{
|
|
|
|
//return the tile index of the final stretch tiles offset by the colour difference
|
|
|
|
if (contents[x + vmult[y]] == 740)
|
|
|
|
{
|
|
|
|
//Special case: animated tiles
|
|
|
|
if (final_mapcol == 1)
|
|
|
|
{
|
Reduce dependency on libc functions
During 2.3 development, there's been a gradual shift to using SDL stdlib
functions instead of libc functions, but there are still some libc
functions (or the same libc function but from the STL) in the code.
Well, this patch replaces all the rest of them in one fell swoop.
SDL's stdlib can replace most of these, but its SDL_min() and SDL_max()
are inadequate - they aren't really functions, they're more like macros
with a nasty penchant for double-evaluation. So I just made my own
VVV_min() and VVV_max() functions and placed them in Maths.h instead,
then replaced all the previous usages of min(), max(), std::min(),
std::max(), SDL_min(), and SDL_max() with VVV_min() and VVV_max().
Additionally, there's no SDL_isxdigit(), so I just implemented my own
VVV_isxdigit().
SDL has SDL_malloc() and SDL_free(), but they have some refcounting
built in to them, so in order to use them with LodePNG, I have to
replace the malloc() and free() that LodePNG uses. Which isn't too hard,
I did it in a new file called ThirdPartyDeps.c, and LodePNG is now
compiled with the LODEPNG_NO_COMPILE_ALLOCATORS definition.
Lastly, I also refactored the awful strcpy() and strcat() usages in
PLATFORM_migrateSaveData() to use SDL_snprintf() instead. I know save
migration is getting axed in 2.4, but it still bothers me to have
something like that in the codebase otherwise.
Without further ado, here is the full list of functions that the
codebase now uses:
- SDL_strlcpy() instead of strcpy()
- SDL_strlcat() instead of strcat()
- SDL_snprintf() instead of sprintf(), strcpy(), or strcat() (see above)
- VVV_min() instead of min(), std::min(), or SDL_min()
- VVV_max() instead of max(), std::max(), or SDL_max()
- VVV_isxdigit() instead of isxdigit()
- SDL_strcmp() instead of strcmp()
- SDL_strcasecmp() instead of strcasecmp() or Win32 strcmpi()
- SDL_strstr() instead of strstr()
- SDL_strlen() instead of strlen()
- SDL_sscanf() instead of sscanf()
- SDL_getenv() instead of getenv()
- SDL_malloc() instead of malloc() (replacing in LodePNG as well)
- SDL_free() instead of free() (replacing in LodePNG as well)
2021-01-12 01:17:45 +01:00
|
|
|
return 737 + (int(fRandom() * 11) * 40);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return contents[x + vmult[y]] - (final_mapcol * 3) + (final_aniframe * 40);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (contents[x + vmult[y]] >= 80)
|
|
|
|
{
|
|
|
|
return contents[x + vmult[y]] - (final_mapcol * 3);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return contents[x + vmult[y]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int mapclass::maptiletoenemycol(int t)
|
|
|
|
{
|
|
|
|
//returns the colour index for enemies that matches the map colour t
|
|
|
|
switch(t)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
return 11;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
return 6;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
return 8;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
return 12;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
return 9;
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
return 7;
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
return 18;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 11;
|
|
|
|
}
|
|
|
|
|
2020-03-31 10:09:42 +02:00
|
|
|
void mapclass::changefinalcol(int t)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//change the map to colour t - for the game's final stretch.
|
|
|
|
//First up, the tiles. This is just a setting:
|
|
|
|
final_mapcol = t;
|
2020-07-03 04:20:22 +02:00
|
|
|
int temp = 6 - t;
|
2020-01-01 21:29:24 +01:00
|
|
|
//Next, entities
|
2020-04-03 22:50:16 +02:00
|
|
|
for (size_t i = 0; i < obj.entities.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (obj.entities[i].type == 1) //something with a movement behavior
|
|
|
|
{
|
|
|
|
if (obj.entities[i].animate == 10 || obj.entities[i].animate == 11) //treadmill
|
|
|
|
{
|
|
|
|
if(temp<3)
|
|
|
|
{
|
|
|
|
obj.entities[i].tile = 907 + (temp * 80);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
obj.entities[i].tile = 911 + ((temp-3) * 80);
|
|
|
|
}
|
|
|
|
if(obj.entities[i].animate == 10) obj.entities[i].tile += 40;
|
|
|
|
}
|
|
|
|
else if (obj.entities[i].isplatform)
|
|
|
|
{
|
|
|
|
obj.entities[i].tile = 915+(temp*40);
|
|
|
|
}
|
|
|
|
else //just an enemy
|
|
|
|
{
|
|
|
|
obj.entities[i].colour = maptiletoenemycol(temp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (obj.entities[i].type == 2) //disappearing platforms
|
|
|
|
{
|
|
|
|
obj.entities[i].tile = 915+(temp*40);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-08 02:37:38 +01:00
|
|
|
void mapclass::setcol(TowerBG& bg_obj, const int r1, const int g1, const int b1 , const int r2, const int g2, const int b2, const int c)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2021-01-08 02:37:38 +01:00
|
|
|
bg_obj.r = intpol(r1, r2, c / 5);
|
|
|
|
bg_obj.g = intpol(g1, g2, c / 5);
|
|
|
|
bg_obj.b = intpol(b1, b2, c / 5);
|
|
|
|
}
|
|
|
|
|
|
|
|
void mapclass::updatebgobj(TowerBG& bg_obj)
|
|
|
|
{
|
|
|
|
const int check = bg_obj.colstate % 5; //current state of phase
|
|
|
|
const int cmode = (bg_obj.colstate - check) / 5; // current colour transition;
|
|
|
|
|
|
|
|
switch(cmode)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
setcol(bg_obj, 255, 93, 107, 255, 255, 93, check);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
setcol(bg_obj, 255, 255, 93, 159, 255, 93, check);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
setcol(bg_obj, 159, 255, 93, 93, 245, 255, check);
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
setcol(bg_obj, 93, 245, 255, 177, 93, 255, check);
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
setcol(bg_obj, 177, 93, 255, 255, 93, 255, check);
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
setcol(bg_obj, 255, 93, 255, 255, 93, 107, check);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
bg_obj.tdrawback = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
void mapclass::setbgobjlerp(TowerBG& bg_obj)
|
|
|
|
{
|
|
|
|
bg_obj.bypos = ypos / 2;
|
|
|
|
bg_obj.bscroll = (ypos - oldypos) / 2;
|
|
|
|
}
|
|
|
|
|
2020-11-03 00:05:24 +01:00
|
|
|
void mapclass::updatetowerglow(TowerBG& bg_obj)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (colstatedelay <= 0 || colsuperstate > 0)
|
|
|
|
{
|
2020-11-03 00:05:24 +01:00
|
|
|
if (colsuperstate > 0) bg_obj.colstate--;
|
|
|
|
bg_obj.colstate++;
|
|
|
|
if (bg_obj.colstate >= 30) bg_obj.colstate = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2021-01-08 02:37:38 +01:00
|
|
|
const int check = bg_obj.colstate % 5;
|
|
|
|
updatebgobj(bg_obj);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
if (check == 0)
|
|
|
|
{
|
|
|
|
colstatedelay = 45;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
colstatedelay = 0;
|
|
|
|
}
|
|
|
|
if (colsuperstate > 0) colstatedelay = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
colstatedelay--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 mapclass::nexttowercolour(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-11-03 00:23:53 +01:00
|
|
|
graphics.titlebg.colstate+=5;
|
|
|
|
if (graphics.titlebg.colstate >= 30) graphics.titlebg.colstate = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2021-01-08 02:37:38 +01:00
|
|
|
updatebgobj(graphics.titlebg);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void mapclass::settowercolour(int t)
|
|
|
|
{
|
2020-11-03 00:23:53 +01:00
|
|
|
graphics.titlebg.colstate=t*5;
|
|
|
|
if (graphics.titlebg.colstate >= 30) graphics.titlebg.colstate = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2021-01-08 02:37:38 +01:00
|
|
|
updatebgobj(graphics.titlebg);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool mapclass::spikecollide(int x, int y)
|
|
|
|
{
|
|
|
|
if (invincibility) return false;
|
|
|
|
if (tower.at(x,y,0)>= 6 && tower.at(x,y,0) <= 11) return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool mapclass::collide(int x, int y)
|
|
|
|
{
|
|
|
|
if (towermode)
|
|
|
|
{
|
|
|
|
if (tower.at(x, y, 0) >= 12 && tower.at(x, y, 0) <= 27) return true;
|
2020-02-05 22:02:00 +01:00
|
|
|
if (invincibility)
|
|
|
|
{
|
|
|
|
if (tower.at(x, y, 0) >= 6 && tower.at(x, y, 0) <= 11) return true;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (tileset == 2)
|
|
|
|
{
|
|
|
|
if (y == -1) return collide(x, y + 1);
|
|
|
|
if (y == 29+extrarow) return collide(x, y - 1);
|
|
|
|
if (x == -1) return collide(x + 1, y);
|
|
|
|
if (x == 40) return collide(x - 1, y);
|
|
|
|
if (x < 0 || y < 0 || x >= 40 || y >= 29 + extrarow) return false;
|
|
|
|
if (contents[x + vmult[y]] >= 12 && contents[x + vmult[y]] <= 27) return true;
|
|
|
|
if (invincibility)
|
|
|
|
{
|
|
|
|
if (contents[x + vmult[y]] >= 6 && contents[x + vmult[y]] <= 11) return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (y == -1) return collide(x, y + 1);
|
|
|
|
if (y == 29+extrarow) return collide(x, y - 1);
|
|
|
|
if (x == -1) return collide(x + 1, y);
|
|
|
|
if (x == 40) return collide(x - 1, y);
|
|
|
|
if (x < 0 || y < 0 || x >= 40 || y >= 29+extrarow) return false;
|
|
|
|
if (contents[x + vmult[y]] == 1) return true;
|
|
|
|
if (tileset==0 && contents[x + vmult[y]] == 59) return true;
|
|
|
|
if (contents[x + vmult[y]]>= 80 && contents[x + vmult[y]] < 680) return true;
|
|
|
|
if (contents[x + vmult[y]] == 740 && tileset==1) return true;
|
|
|
|
if (invincibility)
|
|
|
|
{
|
|
|
|
if (contents[x + vmult[y]]>= 6 && contents[x + vmult[y]] <= 9) return true;
|
|
|
|
if (contents[x + vmult[y]]>= 49 && contents[x + vmult[y]] <= 50) return true;
|
|
|
|
if (tileset == 1)
|
|
|
|
{
|
|
|
|
if (contents[x + vmult[y]]>= 49 && contents[x + vmult[y]] < 80) return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void mapclass::settile(int xp, int yp, int t)
|
|
|
|
{
|
|
|
|
if (xp >= 0 && xp < 40 && yp >= 0 && yp < 29+extrarow)
|
|
|
|
{
|
|
|
|
contents[xp + vmult[yp]] = t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int mapclass::area(int _rx, int _ry)
|
|
|
|
{
|
|
|
|
//THIS IS THE BUG
|
|
|
|
if (finalmode)
|
|
|
|
{
|
|
|
|
return 6;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int lookup = (_rx - 100) + ((_ry - 100) * 20);
|
|
|
|
if(_rx-100>=0 && _rx-100<20 && _ry-100>=0 && _ry-100<20){
|
|
|
|
return areamap[lookup];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return 6;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-24 20:12:39 +01:00
|
|
|
bool mapclass::isexplored(const int rx, const int ry)
|
|
|
|
{
|
|
|
|
const int roomnum = rx + ry*20;
|
2021-03-24 20:14:28 +01:00
|
|
|
if (INBOUNDS_ARR(roomnum, explored))
|
|
|
|
{
|
|
|
|
return explored[roomnum];
|
|
|
|
}
|
|
|
|
return false;
|
2021-03-24 20:12:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void mapclass::setexplored(const int rx, const int ry, const bool status)
|
|
|
|
{
|
|
|
|
const int roomnum = rx + ry*20;
|
2021-03-24 20:14:28 +01:00
|
|
|
if (INBOUNDS_ARR(roomnum, explored))
|
|
|
|
{
|
|
|
|
explored[roomnum] = status;
|
|
|
|
}
|
2021-03-24 20:12:39 +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 mapclass::exploretower(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
for (int i = 0; i < 20; i++)
|
|
|
|
{
|
2021-03-24 20:12:39 +01:00
|
|
|
setexplored(9, i, true);
|
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 mapclass::hideship(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//remove the ship from the explored areas
|
2021-03-24 20:12:39 +01:00
|
|
|
setexplored(2, 10, false);
|
|
|
|
setexplored(3, 10, false);
|
|
|
|
setexplored(4, 10, false);
|
|
|
|
setexplored(2, 11, false);
|
|
|
|
setexplored(3, 11, false);
|
|
|
|
setexplored(4, 11, 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 mapclass::showship(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2021-03-24 20:12:39 +01:00
|
|
|
//show the ship in the explored areas
|
|
|
|
setexplored(2, 10, true);
|
|
|
|
setexplored(3, 10, true);
|
|
|
|
setexplored(4, 10, true);
|
|
|
|
setexplored(2, 11, true);
|
|
|
|
setexplored(3, 11, true);
|
|
|
|
setexplored(4, 11, true);
|
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 mapclass::resetplayer(void)
|
2020-12-28 22:16:16 +01:00
|
|
|
{
|
|
|
|
resetplayer(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void mapclass::resetplayer(const bool player_died)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-01-25 19:20:37 +01:00
|
|
|
bool was_in_tower = towermode;
|
2020-01-01 21:29:24 +01:00
|
|
|
if (game.roomx != game.saverx || game.roomy != game.savery)
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
gotoroom(game.saverx, game.savery);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
game.deathseq = -1;
|
|
|
|
int 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
|
|
|
{
|
|
|
|
obj.entities[i].vx = 0;
|
|
|
|
obj.entities[i].vy = 0;
|
|
|
|
obj.entities[i].ax = 0;
|
|
|
|
obj.entities[i].ay = 0;
|
|
|
|
obj.entities[i].xp = game.savex;
|
|
|
|
obj.entities[i].yp = game.savey;
|
Re-fix glitchy y-position when spawning onto a conveyor
This is a re-do of 942217f871bf1182ded3a915b8c26b90c04d561a (#509), but
with a more conservative fix that only resets the player's newxp and
newyp when they respawn from a checkpoint or spawn in to the map.
Unlike the previous patch, if the player were to suddenly collide with a
conveyor or horizontally-moving platform during gameplay, their
y-position would revert back to the intended next y-position of the
previous frame. But this is the same behavior as before, I haven't ever
seen such a contrived situation come up, and this behavior is probably
more preferable for gameplay than actually going to the conveyor, so
it's fine.
I also decided to reset newxp here, and not just newyp, because while
resetting newyp seems to be enough, it's safer to also reset newxp (and
so future readers won't question why only newyp is reset but not newxp).
I tested this and it once again fixes the death loop issue from earlier,
while also still allowing for that Trench Warfare trick to be possible
(I tested it with the libTAS movie I mentioned in #606; it syncs fine).
There are no other known regressions resulting from this fix
(hopefully).
2021-02-02 01:16:04 +01:00
|
|
|
|
|
|
|
//Fix conveyor death loop glitch
|
|
|
|
obj.entities[i].newxp = obj.entities[i].xp;
|
|
|
|
obj.entities[i].newyp = obj.entities[i].yp;
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
obj.entities[i].dir = game.savedir;
|
|
|
|
obj.entities[i].colour = 0;
|
2020-12-28 22:16:16 +01:00
|
|
|
if (player_died)
|
|
|
|
{
|
|
|
|
game.lifeseq = 10;
|
|
|
|
obj.entities[i].invis = true;
|
|
|
|
}
|
2020-12-28 22:23:42 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
obj.entities[i].invis = false;
|
|
|
|
}
|
Split glitchrunner mode into multiple versions
Previously, turning glitchrunner mode on essentially locked you to
emulating 2.0, and turning it off just meant normal 2.3 behavior. But
what if you wanted 2.2 behavior instead? Well, that's what I had to ask
when a TAS of mine would desync in 2.3 because of the two-frame delay
fix (glitchrunner off), but would also desync because of 2.0 warp lines
(glitchrunner on).
What I've done is made it so there are three states to glitchrunner mode
now: 2.0 (previously just the "on" state), 2.2 (previously a state you
couldn't use), and "off". Furthermore, I made it an enum, so in case
future versions of the game patch out more glitches, we can add them to
the enum (and the only other thing we have to update is a lookup table
in GlitchrunnerMode.c). Also, 2.2 glitches exist in 2.0, so you'll want
to use GlitchrunnerMode_less_than_or_equal() to check glitchrunner
version.
2021-08-05 02:09:49 +02:00
|
|
|
if (!GlitchrunnerMode_less_than_or_equal(Glitchrunner2_2))
|
2020-06-25 23:56:09 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].size = 0;
|
|
|
|
obj.entities[i].cx = 6;
|
|
|
|
obj.entities[i].cy = 2;
|
2020-06-30 00:34:13 +02:00
|
|
|
obj.entities[i].w = 12;
|
2020-06-25 23:56:09 +02:00
|
|
|
obj.entities[i].h = 21;
|
|
|
|
}
|
2020-01-25 19:20:37 +01:00
|
|
|
|
|
|
|
// If we entered a tower as part of respawn, reposition camera
|
|
|
|
if (!was_in_tower && towermode)
|
|
|
|
{
|
|
|
|
ypos = obj.entities[i].yp - 120;
|
|
|
|
if (ypos < 0)
|
|
|
|
{
|
|
|
|
ypos = 0;
|
|
|
|
}
|
2020-04-30 21:02:52 +02:00
|
|
|
oldypos = ypos;
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
setbgobjlerp(graphics.towerbg);
|
2020-01-25 19:20:37 +01:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
game.scmhurt = false; //Just in case the supercrewmate is fucking this up!
|
|
|
|
if (game.supercrewmate)
|
|
|
|
{
|
|
|
|
if (game.roomx == game.scmprogress + 41)
|
|
|
|
{
|
|
|
|
game.scmprogress = game.roomx - 41;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
game.scmprogress = game.roomx - 40;
|
|
|
|
}
|
|
|
|
if (game.scmprogress != 0)
|
|
|
|
{
|
|
|
|
game.scmmoveme = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
game.scmmoveme = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-31 10:09:42 +02:00
|
|
|
void mapclass::warpto(int rx, int ry , int t, int tx, int ty)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
gotoroom(rx, ry);
|
2020-01-01 21:29:24 +01:00
|
|
|
game.teleport = false;
|
2020-09-08 09:31:44 +02:00
|
|
|
if (INBOUNDS_VEC(t, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[t].xp = tx * 8;
|
|
|
|
obj.entities[t].yp = (ty * 8) - obj.entities[t].h;
|
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[t].lerpoldxp = obj.entities[t].xp;
|
|
|
|
obj.entities[t].lerpoldyp = obj.entities[t].yp;
|
2020-06-13 05:36:08 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
game.gravitycontrol = 0;
|
|
|
|
}
|
|
|
|
|
2020-03-31 10:09:42 +02:00
|
|
|
void mapclass::gotoroom(int rx, int ry)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//First, destroy the current room
|
|
|
|
obj.removeallblocks();
|
|
|
|
game.activetele = false;
|
|
|
|
game.readytotele = 0;
|
2020-11-04 09:53:43 +01:00
|
|
|
game.oldreadytotele = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//Ok, let's save the position of all lines on the screen
|
2020-04-03 22:50:16 +02:00
|
|
|
obj.linecrosskludge.clear();
|
|
|
|
for (size_t i = 0; i < obj.entities.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-03 22:50:16 +02:00
|
|
|
if (obj.entities[i].type == 9)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//It's a horizontal line
|
|
|
|
if (obj.entities[i].xp <= 0 || (obj.entities[i].xp + obj.entities[i].w) >= 312)
|
|
|
|
{
|
|
|
|
//it's on a screen edge
|
|
|
|
obj.copylinecross(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 all entities in the room, and deallocate any unnecessary entity slots. */
|
|
|
|
/* However don't disable player entities, but do preserve holes between them (if any). */
|
|
|
|
bool player_found = false;
|
|
|
|
for (int i = obj.entities.size() - 1; i >= 0; --i)
|
2020-01-01 21:29:24 +01: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
|
|
|
/* Iterate in reverse order to prevent unnecessary indice shifting */
|
|
|
|
if (obj.entities[i].rule == 0)
|
|
|
|
{
|
|
|
|
player_found = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!player_found)
|
|
|
|
{
|
|
|
|
obj.entities.erase(obj.entities.begin() + i);
|
|
|
|
}
|
|
|
|
else
|
2020-01-13 23:43:11 +01: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-01-13 23:43:11 +01:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
game.door_up = rx + ((ry - 1) * 100);
|
|
|
|
game.door_down = rx + ((ry + 1) * 100);
|
|
|
|
game.door_right = rx + 1 + (ry * 100);
|
|
|
|
game.door_left = rx -1 + (ry * 100);
|
|
|
|
|
|
|
|
if (rx < game.roomx)
|
|
|
|
{
|
|
|
|
game.roomchangedir = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
game.roomchangedir = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (finalmode)
|
|
|
|
{
|
|
|
|
//Ok, what way are we moving?
|
2021-01-11 04:58:36 +01:00
|
|
|
game.roomx = rx;
|
|
|
|
game.roomy = ry;
|
2020-01-01 21:29:24 +01:00
|
|
|
game.roomchange = true;
|
|
|
|
|
|
|
|
if (game.roomy < 10)
|
|
|
|
{
|
|
|
|
game.roomy = 11;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(game.roomx>=41 && game.roomy>=48 && game.roomx<61 && game.roomy<68 )
|
|
|
|
{
|
|
|
|
game.currentroomdeaths = roomdeathsfinal[game.roomx - 41 + (20 * (game.roomy - 48))];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
game.currentroomdeaths = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Final level for time trial
|
|
|
|
if (game.intimetrial)
|
|
|
|
{
|
|
|
|
if (game.roomx == 46 && game.roomy == 54) music.niceplay(15); //Final level remix
|
|
|
|
}
|
|
|
|
}
|
2020-02-10 03:21:19 +01:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
2020-01-01 21:29:24 +01:00
|
|
|
else if (custommode)
|
|
|
|
{
|
|
|
|
game.roomx = rx;
|
|
|
|
game.roomy = ry;
|
|
|
|
game.roomchange = true;
|
|
|
|
if (game.roomx < 100) game.roomx = 100 + ed.mapwidth-1;
|
|
|
|
if (game.roomy < 100) game.roomy = 100 + ed.mapheight-1;
|
|
|
|
if (game.roomx > 100 + ed.mapwidth-1) game.roomx = 100;
|
|
|
|
if (game.roomy > 100 + ed.mapheight-1) game.roomy = 100;
|
|
|
|
}
|
2020-02-10 01:53:01 +01:00
|
|
|
#endif
|
2020-01-01 21:29:24 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
game.roomx = rx;
|
|
|
|
game.roomy = ry;
|
|
|
|
game.roomchange = true;
|
|
|
|
if (game.roomx < 100) game.roomx = 119;
|
|
|
|
if (game.roomy < 100) game.roomy = 119;
|
|
|
|
if (game.roomx > 119) game.roomx = 100;
|
|
|
|
if (game.roomy > 119) game.roomy = 100;
|
|
|
|
|
|
|
|
game.currentroomdeaths = roomdeaths[game.roomx - 100 + (20 * (game.roomy - 100))];
|
|
|
|
|
|
|
|
//Alright, change music depending on where we are:
|
|
|
|
//Tower
|
|
|
|
if (game.roomx == 107 && game.roomy == 106) music.niceplay(4);
|
|
|
|
if (game.roomx == 107 && game.roomy == 107) music.niceplay(4);
|
|
|
|
if (game.roomx == 107 && game.roomy == 108) music.niceplay(4);
|
|
|
|
if (game.roomx == 107 && game.roomy == 109) music.niceplay(4);
|
|
|
|
if (game.roomx == 108 && game.roomy == 109)
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
if (graphics.setflipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
music.niceplay(9);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
music.niceplay(2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (game.roomx == 109)
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
if (graphics.setflipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
music.niceplay(9);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
music.niceplay(2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//Warp Zone
|
|
|
|
if (game.roomx == 112 && game.roomy == 101) music.niceplay(4);
|
|
|
|
if (game.roomx == 113 && game.roomy == 101) music.niceplay(4);
|
|
|
|
if (game.roomx == 113 && game.roomy == 102) music.niceplay(4);
|
|
|
|
if (game.roomx == 114 && game.roomy == 101) music.niceplay(12);
|
|
|
|
if (game.roomx == 115 && game.roomy == 101) music.niceplay(12);
|
|
|
|
if (game.roomx == 115 && game.roomy == 102) music.niceplay(12);
|
|
|
|
//Lab
|
|
|
|
if (game.roomx == 101 && game.roomy == 115) music.niceplay(4);
|
|
|
|
if (game.roomx == 100 && game.roomy == 115) music.niceplay(4);
|
|
|
|
if (game.roomx == 101 && game.roomy == 116) music.niceplay(4);
|
|
|
|
if (game.roomx == 100 && game.roomy == 116) music.niceplay(4);
|
|
|
|
if (game.roomx == 102 && game.roomy == 116) music.niceplay(3);
|
|
|
|
if (game.roomx == 102 && game.roomy == 117) music.niceplay(3);
|
|
|
|
if (game.roomx == 101 && game.roomy == 117) music.niceplay(3);
|
|
|
|
//Space Station
|
|
|
|
if (game.intimetrial)
|
|
|
|
{
|
|
|
|
if (game.roomx == 111 && game.roomy == 112) music.niceplay(1);
|
|
|
|
if (game.roomx == 111 && game.roomy == 113) music.niceplay(1);
|
|
|
|
if (game.roomx == 112 && game.roomy == 114) music.niceplay(1);
|
|
|
|
if (game.roomx == 112 && game.roomy == 115) music.niceplay(1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (game.roomx == 111 && game.roomy == 112) music.niceplay(1);
|
|
|
|
if (game.roomx == 111 && game.roomy == 113) music.niceplay(1);
|
|
|
|
if (game.roomx == 112 && game.roomy == 114) music.niceplay(4);
|
|
|
|
if (game.roomx == 112 && game.roomy == 115) music.niceplay(4);
|
|
|
|
}
|
|
|
|
//Leaving the Ship
|
|
|
|
if (game.roomx == 104 && game.roomy == 112) music.niceplay(4);
|
|
|
|
}
|
2020-07-03 04:20:22 +02:00
|
|
|
int temp = rx + (ry * 100);
|
2020-03-31 10:09:42 +02:00
|
|
|
loadlevel(game.roomx, game.roomy);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
2020-01-27 23:46:11 +01:00
|
|
|
//Do we need to reload the background?
|
|
|
|
bool redrawbg = game.roomx != game.prevroomx || game.roomy != game.prevroomy;
|
|
|
|
|
|
|
|
if(redrawbg)
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
graphics.backgrounddrawn = false; //Used for background caching speedup
|
2020-01-27 23:46:11 +01:00
|
|
|
}
|
2020-03-31 10:09:42 +02:00
|
|
|
graphics.foregrounddrawn = false; //Used for background caching speedup
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-01-27 23:46:11 +01:00
|
|
|
game.prevroomx = game.roomx;
|
|
|
|
game.prevroomy = game.roomy;
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
//a very special case: if entering the communication room, room 13,4 before tag 5 is set, set the game state to a background
|
|
|
|
//textbox thingy. if tag five is not set when changing room, reset the game state. (tag 5 is set when you get back to the ship)
|
|
|
|
if(!game.intimetrial && !custommode)
|
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[5] && !finalmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
game.state = 0;
|
|
|
|
if (game.roomx == 113 && game.roomy == 104)
|
|
|
|
{
|
|
|
|
game.state = 50;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Ok, kludge to fix lines in crossing rooms - if we're intersecting a gravity line right now, let's
|
|
|
|
//set it to an inactive state.
|
|
|
|
|
|
|
|
//Alright! So, let's look at our lines from the previous rooms, and determine if any of them are actually
|
|
|
|
//continuations!
|
|
|
|
|
|
|
|
temp = 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(temp, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-11-01 05:57:08 +01:00
|
|
|
obj.entities[temp].oldxp = obj.entities[temp].xp;
|
|
|
|
obj.entities[temp].oldyp = obj.entities[temp].yp;
|
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[temp].lerpoldxp = obj.entities[temp].xp - int(obj.entities[temp].vx);
|
|
|
|
obj.entities[temp].lerpoldyp = obj.entities[temp].yp - int(obj.entities[temp].vy);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-04-03 22:50:16 +02:00
|
|
|
for (size_t i = 0; i < obj.entities.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-03 22:50:16 +02:00
|
|
|
if (obj.entities[i].type == 9)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//It's a horizontal line
|
|
|
|
if (obj.entities[i].xp <= 0 || obj.entities[i].xp + obj.entities[i].w >= 312)
|
|
|
|
{
|
|
|
|
//it's on a screen edge
|
2020-06-14 20:21:32 +02:00
|
|
|
for (size_t j = 0; j < obj.linecrosskludge.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (obj.entities[i].yp == obj.linecrosskludge[j].yp)
|
|
|
|
{
|
|
|
|
//y's match, how about x's?
|
|
|
|
//we're moving left:
|
|
|
|
if (game.roomchangedir == 0)
|
|
|
|
{
|
|
|
|
if (obj.entities[i].xp + obj.entities[i].w >= 312 && obj.linecrosskludge[j].xp <= 0)
|
|
|
|
{
|
|
|
|
obj.revertlinecross(i, j);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (obj.entities[i].xp <= 0 && obj.linecrosskludge[j].xp + obj.linecrosskludge[j].w >= 312)
|
|
|
|
{
|
|
|
|
obj.revertlinecross(i, j);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string mapclass::currentarea(int t)
|
|
|
|
{
|
|
|
|
switch(t)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
return "Dimension VVVVVV";
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
return "Dimension VVVVVV";
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
return "Laboratory";
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
return "The Tower";
|
|
|
|
break;
|
|
|
|
case 4:
|
2020-01-16 02:24:56 +01:00
|
|
|
return "Warp Zone";
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
return "Space Station";
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
return "Outside Dimension VVVVVV";
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
return "Outside Dimension VVVVVV";
|
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
return "Outside Dimension VVVVVV";
|
|
|
|
break;
|
|
|
|
case 9:
|
|
|
|
return "Outside Dimension VVVVVV";
|
|
|
|
break;
|
|
|
|
case 10:
|
|
|
|
return "Outside Dimension VVVVVV";
|
|
|
|
break;
|
|
|
|
case 11:
|
|
|
|
return "The Tower";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return "???";
|
|
|
|
}
|
|
|
|
|
2020-03-31 10:09:42 +02:00
|
|
|
void mapclass::loadlevel(int rx, int ry)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
int t;
|
|
|
|
if (!finalmode)
|
|
|
|
{
|
2021-03-24 20:12:39 +01:00
|
|
|
setexplored(rx - 100, ry - 100, true);
|
2020-01-01 21:29:24 +01:00
|
|
|
if (rx == 109 && !custommode)
|
|
|
|
{
|
|
|
|
exploretower();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
roomtexton = false;
|
Refactor roomtext to not use ad-hoc objects / separate length trackers
This refactors the roomtext code to (1) not use ad-hoc objects and (2)
not use a separate length-tracking variable to keep track of the actual
amount of roomtext in a room.
What I mean by ad-hoc object is, instead of formally creating a
fully-fledged struct or class and storing one vector containing that
object, this game instead hacks together an object by storing each
attribute of an object in different vectors.
In the case of roomtext, instead of making a Roomtext object that has
attributes 'x', 'y', and 'text', the 'text' attribute of each is stored
in the vector 'roomtext', the 'x' attribute of each is stored in the
vector 'roomtextx', and the 'y' attribute of each is stored in the
vector 'roomtexty'. It's only an object in the sense that you can grab
the attributes of each roomtext by using the same index across all three
vectors.
This makes it somewhat annoying to maintain and deal with, like when I
wanted add sub-tile positions to roomtext in VVVVVV: Community Edition.
Instead of being able to add attributes to an already-existing
formalized Roomtext object, I would instead have to add two more
vectors, which is inelegant. Or I could refactor the whole system, which
is what I decided to do instead.
Furthermore, this removes the separate length-tracking variable
'roomtextnumlines', which makes the code much more easy to maintain and
deal with, as the amount of roomtext is naturally tracked by C++ instead
of us having to keep track of the actual amount of roomtext manually.
2020-03-01 03:26:12 +01:00
|
|
|
roomtext.clear();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
obj.platformtile = 0;
|
|
|
|
obj.customplatformtile=0;
|
|
|
|
obj.vertplatforms = false;
|
|
|
|
obj.horplatforms = false;
|
|
|
|
roomname = "";
|
Refactor how "hidden names" work
By "hidden names", I'm referring to "Dimension VVVVVV" and "The Ship"
popping up on the quit/pause/teleporter screens, even though those rooms
don't have any roomnames.
Apparently my commit to fix roomname re-draw bleed on the
quit/pause/teleporter screens exposed yet another hardreset()-caused
bug. The issue here is that since hardreset() sets game.roomx and
game.roomy to 0, map.area() will no longer work properly, and since the
hidden roomname check is based on map.area(), it will no longer display
"Dimension VVVVVV" or "The Ship" once you press ACTION to quit. It used
to do this due to the re-draw bleed, but now it doesn't.
I saw that roomnames didn't get reset in hardreset(), so the solution
here is to re-factor hidden names to be an actual variable, instead of
being implicit. map.hiddenname is a variable that's set in
mapclass::loadlevel(), and if isn't empty, it will be drawn on the
quit/pause/teleporter screens. That way it will still display "Dimension
VVVVVV" and "The Ship" when you press ACTION to quit to the menu.
EDIT: Since PR #230 got merged, this commit is no longer strictly
necessary, but it's still good to refactor hidden names like this.
2020-05-02 23:10:22 +02:00
|
|
|
hiddenname = "";
|
2020-01-01 21:29:24 +01:00
|
|
|
background = 1;
|
|
|
|
warpx = false;
|
|
|
|
warpy = false;
|
|
|
|
|
|
|
|
towermode = false;
|
|
|
|
ypos = 0;
|
2020-04-30 21:02:52 +02:00
|
|
|
oldypos = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
extrarow = 0;
|
2020-04-30 21:50:45 +02:00
|
|
|
spikeleveltop = 0;
|
|
|
|
spikelevelbottom = 0;
|
2020-04-30 21:58:08 +02:00
|
|
|
oldspikeleveltop = 0;
|
|
|
|
oldspikelevelbottom = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//Custom stuff for warplines
|
|
|
|
obj.customwarpmode=false;
|
|
|
|
obj.customwarpmodevon=false;
|
|
|
|
obj.customwarpmodehon=false;
|
|
|
|
|
|
|
|
if (finalmode)
|
|
|
|
{
|
|
|
|
t = 6;
|
|
|
|
//check if we're in the towers
|
|
|
|
if (rx == 49 && ry == 52)
|
|
|
|
{
|
|
|
|
//entered tower 1
|
|
|
|
t = 7;
|
|
|
|
}
|
|
|
|
else if (rx == 49 && ry == 53)
|
|
|
|
{
|
|
|
|
//re entered tower 1
|
|
|
|
t = 8;
|
|
|
|
}
|
|
|
|
else if (rx == 51 && ry == 54)
|
|
|
|
{
|
|
|
|
//entered tower 2
|
|
|
|
t = 9;
|
|
|
|
}
|
|
|
|
else if (rx == 51 && ry == 53)
|
|
|
|
{
|
|
|
|
//re entered tower 2
|
|
|
|
t = 10;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (custommode)
|
|
|
|
{
|
|
|
|
t= 12;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
t = area(rx, ry);
|
|
|
|
|
|
|
|
if (t == 3)
|
|
|
|
{
|
|
|
|
//correct position for tower
|
|
|
|
if (ry == 109)
|
|
|
|
{
|
|
|
|
//entered from ground floor
|
|
|
|
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].yp += (671 * 8);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
ypos = (700-29) * 8;
|
2020-04-30 21:02:52 +02:00
|
|
|
oldypos = ypos;
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
setbgobjlerp(graphics.towerbg);
|
2020-01-01 21:29:24 +01:00
|
|
|
cameramode = 0;
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.colstate = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
colsuperstate = 0;
|
|
|
|
}
|
|
|
|
else if (ry == 104)
|
|
|
|
{
|
|
|
|
//you've entered from the top floor
|
|
|
|
ypos = 0;
|
2020-04-30 21:02:52 +02:00
|
|
|
oldypos = ypos;
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
setbgobjlerp(graphics.towerbg);
|
2020-01-01 21:29:24 +01:00
|
|
|
cameramode = 0;
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.colstate = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
colsuperstate = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (t < 2) //on the world map, want to test if we're in the secret lab
|
|
|
|
{
|
|
|
|
if (rx >= 116)
|
|
|
|
{
|
|
|
|
if (ry >= 105)
|
|
|
|
{
|
|
|
|
if (ry <= 107)
|
|
|
|
{
|
|
|
|
if (rx == 119 && ry == 105)
|
|
|
|
{
|
|
|
|
//Ah, this is just a normal area
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//in the secret lab! Crazy background!
|
|
|
|
background = 2;
|
2020-03-31 10:09:42 +02:00
|
|
|
if (rx == 116 && ry == 105) graphics.rcol = 1;
|
|
|
|
if (rx == 117 && ry == 105) graphics.rcol = 5;
|
|
|
|
if (rx == 118 && ry == 105) graphics.rcol = 4;
|
|
|
|
if (rx == 117 && ry == 106) graphics.rcol = 2;
|
|
|
|
if (rx == 118 && ry == 106) graphics.rcol = 0;
|
|
|
|
if (rx == 119 && ry == 106) graphics.rcol = 3;
|
|
|
|
if (rx == 119 && ry == 107) graphics.rcol = 1;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-02 11:37:23 +01:00
|
|
|
if (rx == 119 && ry == 108 && !custommode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
background = 5;
|
2020-03-31 10:09:42 +02:00
|
|
|
graphics.rcol = 3;
|
2020-01-01 21:29:24 +01:00
|
|
|
warpx = true;
|
|
|
|
warpy = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(t)
|
|
|
|
{
|
2020-04-02 22:09:22 +02:00
|
|
|
#if !defined(MAKEANDPLAY)
|
2020-04-16 02:52:25 +02:00
|
|
|
case 0:
|
2020-01-01 21:29:24 +01:00
|
|
|
case 1: //World Map
|
2020-07-03 11:06:46 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
tileset = 1;
|
|
|
|
extrarow = 1;
|
2020-07-19 06:06:35 +02:00
|
|
|
const short* tmap = otherlevel.loadlevel(rx, ry);
|
2020-07-03 11:06:46 +02:00
|
|
|
SDL_memcpy(contents, tmap, sizeof(contents));
|
2020-01-01 21:29:24 +01:00
|
|
|
roomname = otherlevel.roomname;
|
|
|
|
tileset = otherlevel.roomtileset;
|
|
|
|
//do the appear/remove roomname here
|
|
|
|
|
Refactor how "hidden names" work
By "hidden names", I'm referring to "Dimension VVVVVV" and "The Ship"
popping up on the quit/pause/teleporter screens, even though those rooms
don't have any roomnames.
Apparently my commit to fix roomname re-draw bleed on the
quit/pause/teleporter screens exposed yet another hardreset()-caused
bug. The issue here is that since hardreset() sets game.roomx and
game.roomy to 0, map.area() will no longer work properly, and since the
hidden roomname check is based on map.area(), it will no longer display
"Dimension VVVVVV" or "The Ship" once you press ACTION to quit. It used
to do this due to the re-draw bleed, but now it doesn't.
I saw that roomnames didn't get reset in hardreset(), so the solution
here is to re-factor hidden names to be an actual variable, instead of
being implicit. map.hiddenname is a variable that's set in
mapclass::loadlevel(), and if isn't empty, it will be drawn on the
quit/pause/teleporter screens. That way it will still display "Dimension
VVVVVV" and "The Ship" when you press ACTION to quit to the menu.
EDIT: Since PR #230 got merged, this commit is no longer strictly
necessary, but it's still good to refactor hidden names like this.
2020-05-02 23:10:22 +02:00
|
|
|
if (game.roomx >= 102 && game.roomx <= 104 && game.roomy >= 110 && game.roomy <= 111)
|
|
|
|
{
|
|
|
|
hiddenname = "The Ship";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
hiddenname = "Dimension VVVVVV";
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-07-03 11:06:46 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 2: //The Lab
|
2020-07-03 11:06:46 +02:00
|
|
|
{
|
2020-07-19 06:06:35 +02:00
|
|
|
const short* tmap = lablevel.loadlevel(rx, ry);
|
2020-07-03 11:06:46 +02:00
|
|
|
SDL_memcpy(contents, tmap, sizeof(contents));
|
2020-01-01 21:29:24 +01:00
|
|
|
roomname = lablevel.roomname;
|
|
|
|
tileset = 1;
|
|
|
|
background = 2;
|
2020-03-31 10:09:42 +02:00
|
|
|
graphics.rcol = lablevel.rcol;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-07-03 11:06:46 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 3: //The Tower
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.tdrawback = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
minitowermode = false;
|
|
|
|
tower.minitowermode = false;
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.scrolldir = 0;
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
setbgobjlerp(graphics.towerbg);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
roomname = "The Tower";
|
|
|
|
tileset = 1;
|
|
|
|
background = 3;
|
|
|
|
towermode = true;
|
|
|
|
|
|
|
|
//All the entities for here are just loaded here; it's essentially one room after all
|
|
|
|
|
|
|
|
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(48, 5456, 10, 1, 505007); // (savepoint)
|
|
|
|
obj.createentity(224, 4528, 10, 1, 505017); // (savepoint)
|
|
|
|
obj.createentity(232, 4168, 10, 0, 505027); // (savepoint)
|
|
|
|
obj.createentity(280, 3816, 10, 1, 505037); // (savepoint)
|
|
|
|
obj.createentity(152, 3552, 10, 1, 505047); // (savepoint)
|
|
|
|
obj.createentity(216, 3280, 10, 0, 505057); // (savepoint)
|
|
|
|
obj.createentity(216, 4808, 10, 1, 505067); // (savepoint)
|
|
|
|
obj.createentity(72, 3096, 10, 0, 505077); // (savepoint)
|
|
|
|
obj.createentity(176, 2600, 10, 0, 505087); // (savepoint)
|
|
|
|
obj.createentity(216, 2392, 10, 0, 505097); // (savepoint)
|
|
|
|
obj.createentity(152, 1184, 10, 1, 505107); // (savepoint)
|
|
|
|
obj.createentity(152, 912, 10, 1, 505117); // (savepoint)
|
|
|
|
obj.createentity(152, 536, 10, 1, 505127); // (savepoint)
|
|
|
|
obj.createentity(120, 5136, 10, 0, 505137); // (savepoint)
|
|
|
|
obj.createentity(144, 1824, 10, 0, 505147); // (savepoint)
|
|
|
|
obj.createentity(72, 2904, 10, 0, 505157); // (savepoint)
|
|
|
|
obj.createentity(224, 1648, 10, 1, 505167); // (savepoint)
|
|
|
|
obj.createentity(112, 5280, 10, 1, 50517); // (savepoint)
|
|
|
|
|
|
|
|
obj.createentity(24, 4216, 9, 7); // (shiny trinket)
|
|
|
|
obj.createentity(280, 3216, 9, 8); // (shiny trinket)
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4: //The Warpzone
|
2020-07-03 11:06:46 +02:00
|
|
|
{
|
2020-07-19 06:06:35 +02:00
|
|
|
const short* tmap = warplevel.loadlevel(rx, ry);
|
2020-07-03 11:06:46 +02:00
|
|
|
SDL_memcpy(contents, tmap, sizeof(contents));
|
2020-01-01 21:29:24 +01:00
|
|
|
roomname = warplevel.roomname;
|
|
|
|
tileset = 1;
|
|
|
|
background = 3;
|
2020-03-31 10:09:42 +02:00
|
|
|
graphics.rcol = warplevel.rcol;
|
|
|
|
graphics.backgrounddrawn = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
warpx = warplevel.warpx;
|
|
|
|
warpy = warplevel.warpy;
|
|
|
|
background = 5;
|
|
|
|
if (warpy) background = 4;
|
|
|
|
if (warpx) background = 3;
|
|
|
|
if (warpx && warpy) background = 5;
|
|
|
|
break;
|
2020-07-03 11:06:46 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 5: //Space station
|
2020-07-03 11:06:46 +02:00
|
|
|
{
|
2020-07-19 06:06:35 +02:00
|
|
|
const short* tmap = spacestation2.loadlevel(rx, ry);
|
2020-07-03 11:06:46 +02:00
|
|
|
SDL_memcpy(contents, tmap, sizeof(contents));
|
2020-01-01 21:29:24 +01:00
|
|
|
roomname = spacestation2.roomname;
|
|
|
|
tileset = 0;
|
|
|
|
break;
|
2020-07-03 11:06:46 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 6: //final level
|
2020-07-03 11:06:46 +02:00
|
|
|
{
|
2021-01-11 04:58:36 +01:00
|
|
|
const short* tmap = finallevel.loadlevel(rx, ry);
|
2020-07-03 11:06:46 +02:00
|
|
|
SDL_memcpy(contents, tmap, sizeof(contents));
|
2020-01-01 21:29:24 +01:00
|
|
|
roomname = finallevel.roomname;
|
|
|
|
tileset = 1;
|
|
|
|
background = 3;
|
2020-03-31 10:09:42 +02:00
|
|
|
graphics.backgrounddrawn = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
if (finalstretch)
|
|
|
|
{
|
|
|
|
background = 6;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
warpx = finallevel.warpx;
|
|
|
|
warpy = finallevel.warpy;
|
|
|
|
background = 5;
|
|
|
|
if (warpy) background = 4;
|
|
|
|
if (warpx) background = 3;
|
|
|
|
if (warpx && warpy) background = 5;
|
|
|
|
}
|
|
|
|
|
2020-03-31 10:09:42 +02:00
|
|
|
graphics.rcol = 6;
|
|
|
|
changefinalcol(final_mapcol);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-07-03 11:06:46 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 7: //Final Level, Tower 1
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.tdrawback = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
minitowermode = true;
|
|
|
|
tower.minitowermode = true;
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.scrolldir = 1;
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
setbgobjlerp(graphics.towerbg);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
roomname = "Panic Room";
|
|
|
|
tileset = 1;
|
|
|
|
background = 3;
|
|
|
|
towermode = true;
|
|
|
|
|
|
|
|
tower.loadminitower1();
|
|
|
|
|
|
|
|
ypos = 0;
|
2020-04-30 21:02:52 +02:00
|
|
|
oldypos = 0;
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
setbgobjlerp(graphics.towerbg);
|
2020-01-01 21:29:24 +01:00
|
|
|
cameramode = 0;
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.colstate = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
colsuperstate = 0;
|
|
|
|
break;
|
|
|
|
case 8: //Final Level, Tower 1 (reentered from below)
|
|
|
|
{
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.tdrawback = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
minitowermode = true;
|
|
|
|
tower.minitowermode = true;
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.scrolldir = 1;
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
setbgobjlerp(graphics.towerbg);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
roomname = "Panic Room";
|
|
|
|
tileset = 1;
|
|
|
|
background = 3;
|
|
|
|
towermode = true;
|
|
|
|
|
|
|
|
tower.loadminitower1();
|
|
|
|
|
|
|
|
int 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].yp += (71 * 8);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
game.roomy--;
|
|
|
|
|
|
|
|
ypos = (100-29) * 8;
|
2020-04-30 21:02:52 +02:00
|
|
|
oldypos = ypos;
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
setbgobjlerp(graphics.towerbg);
|
2020-01-01 21:29:24 +01:00
|
|
|
cameramode = 0;
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.colstate = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
colsuperstate = 0;}
|
|
|
|
break;
|
|
|
|
case 9: //Final Level, Tower 2
|
|
|
|
{
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.tdrawback = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
minitowermode = true;
|
|
|
|
tower.minitowermode = true;
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.scrolldir = 0;
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
setbgobjlerp(graphics.towerbg);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
roomname = "The Final Challenge";
|
|
|
|
tileset = 1;
|
|
|
|
background = 3;
|
|
|
|
towermode = true;
|
|
|
|
|
|
|
|
tower.loadminitower2();
|
|
|
|
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(56, 556, 11, 136); // (horizontal gravity line)
|
|
|
|
obj.createentity(184, 592, 10, 0, 50500); // (savepoint)
|
|
|
|
obj.createentity(184, 644, 11, 88); // (horizontal gravity line)
|
|
|
|
obj.createentity(56, 460, 11, 136); // (horizontal gravity line)
|
|
|
|
obj.createentity(216, 440, 10, 0, 50501); // (savepoint)
|
|
|
|
obj.createentity(104, 508, 11, 168); // (horizontal gravity line)
|
|
|
|
obj.createentity(219, 264, 12, 56); // (vertical gravity line)
|
|
|
|
obj.createentity(120, 332, 11, 96); // (horizontal gravity line)
|
|
|
|
obj.createentity(219, 344, 12, 56); // (vertical gravity line)
|
|
|
|
obj.createentity(224, 332, 11, 48); // (horizontal gravity line)
|
|
|
|
obj.createentity(56, 212, 11, 144); // (horizontal gravity line)
|
|
|
|
obj.createentity(32, 20, 11, 96); // (horizontal gravity line)
|
|
|
|
obj.createentity(72, 156, 11, 200); // (horizontal gravity line)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
int 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].yp += (71 * 8);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
game.roomy--;
|
|
|
|
|
|
|
|
ypos = (100-29) * 8;
|
2020-04-30 21:02:52 +02:00
|
|
|
oldypos = ypos;
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
setbgobjlerp(graphics.towerbg);
|
2020-01-01 21:29:24 +01:00
|
|
|
cameramode = 0;
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.colstate = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
colsuperstate = 0;
|
|
|
|
break;
|
2020-07-09 11:59:42 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 10: //Final Level, Tower 2
|
|
|
|
{
|
|
|
|
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.tdrawback = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
minitowermode = true;
|
|
|
|
tower.minitowermode = true;
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.scrolldir = 0;
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
setbgobjlerp(graphics.towerbg);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
roomname = "The Final Challenge";
|
|
|
|
tileset = 1;
|
|
|
|
background = 3;
|
|
|
|
towermode = true;
|
|
|
|
|
|
|
|
tower.loadminitower2();
|
|
|
|
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(56, 556, 11, 136); // (horizontal gravity line)
|
|
|
|
obj.createentity(184, 592, 10, 0, 50500); // (savepoint)
|
|
|
|
obj.createentity(184, 644, 11, 88); // (horizontal gravity line)
|
|
|
|
obj.createentity(56, 460, 11, 136); // (horizontal gravity line)
|
|
|
|
obj.createentity(216, 440, 10, 0, 50501); // (savepoint)
|
|
|
|
obj.createentity(104, 508, 11, 168); // (horizontal gravity line)
|
|
|
|
obj.createentity(219, 264, 12, 56); // (vertical gravity line)
|
|
|
|
obj.createentity(120, 332, 11, 96); // (horizontal gravity line)
|
|
|
|
obj.createentity(219, 344, 12, 56); // (vertical gravity line)
|
|
|
|
obj.createentity(224, 332, 11, 48); // (horizontal gravity line)
|
|
|
|
obj.createentity(56, 212, 11, 144); // (horizontal gravity line)
|
|
|
|
obj.createentity(32, 20, 11, 96); // (horizontal gravity line)
|
|
|
|
obj.createentity(72, 156, 11, 200); // (horizontal gravity line)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
ypos = 0;
|
2020-04-30 21:02:52 +02:00
|
|
|
oldypos = 0;
|
Consolidate tower BG bypos and bscroll assignments
Tower backgrounds have a bypos and bscroll. bypos is just the y-position
of the background, and bscroll is the amount of pixels to scroll the
background by on each frame, which is used to scroll it (if it's not
being redrawn) and for linear interpolation.
For the tower background (and not the title background), bypos is
map.ypos / 2, and bscroll is (map.ypos - map.oldypos) / 2. However,
usually bscroll gets assigned at the same time bypos is incremented or
decremented, so you never see that calculation explicitly - except in
the previous commit, where I worked out the calculation because the
change in y-position isn't a known constant.
Having to do all these calculations every time introduces the
possibility of errors where you forget to do it, or you do it wrongly.
But that's not even the worst; you could cause a linear interpolation
glitch if you decide to overwrite bscroll without taking into account
map.oldypos and map.ypos.
So that's why I'm adding a function that automatically updates the tower
background, using the values of map.oldypos and map.ypos, that is used
every time map.ypos is assigned. That way, we have to write less code,
you can be sure that there's no place where we forget to do the
calculations (or at least it will be glaringly obvious) or we do it
wrongly, and it plays nicely with linear interpolation. This also
replaces every instance where the manual calculations are done with the
new function.
2021-04-23 03:47:07 +02:00
|
|
|
setbgobjlerp(graphics.towerbg);
|
2020-01-01 21:29:24 +01:00
|
|
|
cameramode = 0;
|
2020-11-03 00:05:24 +01:00
|
|
|
graphics.towerbg.colstate = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
colsuperstate = 0;
|
|
|
|
break;
|
2020-07-09 11:59:42 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 11: //Tower Hallways //Content is held in final level routine
|
|
|
|
{
|
2020-07-19 06:06:35 +02:00
|
|
|
const short* tmap = finallevel.loadlevel(rx, ry);
|
2020-07-03 11:06:46 +02:00
|
|
|
SDL_memcpy(contents, tmap, sizeof(contents));
|
2020-01-01 21:29:24 +01:00
|
|
|
roomname = finallevel.roomname;
|
|
|
|
tileset = 2;
|
|
|
|
if (rx == 108)
|
|
|
|
{
|
|
|
|
background = 7;
|
|
|
|
rcol = 15;
|
|
|
|
}
|
|
|
|
if (rx == 110)
|
|
|
|
{
|
|
|
|
background = 8;
|
|
|
|
rcol = 10;
|
|
|
|
}
|
|
|
|
if (rx == 111)
|
|
|
|
{
|
|
|
|
background = 9;
|
|
|
|
rcol = 0;
|
|
|
|
}
|
|
|
|
break;
|
2020-07-09 11:59:42 +02:00
|
|
|
}
|
2020-04-02 22:09:22 +02:00
|
|
|
#endif
|
2020-02-10 03:21:19 +01:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
2020-01-01 21:29:24 +01:00
|
|
|
case 12: //Custom level
|
2020-07-09 11:58:16 +02:00
|
|
|
{
|
2021-03-24 19:59:36 +01:00
|
|
|
const edlevelclass* const room = ed.getroomprop(rx - 100, ry - 100);
|
|
|
|
game.customcol = ed.getlevelcol(room->tileset, room->tilecol) + 1;
|
2020-07-09 12:29:53 +02:00
|
|
|
obj.customplatformtile = game.customcol * 12;
|
|
|
|
|
2021-03-24 19:59:36 +01:00
|
|
|
switch (room->tileset)
|
2020-07-09 12:29:53 +02:00
|
|
|
{
|
|
|
|
case 0: // Space Station
|
2020-01-01 21:29:24 +01:00
|
|
|
tileset = 0;
|
|
|
|
background = 1;
|
|
|
|
break;
|
2020-07-09 12:29:53 +02:00
|
|
|
case 1: // Outside
|
2020-01-01 21:29:24 +01:00
|
|
|
tileset = 1;
|
|
|
|
background = 1;
|
|
|
|
break;
|
2020-07-09 12:29:53 +02:00
|
|
|
case 2: // Lab
|
2020-01-01 21:29:24 +01:00
|
|
|
tileset = 1;
|
|
|
|
background = 2;
|
2021-03-24 19:59:36 +01:00
|
|
|
graphics.rcol = room->tilecol;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-07-09 12:29:53 +02:00
|
|
|
case 3: // Warp Zone/intermission
|
2020-01-01 21:29:24 +01:00
|
|
|
tileset = 1;
|
|
|
|
background = 6;
|
|
|
|
break;
|
2020-07-09 12:29:53 +02:00
|
|
|
case 4: // Ship
|
2020-01-01 21:29:24 +01:00
|
|
|
tileset = 1;
|
|
|
|
background = 1;
|
|
|
|
break;
|
2020-07-09 12:18:21 +02:00
|
|
|
default:
|
2020-01-01 21:29:24 +01:00
|
|
|
tileset = 1;
|
|
|
|
background = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-07-09 12:29:53 +02:00
|
|
|
// If screen warping, then override all that:
|
2020-01-27 23:46:11 +01:00
|
|
|
bool redrawbg = game.roomx != game.prevroomx || game.roomy != game.prevroomy;
|
2020-07-09 12:29:53 +02:00
|
|
|
if (redrawbg)
|
|
|
|
{
|
2020-03-31 10:09:42 +02:00
|
|
|
graphics.backgrounddrawn = false;
|
2020-01-27 23:46:11 +01:00
|
|
|
}
|
2020-07-09 12:29:53 +02:00
|
|
|
|
2021-03-24 19:59:36 +01:00
|
|
|
switch (room->warpdir)
|
2020-07-09 12:21:18 +02:00
|
|
|
{
|
|
|
|
case 1:
|
2020-07-09 12:29:53 +02:00
|
|
|
warpx = true;
|
|
|
|
background = 3;
|
|
|
|
graphics.rcol = ed.getwarpbackground(rx - 100, ry - 100);
|
2020-07-09 12:21:18 +02:00
|
|
|
break;
|
|
|
|
case 2:
|
2020-07-09 12:29:53 +02:00
|
|
|
warpy = true;
|
|
|
|
background = 4;
|
|
|
|
graphics.rcol = ed.getwarpbackground(rx - 100, ry - 100);
|
2020-07-09 12:21:18 +02:00
|
|
|
break;
|
|
|
|
case 3:
|
2020-07-09 12:29:53 +02:00
|
|
|
warpx = true;
|
|
|
|
warpy = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
background = 5;
|
2020-07-09 12:29:53 +02:00
|
|
|
graphics.rcol = ed.getwarpbackground(rx - 100, ry - 100);
|
2020-07-09 12:21:18 +02:00
|
|
|
break;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2021-03-24 19:59:36 +01:00
|
|
|
roomname = room->roomname;
|
2020-01-01 21:29:24 +01:00
|
|
|
extrarow = 1;
|
2020-07-19 06:06:35 +02:00
|
|
|
const short* tmap = ed.loadlevel(rx, ry);
|
2020-07-03 11:06:46 +02:00
|
|
|
SDL_memcpy(contents, tmap, sizeof(contents));
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
roomtexton = false;
|
Refactor roomtext to not use ad-hoc objects / separate length trackers
This refactors the roomtext code to (1) not use ad-hoc objects and (2)
not use a separate length-tracking variable to keep track of the actual
amount of roomtext in a room.
What I mean by ad-hoc object is, instead of formally creating a
fully-fledged struct or class and storing one vector containing that
object, this game instead hacks together an object by storing each
attribute of an object in different vectors.
In the case of roomtext, instead of making a Roomtext object that has
attributes 'x', 'y', and 'text', the 'text' attribute of each is stored
in the vector 'roomtext', the 'x' attribute of each is stored in the
vector 'roomtextx', and the 'y' attribute of each is stored in the
vector 'roomtexty'. It's only an object in the sense that you can grab
the attributes of each roomtext by using the same index across all three
vectors.
This makes it somewhat annoying to maintain and deal with, like when I
wanted add sub-tile positions to roomtext in VVVVVV: Community Edition.
Instead of being able to add attributes to an already-existing
formalized Roomtext object, I would instead have to add two more
vectors, which is inelegant. Or I could refactor the whole system, which
is what I decided to do instead.
Furthermore, this removes the separate length-tracking variable
'roomtextnumlines', which makes the code much more easy to maintain and
deal with, as the amount of roomtext is naturally tracked by C++ instead
of us having to keep track of the actual amount of roomtext manually.
2020-03-01 03:26:12 +01:00
|
|
|
roomtext.clear();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-09 12:29:53 +02:00
|
|
|
// Entities have to be created HERE, akwardly
|
|
|
|
int tempcheckpoints = 0;
|
|
|
|
int tempscriptbox = 0;
|
|
|
|
for (size_t edi = 0; edi < edentity.size(); edi++)
|
|
|
|
{
|
|
|
|
// If entity is in this room, create it
|
2020-07-09 11:40:18 +02:00
|
|
|
const edentities& ent = edentity[edi];
|
|
|
|
const int tsx = ent.x / 40;
|
|
|
|
const int tsy = ent.y / 30;
|
2020-07-09 12:29:53 +02:00
|
|
|
|
2020-07-09 11:11:56 +02:00
|
|
|
if (tsx != rx-100 || tsy != ry-100)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2020-07-09 12:29:53 +02:00
|
|
|
|
2020-07-09 11:40:18 +02:00
|
|
|
const int ex = (ent.x % 40) * 8;
|
|
|
|
const int ey = (ent.y % 30) * 8;
|
2020-07-09 11:54:23 +02:00
|
|
|
|
2020-07-09 12:06:20 +02:00
|
|
|
// Platform and enemy bounding boxes
|
2020-08-03 05:43:50 +02:00
|
|
|
int bx1 = 0, by1 = 0, bx2 = 0, by2 = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-08-03 05:47:42 +02:00
|
|
|
bool enemy = ent.t == 1;
|
|
|
|
bool moving_plat = ent.t == 2 && ent.p1 <= 4;
|
|
|
|
if (enemy || moving_plat)
|
2020-07-09 12:06:20 +02:00
|
|
|
{
|
2020-08-03 05:47:42 +02:00
|
|
|
if (enemy)
|
|
|
|
{
|
2021-03-24 19:59:36 +01:00
|
|
|
bx1 = room->enemyx1;
|
|
|
|
by1 = room->enemyy1;
|
|
|
|
bx2 = room->enemyx2;
|
|
|
|
by2 = room->enemyy2;
|
2020-08-03 05:47:42 +02:00
|
|
|
}
|
|
|
|
else if (moving_plat)
|
|
|
|
{
|
2021-03-24 19:59:36 +01:00
|
|
|
bx1 = room->platx1;
|
|
|
|
by1 = room->platy1;
|
|
|
|
bx2 = room->platx2;
|
|
|
|
by2 = room->platy2;
|
2020-08-03 05:47:42 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-09 12:06:20 +02:00
|
|
|
// Enlarge bounding boxes to fix warping entities
|
|
|
|
if (warpx && bx1 == 0 && bx2 == 320)
|
|
|
|
{
|
|
|
|
bx1 -= 100;
|
|
|
|
bx2 += 100;
|
|
|
|
}
|
|
|
|
if (warpy && by1 == 0 && by2 == 240)
|
|
|
|
{
|
|
|
|
by1 -= 100;
|
|
|
|
by2 += 100;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-09 12:29:53 +02:00
|
|
|
switch (ent.t)
|
|
|
|
{
|
|
|
|
case 1: // Enemies
|
2021-03-24 19:59:36 +01:00
|
|
|
obj.customenemy = room->enemytype;
|
2020-07-09 12:29:53 +02:00
|
|
|
obj.createentity(ex, ey, 56, ent.p1, 4, bx1, by1, bx2, by2);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-07-09 12:29:53 +02:00
|
|
|
case 2: // Platforms and conveyors
|
|
|
|
if (ent.p1 <= 4)
|
|
|
|
{
|
2021-03-24 19:59:36 +01:00
|
|
|
obj.createentity(ex, ey, 2, ent.p1, room->platv, bx1, by1, bx2, by2);
|
2020-07-09 12:29:53 +02:00
|
|
|
}
|
|
|
|
else if (ent.p1 >= 5 && ent.p1 <= 8) // Conveyor
|
|
|
|
{
|
|
|
|
obj.createentity(ex, ey, 2, ent.p1 + 3, 4);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
2020-07-09 12:29:53 +02:00
|
|
|
case 3: // Disappearing platforms
|
2020-07-09 11:28:53 +02:00
|
|
|
obj.createentity(ex, ey, 3);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-07-09 12:16:52 +02:00
|
|
|
case 9: // Trinkets
|
2020-07-09 11:28:53 +02:00
|
|
|
obj.createentity(ex, ey, 9, ed.findtrinket(edi));
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-07-09 12:29:53 +02:00
|
|
|
case 10: // Checkpoints
|
|
|
|
obj.createentity(ex, ey, 10, ent.p1, (rx + ry*100) * 20 + tempcheckpoints);
|
2020-01-01 21:29:24 +01:00
|
|
|
tempcheckpoints++;
|
|
|
|
break;
|
2020-07-09 12:29:53 +02:00
|
|
|
case 11: // Gravity Lines
|
|
|
|
if (ent.p1 == 0) //Horizontal
|
|
|
|
{
|
2020-07-09 11:40:18 +02:00
|
|
|
obj.createentity(ent.p2 * 8, ey + 4, 11, ent.p3);
|
2020-07-09 12:29:53 +02:00
|
|
|
}
|
|
|
|
else //Vertical
|
|
|
|
{
|
2020-07-09 11:40:18 +02:00
|
|
|
obj.createentity(ex + 3, ent.p2 * 8, 12, ent.p3);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
2020-07-09 12:29:53 +02:00
|
|
|
case 13: // Warp Tokens
|
2020-07-09 11:40:18 +02:00
|
|
|
obj.createentity(ex, ey, 13, ent.p1, ent.p2);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-07-09 12:29:53 +02:00
|
|
|
case 15: // Collectable crewmate
|
2020-07-09 11:40:18 +02:00
|
|
|
obj.createentity(ex - 4, ey + 1, 55, ed.findcrewmate(edi), ent.p1, ent.p2);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-07-09 12:29:53 +02:00
|
|
|
case 17: // Roomtext!
|
2020-07-09 11:32:52 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
roomtexton = true;
|
Refactor roomtext to not use ad-hoc objects / separate length trackers
This refactors the roomtext code to (1) not use ad-hoc objects and (2)
not use a separate length-tracking variable to keep track of the actual
amount of roomtext in a room.
What I mean by ad-hoc object is, instead of formally creating a
fully-fledged struct or class and storing one vector containing that
object, this game instead hacks together an object by storing each
attribute of an object in different vectors.
In the case of roomtext, instead of making a Roomtext object that has
attributes 'x', 'y', and 'text', the 'text' attribute of each is stored
in the vector 'roomtext', the 'x' attribute of each is stored in the
vector 'roomtextx', and the 'y' attribute of each is stored in the
vector 'roomtexty'. It's only an object in the sense that you can grab
the attributes of each roomtext by using the same index across all three
vectors.
This makes it somewhat annoying to maintain and deal with, like when I
wanted add sub-tile positions to roomtext in VVVVVV: Community Edition.
Instead of being able to add attributes to an already-existing
formalized Roomtext object, I would instead have to add two more
vectors, which is inelegant. Or I could refactor the whole system, which
is what I decided to do instead.
Furthermore, this removes the separate length-tracking variable
'roomtextnumlines', which makes the code much more easy to maintain and
deal with, as the amount of roomtext is naturally tracked by C++ instead
of us having to keep track of the actual amount of roomtext manually.
2020-03-01 03:26:12 +01:00
|
|
|
Roomtext text;
|
2020-07-09 11:28:53 +02:00
|
|
|
text.x = ex / 8;
|
|
|
|
text.y = ey / 8;
|
2020-07-09 11:40:18 +02:00
|
|
|
text.text = ent.scriptname;
|
Refactor roomtext to not use ad-hoc objects / separate length trackers
This refactors the roomtext code to (1) not use ad-hoc objects and (2)
not use a separate length-tracking variable to keep track of the actual
amount of roomtext in a room.
What I mean by ad-hoc object is, instead of formally creating a
fully-fledged struct or class and storing one vector containing that
object, this game instead hacks together an object by storing each
attribute of an object in different vectors.
In the case of roomtext, instead of making a Roomtext object that has
attributes 'x', 'y', and 'text', the 'text' attribute of each is stored
in the vector 'roomtext', the 'x' attribute of each is stored in the
vector 'roomtextx', and the 'y' attribute of each is stored in the
vector 'roomtexty'. It's only an object in the sense that you can grab
the attributes of each roomtext by using the same index across all three
vectors.
This makes it somewhat annoying to maintain and deal with, like when I
wanted add sub-tile positions to roomtext in VVVVVV: Community Edition.
Instead of being able to add attributes to an already-existing
formalized Roomtext object, I would instead have to add two more
vectors, which is inelegant. Or I could refactor the whole system, which
is what I decided to do instead.
Furthermore, this removes the separate length-tracking variable
'roomtextnumlines', which makes the code much more easy to maintain and
deal with, as the amount of roomtext is naturally tracked by C++ instead
of us having to keep track of the actual amount of roomtext manually.
2020-03-01 03:26:12 +01:00
|
|
|
roomtext.push_back(text);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-07-09 11:32:52 +02:00
|
|
|
}
|
2020-07-09 12:29:53 +02:00
|
|
|
case 18: // Terminals
|
2020-07-09 11:32:52 +02:00
|
|
|
{
|
2020-07-09 11:40:18 +02:00
|
|
|
obj.customscript = ent.scriptname;
|
2020-07-01 08:43:33 +02:00
|
|
|
|
2020-07-09 11:40:18 +02:00
|
|
|
int usethistile = ent.p1;
|
2020-07-09 11:28:53 +02:00
|
|
|
int usethisy = ey;
|
2020-07-01 08:43:33 +02:00
|
|
|
|
|
|
|
// This isn't a boolean: we just swap 0 and 1 around and leave the rest alone
|
|
|
|
if (usethistile == 0)
|
|
|
|
{
|
|
|
|
usethistile = 1; // Unflipped
|
|
|
|
}
|
|
|
|
else if (usethistile == 1)
|
|
|
|
{
|
|
|
|
usethistile = 0; // Flipped;
|
|
|
|
usethisy -= 8;
|
|
|
|
}
|
|
|
|
|
2020-07-09 11:28:53 +02:00
|
|
|
obj.createentity(ex, usethisy + 8, 20, usethistile);
|
2020-07-09 12:10:08 +02:00
|
|
|
obj.createblock(ACTIVITY, ex - 8, usethisy + 8, 20, 16, 35);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-07-09 11:32:52 +02:00
|
|
|
}
|
|
|
|
case 19: //Script Box
|
2020-07-10 08:03:16 +02:00
|
|
|
if (INBOUNDS_ARR(tempscriptbox, game.customscript))
|
|
|
|
{
|
2020-07-09 12:29:53 +02:00
|
|
|
game.customscript[tempscriptbox] = ent.scriptname;
|
2020-07-10 08:03:16 +02:00
|
|
|
}
|
2020-07-09 12:29:53 +02:00
|
|
|
obj.createblock(TRIGGER, ex, ey, ent.p1 * 8, ent.p2 * 8, 300 + tempscriptbox, "custom_" + ent.scriptname);
|
2020-01-01 21:29:24 +01:00
|
|
|
tempscriptbox++;
|
|
|
|
break;
|
2020-07-09 12:29:53 +02:00
|
|
|
case 50: // Warp Lines
|
2020-01-01 21:29:24 +01:00
|
|
|
obj.customwarpmode=true;
|
2020-07-09 12:14:17 +02:00
|
|
|
switch (ent.p1)
|
|
|
|
{
|
2020-07-09 12:16:52 +02:00
|
|
|
case 0: // Vertical, left
|
2020-07-09 11:40:18 +02:00
|
|
|
obj.createentity(ex + 4, ent.p2 * 8, 51, ent.p3);
|
2020-07-09 12:14:17 +02:00
|
|
|
break;
|
|
|
|
case 1: //Horizontal, right
|
2020-07-09 11:40:18 +02:00
|
|
|
obj.createentity(ex + 4, ent.p2 * 8, 52, ent.p3);
|
2020-07-09 12:14:17 +02:00
|
|
|
break;
|
|
|
|
case 2: //Vertical, top
|
2020-07-09 11:40:18 +02:00
|
|
|
obj.createentity(ent.p2 * 8, ey + 7, 53, ent.p3);
|
2020-07-09 12:14:17 +02:00
|
|
|
break;
|
2020-07-09 12:16:52 +02:00
|
|
|
case 3: // Horizontal, bottom
|
2020-07-09 11:40:18 +02:00
|
|
|
obj.createentity(ent.p2 * 8, ey, 54, ent.p3);
|
2020-07-09 12:14:17 +02:00
|
|
|
break;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2020-07-09 11:12:31 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//do the appear/remove roomname here
|
|
|
|
break;
|
2020-07-09 11:58:16 +02:00
|
|
|
}
|
2020-02-10 01:53:01 +01:00
|
|
|
#endif
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
//The room's loaded: now we fill out damage blocks based on the tiles.
|
|
|
|
if (towermode)
|
|
|
|
{
|
2020-04-03 02:50:13 +02:00
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (int j = 0; j < 29 + extrarow; j++)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < 40; i++)
|
|
|
|
{
|
|
|
|
//Damage blocks
|
|
|
|
if(tileset==0)
|
|
|
|
{
|
|
|
|
if (contents[i + vmult[j]] == 6 || contents[i + vmult[j]] == 8)
|
|
|
|
{
|
|
|
|
//sticking up
|
|
|
|
obj.createblock(2, (i * 8), (j * 8)+4, 8, 4);
|
|
|
|
}
|
|
|
|
if (contents[i + vmult[j]] == 7 || contents[i + vmult[j]] == 9)
|
|
|
|
{
|
|
|
|
//Sticking down
|
|
|
|
obj.createblock(2, (i * 8), (j * 8), 8, 4);
|
|
|
|
}
|
|
|
|
if (contents[i + vmult[j]] == 49 || contents[i + vmult[j]] == 50)
|
|
|
|
{
|
|
|
|
//left or right
|
|
|
|
obj.createblock(2, (i * 8), (j * 8)+3, 8, 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(tileset==1)
|
|
|
|
{
|
|
|
|
//if (contents[i + vmult[j]] >= 6 && contents[i + vmult[j]] <= 9) obj.createblock(2, (i * 8), (j * 8)+1, 8, 6);
|
|
|
|
//if (contents[i + vmult[j]] >= 49 && contents[i + vmult[j]] <= 79) obj.createblock(2, (i * 8) + 1, (j * 8) + 1, 6, 6);
|
|
|
|
if ((contents[i + vmult[j]] >= 63 && contents[i + vmult[j]] <= 74) ||
|
|
|
|
(contents[i + vmult[j]] >= 6 && contents[i + vmult[j]] <= 9))
|
|
|
|
{
|
|
|
|
//sticking up) {
|
|
|
|
if (contents[i + vmult[j]] < 10) contents[i + vmult[j]]++;
|
|
|
|
//sticking up
|
|
|
|
if(contents[i + vmult[j]]%2==0)
|
|
|
|
{
|
|
|
|
obj.createblock(2, (i * 8), (j * 8), 8, 4);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//Sticking down
|
|
|
|
obj.createblock(2, (i * 8), (j * 8) + 4, 8, 4);
|
|
|
|
}
|
|
|
|
if (contents[i + vmult[j]] < 11) contents[i + vmult[j]]--;
|
|
|
|
}
|
|
|
|
if (contents[i + vmult[j]] >= 49 && contents[i + vmult[j]] <= 62)
|
|
|
|
{
|
|
|
|
//left or right
|
|
|
|
obj.createblock(2, (i * 8), (j * 8)+3, 8, 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(tileset==2)
|
|
|
|
{
|
|
|
|
if (contents[i + vmult[j]] == 6 || contents[i + vmult[j]] == 8)
|
|
|
|
{
|
|
|
|
//sticking up
|
|
|
|
obj.createblock(2, (i * 8), (j * 8)+4, 8, 4);
|
|
|
|
}
|
|
|
|
if (contents[i + vmult[j]] == 7 || contents[i + vmult[j]] == 9)
|
|
|
|
{
|
|
|
|
//Sticking down
|
|
|
|
obj.createblock(2, (i * 8), (j * 8), 8, 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//Breakable blocks
|
|
|
|
if (contents[i + vmult[j]] == 10)
|
|
|
|
{
|
|
|
|
contents[i + vmult[j]] = 0;
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(i * 8, j * 8, 4);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
//Directional blocks
|
|
|
|
if (contents[i + vmult[j]] >= 14 && contents[i + vmult[j]] <= 17)
|
|
|
|
{
|
|
|
|
obj.createblock(3, i * 8, j * 8, 8, 8, contents[i + vmult[j]]-14);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-03 22:50:16 +02:00
|
|
|
for (size_t i = 0; i < obj.entities.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-03 22:57:02 +02:00
|
|
|
if (obj.entities[i].type == 1 && obj.entities[i].behave >= 8 && obj.entities[i].behave < 10)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-03 22:57:02 +02:00
|
|
|
//put a block underneath
|
2020-07-03 04:20:22 +02:00
|
|
|
int temp = obj.entities[i].xp / 8.0f;
|
|
|
|
int temp2 = obj.entities[i].yp / 8.0f;
|
2020-04-03 22:57:02 +02:00
|
|
|
settile(temp, temp2, 1);
|
|
|
|
settile(temp+1, temp2, 1);
|
|
|
|
settile(temp+2, temp2, 1);
|
|
|
|
settile(temp+3, temp2, 1);
|
|
|
|
if (obj.entities[i].w == 64)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-03 22:57:02 +02:00
|
|
|
settile(temp+4, temp2, 1);
|
|
|
|
settile(temp+5, temp2, 1);
|
|
|
|
settile(temp+6, temp2, 1);
|
|
|
|
settile(temp+7, temp2, 1);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Special scripting: Create objects and triggers based on what crewmembers are rescued.
|
2020-04-30 08:16:35 +02:00
|
|
|
if (!finalmode && !custommode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//First up: the extra bits:
|
|
|
|
//Vermilion's quest:
|
|
|
|
if (rx == 100 && ry == 105) //On path to verdigris
|
|
|
|
{
|
|
|
|
if (game.crewstats[3] && !game.crewstats[4])
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(87, 105, 18, 15, 0, 18);
|
2020-01-01 21:29:24 +01:00
|
|
|
obj.createblock(5, 87-32, 0, 32+32+32, 240, 3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (rx == 107 && ry == 100) //victoria
|
|
|
|
{
|
|
|
|
if (game.crewstats[3] && !game.crewstats[5])
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(140, 137, 18, 15, 0, 18);
|
2020-01-01 21:29:24 +01:00
|
|
|
obj.createblock(5, 140-32, 0, 32+32+32, 240, 3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (rx == 114 && ry == 109)
|
|
|
|
{
|
|
|
|
if (game.crewstats[3] && !game.crewstats[2])
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(235, 81, 18, 15, 0, 18);
|
2020-01-01 21:29:24 +01:00
|
|
|
obj.createblock(5, 235-32, 0, 32+32+32, 240, 3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Verdigris fixing the ship
|
|
|
|
if (rx == 101 && ry == 109)
|
|
|
|
{
|
|
|
|
if (game.crewstats[4])
|
|
|
|
{
|
|
|
|
if(game.crewrescued()>4 && game.crewrescued()!=6)
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(175, 121, 18, 13, 0, 18);
|
2020-01-01 21:29:24 +01:00
|
|
|
obj.createblock(5, 175-32, 0, 32+32+32, 240, 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (rx == 103 && ry == 109)
|
|
|
|
{
|
|
|
|
if (game.crewstats[4])
|
|
|
|
{
|
|
|
|
if(game.crewrescued()<=4 && game.crewrescued()!=6)
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(53, 161, 18, 13, 1, 18);
|
2020-01-01 21:29:24 +01:00
|
|
|
obj.createblock(5, 53-32, 0, 32+32+32, 240, 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rx == 104 && ry == 111)
|
|
|
|
{
|
|
|
|
//Red
|
|
|
|
//First: is he rescued?
|
|
|
|
if (game.crewstats[3])
|
|
|
|
{
|
|
|
|
//If so, red will always be at his post
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(107, 121, 18, 15, 0, 18);
|
2020-01-01 21:29:24 +01:00
|
|
|
//What script do we use?
|
|
|
|
obj.createblock(5, 107-32, 0, 32+32+32, 240, 3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (rx == 103 && ry == 111)
|
|
|
|
{
|
|
|
|
//Yellow
|
|
|
|
//First: is he rescued?
|
|
|
|
if (game.crewstats[2])
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(198, 105, 18, 14, 0, 18);
|
2020-01-01 21:29:24 +01:00
|
|
|
//What script do we use?
|
|
|
|
obj.createblock(5, 198-32, 0, 32+32+32, 240, 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (rx == 103 && ry == 110)
|
|
|
|
{
|
|
|
|
//Green
|
|
|
|
//First: is he rescued?
|
|
|
|
if (game.crewstats[4])
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(242, 177, 18, 13, 0, 18);
|
2020-01-01 21:29:24 +01:00
|
|
|
//What script do we use?
|
|
|
|
obj.createblock(5, 242-32, 177-20, 32+32+32, 40, 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (rx == 104 && ry == 110)
|
|
|
|
{
|
|
|
|
//Purple
|
|
|
|
//First: is she rescued?
|
|
|
|
if (game.crewstats[1])
|
|
|
|
{
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(140, 177, 18, 20, 0, 18);
|
2020-01-01 21:29:24 +01:00
|
|
|
//What script do we use?
|
|
|
|
obj.createblock(5, 140-32, 0, 32+32+32, 240, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (rx == 102 && ry == 110)
|
|
|
|
{
|
|
|
|
//Blue
|
|
|
|
//First: is she rescued?
|
|
|
|
if (game.crewstats[5])
|
|
|
|
{
|
|
|
|
//A slight varation - she's upside down
|
2020-03-31 02:46:36 +02:00
|
|
|
obj.createentity(249, 62, 18, 16, 0, 18);
|
2020-06-14 20:21:32 +02:00
|
|
|
int j = obj.getcrewman(5);
|
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(j, obj.entities))
|
|
|
|
{
|
|
|
|
obj.entities[j].rule = 7;
|
|
|
|
obj.entities[j].tile +=6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
//What script do we use?
|
|
|
|
obj.createblock(5, 249-32, 0, 32+32+32, 240, 5);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
Fix the two-frame-delay when entering a room with an "init" script
This patch is very kludge-y, but at least it fixes a semi-noticeable
visual issue in custom levels that use internal scripts to spawn
entities when loading a room.
Basically, the problem here is that when the game checks for script
boxes and sets newscript, newscript has already been processed for that
frame, and when the game does load a script, script.run() has already
been processed for that frame.
That issue can be fixed, but it turns out that due to my over-30-FPS
game loop changes, there's now ANOTHER visible frame of delay between
room load and entity creation, because the render function gets called
in between the script being loaded at the end of gamelogic() and the
script actually getting run.
So... I have to temporary move script.run() to the end of gamelogic()
(in map.twoframedelayfix()), and make sure it doesn't get run next
frame, because double-evaluations are bad. To do that, I have to
introduce the kludge variable script.dontrunnextframe, which does
exactly as it says.
And with all that work, the two-frame (now three-frame) delay is fixed.
2020-06-28 02:08:57 +02:00
|
|
|
|
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 mapclass::twoframedelayfix(void)
|
Fix the two-frame-delay when entering a room with an "init" script
This patch is very kludge-y, but at least it fixes a semi-noticeable
visual issue in custom levels that use internal scripts to spawn
entities when loading a room.
Basically, the problem here is that when the game checks for script
boxes and sets newscript, newscript has already been processed for that
frame, and when the game does load a script, script.run() has already
been processed for that frame.
That issue can be fixed, but it turns out that due to my over-30-FPS
game loop changes, there's now ANOTHER visible frame of delay between
room load and entity creation, because the render function gets called
in between the script being loaded at the end of gamelogic() and the
script actually getting run.
So... I have to temporary move script.run() to the end of gamelogic()
(in map.twoframedelayfix()), and make sure it doesn't get run next
frame, because double-evaluations are bad. To do that, I have to
introduce the kludge variable script.dontrunnextframe, which does
exactly as it says.
And with all that work, the two-frame (now three-frame) delay is fixed.
2020-06-28 02:08:57 +02:00
|
|
|
{
|
|
|
|
// Fixes the two-frame delay in custom levels that use scripts to spawn an entity upon room load.
|
|
|
|
// Because when the room loads and newscript is set to run, newscript has already ran for that frame,
|
|
|
|
// and when the script gets loaded script.run() has already ran for that frame, too.
|
|
|
|
// A bit kludge-y, but it's the least we can do without changing the frame ordering.
|
|
|
|
|
Split glitchrunner mode into multiple versions
Previously, turning glitchrunner mode on essentially locked you to
emulating 2.0, and turning it off just meant normal 2.3 behavior. But
what if you wanted 2.2 behavior instead? Well, that's what I had to ask
when a TAS of mine would desync in 2.3 because of the two-frame delay
fix (glitchrunner off), but would also desync because of 2.0 warp lines
(glitchrunner on).
What I've done is made it so there are three states to glitchrunner mode
now: 2.0 (previously just the "on" state), 2.2 (previously a state you
couldn't use), and "off". Furthermore, I made it an enum, so in case
future versions of the game patch out more glitches, we can add them to
the enum (and the only other thing we have to update is a lookup table
in GlitchrunnerMode.c). Also, 2.2 glitches exist in 2.0, so you'll want
to use GlitchrunnerMode_less_than_or_equal() to check glitchrunner
version.
2021-08-05 02:09:49 +02:00
|
|
|
if (GlitchrunnerMode_less_than_or_equal(Glitchrunner2_2)
|
2020-08-17 21:10:22 +02:00
|
|
|
|| !custommode
|
Move all temporary variables off of entityclass
This is a refactor that simply moves all temporary variables off of
entityclass, and makes it so they are no longer global variables. This
makes the resulting code easier to understand as it is less entangled
with global state.
These attributes were:
- colpoint1
- colpoint2
- tempx
- tempy
- tempw
- temph
- temp
- temp2
- tpx1
- tpy1
- tpx2
- tpy2
- temprect
- temprect2
- x (actually unused)
- dx
- dy
- dr
- px
- py
- linetemp
- activetrigger
- skipblocks
- skipdirblocks
Most of these attributes were assigned before any of the times they were
used, so it's easy to prove that ungloballing them won't change any
behaviors. However, dx, dy, dr, and skipblocks are a bit more tricky to
analyze. They relate to blocks, with dx, dy, and dr more specifically
relating to one-way tiles. So after some testing with the quirks of
one-way tiles, it seems that the jankiness of one-way tiles haven't
changed at all, either.
Unfortunately, the attribute k is clearly used without being assigned
beforehand, so I can't move it off of entityclass. It's the same story
with the attribute k that Graphics has, too.
2020-09-26 21:38:57 +02:00
|
|
|
|| game.deathseq != -1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int block_idx = -1;
|
|
|
|
// obj.checktrigger() sets block_idx
|
|
|
|
int activetrigger = obj.checktrigger(&block_idx);
|
|
|
|
if (activetrigger <= -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
|
|
|
|| !INBOUNDS_VEC(block_idx, obj.blocks)
|
Move all temporary variables off of entityclass
This is a refactor that simply moves all temporary variables off of
entityclass, and makes it so they are no longer global variables. This
makes the resulting code easier to understand as it is less entangled
with global state.
These attributes were:
- colpoint1
- colpoint2
- tempx
- tempy
- tempw
- temph
- temp
- temp2
- tpx1
- tpy1
- tpx2
- tpy2
- temprect
- temprect2
- x (actually unused)
- dx
- dy
- dr
- px
- py
- linetemp
- activetrigger
- skipblocks
- skipdirblocks
Most of these attributes were assigned before any of the times they were
used, so it's easy to prove that ungloballing them won't change any
behaviors. However, dx, dy, dr, and skipblocks are a bit more tricky to
analyze. They relate to blocks, with dx, dy, and dr more specifically
relating to one-way tiles. So after some testing with the quirks of
one-way tiles, it seems that the jankiness of one-way tiles haven't
changed at all, either.
Unfortunately, the attribute k is clearly used without being assigned
beforehand, so I can't move it off of entityclass. It's the same story
with the attribute k that Graphics has, too.
2020-09-26 21:38:57 +02:00
|
|
|
|| activetrigger < 300)
|
Fix the two-frame-delay when entering a room with an "init" script
This patch is very kludge-y, but at least it fixes a semi-noticeable
visual issue in custom levels that use internal scripts to spawn
entities when loading a room.
Basically, the problem here is that when the game checks for script
boxes and sets newscript, newscript has already been processed for that
frame, and when the game does load a script, script.run() has already
been processed for that frame.
That issue can be fixed, but it turns out that due to my over-30-FPS
game loop changes, there's now ANOTHER visible frame of delay between
room load and entity creation, because the render function gets called
in between the script being loaded at the end of gamelogic() and the
script actually getting run.
So... I have to temporary move script.run() to the end of gamelogic()
(in map.twoframedelayfix()), and make sure it doesn't get run next
frame, because double-evaluations are bad. To do that, I have to
introduce the kludge variable script.dontrunnextframe, which does
exactly as it says.
And with all that work, the two-frame (now three-frame) delay is fixed.
2020-06-28 02:08:57 +02:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-10 08:20:03 +02:00
|
|
|
game.newscript = obj.blocks[block_idx].script;
|
Move all temporary variables off of entityclass
This is a refactor that simply moves all temporary variables off of
entityclass, and makes it so they are no longer global variables. This
makes the resulting code easier to understand as it is less entangled
with global state.
These attributes were:
- colpoint1
- colpoint2
- tempx
- tempy
- tempw
- temph
- temp
- temp2
- tpx1
- tpy1
- tpx2
- tpy2
- temprect
- temprect2
- x (actually unused)
- dx
- dy
- dr
- px
- py
- linetemp
- activetrigger
- skipblocks
- skipdirblocks
Most of these attributes were assigned before any of the times they were
used, so it's easy to prove that ungloballing them won't change any
behaviors. However, dx, dy, dr, and skipblocks are a bit more tricky to
analyze. They relate to blocks, with dx, dy, and dr more specifically
relating to one-way tiles. So after some testing with the quirks of
one-way tiles, it seems that the jankiness of one-way tiles haven't
changed at all, either.
Unfortunately, the attribute k is clearly used without being assigned
beforehand, so I can't move it off of entityclass. It's the same story
with the attribute k that Graphics has, too.
2020-09-26 21:38:57 +02:00
|
|
|
obj.removetrigger(activetrigger);
|
Fix the two-frame-delay when entering a room with an "init" script
This patch is very kludge-y, but at least it fixes a semi-noticeable
visual issue in custom levels that use internal scripts to spawn
entities when loading a room.
Basically, the problem here is that when the game checks for script
boxes and sets newscript, newscript has already been processed for that
frame, and when the game does load a script, script.run() has already
been processed for that frame.
That issue can be fixed, but it turns out that due to my over-30-FPS
game loop changes, there's now ANOTHER visible frame of delay between
room load and entity creation, because the render function gets called
in between the script being loaded at the end of gamelogic() and the
script actually getting run.
So... I have to temporary move script.run() to the end of gamelogic()
(in map.twoframedelayfix()), and make sure it doesn't get run next
frame, because double-evaluations are bad. To do that, I have to
introduce the kludge variable script.dontrunnextframe, which does
exactly as it says.
And with all that work, the two-frame (now three-frame) delay is fixed.
2020-06-28 02:08:57 +02:00
|
|
|
game.state = 0;
|
|
|
|
game.statedelay = 0;
|
|
|
|
script.load(game.newscript);
|
|
|
|
}
|