1
0
Fork 0
mirror of https://github.com/TerryCavanagh/VVVVVV.git synced 2024-11-14 06:59:42 +01:00
VVVVVV/desktop_version/src/Map.cpp

2077 lines
48 KiB
C++
Raw Normal View History

#define MAP_DEFINITION
2020-01-01 21:29:24 +01:00
#include "Map.h"
#include "editor.h"
#include "Entity.h"
#include "Game.h"
#include "Graphics.h"
#include "MakeAndPlay.h"
#include "Music.h"
#include "Script.h"
#include "UtilityClass.h"
mapclass::mapclass(void)
2020-01-01 21:29:24 +01:00
{
//Start here!
colstatedelay = 0;
colsuperstate = 0;
spikeleveltop = 0;
spikelevelbottom = 0;
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;
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:
for (size_t i = 0; i < SDL_arraysize(vmult); i++)
2020-01-01 21:29:24 +01:00
{
vmult[i] = i * 40;
2020-01-01 21:29:24 +01:00
}
//We create a blank map
SDL_memset(contents, 0, sizeof(contents));
2020-01-01 21:29:24 +01: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();
ypos = 0;
oldypos = 0;
background = 0;
cameramode = 0;
cameraseek = 0;
minitowermode = false;
roomtexton = false;
}
//Areamap starts at 100,100 and extends 20x20
const int mapclass::areamap[] = {
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-01-01 21:29:24 +01:00
int mapclass::intpol(int a, int b, float c)
{
return static_cast<int>(a + ((b - a) * c));
}
void mapclass::setteleporter(int x, int y)
2020-01-01 21:29:24 +01:00
{
point temp;
temp.x = x;
temp.y = y;
teleporters.push_back(temp);
2020-01-01 21:29:24 +01:00
}
void mapclass::settrinket(int x, int y)
2020-01-01 21:29:24 +01:00
{
point temp;
temp.x = x;
temp.y = y;
shinytrinkets.push_back(temp);
2020-01-01 21:29:24 +01:00
}
void mapclass::resetmap(void)
2020-01-01 21:29:24 +01:00
{
//clear the explored area of the map
SDL_memset(explored, 0, sizeof(explored));
2020-01-01 21:29:24 +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;
}
void mapclass::initmapdata(void)
2020-01-01 21:29:24 +01: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.
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
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
}
void mapclass::initcustommapdata(void)
{
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;
}
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;
int temp = 6 - t;
2020-01-01 21:29:24 +01:00
//Next, entities
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);
}
}
}
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
{
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
}
void mapclass::updatetowerglow(TowerBG& bg_obj)
2020-01-01 21:29:24 +01:00
{
if (colstatedelay <= 0 || colsuperstate > 0)
{
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
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--;
}
}
void mapclass::nexttowercolour(void)
2020-01-01 21:29:24 +01:00
{
graphics.titlebg.colstate+=5;
if (graphics.titlebg.colstate >= 30) graphics.titlebg.colstate = 0;
2020-01-01 21:29:24 +01:00
updatebgobj(graphics.titlebg);
2020-01-01 21:29:24 +01:00
}
void mapclass::settowercolour(int t)
{
graphics.titlebg.colstate=t*5;
if (graphics.titlebg.colstate >= 30) graphics.titlebg.colstate = 0;
2020-01-01 21:29:24 +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;
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;
}
}
}
bool mapclass::isexplored(const int rx, const int ry)
{
const int roomnum = rx + ry*20;
if (INBOUNDS_ARR(roomnum, explored))
{
return explored[roomnum];
}
return false;
}
void mapclass::setexplored(const int rx, const int ry, const bool status)
{
const int roomnum = rx + ry*20;
if (INBOUNDS_ARR(roomnum, explored))
{
explored[roomnum] = status;
}
}
void mapclass::exploretower(void)
2020-01-01 21:29:24 +01:00
{
for (int i = 0; i < 20; i++)
{
setexplored(9, i, true);
2020-01-01 21:29:24 +01:00
}
}
void mapclass::hideship(void)
2020-01-01 21:29:24 +01:00
{
//remove the ship from the explored areas
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
}
void mapclass::showship(void)
2020-01-01 21:29:24 +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
}
void mapclass::resetplayer(void)
{
resetplayer(false);
}
void mapclass::resetplayer(const bool player_died)
2020-01-01 21:29:24 +01:00
{
bool was_in_tower = towermode;
2020-01-01 21:29:24 +01:00
if (game.roomx != game.saverx || game.roomy != game.savery)
{
gotoroom(game.saverx, game.savery);
2020-01-01 21:29:24 +01:00
}
game.deathseq = -1;
int i = obj.getplayer();
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;
//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;
if (player_died)
{
game.lifeseq = 10;
obj.entities[i].invis = true;
}
else
{
obj.entities[i].invis = false;
}
if (!game.glitchrunnermode)
{
obj.entities[i].size = 0;
obj.entities[i].cx = 6;
obj.entities[i].cy = 2;
obj.entities[i].w = 12;
obj.entities[i].h = 21;
}
// 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;
}
oldypos = ypos;
graphics.towerbg.bypos = ypos / 2;
}
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;
}
}
}
void mapclass::warpto(int rx, int ry , int t, int tx, int ty)
2020-01-01 21:29:24 +01:00
{
gotoroom(rx, ry);
2020-01-01 21:29:24 +01:00
game.teleport = false;
if (INBOUNDS_VEC(t, obj.entities))
{
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-01-01 21:29:24 +01:00
game.gravitycontrol = 0;
}
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;
game.oldreadytotele = 0;
2020-01-01 21:29:24 +01:00
//Ok, let's save the position of all lines on the screen
obj.linecrosskludge.clear();
for (size_t i = 0; i < obj.entities.size(); i++)
2020-01-01 21:29:24 +01: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
{
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-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?
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
}
}
#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;
}
#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)
{
if (graphics.setflipmode)
2020-01-01 21:29:24 +01:00
{
music.niceplay(9);
}
else
{
music.niceplay(2);
}
}
if (game.roomx == 109)
{
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);
}
int temp = rx + (ry * 100);
loadlevel(game.roomx, game.roomy);
2020-01-01 21:29:24 +01:00
//Do we need to reload the background?
bool redrawbg = game.roomx != game.prevroomx || game.roomy != game.prevroomy;
if(redrawbg)
{
graphics.backgrounddrawn = false; //Used for background caching speedup
}
graphics.foregrounddrawn = false; //Used for background caching speedup
2020-01-01 21:29:24 +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)
{
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();
if(INBOUNDS_VEC(temp, obj.entities))
2020-01-01 21:29:24 +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
}
for (size_t i = 0; i < obj.entities.size(); i++)
2020-01-01 21:29:24 +01: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
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:
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 "???";
}
void mapclass::loadlevel(int rx, int ry)
2020-01-01 21:29:24 +01:00
{
int t;
if (!finalmode)
{
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 = "";
hiddenname = "";
2020-01-01 21:29:24 +01:00
background = 1;
warpx = false;
warpy = false;
towermode = false;
ypos = 0;
oldypos = 0;
2020-01-01 21:29:24 +01:00
extrarow = 0;
spikeleveltop = 0;
spikelevelbottom = 0;
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();
if (INBOUNDS_VEC(player, obj.entities))
{
obj.entities[player].yp += (671 * 8);
}
2020-01-01 21:29:24 +01:00
ypos = (700-29) * 8;
oldypos = ypos;
graphics.towerbg.bypos = ypos / 2;
2020-01-01 21:29:24 +01:00
cameramode = 0;
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;
oldypos = ypos;
graphics.towerbg.bypos = 0;
2020-01-01 21:29:24 +01:00
cameramode = 0;
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;
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
}
}
}
}
}
}
if (rx == 119 && ry == 108 && !custommode)
2020-01-01 21:29:24 +01:00
{
background = 5;
graphics.rcol = 3;
2020-01-01 21:29:24 +01:00
warpx = true;
warpy = true;
}
switch(t)
{
#if !defined(MAKEANDPLAY)
case 0:
2020-01-01 21:29:24 +01:00
case 1: //World Map
{
2020-01-01 21:29:24 +01:00
tileset = 1;
extrarow = 1;
const short* tmap = otherlevel.loadlevel(rx, ry);
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
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-01-01 21:29:24 +01:00
case 2: //The Lab
{
const short* tmap = lablevel.loadlevel(rx, ry);
SDL_memcpy(contents, tmap, sizeof(contents));
2020-01-01 21:29:24 +01:00
roomname = lablevel.roomname;
tileset = 1;
background = 2;
graphics.rcol = lablevel.rcol;
2020-01-01 21:29:24 +01:00
break;
}
2020-01-01 21:29:24 +01:00
case 3: //The Tower
graphics.towerbg.tdrawback = true;
2020-01-01 21:29:24 +01:00
minitowermode = false;
tower.minitowermode = false;
graphics.towerbg.bscroll = 0;
graphics.towerbg.scrolldir = 0;
2020-01-01 21:29:24 +01:00
roomname = "The Tower";
tileset = 1;
background = 3;
towermode = true;
//graphics.towerbg.bypos = 0; ypos = 0; cameramode = 0;
2020-01-01 21:29:24 +01:00
//All the entities for here are just loaded here; it's essentially one room after all
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
{
const short* tmap = warplevel.loadlevel(rx, ry);
SDL_memcpy(contents, tmap, sizeof(contents));
2020-01-01 21:29:24 +01:00
roomname = warplevel.roomname;
tileset = 1;
background = 3;
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-01-01 21:29:24 +01:00
case 5: //Space station
{
const short* tmap = spacestation2.loadlevel(rx, ry);
SDL_memcpy(contents, tmap, sizeof(contents));
2020-01-01 21:29:24 +01:00
roomname = spacestation2.roomname;
tileset = 0;
break;
}
2020-01-01 21:29:24 +01:00
case 6: //final level
{
const short* tmap = finallevel.loadlevel(rx, ry);
SDL_memcpy(contents, tmap, sizeof(contents));
2020-01-01 21:29:24 +01:00
roomname = finallevel.roomname;
tileset = 1;
background = 3;
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;
}
graphics.rcol = 6;
changefinalcol(final_mapcol);
2020-01-01 21:29:24 +01:00
break;
}
2020-01-01 21:29:24 +01:00
case 7: //Final Level, Tower 1
graphics.towerbg.tdrawback = true;
2020-01-01 21:29:24 +01:00
minitowermode = true;
tower.minitowermode = true;
graphics.towerbg.bscroll = 0;
graphics.towerbg.scrolldir = 1;
2020-01-01 21:29:24 +01:00
roomname = "Panic Room";
tileset = 1;
background = 3;
towermode = true;
tower.loadminitower1();
ypos = 0;
oldypos = 0;
graphics.towerbg.bypos = 0;
2020-01-01 21:29:24 +01:00
cameramode = 0;
graphics.towerbg.colstate = 0;
2020-01-01 21:29:24 +01:00
colsuperstate = 0;
break;
case 8: //Final Level, Tower 1 (reentered from below)
{
graphics.towerbg.tdrawback = true;
2020-01-01 21:29:24 +01:00
minitowermode = true;
tower.minitowermode = true;
graphics.towerbg.bscroll = 0;
graphics.towerbg.scrolldir = 1;
2020-01-01 21:29:24 +01:00
roomname = "Panic Room";
tileset = 1;
background = 3;
towermode = true;
tower.loadminitower1();
int i = obj.getplayer();
if (INBOUNDS_VEC(i, obj.entities))
{
obj.entities[i].yp += (71 * 8);
}
2020-01-01 21:29:24 +01:00
game.roomy--;
ypos = (100-29) * 8;
oldypos = ypos;
graphics.towerbg.bypos = ypos/2;
2020-01-01 21:29:24 +01:00
cameramode = 0;
graphics.towerbg.colstate = 0;
2020-01-01 21:29:24 +01:00
colsuperstate = 0;}
break;
case 9: //Final Level, Tower 2
{
graphics.towerbg.tdrawback = true;
2020-01-01 21:29:24 +01:00
minitowermode = true;
tower.minitowermode = true;
graphics.towerbg.bscroll = 0;
graphics.towerbg.scrolldir = 0;
2020-01-01 21:29:24 +01:00
final_colorframe = 2;
roomname = "The Final Challenge";
tileset = 1;
background = 3;
towermode = true;
tower.loadminitower2();
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();
if (INBOUNDS_VEC(i, obj.entities))
{
obj.entities[i].yp += (71 * 8);
}
2020-01-01 21:29:24 +01:00
game.roomy--;
ypos = (100-29) * 8;
oldypos = ypos;
graphics.towerbg.bypos = ypos/2;
2020-01-01 21:29:24 +01:00
cameramode = 0;
graphics.towerbg.colstate = 0;
2020-01-01 21:29:24 +01:00
colsuperstate = 0;
break;
}
2020-01-01 21:29:24 +01:00
case 10: //Final Level, Tower 2
{
graphics.towerbg.tdrawback = true;
2020-01-01 21:29:24 +01:00
minitowermode = true;
tower.minitowermode = true;
graphics.towerbg.bscroll = 0;
graphics.towerbg.scrolldir = 0;
2020-01-01 21:29:24 +01:00
final_colorframe = 2;
roomname = "The Final Challenge";
tileset = 1;
background = 3;
towermode = true;
tower.loadminitower2();
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;
oldypos = 0;
graphics.towerbg.bypos = 0;
2020-01-01 21:29:24 +01:00
cameramode = 0;
graphics.towerbg.colstate = 0;
2020-01-01 21:29:24 +01:00
colsuperstate = 0;
break;
}
2020-01-01 21:29:24 +01:00
case 11: //Tower Hallways //Content is held in final level routine
{
const short* tmap = finallevel.loadlevel(rx, ry);
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;
}
#endif
#if !defined(NO_CUSTOM_LEVELS)
2020-01-01 21:29:24 +01:00
case 12: //Custom level
{
const edlevelclass* const room = ed.getroomprop(rx - 100, ry - 100);
game.customcol = ed.getlevelcol(room->tileset, room->tilecol) + 1;
obj.customplatformtile = game.customcol * 12;
switch (room->tileset)
{
case 0: // Space Station
2020-01-01 21:29:24 +01:00
tileset = 0;
background = 1;
break;
case 1: // Outside
2020-01-01 21:29:24 +01:00
tileset = 1;
background = 1;
break;
case 2: // Lab
2020-01-01 21:29:24 +01:00
tileset = 1;
background = 2;
graphics.rcol = room->tilecol;
2020-01-01 21:29:24 +01:00
break;
case 3: // Warp Zone/intermission
2020-01-01 21:29:24 +01:00
tileset = 1;
background = 6;
break;
case 4: // Ship
2020-01-01 21:29:24 +01:00
tileset = 1;
background = 1;
break;
default:
2020-01-01 21:29:24 +01:00
tileset = 1;
background = 1;
break;
}
// If screen warping, then override all that:
bool redrawbg = game.roomx != game.prevroomx || game.roomy != game.prevroomy;
if (redrawbg)
{
graphics.backgrounddrawn = false;
}
switch (room->warpdir)
{
case 1:
warpx = true;
background = 3;
graphics.rcol = ed.getwarpbackground(rx - 100, ry - 100);
break;
case 2:
warpy = true;
background = 4;
graphics.rcol = ed.getwarpbackground(rx - 100, ry - 100);
break;
case 3:
warpx = true;
warpy = true;
2020-01-01 21:29:24 +01:00
background = 5;
graphics.rcol = ed.getwarpbackground(rx - 100, ry - 100);
break;
2020-01-01 21:29:24 +01:00
}
roomname = room->roomname;
2020-01-01 21:29:24 +01:00
extrarow = 1;
const short* tmap = ed.loadlevel(rx, ry);
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
// 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
const edentities& ent = edentity[edi];
const int tsx = ent.x / 40;
const int tsy = ent.y / 30;
if (tsx != rx-100 || tsy != ry-100)
{
continue;
}
const int ex = (ent.x % 40) * 8;
const int ey = (ent.y % 30) * 8;
// Platform and enemy bounding boxes
int bx1 = 0, by1 = 0, bx2 = 0, by2 = 0;
2020-01-01 21:29:24 +01:00
bool enemy = ent.t == 1;
bool moving_plat = ent.t == 2 && ent.p1 <= 4;
if (enemy || moving_plat)
{
if (enemy)
{
bx1 = room->enemyx1;
by1 = room->enemyy1;
bx2 = room->enemyx2;
by2 = room->enemyy2;
}
else if (moving_plat)
{
bx1 = room->platx1;
by1 = room->platy1;
bx2 = room->platx2;
by2 = room->platy2;
}
2020-01-01 21:29:24 +01: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;
}
}
switch (ent.t)
{
case 1: // Enemies
obj.customenemy = room->enemytype;
obj.createentity(ex, ey, 56, ent.p1, 4, bx1, by1, bx2, by2);
2020-01-01 21:29:24 +01:00
break;
case 2: // Platforms and conveyors
if (ent.p1 <= 4)
{
obj.createentity(ex, ey, 2, ent.p1, room->platv, bx1, by1, bx2, by2);
}
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;
case 3: // Disappearing platforms
obj.createentity(ex, ey, 3);
2020-01-01 21:29:24 +01:00
break;
case 9: // Trinkets
obj.createentity(ex, ey, 9, ed.findtrinket(edi));
2020-01-01 21:29:24 +01:00
break;
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;
case 11: // Gravity Lines
if (ent.p1 == 0) //Horizontal
{
obj.createentity(ent.p2 * 8, ey + 4, 11, ent.p3);
}
else //Vertical
{
obj.createentity(ex + 3, ent.p2 * 8, 12, ent.p3);
2020-01-01 21:29:24 +01:00
}
break;
case 13: // Warp Tokens
obj.createentity(ex, ey, 13, ent.p1, ent.p2);
2020-01-01 21:29:24 +01:00
break;
case 15: // Collectable crewmate
obj.createentity(ex - 4, ey + 1, 55, ed.findcrewmate(edi), ent.p1, ent.p2);
2020-01-01 21:29:24 +01:00
break;
case 17: // Roomtext!
{
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;
text.x = ex / 8;
text.y = ey / 8;
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;
}
case 18: // Terminals
{
obj.customscript = ent.scriptname;
int usethistile = ent.p1;
int usethisy = ey;
// 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;
}
obj.createentity(ex, usethisy + 8, 20, usethistile);
obj.createblock(ACTIVITY, ex - 8, usethisy + 8, 20, 16, 35);
2020-01-01 21:29:24 +01:00
break;
}
case 19: //Script Box
if (INBOUNDS_ARR(tempscriptbox, game.customscript))
{
game.customscript[tempscriptbox] = ent.scriptname;
}
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;
case 50: // Warp Lines
2020-01-01 21:29:24 +01:00
obj.customwarpmode=true;
switch (ent.p1)
{
case 0: // Vertical, left
obj.createentity(ex + 4, ent.p2 * 8, 51, ent.p3);
break;
case 1: //Horizontal, right
obj.createentity(ex + 4, ent.p2 * 8, 52, ent.p3);
break;
case 2: //Vertical, top
obj.createentity(ent.p2 * 8, ey + 7, 53, ent.p3);
break;
case 3: // Horizontal, bottom
obj.createentity(ent.p2 * 8, ey, 54, ent.p3);
break;
2020-01-01 21:29:24 +01:00
}
break;
}
}
2020-01-01 21:29:24 +01:00
//do the appear/remove roomname here
break;
}
#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-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;
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);
}
}
}
for (size_t i = 0; i < obj.entities.size(); i++)
2020-01-01 21:29:24 +01: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
{
//put a block underneath
int temp = obj.entities[i].xp / 8.0f;
int temp2 = obj.entities[i].yp / 8.0f;
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
{
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.
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])
{
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])
{
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])
{
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)
{
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)
{
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
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])
{
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])
{
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])
{
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
obj.createentity(249, 62, 18, 16, 0, 18);
int j = obj.getcrewman(5);
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);
}
}
}
}
void mapclass::twoframedelayfix(void)
{
// 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.
if (game.glitchrunnermode
|| !custommode
|| game.deathseq != -1)
return;
int block_idx = -1;
// obj.checktrigger() sets block_idx
int activetrigger = obj.checktrigger(&block_idx);
if (activetrigger <= -1
|| !INBOUNDS_VEC(block_idx, obj.blocks)
|| activetrigger < 300)
{
return;
}
game.newscript = obj.blocks[block_idx].script;
obj.removetrigger(activetrigger);
game.state = 0;
game.statedelay = 0;
script.load(game.newscript);
}