VVVVVV/desktop_version/src/Entity.cpp

4880 lines
149 KiB
C++
Raw Normal View History

#define OBJ_DEFINITION
2020-01-01 21:29:24 +01:00
#include "Entity.h"
#include <SDL.h>
#include "editor.h"
2020-01-01 21:29:24 +01:00
#include "Game.h"
#include "Graphics.h"
2020-01-01 21:29:24 +01:00
#include "Map.h"
#include "Music.h"
#include "Script.h"
2020-01-01 21:29:24 +01:00
#include "UtilityClass.h"
bool entityclass::checktowerspikes(int t)
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(t, entities))
{
puts("checktowerspikes() out-of-bounds!");
return false;
}
SDL_Rect temprect;
temprect.x = entities[t].xp + entities[t].cx;
temprect.y = entities[t].yp + entities[t].cy;
temprect.w = entities[t].w;
temprect.h = entities[t].h;
2020-01-01 21:29:24 +01:00
int tempx = getgridpoint(temprect.x);
int tempy = getgridpoint(temprect.y);
int tempw = getgridpoint(temprect.x + temprect.w - 1);
int temph = getgridpoint(temprect.y + temprect.h - 1);
2020-01-01 21:29:24 +01:00
if (map.spikecollide(tempx, tempy)) return true;
if (map.spikecollide(tempw, tempy)) return true;
if (map.spikecollide(tempx, temph)) return true;
if (map.spikecollide(tempw, temph)) return true;
if (temprect.h >= 12)
{
int tpy1 = getgridpoint(temprect.y + 6);
2020-01-01 21:29:24 +01:00
if (map.spikecollide(tempx, tpy1)) return true;
if (map.spikecollide(tempw, tpy1)) return true;
if (temprect.h >= 18)
{
tpy1 = getgridpoint(temprect.y + 12);
if (map.spikecollide(tempx, tpy1)) return true;
if (map.spikecollide(tempw, tpy1)) return true;
if (temprect.h >= 24)
{
tpy1 = getgridpoint(temprect.y + 18);
if (map.spikecollide(tempx, tpy1)) return true;
if (map.spikecollide(tempw, tpy1)) return true;
}
}
}
return false;
}
void entityclass::init(void)
2020-01-01 21:29:24 +01:00
{
platformtile = 0;
customplatformtile=0;
vertplatforms = false;
horplatforms = false;
nearelephant = false;
upsetmode = false;
upset = 0;
customenemy=0;
customwarpmode=false; customwarpmodevon=false; customwarpmodehon=false;
trophytext = 0 ;
oldtrophytext = 0;
2020-01-01 21:29:24 +01:00
trophytype = 0;
altstates = 0;
SDL_memset(customcrewmoods, true, sizeof(customcrewmoods));
2020-01-01 21:29:24 +01:00
resetallflags();
SDL_memset(collect, false, sizeof(collect));
SDL_memset(customcollect, false, sizeof(customcollect));
k = 0;
2020-01-01 21:29:24 +01:00
}
void entityclass::resetallflags(void)
2020-01-01 21:29:24 +01:00
{
SDL_memset(flags, false, sizeof(flags));
2020-01-01 21:29:24 +01:00
}
int entityclass::swncolour( int t )
{
//given colour t, return colour in setcol
if (t == 0) return 11;
if (t == 1) return 6;
if (t == 2) return 8;
if (t == 3) return 12;
if (t == 4) return 9;
if (t == 5) return 7;
return 0;
}
void entityclass::swnenemiescol( int t )
{
//change the colour of all SWN enemies to the current one
for (size_t i = 0; i < entities.size(); i++)
2020-01-01 21:29:24 +01:00
{
if (entities[i].type == 23)
2020-01-01 21:29:24 +01:00
{
entities[i].colour = swncolour(t);
2020-01-01 21:29:24 +01:00
}
}
}
void entityclass::gravcreate( int ypos, int dir, int xoff /*= 0*/, int yoff /*= 0*/ )
2020-01-01 21:29:24 +01:00
{
if (dir == 0)
{
createentity(-150 - xoff, 58 + (ypos * 20)+yoff, 23, 0, 0);
2020-01-01 21:29:24 +01:00
}
else
{
createentity(320+150 + xoff, 58 + (ypos * 20)+yoff, 23, 1, 0);
2020-01-01 21:29:24 +01:00
}
}
void entityclass::generateswnwave( int t )
2020-01-01 21:29:24 +01:00
{
//generate a wave for the SWN game
if(game.swndelay<=0)
{
if (t == 0) //game 0, survive for 30 seconds
{
switch(game.swnstate)
{
case 0:
//Decide on a wave here
//default case
game.swnstate = 1;
game.swndelay = 5;
if (game.swntimer <= 150) //less than 5 seconds
{
game.swnstate = 9;
game.swndelay = 8;
}
else if (game.swntimer <= 300) //less than 10 seconds
{
game.swnstate = 6;
game.swndelay = 12;
}
else if (game.swntimer <= 360) //less than 12 seconds
{
game.swnstate = 5+game.swnstate2;
game.swndelay = 15;
}
else if (game.swntimer <= 420) //less than 14 seconds
{
game.swnstate = 7+game.swnstate2;
game.swndelay = 15;
}
else if (game.swntimer <= 480) //less than 16 seconds
{
game.swnstate = 5+game.swnstate2;
game.swndelay = 15;
}
else if (game.swntimer <= 540) //less than 18 seconds
{
game.swnstate = 7+game.swnstate2;
game.swndelay = 15;
}
else if (game.swntimer <= 600) //less than 20 seconds
{
game.swnstate = 5+game.swnstate2;
game.swndelay = 15;
}
else if (game.swntimer <= 900) //less than 30 seconds
{
game.swnstate = 4;
game.swndelay = 20;
}
else if (game.swntimer <= 1050) //less than 35 seconds
{
game.swnstate = 3;
game.swndelay = 10;
}
else if (game.swntimer <= 1200) //less than 40 seconds
{
game.swnstate = 3;
game.swndelay = 20;
}
else if (game.swntimer <= 1500) //less than 50 seconds
{
game.swnstate = 2;
game.swndelay = 10;
}
else if (game.swntimer <= 1650) //less than 55 seconds
{
game.swnstate = 1;
game.swndelay = 15;
}
else if (game.swntimer <= 1800) //less than 60 seconds
{
game.swnstate = 1;
game.swndelay = 25;
}
if (game.deathcounts - game.swndeaths > 7) game.swndelay += 2;
if (game.deathcounts - game.swndeaths > 15) game.swndelay += 2;
if (game.deathcounts - game.swndeaths > 25) game.swndelay += 4;
break;
case 1:
createentity(-150, 58 + (int(fRandom() * 6) * 20), 23, 0, 0);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 0; //return to decision state
break;
case 2:
if(game.swnstate3==0)
{
game.swnstate2++;
if (game.swnstate2 >= 6)
{
game.swnstate3 = 1;
game.swnstate2--;
}
}
else
{
game.swnstate2--;
if (game.swnstate2 < 0)
{
game.swnstate3 = 0;
game.swnstate2++;
}
}
createentity(-150, 58 + (int(game.swnstate2) * 20), 23, 0, 0);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 0; //return to decision state
break;
case 3:
createentity(320+150, 58 + (int(fRandom() * 6) * 20), 23, 1, 0);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 0; //return to decision state
break;
case 4:
//left and right compliments
game.swnstate2 = int(fRandom() * 6);
createentity(-150, 58 + (game.swnstate2 * 20), 23, 0, 0);
createentity(320+150, 58 + ((5-game.swnstate2) * 20), 23, 1, 0);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 0; //return to decision state
game.swnstate2 = 0;
break;
case 5:
//Top and bottom
createentity(-150, 58, 23, 0, 0);
createentity(-150, 58 + (5 * 20), 23, 0, 0);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 0; //return to decision state
game.swnstate2 = 1;
break;
case 6:
//Middle
createentity(-150, 58 + (2 * 20), 23, 0, 0);
createentity(-150, 58 + (3 * 20), 23, 0, 0);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 0; //return to decision state
game.swnstate2 = 0;
break;
case 7:
//Top and bottom
createentity(320+150, 58, 23, 1, 0);
createentity(320+150, 58 + (5 * 20), 23, 1, 0);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 0; //return to decision state
game.swnstate2 = 1;
break;
case 8:
//Middle
createentity(320+150, 58 + (2 * 20), 23, 1, 0);
createentity(320+150, 58 + (3 * 20), 23, 1, 0);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 0; //return to decision state
game.swnstate2 = 0;
break;
case 9:
if(game.swnstate3==0)
{
game.swnstate2++;
if (game.swnstate2 >= 6)
{
game.swnstate3 = 1;
game.swnstate2--;
}
}
else
{
game.swnstate2--;
if (game.swnstate2 < 0)
{
game.swnstate3 = 0;
game.swnstate2++;
}
}
createentity(320 + 150, 58 + (int(game.swnstate2) * 20), 23, 1, 0);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 0; //return to decision state
break;
}
}
else if (t == 1)
{
//Game 2, super gravitron
switch(game.swnstate)
{
case 0:
//Choose either simple or filler
game.swnstate2 = 0;
game.swnstate3 = 0;
game.swnstate4 = 0;
game.swnstate2 = int(fRandom() * 100);
if (game.swnstate2 < 25)
{
//simple
game.swnstate = 2;
game.swndelay = 0;
}
else
{
//filler
game.swnstate = 4;
game.swndelay = 0;
}
game.swnstate2 = 0;
break;
case 1:
//complex chain
game.swnstate2 = int(fRandom() * 8);
if (game.swnstate2 == 0)
{
game.swnstate = 10;
game.swndelay = 0;
}
else if (game.swnstate2 == 1)
{
game.swnstate = 12;
game.swndelay = 0;
}
else if (game.swnstate2 == 2)
{
game.swnstate = 14;
game.swndelay = 0;
}
else if (game.swnstate2 == 3)
{
game.swnstate = 20;
game.swndelay = 0;
}
else if (game.swnstate2 == 4)
{
game.swnstate = 21;
game.swndelay = 0;
}
else if (game.swnstate2 == 5)
{
game.swnstate = 22;
game.swndelay = 0;
}
else if (game.swnstate2 == 6)
{
game.swnstate = 22;
game.swndelay = 0;
}
else if (game.swnstate2 == 7)
{
game.swnstate = 14;
game.swndelay = 0;
}
game.swnstate2 = 0;
break;
case 2:
//simple chain
game.swnstate2 = int(fRandom() * 6);
if (game.swnstate2 == 0)
{
game.swnstate = 23;
game.swndelay = 0;
}
else if (game.swnstate2 == 1)
{
game.swnstate = 24;
game.swndelay = 0;
}
else if (game.swnstate2 == 2)
{
game.swnstate = 25;
game.swndelay = 0;
}
else if (game.swnstate2 == 3)
{
game.swnstate = 26;
game.swndelay = 0;
}
else if (game.swnstate2 == 4)
{
game.swnstate = 27;
game.swndelay = 0;
}
else if (game.swnstate2 == 5)
{
game.swnstate = 14;
game.swndelay = 0;
}
game.swnstate2 = 0;
break;
case 3:
//Choose a major action
game.swnstate2 = int(fRandom() * 100);
game.swnstate4 = 0;
2020-01-01 21:29:24 +01:00
if (game.swnstate2 < 25)
{
//complex
game.swnstate = 1;
game.swndelay = 0;
}
else
{
//simple
game.swnstate = 2;
game.swndelay = 0;
}
break;
case 4:
//filler chain
game.swnstate2 = int(fRandom() * 6);
if (game.swnstate2 == 0)
{
game.swnstate = 28;
game.swndelay = 0;
}
else if (game.swnstate2 == 1)
{
game.swnstate = 29;
game.swndelay = 0;
}
else if (game.swnstate2 == 2)
{
game.swnstate = 28;
game.swndelay = 0;
}
else if (game.swnstate2 == 3)
{
game.swnstate = 29;
game.swndelay = 0;
}
else if (game.swnstate2 == 4)
{
game.swnstate = 30;
game.swndelay = 0;
}
else if (game.swnstate2 == 5)
{
game.swnstate = 31;
game.swndelay = 0;
}
game.swnstate2 = 0;
break;
case 10:
gravcreate(0, 0);
gravcreate(1, 0);
gravcreate(2, 0);
2020-01-01 21:29:24 +01:00
game.swnstate++;
game.swndelay = 10; //return to decision state
break;
case 11:
gravcreate(3, 0);
gravcreate(4, 0);
gravcreate(5, 0);
2020-01-01 21:29:24 +01:00
game.swnstate2++;
if(game.swnstate2==3)
{
game.swnstate = 0;
game.swndelay = 30; //return to decision state
}
else
{
game.swnstate--;
game.swndelay = 10; //return to decision state
}
break;
case 12:
gravcreate(0, 1);
gravcreate(1, 1);
gravcreate(2, 1);
2020-01-01 21:29:24 +01:00
game.swnstate++;
game.swndelay = 10; //return to decision state
break;
case 13:
gravcreate(3, 1);
gravcreate(4, 1);
gravcreate(5, 1);
2020-01-01 21:29:24 +01:00
game.swnstate2++;
if(game.swnstate2==3)
{
game.swnstate = 0;
game.swndelay = 30; //return to decision state
}
else
{
game.swnstate--;
game.swndelay = 10; //return to decision state
}
break;
case 14:
gravcreate(0, 0, 0);
gravcreate(5, 1, 0);
2020-01-01 21:29:24 +01:00
game.swnstate++;
game.swndelay = 20; //return to decision state
break;
case 15:
gravcreate(1, 0);
gravcreate(4, 1);
2020-01-01 21:29:24 +01:00
game.swnstate++;
game.swndelay = 20; //return to decision state
break;
case 16:
gravcreate(2, 0);
gravcreate(3, 1);
2020-01-01 21:29:24 +01:00
game.swnstate++;
game.swndelay = 20; //return to decision state
break;
case 17:
gravcreate(3, 0);
gravcreate(2, 1);
2020-01-01 21:29:24 +01:00
game.swnstate++;
game.swndelay = 20; //return to decision state
break;
case 18:
gravcreate(4, 0);
gravcreate(1, 1);
2020-01-01 21:29:24 +01:00
game.swnstate++;
game.swndelay = 20; //return to decision state
break;
case 19:
gravcreate(5, 0);
gravcreate(0, 1);
2020-01-01 21:29:24 +01:00
game.swnstate=0;
game.swndelay = 20; //return to decision state
break;
case 20:
game.swnstate4++;
if(game.swnstate3==0)
{
game.swnstate2++;
if (game.swnstate2 >= 6)
{
game.swnstate3 = 1;
game.swnstate2--;
}
}
else
{
game.swnstate2--;
if (game.swnstate2 < 0)
{
game.swnstate3 = 0;
game.swnstate2++;
}
}
createentity(-150, 58 + (int(game.swnstate2) * 20), 23, 0, 0);
2020-01-01 21:29:24 +01:00
if(game.swnstate4<=6)
{
game.swnstate = 20;
game.swndelay = 10; //return to decision state
}
else
{
game.swnstate = 0;
game.swndelay = 10; //return to decision state
}
break;
case 21:
game.swnstate4++;
if(game.swnstate3==0)
{
game.swnstate2++;
if (game.swnstate2 >= 6)
{
game.swnstate3 = 1;
game.swnstate2--;
}
}
else
{
game.swnstate2--;
if (game.swnstate2 < 0)
{
game.swnstate3 = 0;
game.swnstate2++;
}
}
createentity(320+150, 58 + (int(game.swnstate2) * 20), 23, 1, 0);
2020-01-01 21:29:24 +01:00
if(game.swnstate4<=6)
{
game.swnstate = 21;
game.swndelay = 10; //return to decision state
}
else
{
game.swnstate = 0;
game.swndelay = 10; //return to decision state
}
break;
case 22:
game.swnstate4++;
//left and right compliments
game.swnstate2 = int(fRandom() * 6);
createentity(-150, 58 + (game.swnstate2 * 20), 23, 0, 0);
createentity(320 + 150, 58 + ((5 - game.swnstate2) * 20), 23, 1, 0);
2020-01-01 21:29:24 +01:00
if(game.swnstate4<=12)
{
game.swnstate = 22;
game.swndelay = 18; //return to decision state
}
else
{
game.swnstate = 0;
game.swndelay = 18; //return to decision state
}
game.swnstate2 = 0;
break;
case 23:
gravcreate(1, 0);
gravcreate(2, 0, 15);
gravcreate(2, 0, -15);
gravcreate(3, 0, 15);
gravcreate(3, 0, -15);
gravcreate(4, 0);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 15; //return to decision state
break;
case 24:
gravcreate(1, 1);
gravcreate(2, 1, 15);
gravcreate(2, 1, -15);
gravcreate(3, 1, 15);
gravcreate(3, 1, -15);
gravcreate(4, 1);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 15; //return to decision state
break;
case 25:
gravcreate(0, 0);
gravcreate(1, 1,0,10);
gravcreate(4, 1,0,-10);
gravcreate(5, 0);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 20; //return to decision state
break;
case 26:
gravcreate(0, 1, 0);
gravcreate(1, 1, 10);
gravcreate(4, 1, 40);
gravcreate(5, 1, 50);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 20; //return to decision state
break;
case 27:
gravcreate(0, 0, 0);
gravcreate(1, 0, 10);
gravcreate(4, 0, 40);
gravcreate(5, 0, 50);
2020-01-01 21:29:24 +01:00
game.swnstate = 0;
game.swndelay = 20; //return to decision state
break;
case 28:
game.swnstate4++;
game.swnstate2 = int(fRandom() * 6);
createentity(-150, 58 + (game.swnstate2 * 20), 23, 0, 0);
2020-01-01 21:29:24 +01:00
if(game.swnstate4<=6)
{
game.swnstate = 28;
game.swndelay = 8; //return to decision state
}
else
{
game.swnstate = 3;
game.swndelay = 15; //return to decision state
}
game.swnstate2 = 0;
break;
case 29:
game.swnstate4++;
game.swnstate2 = int(fRandom() * 6);
gravcreate(game.swnstate2, 1);
2020-01-01 21:29:24 +01:00
if(game.swnstate4<=6)
{
game.swnstate = 29;
game.swndelay = 8; //return to decision state
}
else
{
game.swnstate = 3;
game.swndelay = 15; //return to decision state
}
game.swnstate2 = 0;
break;
case 30:
game.swnstate4++;
game.swnstate2 = int(fRandom() * 3);
gravcreate(game.swnstate2, 0);
gravcreate(5-game.swnstate2, 0);
2020-01-01 21:29:24 +01:00
if(game.swnstate4<=2)
{
game.swnstate = 30;
game.swndelay = 14; //return to decision state
}
else
{
game.swnstate = 3;
game.swndelay = 15; //return to decision state
}
game.swnstate2 = 0;
break;
case 31:
game.swnstate4++;
game.swnstate2 = int(fRandom() * 3);
gravcreate(game.swnstate2, 1);
gravcreate(5-game.swnstate2, 1);
2020-01-01 21:29:24 +01:00
if(game.swnstate4<=2)
{
game.swnstate = 31;
game.swndelay = 14; //return to decision state
}
else
{
game.swnstate = 3;
game.swndelay = 15; //return to decision state
}
game.swnstate2 = 0;
break;
}
}
}
else
{
game.swndelay--;
}
}
void entityclass::createblock( int t, int xp, int yp, int w, int h, int trig /*= 0*/, const std::string& script /*= ""*/ )
2020-01-01 21:29:24 +01:00
{
k = blocks.size();
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
blockclass newblock;
blockclass* blockptr;
/* Can we reuse the slot of a disabled block? */
bool reuse = false;
for (size_t i = 0; i < blocks.size(); ++i)
{
if (blocks[i].wp == 0
Fix entity and block indices after destroying them This patch restores some 2.2 behavior, fixing a regression caused by the refactor of properly using std::vectors. In 2.2, the game allocated 200 items in obj.entities, but used a system where each entity had an `active` attribute to signify if the entity actually existed or not. When dealing with entities, you would have to check this `active` flag, or else you'd be dealing with an entity that didn't actually exist. (By the way, what I'm saying applies to blocks and obj.blocks as well, except for some small differing details like the game allocating 500 block slots versus obj.entities's 200.) As a consequence, the game had to use a separate tracking variable, obj.nentity, because obj.entities.size() would just report 200, instead of the actual amount of entities. Needless to say, having to check for `active` and use `obj.nentity` is a bit error-prone, and it's messier than simply using the std::vector the way it was intended. Also, this resulted in a hard limit of 200 entities, which custom level makers ran into surprisingly quite often. 2.3 comes along, and removes the whole system. Now, std::vectors are properly being used, and obj.entities.size() reports the actual number of entities in the vector; you no longer have to check for `active` when dealing with entities of any sort. But there was one previous behavior of 2.2 that this system kind of forgets about - namely, the ability to have holes in between entities. You see, when an entity got disabled in 2.2 (which just meant turning its `active` off), the indices of all other entities stayed the same; the indice of the entity that got disabled stays there as a hole in the array. But when an entity gets removed in 2.3 (previous to this patch), the indices of every entity afterwards in the array get shifted down by one. std::vector isn't really meant to be able to contain holes. Do the indices of entities and blocks matter? Yes; they determine the order in which entities and blocks get evaluated (the highest indice gets evaluated first), and I had to fix some block evaluation order stuff in previous PRs. And in the case of entities, they matter hugely when using the recently-discovered Arbitrary Entity Manipulation glitch (where crewmate script commands are used on arbitrary entities by setting the `i` attribute of `scriptclass` and passing invalid crewmate identifiers to the commands). If you use Arbitrary Entity Manipulation after destroying some entities, there is a chance that your script won't work between 2.2 and 2.3. The indices also still determine the rendering order of entities (highest indice gets drawn first, which means lowest indice gets drawn in front of other entities). As an example: let's say we have the player at 0, a gravity line at 1, and a checkpoint at 2; then we destroy the gravity line and create a crewmate (let's do Violet). If we're able to have holes, then after removing the gravity line, none of the other indices shift. Then Violet will be created at indice 1, and will be drawn in front of the checkpoint. But if we can't have holes, then removing the gravity line results in the indice of the checkpoint shifting down to indice 1. Then Violet is created at indice 2, and gets drawn behind the checkpoint! This is a clear illustration of changing the behavior that existed in 2.2. However, I also don't want to go back to the `active` system of having to check an attribute before operating on an entity. So... what do we do to restore the holes? Well, we don't need to have an `active` attribute, or modify any existing code that operates on entities. Instead, we can just set the attributes of the entities so that they naturally get ignored by everything that comes into contact with it. For entities, we set their invis to true, and their size, type, and rule to -1 (the game never uses a size, type, or rule of -1 anywhere); for blocks, we set their type to -1, and their width and height to 0. obj.entities.size() will no longer necessarily equal the amount of entities in the room; rather, it will be the amount of entity SLOTS that have been allocated. But nothing that uses obj.entities.size() needs to actually know the amount of entities; it's mostly used for iterating over every entity in the vector. Excess entity slots get cleaned up upon every call of mapclass::gotoroom(), which will now deallocate entity slots starting from the end until it hits a player, at which point it will switch to disabling entity slots instead of removing them entirely. The entclass::clear() and blockclass::clear() functions have been restored because we need to call their initialization functions when reusing a block/entity slot; it's possible to create an entity with an invalid type number (it creates a glitchy Viridian), and without calling the initialization function again, it would simply not create anything. After this patch is applied, entity and block indices will be restored to how they behaved in 2.2.
2020-12-27 07:11:34 +01:00
&& blocks[i].hp == 0
&& blocks[i].rect.w == 0
&& blocks[i].rect.h == 0)
{
reuse = true;
blockptr = &blocks[i];
break;
}
}
if (!reuse)
{
blockptr = &newblock;
}
else
{
blockptr->clear();
}
blockclass& block = *blockptr;
2020-01-01 21:29:24 +01:00
switch(t)
{
case BLOCK: //Block
block.type = BLOCK;
block.xp = xp;
block.yp = yp;
block.wp = w;
block.hp = h;
block.rectset(xp, yp, w, h);
2020-01-01 21:29:24 +01:00
break;
case TRIGGER: //Trigger
block.type = TRIGGER;
block.wp = w;
block.hp = h;
block.rectset(xp, yp, w, h);
block.trigger = trig;
block.script = script;
2020-01-01 21:29:24 +01:00
break;
case DAMAGE: //Damage
block.type = DAMAGE;
block.wp = w;
block.hp = h;
block.rectset(xp, yp, w, h);
2020-01-01 21:29:24 +01:00
break;
case DIRECTIONAL: //Directional
block.type = DIRECTIONAL;
block.wp = w;
block.hp = h;
block.rectset(xp, yp, w, h);
block.trigger = trig;
2020-01-01 21:29:24 +01:00
break;
case SAFE: //Safe block
block.type = SAFE;
block.xp = xp;
block.yp = yp;
block.wp = w;
block.hp = h;
block.rectset(xp, yp, w, h);
2020-01-01 21:29:24 +01:00
break;
case ACTIVITY: //Activity Zone
block.type = ACTIVITY;
block.wp = w;
block.hp = h;
block.rectset(xp, yp, w, h);
2020-01-01 21:29:24 +01:00
//Ok, each and every activity zone in the game is initilised here. "Trig" in this case is a variable that
//assigns all the details.
switch(trig)
{
case 0: //testing zone
block.prompt = "Press %s to explode";
block.script = "intro";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=1;
break;
case 1:
block.prompt = "Press %s to talk to Violet";
block.script = "talkpurple";
block.setblockcolour("purple");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 2:
block.prompt = "Press %s to talk to Vitellary";
block.script = "talkyellow";
block.setblockcolour("yellow");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 3:
block.prompt = "Press %s to talk to Vermilion";
block.script = "talkred";
block.setblockcolour("red");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 4:
block.prompt = "Press %s to talk to Verdigris";
block.script = "talkgreen";
block.setblockcolour("green");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 5:
block.prompt = "Press %s to talk to Victoria";
block.script = "talkblue";
block.setblockcolour("blue");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 6:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_station_1";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 7:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_outside_1";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 8:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_outside_2";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 9:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_outside_3";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 10:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_outside_4";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 11:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_outside_5";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 12:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_outside_6";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 13:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_finallevel";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 14:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_station_2";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 15:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_station_3";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 16:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_station_4";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 17:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_warp_1";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 18:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_warp_2";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 19:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_lab_1";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 20:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_lab_2";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 21:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_secretlab";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 22:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_shipcomputer";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 23:
block.prompt = "Press %s to activate terminals";
block.script = "terminal_radio";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 24:
block.prompt = "Press %s to activate terminal";
block.script = "terminal_jukebox";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 25:
block.prompt = "Passion for Exploring";
block.script = "terminal_juke1";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 26:
block.prompt = "Pushing Onwards";
block.script = "terminal_juke2";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 27:
block.prompt = "Positive Force";
block.script = "terminal_juke3";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 28:
block.prompt = "Presenting VVVVVV";
block.script = "terminal_juke4";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 29:
block.prompt = "Potential for Anything";
block.script = "terminal_juke5";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 30:
block.prompt = "Predestined Fate";
block.script = "terminal_juke6";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 31:
block.prompt = "Pipe Dream";
block.script = "terminal_juke7";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 32:
block.prompt = "Popular Potpourri";
block.script = "terminal_juke8";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 33:
block.prompt = "Pressure Cooker";
block.script = "terminal_juke9";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 34:
block.prompt = "ecroF evitisoP";
block.script = "terminal_juke10";
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
case 35:
block.prompt = "Press %s to activate terminal";
block.script = "custom_"+customscript;
block.setblockcolour("orange");
2020-01-01 21:29:24 +01:00
trig=0;
break;
}
break;
}
Fix entity and block indices after destroying them This patch restores some 2.2 behavior, fixing a regression caused by the refactor of properly using std::vectors. In 2.2, the game allocated 200 items in obj.entities, but used a system where each entity had an `active` attribute to signify if the entity actually existed or not. When dealing with entities, you would have to check this `active` flag, or else you'd be dealing with an entity that didn't actually exist. (By the way, what I'm saying applies to blocks and obj.blocks as well, except for some small differing details like the game allocating 500 block slots versus obj.entities's 200.) As a consequence, the game had to use a separate tracking variable, obj.nentity, because obj.entities.size() would just report 200, instead of the actual amount of entities. Needless to say, having to check for `active` and use `obj.nentity` is a bit error-prone, and it's messier than simply using the std::vector the way it was intended. Also, this resulted in a hard limit of 200 entities, which custom level makers ran into surprisingly quite often. 2.3 comes along, and removes the whole system. Now, std::vectors are properly being used, and obj.entities.size() reports the actual number of entities in the vector; you no longer have to check for `active` when dealing with entities of any sort. But there was one previous behavior of 2.2 that this system kind of forgets about - namely, the ability to have holes in between entities. You see, when an entity got disabled in 2.2 (which just meant turning its `active` off), the indices of all other entities stayed the same; the indice of the entity that got disabled stays there as a hole in the array. But when an entity gets removed in 2.3 (previous to this patch), the indices of every entity afterwards in the array get shifted down by one. std::vector isn't really meant to be able to contain holes. Do the indices of entities and blocks matter? Yes; they determine the order in which entities and blocks get evaluated (the highest indice gets evaluated first), and I had to fix some block evaluation order stuff in previous PRs. And in the case of entities, they matter hugely when using the recently-discovered Arbitrary Entity Manipulation glitch (where crewmate script commands are used on arbitrary entities by setting the `i` attribute of `scriptclass` and passing invalid crewmate identifiers to the commands). If you use Arbitrary Entity Manipulation after destroying some entities, there is a chance that your script won't work between 2.2 and 2.3. The indices also still determine the rendering order of entities (highest indice gets drawn first, which means lowest indice gets drawn in front of other entities). As an example: let's say we have the player at 0, a gravity line at 1, and a checkpoint at 2; then we destroy the gravity line and create a crewmate (let's do Violet). If we're able to have holes, then after removing the gravity line, none of the other indices shift. Then Violet will be created at indice 1, and will be drawn in front of the checkpoint. But if we can't have holes, then removing the gravity line results in the indice of the checkpoint shifting down to indice 1. Then Violet is created at indice 2, and gets drawn behind the checkpoint! This is a clear illustration of changing the behavior that existed in 2.2. However, I also don't want to go back to the `active` system of having to check an attribute before operating on an entity. So... what do we do to restore the holes? Well, we don't need to have an `active` attribute, or modify any existing code that operates on entities. Instead, we can just set the attributes of the entities so that they naturally get ignored by everything that comes into contact with it. For entities, we set their invis to true, and their size, type, and rule to -1 (the game never uses a size, type, or rule of -1 anywhere); for blocks, we set their type to -1, and their width and height to 0. obj.entities.size() will no longer necessarily equal the amount of entities in the room; rather, it will be the amount of entity SLOTS that have been allocated. But nothing that uses obj.entities.size() needs to actually know the amount of entities; it's mostly used for iterating over every entity in the vector. Excess entity slots get cleaned up upon every call of mapclass::gotoroom(), which will now deallocate entity slots starting from the end until it hits a player, at which point it will switch to disabling entity slots instead of removing them entirely. The entclass::clear() and blockclass::clear() functions have been restored because we need to call their initialization functions when reusing a block/entity slot; it's possible to create an entity with an invalid type number (it creates a glitchy Viridian), and without calling the initialization function again, it would simply not create anything. After this patch is applied, entity and block indices will be restored to how they behaved in 2.2.
2020-12-27 07:11:34 +01:00
if (!reuse)
{
blocks.push_back(block);
}
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
/* Disable entity, and return true if entity was successfully disabled */
bool entityclass::disableentity(int t)
{
if (!INBOUNDS_VEC(t, entities))
{
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
puts("disableentity() out-of-bounds!");
Prevent removing the player entity Removing the player entity has all sorts of nasty effects, such as softlocking the game because many inputs require there to be a player present, such as opening the quit menu. The most infamous glitch to remove the player entity is the Gravitron Fling, where the game doesn't see a gravity line at a specific y-position in the current room, and when it moves the bottom gravity line it moves the player instead. When the gravity line gets outside the room, it gets destroyed, so if the player gets dragged outside the room, they get destroyed, too. (Don't misinterpret this as saying anytime the player gets dragged outside the room, they get destroyed - it's only the Gravitron logic that destroys them.) Also, there are many places in the code that use entity-getting functions that have a fallback value of 0. If it was possible to remove the player, then it's possible for this fallback value of 0 to index obj.entities out-of-bounds, which is not good. To fix this, entityclass::removeentity() is now a bool that signifies if the entity was successfully removed or not. If the entity given is the player (meaning it first checks if it's rule 0, just so in 99% of cases it'll short-circuit and won't do the next check, which is if entityclass::getplayer() says the indice to be removed is the player), then it'll refuse to remove the entity, and return false. This is a change in behavior where callers might expect entityclass::removeentity() to always succeed, so I changed the removeentity_iter() macro to only decrement if removing the entity succeeded. I also changed entityclass::updateentities() from 'removeentity(i); return true;' to 'return removeentity(i);'.
2020-06-13 21:00:04 +02:00
return true;
}
if (entities[t].rule == 0 && t == getplayer())
{
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
/* Don't disable the player entity! */
Prevent removing the player entity Removing the player entity has all sorts of nasty effects, such as softlocking the game because many inputs require there to be a player present, such as opening the quit menu. The most infamous glitch to remove the player entity is the Gravitron Fling, where the game doesn't see a gravity line at a specific y-position in the current room, and when it moves the bottom gravity line it moves the player instead. When the gravity line gets outside the room, it gets destroyed, so if the player gets dragged outside the room, they get destroyed, too. (Don't misinterpret this as saying anytime the player gets dragged outside the room, they get destroyed - it's only the Gravitron logic that destroys them.) Also, there are many places in the code that use entity-getting functions that have a fallback value of 0. If it was possible to remove the player, then it's possible for this fallback value of 0 to index obj.entities out-of-bounds, which is not good. To fix this, entityclass::removeentity() is now a bool that signifies if the entity was successfully removed or not. If the entity given is the player (meaning it first checks if it's rule 0, just so in 99% of cases it'll short-circuit and won't do the next check, which is if entityclass::getplayer() says the indice to be removed is the player), then it'll refuse to remove the entity, and return false. This is a change in behavior where callers might expect entityclass::removeentity() to always succeed, so I changed the removeentity_iter() macro to only decrement if removing the entity succeeded. I also changed entityclass::updateentities() from 'removeentity(i); return true;' to 'return removeentity(i);'.
2020-06-13 21:00:04 +02:00
return false;
}
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
entities[t].invis = true;
entities[t].size = -1;
entities[t].type = -1;
entities[t].rule = -1;
Prevent removing the player entity Removing the player entity has all sorts of nasty effects, such as softlocking the game because many inputs require there to be a player present, such as opening the quit menu. The most infamous glitch to remove the player entity is the Gravitron Fling, where the game doesn't see a gravity line at a specific y-position in the current room, and when it moves the bottom gravity line it moves the player instead. When the gravity line gets outside the room, it gets destroyed, so if the player gets dragged outside the room, they get destroyed, too. (Don't misinterpret this as saying anytime the player gets dragged outside the room, they get destroyed - it's only the Gravitron logic that destroys them.) Also, there are many places in the code that use entity-getting functions that have a fallback value of 0. If it was possible to remove the player, then it's possible for this fallback value of 0 to index obj.entities out-of-bounds, which is not good. To fix this, entityclass::removeentity() is now a bool that signifies if the entity was successfully removed or not. If the entity given is the player (meaning it first checks if it's rule 0, just so in 99% of cases it'll short-circuit and won't do the next check, which is if entityclass::getplayer() says the indice to be removed is the player), then it'll refuse to remove the entity, and return false. This is a change in behavior where callers might expect entityclass::removeentity() to always succeed, so I changed the removeentity_iter() macro to only decrement if removing the entity succeeded. I also changed entityclass::updateentities() from 'removeentity(i); return true;' to 'return removeentity(i);'.
2020-06-13 21:00:04 +02:00
return true;
}
void entityclass::removeallblocks(void)
2020-01-01 21:29:24 +01:00
{
blocks.clear();
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
void entityclass::disableblock( int t )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(t, blocks))
{
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
puts("disableblock() out-of-bounds!");
return;
}
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
blocks[t].wp = 0;
blocks[t].hp = 0;
blocks[t].rect.w = blocks[t].wp;
blocks[t].rect.h = blocks[t].hp;
2020-01-01 21:29:24 +01:00
}
void entityclass::moveblockto(int x1, int y1, int x2, int y2, int w, int h)
{
for (size_t i = 0; i < blocks.size(); i++)
{
if (blocks[i].xp == x1 && blocks[i].yp == y1)
{
blocks[i].xp = x2;
blocks[i].yp = y2;
blocks[i].wp = w;
blocks[i].hp = h;
blocks[i].rectset(blocks[i].xp, blocks[i].yp, blocks[i].wp, blocks[i].hp);
break;
}
}
}
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
void entityclass::disableblockat(int x, int y)
{
for (size_t i = 0; i < blocks.size(); i++)
{
if (blocks[i].xp == x && blocks[i].yp == y)
{
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
disableblock(i);
}
}
}
2020-01-01 21:29:24 +01:00
void entityclass::removetrigger( int t )
{
for(size_t i=0; i<blocks.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(blocks[i].type == TRIGGER && blocks[i].trigger == t)
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
disableblock(i);
2020-01-01 21:29:24 +01:00
}
}
}
void entityclass::copylinecross( int t )
{
if (!INBOUNDS_VEC(t, entities))
{
puts("copylinecross() out-of-bounds!");
return;
}
2020-01-01 21:29:24 +01:00
//Copy entity t into the first free linecrosskludge entity
linecrosskludge.push_back(entities[t]);
2020-01-01 21:29:24 +01:00
}
void entityclass::revertlinecross( int t, int s )
{
if (!INBOUNDS_VEC(t, entities) || !INBOUNDS_VEC(s, linecrosskludge))
{
puts("revertlinecross() out-of-bounds!");
return;
}
2020-01-01 21:29:24 +01:00
//Restore entity t info from linecrossing s
entities[t].onentity = linecrosskludge[s].onentity;
entities[t].state = linecrosskludge[s].state;
entities[t].life = linecrosskludge[s].life;
}
bool entityclass::gridmatch( int p1, int p2, int p3, int p4, int p11, int p21, int p31, int p41 )
{
if (p1 == p11 && p2 == p21 && p3 == p31 && p4 == p41) return true;
return false;
}
int entityclass::crewcolour( int t )
{
//Return the colour of the indexed crewmate
switch(t)
{
case 0:
return 0;
break;
case 1:
return 20;
break;
case 2:
return 14;
break;
case 3:
return 15;
break;
case 4:
return 13;
break;
case 5:
return 16;
break;
}
return 0;
}
void entityclass::createentity(int xp, int yp, int t, int meta1, int meta2, int p1, int p2, int p3, int p4)
2020-01-01 21:29:24 +01:00
{
k = entities.size();
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
entclass newent;
entclass* entptr;
/* Can we reuse the slot of a disabled entity? */
bool reuse = false;
for (size_t i = 0; i < entities.size(); ++i)
{
if (entities[i].invis
&& entities[i].size == -1
&& entities[i].type == -1
&& entities[i].rule == -1)
{
reuse = true;
entptr = &entities[i];
break;
}
}
if (!reuse)
{
entptr = &newent;
}
else
{
entptr->clear();
}
2020-01-01 21:29:24 +01:00
//Size 0 is a sprite
//Size 1 is a tile
//Beyond that are special cases (to do)
//Size 2 is a moving platform of width 4 (32)
//Size 3 is apparently a "bug chunky pixel"
//Size 4 is a coin/small pickup
//Size 5 is a horizontal line, 6 is vertical
//Rule 0 is the playable character
//Rule 1 is anything harmful
//Rule 2 is anything decorative (no collisions)
//Rule 3 is anything that results in an entity to entity collision and state change
//Rule 4 is a horizontal line, 5 is vertical
//Rule 6 is a crew member
#if !defined(NO_CUSTOM_LEVELS)
// Special case for gray Warp Zone tileset!
const edlevelclass* const room = ed.getroomprop(game.roomx - 100, game.roomy - 100);
bool custom_gray = room->tileset == 3 && room->tilecol == 6;
#else
bool custom_gray = false;
#endif
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
entclass& entity = *entptr;
entity.xp = xp;
entity.yp = yp;
entity.type = t;
2020-01-01 21:29:24 +01:00
switch(t)
{
case 0: //Player
entity.rule = 0; //Playable character
entity.tile = 0;
entity.colour = 0;
entity.cx = 6;
entity.cy = 2;
entity.w = 12;
entity.h = 21;
entity.dir = 1;
/* Fix wrong y-position if spawning in on conveyor */
entity.newxp = xp;
entity.newyp = yp;
if (meta1 == 1) entity.invis = true;
entity.gravity = true;
2020-01-01 21:29:24 +01:00
break;
case 1: //Simple enemy, bouncing off the walls
entity.rule = 1;
entity.behave = meta1;
entity.para = meta2;
entity.w = 16;
entity.h = 16;
entity.cx = 0;
entity.cy = 0;
entity.x1 = p1;
entity.y1 = p2;
entity.x2 = p3;
entity.y2 = p4;
entity.harmful = true;
entity.tile = 24;
entity.animate = 0;
entity.colour = 8;
2020-01-01 21:29:24 +01:00
if (game.roomy == 111 && (game.roomx >= 113 && game.roomx <= 117))
{
entity.setenemy(0);
entity.setenemyroom(game.roomx, game.roomy); //For colour
2020-01-01 21:29:24 +01:00
}
else if (game.roomx == 113 && (game.roomy <= 110 && game.roomy >= 108))
{
entity.setenemy(1);
entity.setenemyroom(game.roomx, game.roomy); //For colour
2020-01-01 21:29:24 +01:00
}
else if (game.roomx == 113 && game.roomy == 107)
{
//MAVVERRRICK
entity.tile = 96;
entity.colour = 6;
entity.size = 9;
entity.w = 64;
entity.h = 44;
entity.animate = 4;
2020-01-01 21:29:24 +01:00
}
else
{
entity.setenemyroom(game.roomx, game.roomy);
2020-01-01 21:29:24 +01:00
}
break;
case 2: //A moving platform
entity.rule = 2;
entity.type = 1;
entity.size = 2;
entity.tile = 1;
2020-01-01 21:29:24 +01:00
if (customplatformtile > 0){
entity.tile = customplatformtile;
2020-01-01 21:29:24 +01:00
}else if (platformtile > 0) {
entity.tile = platformtile;
2020-01-01 21:29:24 +01:00
}else{
//appearance again depends on location
if (gridmatch(p1, p2, p3, p4, 100, 70, 320, 160)) entity.tile = 616;
if (gridmatch(p1, p2, p3, p4, 72, 0, 248, 240)) entity.tile = 610;
if (gridmatch(p1, p2, p3, p4, -20, 0, 320, 240)) entity.tile = 413;
2020-01-01 21:29:24 +01:00
if (gridmatch(p1, p2, p3, p4, -96, -72, 400, 312)) entity.tile = 26;
if (gridmatch(p1, p2, p3, p4, -32, -40, 352, 264)) entity.tile = 27;
2020-01-01 21:29:24 +01:00
}
entity.w = 32;
entity.h = 8;
2020-01-01 21:29:24 +01:00
if (meta1 <= 1) vertplatforms = true;
if (meta1 >= 2 && meta1 <= 5) horplatforms = true;
if (meta1 == 14 || meta1 == 15) horplatforms = true; //special case for last part of Space Station
if (meta1 >= 6 && meta1 <= 7) vertplatforms = true;
2020-01-01 21:29:24 +01:00
if (meta1 >= 10 && meta1 <= 11)
2020-01-01 21:29:24 +01:00
{
//Double sized threadmills
entity.w = 64;
entity.h = 8;
meta1 -= 2;
entity.size = 8;
2020-01-01 21:29:24 +01:00
}
entity.behave = meta1;
entity.para = meta2;
2020-01-01 21:29:24 +01:00
if (meta1 >= 8 && meta1 <= 9)
2020-01-01 21:29:24 +01:00
{
horplatforms = true; //threadmill!
entity.animate = 10;
2020-01-01 21:29:24 +01:00
if(customplatformtile>0){
entity.tile = customplatformtile+4;
if (meta1 == 8) entity.tile += 4;
if (meta1 == 9) entity.animate = 11;
2020-01-01 21:29:24 +01:00
}else{
entity.settreadmillcolour(game.roomx, game.roomy);
if (meta1 == 8) entity.tile += 40;
if (meta1 == 9) entity.animate = 11;
2020-01-01 21:29:24 +01:00
}
}
else
{
entity.animate = 100;
2020-01-01 21:29:24 +01:00
}
entity.x1 = p1;
entity.y1 = p2;
entity.x2 = p3;
entity.y2 = p4;
2020-01-01 21:29:24 +01:00
entity.isplatform = true;
2020-01-01 21:29:24 +01:00
createblock(0, xp, yp, 32, 8);
break;
case 3: //Disappearing platforms
entity.rule = 3;
entity.type = 2;
entity.size = 2;
entity.tile = 2;
2020-01-01 21:29:24 +01:00
//appearance again depends on location
if(customplatformtile>0)
{
entity.tile=customplatformtile;
2020-01-01 21:29:24 +01:00
}
else if (meta1 > 0)
2020-01-01 21:29:24 +01:00
{
entity.tile = meta1;
2020-01-01 21:29:24 +01:00
}
else
{
if(game.roomx==49 && game.roomy==52) entity.tile = 18;
if (game.roomx == 50 && game.roomy == 52) entity.tile = 22;
2020-01-01 21:29:24 +01:00
}
entity.cy = -1;
entity.w = 32;
entity.h = 10;
entity.behave = meta1;
entity.para = meta2;
entity.onentity = 1;
entity.animate = 100;
2020-01-01 21:29:24 +01:00
createblock(0, xp, yp, 32, 8);
break;
case 4: //Breakable blocks
entity.rule = 6;
entity.type = 3;
entity.size = 1;
entity.tile = 10;
entity.cy = -1;
entity.w = 8;
entity.h = 10;
entity.behave = meta1;
entity.para = meta2;
entity.onentity = 1;
entity.animate = 100;
2020-01-01 21:29:24 +01:00
createblock(0, xp, yp, 8, 8);
break;
case 5: //Gravity Tokens
entity.rule = 3;
entity.type = 4;
entity.size = 0;
entity.tile = 11;
entity.w = 16;
entity.h = 16;
entity.behave = meta1;
entity.para = meta2;
entity.onentity = 1;
entity.animate = 100;
2020-01-01 21:29:24 +01:00
break;
case 6: //Decorative particles
entity.rule = 2;
entity.type = 5; //Particles
entity.colour = 1;
entity.size = 3;
entity.vx = meta1;
entity.vy = meta2;
entity.life = 12;
2020-01-01 21:29:24 +01:00
break;
case 7: //Decorative particles
entity.rule = 2;
entity.type = 5; //Particles
entity.colour = 2;
entity.size = 3;
entity.vx = meta1;
entity.vy = meta2;
entity.life = 12;
2020-01-01 21:29:24 +01:00
break;
case 8: //Small collectibles
entity.rule = 3;
entity.type = 6;
entity.size = 4;
entity.tile = 48;
entity.w = 8;
entity.h = 8;
entity.onentity = 1;
entity.animate = 100;
2020-01-01 21:29:24 +01:00
//Check if it's already been collected
entity.para = meta1;
if (!INBOUNDS_ARR(meta1, collect) || collect[meta1]) return;
2020-01-01 21:29:24 +01:00
break;
case 9: //Something Shiny
entity.rule = 3;
entity.type = 7;
entity.size = 0;
entity.tile = 22;
entity.w = 16;
entity.h = 16;
entity.colour = 3;
entity.onentity = 1;
entity.animate = 100;
2020-01-01 21:29:24 +01:00
//Check if it's already been collected
entity.para = meta1;
if (!INBOUNDS_ARR(meta1, collect) || collect[meta1]) return;
2020-01-01 21:29:24 +01:00
break;
case 10: //Savepoint
entity.rule = 3;
entity.type = 8;
entity.size = 0;
entity.tile = 20 + meta1;
entity.w = 16;
entity.h = 16;
entity.colour = 4;
entity.onentity = 1;
entity.animate = 100;
entity.para = meta2;
2020-01-01 21:29:24 +01:00
if (game.savepoint == meta2)
2020-01-01 21:29:24 +01:00
{
entity.colour = 5;
entity.onentity = 0;
2020-01-01 21:29:24 +01:00
}
if (game.nodeathmode)
{
return;
2020-01-01 21:29:24 +01:00
}
break;
case 11: //Horizontal Gravity Line
entity.rule = 4;
entity.type = 9;
entity.size = 5;
entity.life = 0;
entity.w = meta1;
entity.h = 1;
entity.onentity = 1;
2020-01-01 21:29:24 +01:00
break;
case 12: //Vertical Gravity Line
entity.rule = 5;
entity.type = 10;
entity.size = 6;
entity.life = 0;
entity.w = 1;
entity.h = meta1;
//entity.colour = 0;
entity.onentity = 1;
2020-01-01 21:29:24 +01:00
break;
case 13: //Warp token
entity.rule = 3;
entity.type = 11;
entity.size = 0;
entity.tile = 18;
entity.w = 16;
entity.h = 16;
entity.colour = 10;
entity.onentity = 1;
entity.animate = 2;
2020-01-01 21:29:24 +01:00
//Added in port, hope it doesn't break anything
entity.behave = meta1;
entity.para = meta2;
2020-01-01 21:29:24 +01:00
break;
case 14: // Teleporter
entity.rule = 3;
entity.type = 100;
entity.size = 7;
entity.tile = 1; //inactive
entity.w = 96;
entity.h = 96;
entity.colour = 100;
entity.onentity = 1;
entity.animate = 100;
entity.para = meta2;
2020-01-01 21:29:24 +01:00
break;
case 15: // Crew Member (warp zone)
entity.rule = 6;
entity.type = 12; //A special case!
entity.tile = 144;
entity.colour = 13; //144 for sad :(
entity.cx = 6;
entity.cy = 2;
entity.w = 12;
entity.h = 21;
entity.dir = 0;
entity.state = meta1;
entity.gravity = true;
2020-01-01 21:29:24 +01:00
break;
case 16: // Crew Member, upside down (space station)
entity.rule = 7;
entity.type = 12; //A special case!
entity.tile = 144+6;
entity.colour = 14; //144 for sad (upside down+12):(
entity.cx = 6;
entity.cy = 2;
entity.w = 12;
entity.h = 21;
entity.dir = 1;
entity.state = meta1;
entity.gravity = true;
2020-01-01 21:29:24 +01:00
break;
case 17: // Crew Member (Lab)
entity.rule = 6;
entity.type = 12; //A special case!
entity.tile = 144;
entity.colour = 16; //144 for sad :(
entity.cx = 6;
entity.cy = 2;
entity.w = 12;
entity.h = 21;
entity.dir = 1;
entity.state = meta1;
entity.gravity = true;
2020-01-01 21:29:24 +01:00
break;
case 18: // Crew Member (Ship)
//This is the scriping crewmember
entity.rule = 6;
entity.type = 12; //A special case!
entity.colour = meta1;
if (meta2 == 0)
2020-01-01 21:29:24 +01:00
{
entity.tile = 0;
2020-01-01 21:29:24 +01:00
}
else
{
entity.tile = 144;
2020-01-01 21:29:24 +01:00
}
entity.cx = 6;
entity.cy = 2;
entity.w = 12;
entity.h = 21;
entity.dir = 0;
2020-01-01 21:29:24 +01:00
entity.state = p1;
entity.para = p2;
2020-01-01 21:29:24 +01:00
if (p1 == 17)
{
entity.dir = p2;
2020-01-01 21:29:24 +01:00
}
entity.gravity = true;
2020-01-01 21:29:24 +01:00
break;
case 19: // Crew Member (Ship) More tests!
entity.rule = 6;
entity.type = 12; //A special case!
entity.tile = 0;
entity.colour = 6; //54 for sad :(
entity.cx = 6;
entity.cy = 2;
entity.w = 12;
entity.h = 21;
entity.dir = 1;
entity.state = meta1;
entity.gravity = true;
2020-01-01 21:29:24 +01:00
break;
case 20: //Terminal
entity.rule = 3;
entity.type = 13;
entity.size = 0;
entity.tile = 16 + meta1;
entity.w = 16;
entity.h = 16;
entity.colour = 4;
entity.onentity = 1;
entity.animate = 100;
entity.para = meta2;
2020-01-01 21:29:24 +01:00
break;
case 21: //as above, except doesn't highlight
entity.rule = 3;
entity.type = 13;
entity.size = 0;
entity.tile = 16 + meta1;
entity.w = 16;
entity.h = 16;
entity.colour = 4;
entity.onentity = 0;
entity.animate = 100;
entity.para = meta2;
2020-01-01 21:29:24 +01:00
break;
case 22: //Fake trinkets, only appear if you've collected them
entity.rule = 3;
entity.type = 7;
entity.size = 0;
entity.tile = 22;
entity.w = 16;
entity.h = 16;
entity.colour = 3;
entity.onentity = 0;
entity.animate = 100;
2020-01-01 21:29:24 +01:00
//Check if it's already been collected
entity.para = meta1;
if (INBOUNDS_ARR(meta1, collect) && !collect[meta1]) return;
2020-01-01 21:29:24 +01:00
break;
case 23: //SWN Enemies
//Given a different behavior, these enemies are especially for SWN mode and disappear outside the screen.
entity.rule = 1;
entity.type = 23;
entity.behave = meta1;
entity.para = meta2;
entity.w = 16;
entity.h = 16;
entity.cx = 0;
entity.cy = 0;
entity.x1 = -2000;
entity.y1 = -100;
entity.x2 = 5200;
entity.y2 = 340;
entity.harmful = true;
2020-01-01 21:29:24 +01:00
//initilise tiles here based on behavior
entity.size = 12; //don't wrap around
entity.colour = 21;
entity.tile = 78; //default case
entity.animate = 1;
2020-01-01 21:29:24 +01:00
if (game.swngame == 1)
{
//set colour based on current state
entity.colour = swncolour(game.swncolstate);
2020-01-01 21:29:24 +01:00
}
break;
case 24: // Super Crew Member
//This special crewmember is way more advanced than the usual kind, and can interact with game objects
entity.rule = 6;
entity.type = 14; //A special case!
entity.colour = meta1;
if (meta1 == 16)
2020-01-01 21:29:24 +01:00
{
//victoria is sad!
if (meta2 == 2) meta2 = 1;
2020-01-01 21:29:24 +01:00
}
else
{
if (meta2 == 2) meta2 = 0;
2020-01-01 21:29:24 +01:00
}
if (meta2 == 0)
2020-01-01 21:29:24 +01:00
{
entity.tile = 0;
2020-01-01 21:29:24 +01:00
}
else
{
entity.tile = 144;
2020-01-01 21:29:24 +01:00
}
entity.cx = 6;
entity.cy = 2;
entity.w = 12;
entity.h = 21;
entity.dir = 1;
entity.x1 = -2000;
entity.y1 = -100;
entity.x2 = 5200;
entity.y2 = 340;
entity.state = p1;
entity.para = p2;
2020-01-01 21:29:24 +01:00
if (p1 == 17)
{
entity.dir = p2;
2020-01-01 21:29:24 +01:00
}
entity.gravity = true;
2020-01-01 21:29:24 +01:00
break;
case 25: //Trophies
entity.rule = 3;
entity.type = 15;
entity.size = 0;
entity.w = 16;
entity.h = 16;
entity.colour = 4;
entity.onentity = 1;
entity.animate = 100;
entity.para = meta2;
2020-01-01 21:29:24 +01:00
//Decide tile here based on given achievement: both whether you have them and what they are
//default is just a trophy base:
entity.tile = 180 + meta1;
switch (meta2)
2020-01-01 21:29:24 +01:00
{
case 1:
if(game.bestrank[0]>=3)
{
entity.tile = 184 + meta1;
entity.colour = 31;
2020-01-01 21:29:24 +01:00
}
break;
case 2:
if(game.bestrank[1]>=3)
{
entity.tile = 186 + meta1;
entity.colour = 33;
2020-01-01 21:29:24 +01:00
}
break;
case 3:
if(game.bestrank[2]>=3)
{
entity.tile = 184 + meta1;
entity.colour = 35;
2020-01-01 21:29:24 +01:00
}
break;
case 4:
if(game.bestrank[3]>=3)
{
entity.tile = 184 + meta1;
entity.colour = 30;
2020-01-01 21:29:24 +01:00
}
break;
case 5:
if(game.bestrank[4]>=3)
{
entity.tile = 184 + meta1;
entity.colour = 34;
2020-01-01 21:29:24 +01:00
}
break;
case 6:
if(game.bestrank[5]>=3)
{
entity.tile = 184 + meta1;
entity.colour = 32;
2020-01-01 21:29:24 +01:00
}
break;
case 7:
if(game.unlock[5])
{
entity.tile = 188 + meta1;
entity.colour = 37;
entity.h += 3;
entity.yp -= 3;
2020-01-01 21:29:24 +01:00
}
break;
case 8:
if(game.unlock[19])
{
entity.tile = 188 + meta1;
entity.colour = 37;
entity.h += 3;
2020-01-01 21:29:24 +01:00
}
break;
case 9:
if (game.bestgamedeaths > -1)
{
if (game.bestgamedeaths <= 50)
{
entity.tile = 182 + meta1;
entity.colour = 40;
2020-01-01 21:29:24 +01:00
}
}
break;
case 10:
if (game.bestgamedeaths > -1)
{
if (game.bestgamedeaths <= 100)
{
entity.tile = 182 + meta1;
entity.colour = 36;
2020-01-01 21:29:24 +01:00
}
}
break;
case 11:
if (game.bestgamedeaths > -1)
{
if (game.bestgamedeaths <= 250)
{
entity.tile = 182 + meta1;
entity.colour = 38;
2020-01-01 21:29:24 +01:00
}
}
break;
case 12:
if (game.bestgamedeaths > -1)
{
if (game.bestgamedeaths <= 500)
{
entity.tile = 182 + meta1;
entity.colour = 39;
2020-01-01 21:29:24 +01:00
}
}
break;
case 13:
if(game.swnbestrank>=1)
{
entity.tile = 182 + meta1;
entity.colour = 39;
2020-01-01 21:29:24 +01:00
}
break;
case 14:
if(game.swnbestrank>=2)
{
entity.tile = 182 + meta1;
entity.colour = 39;
2020-01-01 21:29:24 +01:00
}
break;
case 15:
if(game.swnbestrank>=3)
{
entity.tile = 182 + meta1;
entity.colour = 39;
2020-01-01 21:29:24 +01:00
}
break;
case 16:
if(game.swnbestrank>=4)
{
entity.tile = 182 + meta1;
entity.colour = 38;
2020-01-01 21:29:24 +01:00
}
break;
case 17:
if(game.swnbestrank>=5)
{
entity.tile = 182 + meta1;
entity.colour = 36;
2020-01-01 21:29:24 +01:00
}
break;
case 18:
if(game.swnbestrank>=6)
{
entity.tile = 182 + meta1;
entity.colour = 40;
2020-01-01 21:29:24 +01:00
}
break;
case 19:
if(game.unlock[20])
{
entity.tile = 3;
entity.colour = 102;
entity.size = 13;
entity.xp -= 64;
entity.yp -= 128;
2020-01-01 21:29:24 +01:00
}
break;
}
break;
case 26: //Epilogue super warp token
entity.rule = 3;
entity.type = 11;
entity.size = 0;
entity.tile = 18;
entity.w = 16;
entity.h = 16;
entity.colour = 3;
entity.onentity = 0;
entity.animate = 100;
entity.para = meta2;
entity.size = 13;
2020-01-01 21:29:24 +01:00
break;
/* Warp lines */
case 51: /* Vertical */
case 52: /* Vertical */
case 53: /* Horizontal */
case 54: /* Horizontal */
entity.type = t;
entity.onentity = 1;
entity.invis = true;
entity.life = 0;
switch (t)
{
case 51:
case 52:
entity.rule = 5;
entity.size = 6;
entity.w = 1;
entity.h = meta1;
break;
case 53:
case 54:
entity.rule = 7;
entity.size = 5;
entity.w = meta1;
entity.h = 1;
break;
}
if (map.custommode)
{
customwarpmode = true;
map.warpx = false;
map.warpy = false;
}
2020-01-01 21:29:24 +01:00
break;
case 55: // Crew Member (custom, collectable)
//1 - position in array
//2 - colour
entity.rule = 3;
entity.type = 55;
if(INBOUNDS_ARR(meta2, customcrewmoods)
&& customcrewmoods[meta2]==1){
entity.tile = 144;
2020-01-01 21:29:24 +01:00
}else{
entity.tile = 0;
2020-01-01 21:29:24 +01:00
}
entity.colour = crewcolour(meta2);
entity.cx = 6;
entity.cy = 2;
entity.w = 12;
entity.h = 21;
entity.dir = 0;
2020-01-01 21:29:24 +01:00
entity.state = 0;
entity.onentity = 1;
//entity.state = meta1;
2020-01-01 21:29:24 +01:00
entity.gravity = true;
2020-01-01 21:29:24 +01:00
//Check if it's already been collected
entity.para = meta1;
if (!INBOUNDS_ARR(meta1, customcollect) || customcollect[meta1]) return;
2020-01-01 21:29:24 +01:00
break;
case 56: //Custom enemy
entity.rule = 1;
entity.type = 1;
entity.behave = meta1;
entity.para = meta2;
entity.w = 16;
entity.h = 16;
entity.cx = 0;
entity.cy = 0;
entity.x1 = p1;
entity.y1 = p2;
entity.x2 = p3;
entity.y2 = p4;
entity.harmful = true;
2020-01-01 21:29:24 +01:00
switch(customenemy){
case 0: entity.setenemyroom(4+100, 0+100); break;
case 1: entity.setenemyroom(2+100, 0+100); break;
case 2: entity.setenemyroom(12+100, 3+100); break;
case 3: entity.setenemyroom(13+100, 12+100); break;
case 4: entity.setenemyroom(16+100, 9+100); break;
case 5: entity.setenemyroom(19+100, 1+100); break;
case 6: entity.setenemyroom(19+100, 2+100); break;
case 7: entity.setenemyroom(18+100, 3+100); break;
case 8: entity.setenemyroom(16+100, 0+100); break;
case 9: entity.setenemyroom(14+100, 2+100); break;
default: entity.setenemyroom(4+100, 0+100); break;
2020-01-01 21:29:24 +01:00
}
//Set colour based on room tile
//Set custom colours
if(customplatformtile>0){
int entcol=(customplatformtile/12);
switch(entcol){
//RED
case 3: case 7: case 12: case 23: case 28:
case 34: case 42: case 48: case 58:
entity.colour = 6; break;
2020-01-01 21:29:24 +01:00
//GREEN
case 5: case 9: case 22: case 25: case 29:
case 31: case 38: case 46: case 52: case 53:
entity.colour = 7; break;
2020-01-01 21:29:24 +01:00
//BLUE
case 1: case 6: case 14: case 27: case 33:
case 44: case 50: case 57:
entity.colour = 12; break;
2020-01-01 21:29:24 +01:00
//YELLOW
case 4: case 17: case 24: case 30: case 37:
case 45: case 51: case 55:
entity.colour = 9; break;
2020-01-01 21:29:24 +01:00
//PURPLE
case 2: case 11: case 15: case 19: case 32:
case 36: case 49:
entity.colour = 20; break;
2020-01-01 21:29:24 +01:00
//CYAN
case 8: case 10: case 13: case 18: case 26:
case 35: case 41: case 47: case 54:
entity.colour = 11; break;
2020-01-01 21:29:24 +01:00
//PINK
case 16: case 20: case 39: case 43: case 56:
entity.colour = 8; break;
2020-01-01 21:29:24 +01:00
//ORANGE
case 21: case 40:
entity.colour = 17; break;
2020-01-01 21:29:24 +01:00
default:
entity.colour = 6;
2020-01-01 21:29:24 +01:00
break;
}
}
if(custom_gray){
entity.colour = 18;
}
2020-01-01 21:29:24 +01:00
break;
}
entity.lerpoldxp = entity.xp;
entity.lerpoldyp = entity.yp;
entity.drawframe = entity.tile;
Fix entity and block indices after destroying them This patch restores some 2.2 behavior, fixing a regression caused by the refactor of properly using std::vectors. In 2.2, the game allocated 200 items in obj.entities, but used a system where each entity had an `active` attribute to signify if the entity actually existed or not. When dealing with entities, you would have to check this `active` flag, or else you'd be dealing with an entity that didn't actually exist. (By the way, what I'm saying applies to blocks and obj.blocks as well, except for some small differing details like the game allocating 500 block slots versus obj.entities's 200.) As a consequence, the game had to use a separate tracking variable, obj.nentity, because obj.entities.size() would just report 200, instead of the actual amount of entities. Needless to say, having to check for `active` and use `obj.nentity` is a bit error-prone, and it's messier than simply using the std::vector the way it was intended. Also, this resulted in a hard limit of 200 entities, which custom level makers ran into surprisingly quite often. 2.3 comes along, and removes the whole system. Now, std::vectors are properly being used, and obj.entities.size() reports the actual number of entities in the vector; you no longer have to check for `active` when dealing with entities of any sort. But there was one previous behavior of 2.2 that this system kind of forgets about - namely, the ability to have holes in between entities. You see, when an entity got disabled in 2.2 (which just meant turning its `active` off), the indices of all other entities stayed the same; the indice of the entity that got disabled stays there as a hole in the array. But when an entity gets removed in 2.3 (previous to this patch), the indices of every entity afterwards in the array get shifted down by one. std::vector isn't really meant to be able to contain holes. Do the indices of entities and blocks matter? Yes; they determine the order in which entities and blocks get evaluated (the highest indice gets evaluated first), and I had to fix some block evaluation order stuff in previous PRs. And in the case of entities, they matter hugely when using the recently-discovered Arbitrary Entity Manipulation glitch (where crewmate script commands are used on arbitrary entities by setting the `i` attribute of `scriptclass` and passing invalid crewmate identifiers to the commands). If you use Arbitrary Entity Manipulation after destroying some entities, there is a chance that your script won't work between 2.2 and 2.3. The indices also still determine the rendering order of entities (highest indice gets drawn first, which means lowest indice gets drawn in front of other entities). As an example: let's say we have the player at 0, a gravity line at 1, and a checkpoint at 2; then we destroy the gravity line and create a crewmate (let's do Violet). If we're able to have holes, then after removing the gravity line, none of the other indices shift. Then Violet will be created at indice 1, and will be drawn in front of the checkpoint. But if we can't have holes, then removing the gravity line results in the indice of the checkpoint shifting down to indice 1. Then Violet is created at indice 2, and gets drawn behind the checkpoint! This is a clear illustration of changing the behavior that existed in 2.2. However, I also don't want to go back to the `active` system of having to check an attribute before operating on an entity. So... what do we do to restore the holes? Well, we don't need to have an `active` attribute, or modify any existing code that operates on entities. Instead, we can just set the attributes of the entities so that they naturally get ignored by everything that comes into contact with it. For entities, we set their invis to true, and their size, type, and rule to -1 (the game never uses a size, type, or rule of -1 anywhere); for blocks, we set their type to -1, and their width and height to 0. obj.entities.size() will no longer necessarily equal the amount of entities in the room; rather, it will be the amount of entity SLOTS that have been allocated. But nothing that uses obj.entities.size() needs to actually know the amount of entities; it's mostly used for iterating over every entity in the vector. Excess entity slots get cleaned up upon every call of mapclass::gotoroom(), which will now deallocate entity slots starting from the end until it hits a player, at which point it will switch to disabling entity slots instead of removing them entirely. The entclass::clear() and blockclass::clear() functions have been restored because we need to call their initialization functions when reusing a block/entity slot; it's possible to create an entity with an invalid type number (it creates a glitchy Viridian), and without calling the initialization function again, it would simply not create anything. After this patch is applied, entity and block indices will be restored to how they behaved in 2.2.
2020-12-27 07:11:34 +01:00
if (!reuse)
{
entities.push_back(entity);
}
Re-fix crewmate directions (without copy-pasting) This once again fixes the facing directions of crewmates upon room load, except now it covers more cases. So, here is the saga so far: - 2.0 (presumably) to 2.2: crewmate direction fix is special-cased at the end of mapclass::loadlevel(). Only covers crewmates created during the room load, does not cover crewmates created from scripts, only covers state 18 of crewmates. - 2.3 currently (after #220): crewmate direction fix is moved to entityclass::createentity(), which covers every avenue of crewmate creation (including from scripts), but still only covers state 18. - This commit: crewmate direction fix now covers every possible state of the crewmate, also does not copy-paste any code. What I've done instead is to make it so createentity() will immediately call updateentities() on the pushed-back entity. This is kludge-y, but is completely okay to do, because unlike other entities, crewmate entities never change their state or have any side-effects from double-evaluation, meaning calling updateentities() on them is idempotent and it's okay to call their updateentities() more than once. This does have the slight danger that if the states of crewmates were to change in the future to no longer be idempotent, this would end up resulting in a somewhat hard-to-track-down double-evaluation bug, but it's worth taking that risk. This fix is not applied to entity 14 (the supercrewmate) because it is possible that calling updateentities() on it will immediately remove the entity, which is not idempotent (it's changing the state of something outside the object). Supercrewmates are a bit difficult to work with outside of the main game anyways, and if you spawn them you could probably just use the changedir() script command to fix their direction, so I'm not inclined to fix this for them anyway.
2021-01-18 21:44:22 +01:00
/* Fix crewmate facing directions
* This is a bit kludge-y but it's better than copy-pasting
* and is okay to do because entity 12 does not change state on its own
*/
if (entity.type == 12)
{
size_t indice;
if (reuse)
{
indice = entptr - entities.data();
}
else
{
indice = entities.size() - 1;
}
updateentities(indice);
}
2020-01-01 21:29:24 +01:00
}
void entityclass::createentity(int xp, int yp, int t, int meta1, int meta2, int p1, int p2)
{
createentity(xp, yp, t, meta1, meta2, p1, p2, 320, 240);
}
void entityclass::createentity(int xp, int yp, int t, int meta1, int meta2, int p1)
{
createentity(xp, yp, t, meta1, meta2, p1, 0);
}
void entityclass::createentity(int xp, int yp, int t, int meta1, int meta2)
{
createentity(xp, yp, t, meta1, meta2, 0);
}
void entityclass::createentity(int xp, int yp, int t, int meta1)
{
createentity(xp, yp, t, meta1, 0);
}
void entityclass::createentity(int xp, int yp, int t)
{
createentity(xp, yp, t, 0);
}
//Returns true if entity is removed
bool entityclass::updateentities( int i )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(i, entities))
{
puts("updateentities() out-of-bounds!");
return true;
}
if(entities[i].statedelay<=0)
2020-01-01 21:29:24 +01:00
{
switch(entities[i].type)
2020-01-01 21:29:24 +01:00
{
case 0: //Player
break;
case 1: //Movement behaviors
//Enemies can have a number of different behaviors:
switch(entities[i].behave)
{
case 0: //Bounce, Start moving down
if (entities[i].state == 0) //Init
2020-01-01 21:29:24 +01:00
{
entities[i].state = 3;
bool entitygone = updateentities(i);
if (entitygone) return true;
2020-01-01 21:29:24 +01:00
}
else if (entities[i].state == 1)
2020-01-01 21:29:24 +01:00
{
if (entities[i].outside()) entities[i].state = entities[i].onwall;
}
else if (entities[i].state == 2)
{
entities[i].vy = -entities[i].para;
entities[i].onwall = 3;
entities[i].state = 1;
}
else if (entities[i].state == 3)
{
entities[i].vy = entities[i].para;
entities[i].onwall = 2;
entities[i].state = 1;
2020-01-01 21:29:24 +01:00
}
break;
case 1: //Bounce, Start moving up
if (entities[i].state == 0) //Init
2020-01-01 21:29:24 +01:00
{
entities[i].state = 2;
bool entitygone = updateentities(i);
if (entitygone) return true;
}
else if (entities[i].state == 1)
{
if (entities[i].outside()) entities[i].state = entities[i].onwall;
2020-01-01 21:29:24 +01:00
}
else if (entities[i].state == 2)
{
entities[i].vy = -entities[i].para;
entities[i].onwall = 3;
entities[i].state = 1;
2020-01-01 21:29:24 +01:00
}
else if (entities[i].state == 3)
{
entities[i].vy = entities[i].para;
entities[i].onwall = 2;
entities[i].state = 1;
2020-01-01 21:29:24 +01:00
}
break;
case 2: //Bounce, Start moving left
if (entities[i].state == 0) //Init
2020-01-01 21:29:24 +01:00
{
entities[i].state = 3;
bool entitygone = updateentities(i);
if (entitygone) return true;
2020-01-01 21:29:24 +01:00
}
else if (entities[i].state == 1)
2020-01-01 21:29:24 +01:00
{
if (entities[i].outside()) entities[i].state = entities[i].onwall;
}
else if (entities[i].state == 2)
{
entities[i].vx = entities[i].para;
entities[i].onwall = 3;
entities[i].state = 1;
}
else if (entities[i].state == 3)
{
entities[i].vx = -entities[i].para;
entities[i].onwall = 2;
entities[i].state = 1;
2020-01-01 21:29:24 +01:00
}
break;
case 3: //Bounce, Start moving right
if (entities[i].state == 0) //Init
2020-01-01 21:29:24 +01:00
{
entities[i].state = 3;
bool entitygone = updateentities(i);
if (entitygone) return true;
}
else if (entities[i].state == 1)
{
if (entities[i].outside()) entities[i].state = entities[i].onwall;
2020-01-01 21:29:24 +01:00
}
else if (entities[i].state == 2)
{
entities[i].vx = -entities[i].para;
entities[i].onwall = 3;
entities[i].state = 1;
}
else if (entities[i].state == 3)
{
entities[i].vx = entities[i].para;
entities[i].onwall = 2;
entities[i].state = 1;
2020-01-01 21:29:24 +01:00
}
break;
case 4: //Always move left
if (entities[i].state == 0) //Init
2020-01-01 21:29:24 +01:00
{
entities[i].vx = entities[i].para;
2020-01-01 21:29:24 +01:00
}
break;
case 5: //Always move right
2020-01-01 21:29:24 +01:00
if (entities[i].state == 0)
{
//Init
entities[i].vx = static_cast<int>(entities[i].para);
entities[i].state = 1;
entities[i].onwall = 2;
}
else if (entities[i].state == 2)
{
entities[i].vx = 0;
entities[i].onwall = 0;
entities[i].xp -= static_cast<int>(entities[i].para);
entities[i].statedelay=8;
entities[i].state=0;
2020-01-01 21:29:24 +01:00
}
break;
case 6: //Always move up
if (entities[i].state == 0) //Init
2020-01-01 21:29:24 +01:00
{
entities[i].vy = static_cast<int>(entities[i].para);
entities[i].state = 1;
entities[i].onwall = 2;
}
else if (entities[i].state == 2)
{
entities[i].vy = static_cast<int>(-entities[i].para);
entities[i].onwall = 0;
entities[i].yp -= (entities[i].para);
entities[i].statedelay=8;
entities[i].state=0;
2020-01-01 21:29:24 +01:00
}
break;
case 7: //Always move down
if (entities[i].state == 0) //Init
2020-01-01 21:29:24 +01:00
{
entities[i].vx = static_cast<int>(entities[i].para);
2020-01-01 21:29:24 +01:00
}
break;
case 8:
case 9:
//Threadmill: don't move, just impart velocity
if (entities[i].state == 0) //Init
{
entities[i].vx = 0;
entities[i].state = 1;
entities[i].onwall = 0;
}
break;
case 10:
//Emitter: shoot an enemy every so often
if (entities[i].state == 0)
{
createentity(entities[i].xp+28, entities[i].yp, 1, 10, 1);
entities[i].state = 1;
entities[i].statedelay = 12;
}
else if (entities[i].state == 1)
2020-01-01 21:29:24 +01:00
{
entities[i].state = 0;
}
break;
case 11: //Always move right, destroy when outside screen
if (entities[i].state == 0) //Init
2020-01-01 21:29:24 +01:00
{
entities[i].vx = entities[i].para;
entities[i].state = 1;
}
else if (entities[i].state == 1)
{
if (entities[i].xp >= 335)
{
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
return disableentity(i);
}
if (game.roomx == 117)
2020-01-01 21:29:24 +01:00
{
if (entities[i].xp >= (33*8)-32)
{
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
return disableentity(i);
}
//collector for LIES
2020-01-01 21:29:24 +01:00
}
}
break;
case 12:
//Emitter: shoot an enemy every so often (up)
if (entities[i].state == 0)
2020-01-01 21:29:24 +01:00
{
createentity(entities[i].xp, entities[i].yp, 1, 12, 1);
entities[i].state = 1;
entities[i].statedelay = 16;
}
else if (entities[i].state == 1)
{
entities[i].state = 0;
}
break;
case 13: //Always move up, destroy when outside screen
if (entities[i].state == 0) //Init
{
entities[i].vy = entities[i].para;
entities[i].state = 1;
}
else if (entities[i].state == 1)
{
if (entities[i].yp <= -60)
{
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
return disableentity(i);
}
if (game.roomx == 113 && game.roomy == 108)
2020-01-01 21:29:24 +01:00
{
if (entities[i].yp <= 60)
{
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
return disableentity(i);
}
//collector for factory
2020-01-01 21:29:24 +01:00
}
}
break;
case 14: //Very special hack: as two, but doesn't move in specific circumstances
if (entities[i].state == 0) //Init
2020-01-01 21:29:24 +01:00
{
for (size_t j = 0; j < entities.size(); j++)
2020-01-01 21:29:24 +01:00
{
if (entities[j].type == 2 && entities[j].state== 3 && entities[j].xp == (entities[i].xp-32) )
{
entities[i].state = 3;
bool entitygone = updateentities(i);
if (entitygone) return true;
}
2020-01-01 21:29:24 +01:00
}
}
else if (entities[i].state == 1)
2020-01-01 21:29:24 +01:00
{
if (entities[i].outside()) entities[i].state = entities[i].onwall;
2020-01-01 21:29:24 +01:00
}
else if (entities[i].state == 2)
2020-01-01 21:29:24 +01:00
{
entities[i].vx = entities[i].para;
entities[i].onwall = 3;
entities[i].state = 1;
2020-01-01 21:29:24 +01:00
}
else if (entities[i].state == 3)
2020-01-01 21:29:24 +01:00
{
entities[i].vx = -entities[i].para;
entities[i].onwall = 2;
entities[i].state = 1;
2020-01-01 21:29:24 +01:00
}
break;
case 15: //As above, but for 3!
if (entities[i].state == 0) //Init
{
for (size_t j = 0; j < entities.size(); j++)
2020-01-01 21:29:24 +01:00
{
if (entities[j].type == 2 && entities[j].state==3 && entities[j].xp==entities[i].xp+32)
2020-01-01 21:29:24 +01:00
{
entities[i].state = 3;
bool entitygone = updateentities(i);
if (entitygone) return true;
2020-01-01 21:29:24 +01:00
}
}
}
else if (entities[i].state == 1)
{
if (entities[i].outside()) entities[i].state = entities[i].onwall;
}
2020-01-01 21:29:24 +01:00
else if (entities[i].state == 2)
{
entities[i].vx = -entities[i].para;
entities[i].onwall = 3;
entities[i].state = 1;
}
else if (entities[i].state == 3)
{
entities[i].vx = entities[i].para;
entities[i].onwall = 2;
entities[i].state = 1;
}
break;
case 16: //MAVERICK BUS FOLLOWS HIS OWN RULES
if (entities[i].state == 0) //Init
{
int player = getplayer();
//first, y position
if (INBOUNDS_VEC(player, entities) && entities[player].yp > 14 * 8)
2020-01-01 21:29:24 +01:00
{
entities[i].tile = 120;
entities[i].yp = (28*8)-62;
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
entities[i].lerpoldyp = (28*8)-62;
2020-01-01 21:29:24 +01:00
}
else
2020-01-01 21:29:24 +01:00
{
entities[i].tile = 96;
entities[i].yp = 24;
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
entities[i].lerpoldyp = 24;
2020-01-01 21:29:24 +01:00
}
//now, x position
if (INBOUNDS_VEC(player, entities) && entities[player].xp > 20 * 8)
2020-01-01 21:29:24 +01:00
{
//approach from the left
entities[i].xp = -64;
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
entities[i].lerpoldxp = -64;
entities[i].state = 2;
bool entitygone = updateentities(i); //right
if (entitygone) return true;
2020-01-01 21:29:24 +01:00
}
else
2020-01-01 21:29:24 +01:00
{
//approach from the left
entities[i].xp = 320;
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
entities[i].lerpoldxp = 320;
entities[i].state = 3;
bool entitygone = updateentities(i); //left
if (entitygone) return true;
2020-01-01 21:29:24 +01:00
}
2020-01-01 21:29:24 +01:00
}
else if (entities[i].state == 1)
2020-01-01 21:29:24 +01:00
{
if (entities[i].outside()) entities[i].state = entities[i].onwall;
}
else if (entities[i].state == 2)
{
entities[i].vx = int(entities[i].para);
entities[i].onwall = 3;
entities[i].state = 1;
}
else if (entities[i].state == 3)
{
entities[i].vx = int(-entities[i].para);
entities[i].onwall = 2;
entities[i].state = 1;
}
break;
case 17: //Special for ASCII Snake (left)
if (entities[i].state == 0) //Init
{
entities[i].statedelay = 6;
entities[i].xp -= int(entities[i].para);
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
entities[i].lerpoldxp -= int(entities[i].para);
}
break;
case 18: //Special for ASCII Snake (right)
if (entities[i].state == 0) //Init
{
entities[i].statedelay = 6;
entities[i].xp += int(entities[i].para);
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
entities[i].lerpoldxp += int(entities[i].para);
}
break;
}
break;
case 2: //Disappearing platforms
//wait for collision
if (entities[i].state == 1)
{
entities[i].life = 12;
entities[i].state = 2;
entities[i].onentity = 0;
2020-01-01 21:29:24 +01:00
music.playef(7);
}
else if (entities[i].state == 2)
{
entities[i].life--;
Fix tile of disappearing platforms during final stretch During the final stretch, after Viridian turns off the Dimensional Stability Generator, the map goes all psychedelic and changes colors every 40 frames. Entities change their colors too, including conveyors, moving platforms, and disappearing platforms. But play around with the disappearing platforms for a bit and you'll notice they seem a bit glitchy. If you run on them at the right time, the tile they use while disappearing seems to abruptly change whenever the color of the room changes. If there's a color change while they're reappearing (when you die and respawn in the same room as them), they'll have the wrong tile and look like a conveyor. And even if you've never interacted with them at all, dying and respawning in the same room as them will change their tile to something wrong and also look like a conveyor. So, what's the problem? Well, first off, the tile of every untouched disappearing platform changing into a conveyor after you die and respawn in the same room is caused by a block of code in gamelogic() that gets run on each entity whenever you die. This block of code is the exact same block of code that gets ran on a disappearing platform if it's in the middle of disappearing. As a quick primer, every entity in the game has a state, which is just a number. You can view each entity's state in entityclass::updateentities(). State 0 of disappearing platforms is doing nothing, and they start with an onentity of 1, which means they turn to state 1 when they get touched. State 1 moves to state 2. State 2 does some decrementing, then moves to state 3 and sets the onentity to 4. State 3 also does nothing. After being touched, state 4 makes the platform reappear and move to state 5, but state 5 does the actual reappearing; state 5 then sets the state back to 0 and onentity back to 1. So, back to the copy-pasted block of code. The block of code was originally intended to fast-forward disappearing platforms if they were in the middle of disappearing, so the player respawn code would properly respawn the disappearing platform, instead of leaving it disappeared. What it does is keep updating the entity, while the state of the entity is 2, until it is no longer in state 2, then sets it to state 4. Crucially, the original block of code only ran if the disappearing platform was in state 2. But the other block of code, which was copy-pasted with slight modifications, runs on ALL disappearing platforms in final stretch, regardless of if they are in state 2 or not. Thus, all untouched platforms will be set to state 4, and state 4 will do the animation of the platform reappearing, which is invalid given that the platform never disappeared in the first place. So that's why dying and respawning in the same room as some disappearing platforms during final stretch will change their tiles to be conveyors. It seems to me that doing anything with death is wrong, here. The root cause is that map.changefinalcol() "resets" the tile of every disappearing platform, which is a function that gets called on every color change. The color change has nothing to do with dying, so why fiddle with the death code? Thus, I've deleted that entire block of code. What I've done to fix the issue is to make it so the tile of disappearing platforms aren't manually controlled. You see, unlike other entities in the game, the tile of disappearing platforms gets manually modified whenever it disappears or reappears. Other entities use the tile as a base and store their tile offset in the separate walkingframe attribute, which will be added to the tile attribute to produce the drawframe, which is the final thing that gets rendered - but for disappearing platforms, their tile gets directly incremented or decremented whenever they disappear or reappear, so when map.changefinalcol() gets ran to update the tile of every platform and conveyor, it basically discards the tile offset that was manually added in. Instead, what I've done is make it so disappearing platforms now use walkingframe, and thus their final drawframe will be their tile plus their walkingframe. Whenever map.changefinalcol() gets called, it is now free to modify the tile of disappearing platforms accordingly - after all, the tile offset is now stored in walkingframe, so no weird glitchiness can happen there.
2021-01-09 23:50:06 +01:00
if (entities[i].life % 3 == 0) entities[i].walkingframe++;
if (entities[i].life <= 0)
{
Fix entity and block indices after destroying them This patch restores some 2.2 behavior, fixing a regression caused by the refactor of properly using std::vectors. In 2.2, the game allocated 200 items in obj.entities, but used a system where each entity had an `active` attribute to signify if the entity actually existed or not. When dealing with entities, you would have to check this `active` flag, or else you'd be dealing with an entity that didn't actually exist. (By the way, what I'm saying applies to blocks and obj.blocks as well, except for some small differing details like the game allocating 500 block slots versus obj.entities's 200.) As a consequence, the game had to use a separate tracking variable, obj.nentity, because obj.entities.size() would just report 200, instead of the actual amount of entities. Needless to say, having to check for `active` and use `obj.nentity` is a bit error-prone, and it's messier than simply using the std::vector the way it was intended. Also, this resulted in a hard limit of 200 entities, which custom level makers ran into surprisingly quite often. 2.3 comes along, and removes the whole system. Now, std::vectors are properly being used, and obj.entities.size() reports the actual number of entities in the vector; you no longer have to check for `active` when dealing with entities of any sort. But there was one previous behavior of 2.2 that this system kind of forgets about - namely, the ability to have holes in between entities. You see, when an entity got disabled in 2.2 (which just meant turning its `active` off), the indices of all other entities stayed the same; the indice of the entity that got disabled stays there as a hole in the array. But when an entity gets removed in 2.3 (previous to this patch), the indices of every entity afterwards in the array get shifted down by one. std::vector isn't really meant to be able to contain holes. Do the indices of entities and blocks matter? Yes; they determine the order in which entities and blocks get evaluated (the highest indice gets evaluated first), and I had to fix some block evaluation order stuff in previous PRs. And in the case of entities, they matter hugely when using the recently-discovered Arbitrary Entity Manipulation glitch (where crewmate script commands are used on arbitrary entities by setting the `i` attribute of `scriptclass` and passing invalid crewmate identifiers to the commands). If you use Arbitrary Entity Manipulation after destroying some entities, there is a chance that your script won't work between 2.2 and 2.3. The indices also still determine the rendering order of entities (highest indice gets drawn first, which means lowest indice gets drawn in front of other entities). As an example: let's say we have the player at 0, a gravity line at 1, and a checkpoint at 2; then we destroy the gravity line and create a crewmate (let's do Violet). If we're able to have holes, then after removing the gravity line, none of the other indices shift. Then Violet will be created at indice 1, and will be drawn in front of the checkpoint. But if we can't have holes, then removing the gravity line results in the indice of the checkpoint shifting down to indice 1. Then Violet is created at indice 2, and gets drawn behind the checkpoint! This is a clear illustration of changing the behavior that existed in 2.2. However, I also don't want to go back to the `active` system of having to check an attribute before operating on an entity. So... what do we do to restore the holes? Well, we don't need to have an `active` attribute, or modify any existing code that operates on entities. Instead, we can just set the attributes of the entities so that they naturally get ignored by everything that comes into contact with it. For entities, we set their invis to true, and their size, type, and rule to -1 (the game never uses a size, type, or rule of -1 anywhere); for blocks, we set their type to -1, and their width and height to 0. obj.entities.size() will no longer necessarily equal the amount of entities in the room; rather, it will be the amount of entity SLOTS that have been allocated. But nothing that uses obj.entities.size() needs to actually know the amount of entities; it's mostly used for iterating over every entity in the vector. Excess entity slots get cleaned up upon every call of mapclass::gotoroom(), which will now deallocate entity slots starting from the end until it hits a player, at which point it will switch to disabling entity slots instead of removing them entirely. The entclass::clear() and blockclass::clear() functions have been restored because we need to call their initialization functions when reusing a block/entity slot; it's possible to create an entity with an invalid type number (it creates a glitchy Viridian), and without calling the initialization function again, it would simply not create anything. After this patch is applied, entity and block indices will be restored to how they behaved in 2.2.
2020-12-27 07:11:34 +01:00
disableblockat(entities[i].xp, entities[i].yp);
entities[i].state = 3;// = false;
entities[i].invis = true;
2020-01-01 21:29:24 +01:00
}
}
else if (entities[i].state == 3)
{
//wait until recharged!
}
else if (entities[i].state == 4)
{
//restart!
createblock(0, entities[i].xp, entities[i].yp, 32, 8);
entities[i].state = 4;
entities[i].invis = false;
Fix tile of disappearing platforms during final stretch During the final stretch, after Viridian turns off the Dimensional Stability Generator, the map goes all psychedelic and changes colors every 40 frames. Entities change their colors too, including conveyors, moving platforms, and disappearing platforms. But play around with the disappearing platforms for a bit and you'll notice they seem a bit glitchy. If you run on them at the right time, the tile they use while disappearing seems to abruptly change whenever the color of the room changes. If there's a color change while they're reappearing (when you die and respawn in the same room as them), they'll have the wrong tile and look like a conveyor. And even if you've never interacted with them at all, dying and respawning in the same room as them will change their tile to something wrong and also look like a conveyor. So, what's the problem? Well, first off, the tile of every untouched disappearing platform changing into a conveyor after you die and respawn in the same room is caused by a block of code in gamelogic() that gets run on each entity whenever you die. This block of code is the exact same block of code that gets ran on a disappearing platform if it's in the middle of disappearing. As a quick primer, every entity in the game has a state, which is just a number. You can view each entity's state in entityclass::updateentities(). State 0 of disappearing platforms is doing nothing, and they start with an onentity of 1, which means they turn to state 1 when they get touched. State 1 moves to state 2. State 2 does some decrementing, then moves to state 3 and sets the onentity to 4. State 3 also does nothing. After being touched, state 4 makes the platform reappear and move to state 5, but state 5 does the actual reappearing; state 5 then sets the state back to 0 and onentity back to 1. So, back to the copy-pasted block of code. The block of code was originally intended to fast-forward disappearing platforms if they were in the middle of disappearing, so the player respawn code would properly respawn the disappearing platform, instead of leaving it disappeared. What it does is keep updating the entity, while the state of the entity is 2, until it is no longer in state 2, then sets it to state 4. Crucially, the original block of code only ran if the disappearing platform was in state 2. But the other block of code, which was copy-pasted with slight modifications, runs on ALL disappearing platforms in final stretch, regardless of if they are in state 2 or not. Thus, all untouched platforms will be set to state 4, and state 4 will do the animation of the platform reappearing, which is invalid given that the platform never disappeared in the first place. So that's why dying and respawning in the same room as some disappearing platforms during final stretch will change their tiles to be conveyors. It seems to me that doing anything with death is wrong, here. The root cause is that map.changefinalcol() "resets" the tile of every disappearing platform, which is a function that gets called on every color change. The color change has nothing to do with dying, so why fiddle with the death code? Thus, I've deleted that entire block of code. What I've done to fix the issue is to make it so the tile of disappearing platforms aren't manually controlled. You see, unlike other entities in the game, the tile of disappearing platforms gets manually modified whenever it disappears or reappears. Other entities use the tile as a base and store their tile offset in the separate walkingframe attribute, which will be added to the tile attribute to produce the drawframe, which is the final thing that gets rendered - but for disappearing platforms, their tile gets directly incremented or decremented whenever they disappear or reappear, so when map.changefinalcol() gets ran to update the tile of every platform and conveyor, it basically discards the tile offset that was manually added in. Instead, what I've done is make it so disappearing platforms now use walkingframe, and thus their final drawframe will be their tile plus their walkingframe. Whenever map.changefinalcol() gets called, it is now free to modify the tile of disappearing platforms accordingly - after all, the tile offset is now stored in walkingframe, so no weird glitchiness can happen there.
2021-01-09 23:50:06 +01:00
entities[i].walkingframe--;
entities[i].state++;
entities[i].onentity = 1;
}
else if (entities[i].state == 5)
{
entities[i].life+=3;
Fix tile of disappearing platforms during final stretch During the final stretch, after Viridian turns off the Dimensional Stability Generator, the map goes all psychedelic and changes colors every 40 frames. Entities change their colors too, including conveyors, moving platforms, and disappearing platforms. But play around with the disappearing platforms for a bit and you'll notice they seem a bit glitchy. If you run on them at the right time, the tile they use while disappearing seems to abruptly change whenever the color of the room changes. If there's a color change while they're reappearing (when you die and respawn in the same room as them), they'll have the wrong tile and look like a conveyor. And even if you've never interacted with them at all, dying and respawning in the same room as them will change their tile to something wrong and also look like a conveyor. So, what's the problem? Well, first off, the tile of every untouched disappearing platform changing into a conveyor after you die and respawn in the same room is caused by a block of code in gamelogic() that gets run on each entity whenever you die. This block of code is the exact same block of code that gets ran on a disappearing platform if it's in the middle of disappearing. As a quick primer, every entity in the game has a state, which is just a number. You can view each entity's state in entityclass::updateentities(). State 0 of disappearing platforms is doing nothing, and they start with an onentity of 1, which means they turn to state 1 when they get touched. State 1 moves to state 2. State 2 does some decrementing, then moves to state 3 and sets the onentity to 4. State 3 also does nothing. After being touched, state 4 makes the platform reappear and move to state 5, but state 5 does the actual reappearing; state 5 then sets the state back to 0 and onentity back to 1. So, back to the copy-pasted block of code. The block of code was originally intended to fast-forward disappearing platforms if they were in the middle of disappearing, so the player respawn code would properly respawn the disappearing platform, instead of leaving it disappeared. What it does is keep updating the entity, while the state of the entity is 2, until it is no longer in state 2, then sets it to state 4. Crucially, the original block of code only ran if the disappearing platform was in state 2. But the other block of code, which was copy-pasted with slight modifications, runs on ALL disappearing platforms in final stretch, regardless of if they are in state 2 or not. Thus, all untouched platforms will be set to state 4, and state 4 will do the animation of the platform reappearing, which is invalid given that the platform never disappeared in the first place. So that's why dying and respawning in the same room as some disappearing platforms during final stretch will change their tiles to be conveyors. It seems to me that doing anything with death is wrong, here. The root cause is that map.changefinalcol() "resets" the tile of every disappearing platform, which is a function that gets called on every color change. The color change has nothing to do with dying, so why fiddle with the death code? Thus, I've deleted that entire block of code. What I've done to fix the issue is to make it so the tile of disappearing platforms aren't manually controlled. You see, unlike other entities in the game, the tile of disappearing platforms gets manually modified whenever it disappears or reappears. Other entities use the tile as a base and store their tile offset in the separate walkingframe attribute, which will be added to the tile attribute to produce the drawframe, which is the final thing that gets rendered - but for disappearing platforms, their tile gets directly incremented or decremented whenever they disappear or reappear, so when map.changefinalcol() gets ran to update the tile of every platform and conveyor, it basically discards the tile offset that was manually added in. Instead, what I've done is make it so disappearing platforms now use walkingframe, and thus their final drawframe will be their tile plus their walkingframe. Whenever map.changefinalcol() gets called, it is now free to modify the tile of disappearing platforms accordingly - after all, the tile offset is now stored in walkingframe, so no weird glitchiness can happen there.
2021-01-09 23:50:06 +01:00
if (entities[i].life % 3 == 0) entities[i].walkingframe--;
if (entities[i].life >= 12)
2020-01-01 21:29:24 +01:00
{
entities[i].life = 12;
entities[i].state = 0;
Fix tile of disappearing platforms during final stretch During the final stretch, after Viridian turns off the Dimensional Stability Generator, the map goes all psychedelic and changes colors every 40 frames. Entities change their colors too, including conveyors, moving platforms, and disappearing platforms. But play around with the disappearing platforms for a bit and you'll notice they seem a bit glitchy. If you run on them at the right time, the tile they use while disappearing seems to abruptly change whenever the color of the room changes. If there's a color change while they're reappearing (when you die and respawn in the same room as them), they'll have the wrong tile and look like a conveyor. And even if you've never interacted with them at all, dying and respawning in the same room as them will change their tile to something wrong and also look like a conveyor. So, what's the problem? Well, first off, the tile of every untouched disappearing platform changing into a conveyor after you die and respawn in the same room is caused by a block of code in gamelogic() that gets run on each entity whenever you die. This block of code is the exact same block of code that gets ran on a disappearing platform if it's in the middle of disappearing. As a quick primer, every entity in the game has a state, which is just a number. You can view each entity's state in entityclass::updateentities(). State 0 of disappearing platforms is doing nothing, and they start with an onentity of 1, which means they turn to state 1 when they get touched. State 1 moves to state 2. State 2 does some decrementing, then moves to state 3 and sets the onentity to 4. State 3 also does nothing. After being touched, state 4 makes the platform reappear and move to state 5, but state 5 does the actual reappearing; state 5 then sets the state back to 0 and onentity back to 1. So, back to the copy-pasted block of code. The block of code was originally intended to fast-forward disappearing platforms if they were in the middle of disappearing, so the player respawn code would properly respawn the disappearing platform, instead of leaving it disappeared. What it does is keep updating the entity, while the state of the entity is 2, until it is no longer in state 2, then sets it to state 4. Crucially, the original block of code only ran if the disappearing platform was in state 2. But the other block of code, which was copy-pasted with slight modifications, runs on ALL disappearing platforms in final stretch, regardless of if they are in state 2 or not. Thus, all untouched platforms will be set to state 4, and state 4 will do the animation of the platform reappearing, which is invalid given that the platform never disappeared in the first place. So that's why dying and respawning in the same room as some disappearing platforms during final stretch will change their tiles to be conveyors. It seems to me that doing anything with death is wrong, here. The root cause is that map.changefinalcol() "resets" the tile of every disappearing platform, which is a function that gets called on every color change. The color change has nothing to do with dying, so why fiddle with the death code? Thus, I've deleted that entire block of code. What I've done to fix the issue is to make it so the tile of disappearing platforms aren't manually controlled. You see, unlike other entities in the game, the tile of disappearing platforms gets manually modified whenever it disappears or reappears. Other entities use the tile as a base and store their tile offset in the separate walkingframe attribute, which will be added to the tile attribute to produce the drawframe, which is the final thing that gets rendered - but for disappearing platforms, their tile gets directly incremented or decremented whenever they disappear or reappear, so when map.changefinalcol() gets ran to update the tile of every platform and conveyor, it basically discards the tile offset that was manually added in. Instead, what I've done is make it so disappearing platforms now use walkingframe, and thus their final drawframe will be their tile plus their walkingframe. Whenever map.changefinalcol() gets called, it is now free to modify the tile of disappearing platforms accordingly - after all, the tile offset is now stored in walkingframe, so no weird glitchiness can happen there.
2021-01-09 23:50:06 +01:00
entities[i].walkingframe++;
}
}
break;
case 3: //Breakable blocks
//Only counts if vy of player entity is non zero
if (entities[i].state == 1)
{
entities[i].life = 4;
entities[i].state = 2;
entities[i].onentity = 0;
music.playef(6);
}
else if (entities[i].state == 2)
{
entities[i].life--;
entities[i].tile++;
if (entities[i].life <= 0)
{
Fix entity and block indices after destroying them This patch restores some 2.2 behavior, fixing a regression caused by the refactor of properly using std::vectors. In 2.2, the game allocated 200 items in obj.entities, but used a system where each entity had an `active` attribute to signify if the entity actually existed or not. When dealing with entities, you would have to check this `active` flag, or else you'd be dealing with an entity that didn't actually exist. (By the way, what I'm saying applies to blocks and obj.blocks as well, except for some small differing details like the game allocating 500 block slots versus obj.entities's 200.) As a consequence, the game had to use a separate tracking variable, obj.nentity, because obj.entities.size() would just report 200, instead of the actual amount of entities. Needless to say, having to check for `active` and use `obj.nentity` is a bit error-prone, and it's messier than simply using the std::vector the way it was intended. Also, this resulted in a hard limit of 200 entities, which custom level makers ran into surprisingly quite often. 2.3 comes along, and removes the whole system. Now, std::vectors are properly being used, and obj.entities.size() reports the actual number of entities in the vector; you no longer have to check for `active` when dealing with entities of any sort. But there was one previous behavior of 2.2 that this system kind of forgets about - namely, the ability to have holes in between entities. You see, when an entity got disabled in 2.2 (which just meant turning its `active` off), the indices of all other entities stayed the same; the indice of the entity that got disabled stays there as a hole in the array. But when an entity gets removed in 2.3 (previous to this patch), the indices of every entity afterwards in the array get shifted down by one. std::vector isn't really meant to be able to contain holes. Do the indices of entities and blocks matter? Yes; they determine the order in which entities and blocks get evaluated (the highest indice gets evaluated first), and I had to fix some block evaluation order stuff in previous PRs. And in the case of entities, they matter hugely when using the recently-discovered Arbitrary Entity Manipulation glitch (where crewmate script commands are used on arbitrary entities by setting the `i` attribute of `scriptclass` and passing invalid crewmate identifiers to the commands). If you use Arbitrary Entity Manipulation after destroying some entities, there is a chance that your script won't work between 2.2 and 2.3. The indices also still determine the rendering order of entities (highest indice gets drawn first, which means lowest indice gets drawn in front of other entities). As an example: let's say we have the player at 0, a gravity line at 1, and a checkpoint at 2; then we destroy the gravity line and create a crewmate (let's do Violet). If we're able to have holes, then after removing the gravity line, none of the other indices shift. Then Violet will be created at indice 1, and will be drawn in front of the checkpoint. But if we can't have holes, then removing the gravity line results in the indice of the checkpoint shifting down to indice 1. Then Violet is created at indice 2, and gets drawn behind the checkpoint! This is a clear illustration of changing the behavior that existed in 2.2. However, I also don't want to go back to the `active` system of having to check an attribute before operating on an entity. So... what do we do to restore the holes? Well, we don't need to have an `active` attribute, or modify any existing code that operates on entities. Instead, we can just set the attributes of the entities so that they naturally get ignored by everything that comes into contact with it. For entities, we set their invis to true, and their size, type, and rule to -1 (the game never uses a size, type, or rule of -1 anywhere); for blocks, we set their type to -1, and their width and height to 0. obj.entities.size() will no longer necessarily equal the amount of entities in the room; rather, it will be the amount of entity SLOTS that have been allocated. But nothing that uses obj.entities.size() needs to actually know the amount of entities; it's mostly used for iterating over every entity in the vector. Excess entity slots get cleaned up upon every call of mapclass::gotoroom(), which will now deallocate entity slots starting from the end until it hits a player, at which point it will switch to disabling entity slots instead of removing them entirely. The entclass::clear() and blockclass::clear() functions have been restored because we need to call their initialization functions when reusing a block/entity slot; it's possible to create an entity with an invalid type number (it creates a glitchy Viridian), and without calling the initialization function again, it would simply not create anything. After this patch is applied, entity and block indices will be restored to how they behaved in 2.2.
2020-12-27 07:11:34 +01:00
disableblockat(entities[i].xp, entities[i].yp);
return disableentity(i);
}
}
break;
case 4: //Gravity token
//wait for collision
if (entities[i].state == 1)
{
game.gravitycontrol = (game.gravitycontrol + 1) % 2;
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
return disableentity(i);
2020-01-01 21:29:24 +01:00
}
break;
case 5: //Particle sprays
if (entities[i].state == 0)
{
entities[i].life--;
if (entities[i].life < 0)
{
Fix entity and block indices after destroying them This patch restores some 2.2 behavior, fixing a regression caused by the refactor of properly using std::vectors. In 2.2, the game allocated 200 items in obj.entities, but used a system where each entity had an `active` attribute to signify if the entity actually existed or not. When dealing with entities, you would have to check this `active` flag, or else you'd be dealing with an entity that didn't actually exist. (By the way, what I'm saying applies to blocks and obj.blocks as well, except for some small differing details like the game allocating 500 block slots versus obj.entities's 200.) As a consequence, the game had to use a separate tracking variable, obj.nentity, because obj.entities.size() would just report 200, instead of the actual amount of entities. Needless to say, having to check for `active` and use `obj.nentity` is a bit error-prone, and it's messier than simply using the std::vector the way it was intended. Also, this resulted in a hard limit of 200 entities, which custom level makers ran into surprisingly quite often. 2.3 comes along, and removes the whole system. Now, std::vectors are properly being used, and obj.entities.size() reports the actual number of entities in the vector; you no longer have to check for `active` when dealing with entities of any sort. But there was one previous behavior of 2.2 that this system kind of forgets about - namely, the ability to have holes in between entities. You see, when an entity got disabled in 2.2 (which just meant turning its `active` off), the indices of all other entities stayed the same; the indice of the entity that got disabled stays there as a hole in the array. But when an entity gets removed in 2.3 (previous to this patch), the indices of every entity afterwards in the array get shifted down by one. std::vector isn't really meant to be able to contain holes. Do the indices of entities and blocks matter? Yes; they determine the order in which entities and blocks get evaluated (the highest indice gets evaluated first), and I had to fix some block evaluation order stuff in previous PRs. And in the case of entities, they matter hugely when using the recently-discovered Arbitrary Entity Manipulation glitch (where crewmate script commands are used on arbitrary entities by setting the `i` attribute of `scriptclass` and passing invalid crewmate identifiers to the commands). If you use Arbitrary Entity Manipulation after destroying some entities, there is a chance that your script won't work between 2.2 and 2.3. The indices also still determine the rendering order of entities (highest indice gets drawn first, which means lowest indice gets drawn in front of other entities). As an example: let's say we have the player at 0, a gravity line at 1, and a checkpoint at 2; then we destroy the gravity line and create a crewmate (let's do Violet). If we're able to have holes, then after removing the gravity line, none of the other indices shift. Then Violet will be created at indice 1, and will be drawn in front of the checkpoint. But if we can't have holes, then removing the gravity line results in the indice of the checkpoint shifting down to indice 1. Then Violet is created at indice 2, and gets drawn behind the checkpoint! This is a clear illustration of changing the behavior that existed in 2.2. However, I also don't want to go back to the `active` system of having to check an attribute before operating on an entity. So... what do we do to restore the holes? Well, we don't need to have an `active` attribute, or modify any existing code that operates on entities. Instead, we can just set the attributes of the entities so that they naturally get ignored by everything that comes into contact with it. For entities, we set their invis to true, and their size, type, and rule to -1 (the game never uses a size, type, or rule of -1 anywhere); for blocks, we set their type to -1, and their width and height to 0. obj.entities.size() will no longer necessarily equal the amount of entities in the room; rather, it will be the amount of entity SLOTS that have been allocated. But nothing that uses obj.entities.size() needs to actually know the amount of entities; it's mostly used for iterating over every entity in the vector. Excess entity slots get cleaned up upon every call of mapclass::gotoroom(), which will now deallocate entity slots starting from the end until it hits a player, at which point it will switch to disabling entity slots instead of removing them entirely. The entclass::clear() and blockclass::clear() functions have been restored because we need to call their initialization functions when reusing a block/entity slot; it's possible to create an entity with an invalid type number (it creates a glitchy Viridian), and without calling the initialization function again, it would simply not create anything. After this patch is applied, entity and block indices will be restored to how they behaved in 2.2.
2020-12-27 07:11:34 +01:00
return disableentity(i);
}
}
break;
case 6: //Small pickup
//wait for collision
if (entities[i].state == 1)
{
music.playef(4);
if (INBOUNDS_ARR(entities[i].para, collect))
{
collect[(int) entities[i].para] = true;
}
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
return disableentity(i);
}
break;
case 7: //Found a trinket
//wait for collision
if (entities[i].state == 1)
{
if (INBOUNDS_ARR(entities[i].para, collect))
{
collect[(int) entities[i].para] = true;
}
if (game.intimetrial)
{
music.playef(25);
2020-01-01 21:29:24 +01:00
}
else
2020-01-01 21:29:24 +01:00
{
game.state = 1000;
if(music.currentsong!=-1) music.silencedasmusik();
music.playef(3);
if (game.trinkets() > game.stat_trinkets && !map.custommode)
2020-01-01 21:29:24 +01:00
{
game.stat_trinkets = game.trinkets();
2020-01-01 21:29:24 +01:00
}
}
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
return disableentity(i);
}
break;
case 8: //Savepoints
//wait for collision
if (entities[i].state == 1)
{
//First, deactivate all other savepoints
for (size_t j = 0; j < entities.size(); j++)
{
if (entities[j].type == 8)
2020-01-01 21:29:24 +01:00
{
entities[j].colour = 4;
entities[j].onentity = 1;
2020-01-01 21:29:24 +01:00
}
}
entities[i].colour = 5;
entities[i].onentity = 0;
game.savepoint = entities[i].para;
music.playef(5);
game.savex = entities[i].xp - 4;
if (entities[i].tile == 20)
2020-01-01 21:29:24 +01:00
{
game.savey = entities[i].yp - 2;
game.savegc = 1;
}
else if (entities[i].tile == 21)
{
game.savey = entities[i].yp - 7;
game.savegc = 0;
}
2020-01-01 21:29:24 +01:00
game.saverx = game.roomx;
game.savery = game.roomy;
int player = getplayer();
if (INBOUNDS_VEC(player, entities))
{
game.savedir = entities[player].dir;
}
entities[i].state = 0;
}
break;
case 9: //Gravity Lines
if (entities[i].state == 1)
{
entities[i].life--;
entities[i].onentity = 0;
if (entities[i].life <= 0)
{
entities[i].state = 0;
entities[i].onentity = 1;
2020-01-01 21:29:24 +01:00
}
}
break;
case 10: //Vertical gravity Lines
if (entities[i].state == 1)
{
entities[i].onentity = 3;
entities[i].state = 2;
music.playef(8);
game.gravitycontrol = (game.gravitycontrol + 1) % 2;
game.totalflips++;
int temp = getplayer();
if (game.gravitycontrol == 0)
2020-01-01 21:29:24 +01:00
{
if (INBOUNDS_VEC(temp, entities) && entities[temp].vy < 3) entities[temp].vy = 3;
}
else
{
if (INBOUNDS_VEC(temp, entities) && entities[temp].vy > -3) entities[temp].vy = -3;
}
}
else if (entities[i].state == 2)
{
entities[i].life--;
if (entities[i].life <= 0)
{
entities[i].state = 0;
entities[i].onentity = 1;
}
}
else if (entities[i].state == 3)
{
entities[i].state = 2;
entities[i].life = 4;
entities[i].onentity = 3;
}
else if (entities[i].state == 4)
{
//Special case for room initilisations: As state one, except without the reversal
entities[i].onentity = 3;
entities[i].state = 2;
}
break;
case 11: //Warp point
//wait for collision
if (entities[i].state == 1)
{
//Depending on the room the warp point is in, teleport to a new location!
entities[i].onentity = 0;
//play a sound or somefink
music.playef(10);
game.teleport = true;
game.edteleportent = i;
//for the multiple room:
if (int(entities[i].xp) == 12*8) game.teleportxpos = 1;
if (int(entities[i].xp) == 5*8) game.teleportxpos = 2;
if (int(entities[i].xp) == 28*8) game.teleportxpos = 3;
if (int(entities[i].xp) == 21*8) game.teleportxpos = 4;
}
break;
case 12: //Crew member
//Somewhat complex AI: exactly what they do depends on room, location, state etc
//At state 0, do nothing at all.
if (entities[i].state == 1)
{
//happy!
if (INBOUNDS_VEC(k, entities) && entities[k].rule == 6) entities[k].tile = 0;
if (INBOUNDS_VEC(k, entities) && entities[k].rule == 7) entities[k].tile = 6;
//Stay close to the hero!
int j = getplayer();
if (INBOUNDS_VEC(j, entities) && entities[j].xp > entities[i].xp + 5)
{
entities[i].dir = 1;
}
else if (INBOUNDS_VEC(j, entities) && entities[j].xp < entities[i].xp - 5)
{
entities[i].dir = 0;
}
2020-01-01 21:29:24 +01:00
if (INBOUNDS_VEC(j, entities) && entities[j].xp > entities[i].xp + 45)
{
entities[i].ax = 3;
2020-01-01 21:29:24 +01:00
}
else if (INBOUNDS_VEC(j, entities) && entities[j].xp < entities[i].xp - 45)
2020-01-01 21:29:24 +01:00
{
entities[i].ax = -3;
}
2020-01-01 21:29:24 +01:00
//Special rules:
if (game.roomx == 110 && game.roomy == 105 && !map.custommode)
{
if (entities[i].xp < 155)
2020-01-01 21:29:24 +01:00
{
if (entities[i].ax < 0) entities[i].ax = 0;
2020-01-01 21:29:24 +01:00
}
}
}
else if (entities[i].state == 2)
{
//Basic rules, don't change expression
int j = getplayer();
if (INBOUNDS_VEC(j, entities) && entities[j].xp > entities[i].xp + 5)
2020-01-01 21:29:24 +01:00
{
entities[i].dir = 1;
}
else if (INBOUNDS_VEC(j, entities) && entities[j].xp < entities[i].xp - 5)
{
entities[i].dir = 0;
}
2020-01-01 21:29:24 +01:00
if (INBOUNDS_VEC(j, entities) && entities[j].xp > entities[i].xp + 45)
{
entities[i].ax = 3;
2020-01-01 21:29:24 +01:00
}
else if (INBOUNDS_VEC(j, entities) && entities[j].xp < entities[i].xp - 45)
2020-01-01 21:29:24 +01:00
{
entities[i].ax = -3;
2020-01-01 21:29:24 +01:00
}
}
else if (entities[i].state == 10)
{
//Everything from 10 on is for cutscenes
//Basic rules, don't change expression
int j = getplayer();
if (INBOUNDS_VEC(j, entities) && entities[j].xp > entities[i].xp + 5)
2020-01-01 21:29:24 +01:00
{
entities[i].dir = 1;
2020-01-01 21:29:24 +01:00
}
else if (INBOUNDS_VEC(j, entities) && entities[j].xp < entities[i].xp - 5)
2020-01-01 21:29:24 +01:00
{
entities[i].dir = 0;
2020-01-01 21:29:24 +01:00
}
if (INBOUNDS_VEC(j, entities) && entities[j].xp > entities[i].xp + 45)
2020-01-01 21:29:24 +01:00
{
entities[i].ax = 3;
}
else if (INBOUNDS_VEC(j, entities) && entities[j].xp < entities[i].xp - 45)
{
entities[i].ax = -3;
}
}
else if (entities[i].state == 11)
{
//11-15 means to follow a specific character, in crew order (cyan, purple, yellow, red, green, blue)
int j=getcrewman(1); //purple
if (INBOUNDS_VEC(j, entities))
{
if (entities[j].xp > entities[i].xp + 5)
{
entities[i].dir = 1;
}
else if (entities[j].xp < entities[i].xp - 5)
{
entities[i].dir = 0;
}
2020-01-01 21:29:24 +01:00
if (entities[j].xp > entities[i].xp + 45)
{
entities[i].ax = 3;
}
else if (entities[j].xp < entities[i].xp - 45)
{
entities[i].ax = -3;
}
}
}
else if (entities[i].state == 12)
{
//11-15 means to follow a specific character, in crew order (cyan, purple, yellow, red, green, blue)
int j=getcrewman(2); //yellow
if (INBOUNDS_VEC(j, entities))
{
if (entities[j].xp > entities[i].xp + 5)
{
entities[i].dir = 1;
}
else if (entities[j].xp < entities[i].xp - 5)
{
entities[i].dir = 0;
}
2020-01-01 21:29:24 +01:00
if (entities[j].xp > entities[i].xp + 45)
{
entities[i].ax = 3;
}
else if (entities[j].xp < entities[i].xp - 45)
{
entities[i].ax = -3;
}
}
}
else if (entities[i].state == 13)
{
//11-15 means to follow a specific character, in crew order (cyan, purple, yellow, red, green, blue)
int j=getcrewman(3); //red
if (INBOUNDS_VEC(j, entities))
{
if (entities[j].xp > entities[i].xp + 5)
{
entities[i].dir = 1;
}
else if (entities[j].xp < entities[i].xp - 5)
{
entities[i].dir = 0;
}
if (entities[j].xp > entities[i].xp + 45)
{
entities[i].ax = 3;
}
else if (entities[j].xp < entities[i].xp - 45)
{
entities[i].ax = -3;
}
}
}
else if (entities[i].state == 14)
{
//11-15 means to follow a specific character, in crew order (cyan, purple, yellow, red, green, blue)
int j=getcrewman(4); //green
if (INBOUNDS_VEC(j, entities))
{
if (entities[j].xp > entities[i].xp + 5)
{
entities[i].dir = 1;
}
else if (entities[j].xp < entities[i].xp - 5)
{
entities[i].dir = 0;
}
if (entities[j].xp > entities[i].xp + 45)
{
entities[i].ax = 3;
}
else if (entities[j].xp < entities[i].xp - 45)
{
entities[i].ax = -3;
}
}
}
else if (entities[i].state == 15)
{
//11-15 means to follow a specific character, in crew order (cyan, purple, yellow, red, green, blue)
int j=getcrewman(5); //blue
if (INBOUNDS_VEC(j, entities))
{
if (entities[j].xp > entities[i].xp + 5)
{
entities[i].dir = 1;
}
else if (entities[j].xp < entities[i].xp - 5)
{
entities[i].dir = 0;
}
if (entities[j].xp > entities[i].xp + 45)
{
entities[i].ax = 3;
}
else if (entities[j].xp < entities[i].xp - 45)
{
entities[i].ax = -3;
}
}
}
else if (entities[i].state == 16)
{
//Follow a position: given an x coordinate, seek it out.
if (entities[i].para > entities[i].xp + 5)
{
entities[i].dir = 1;
}
else if (entities[i].para < entities[i].xp - 5)
{
entities[i].dir = 0;
}
if (entities[i].para > entities[i].xp + 45)
{
entities[i].ax = 3;
}
else if (entities[i].para < entities[i].xp - 45)
{
entities[i].ax = -3;
}
}
else if (entities[i].state == 17)
{
//stand still
}
else if (entities[i].state == 18)
{
//Stand still and face the player
int j = getplayer();
if (INBOUNDS_VEC(j, entities) && entities[j].xp > entities[i].xp + 5)
{
entities[i].dir = 1;
}
else if (INBOUNDS_VEC(j, entities) && entities[j].xp < entities[i].xp - 5)
{
entities[i].dir = 0;
}
}
else if (entities[i].state == 19)
{
//Walk right off the screen after time t
if (entities[i].para <= 0)
{
entities[i].dir = 1;
entities[i].ax = 3;
2020-01-01 21:29:24 +01:00
}
else
2020-01-01 21:29:24 +01:00
{
entities[i].para--;
2020-01-01 21:29:24 +01:00
}
}
else if (entities[i].state == 20)
{
//Panic! For briefing script
if (entities[i].life == 0)
2020-01-01 21:29:24 +01:00
{
//walk left for a bit
entities[i].ax = 0;
if (40 > entities[i].xp + 5)
2020-01-01 21:29:24 +01:00
{
entities[i].dir = 1;
2020-01-01 21:29:24 +01:00
}
else if (40 < entities[i].xp - 5)
2020-01-01 21:29:24 +01:00
{
entities[i].dir = 0;
2020-01-01 21:29:24 +01:00
}
if (40 > entities[i].xp + 45)
2020-01-01 21:29:24 +01:00
{
entities[i].ax = 3;
}
else if (40 < entities[i].xp - 45)
{
entities[i].ax = -3;
}
if ( (entities[i].ax) == 0)
2020-01-01 21:29:24 +01:00
{
entities[i].life = 1;
entities[i].para = 30;
2020-01-01 21:29:24 +01:00
}
}
else if (entities[i].life == 1)
2020-01-01 21:29:24 +01:00
{
//Stand around for a bit
entities[i].para--;
if (entities[i].para <= 0)
{
entities[i].life++;
}
2020-01-01 21:29:24 +01:00
}
else if (entities[i].life == 2)
2020-01-01 21:29:24 +01:00
{
//walk right for a bit
entities[i].ax = 0;
if (280 > entities[i].xp + 5)
2020-01-01 21:29:24 +01:00
{
entities[i].dir = 1;
2020-01-01 21:29:24 +01:00
}
else if (280 < entities[i].xp - 5)
2020-01-01 21:29:24 +01:00
{
entities[i].dir = 0;
2020-01-01 21:29:24 +01:00
}
if (280 > entities[i].xp + 45)
{
entities[i].ax = 3;
}
else if (280 < entities[i].xp - 45)
{
entities[i].ax = -3;
}
if ( (entities[i].ax) == 0)
{
entities[i].life = 3;
entities[i].para = 30;
}
2020-01-01 21:29:24 +01:00
}
else if (entities[i].life == 3)
2020-01-01 21:29:24 +01:00
{
//Stand around for a bit
entities[i].para--;
if (entities[i].para <= 0)
{
entities[i].life=0;
}
2020-01-01 21:29:24 +01:00
}
}
break;
case 13: //Terminals (very similar to savepoints)
//wait for collision
if (entities[i].state == 1)
{
entities[i].colour = 5;
entities[i].onentity = 0;
music.playef(17);
entities[i].state = 0;
}
break;
case 14: //Super Crew member
//Actually needs less complex AI than the scripting crewmember
if (entities[i].state == 0)
{
//follow player, but only if he's on the floor!
int j = getplayer();
if(INBOUNDS_VEC(j, entities) && entities[j].onground>0)
2020-01-01 21:29:24 +01:00
{
if (entities[j].xp > entities[i].xp + 5)
{
entities[i].dir = 1;
}
else if (entities[j].xp>15 && entities[j].xp < entities[i].xp - 5)
2020-01-01 21:29:24 +01:00
{
entities[i].dir = 0;
}
if (entities[j].xp > entities[i].xp + 45)
{
entities[i].ax = 3;
}
else if (entities[j].xp < entities[i].xp - 45)
{
entities[i].ax = -3;
}
if (entities[i].ax < 0 && entities[i].xp < 60)
{
entities[i].ax = 0;
}
2020-01-01 21:29:24 +01:00
}
else
2020-01-01 21:29:24 +01:00
{
if (INBOUNDS_VEC(j, entities) && entities[j].xp > entities[i].xp + 5)
2020-01-01 21:29:24 +01:00
{
entities[i].dir = 1;
2020-01-01 21:29:24 +01:00
}
else if (INBOUNDS_VEC(j, entities) && entities[j].xp < entities[i].xp - 5)
2020-01-01 21:29:24 +01:00
{
entities[i].dir = 0;
2020-01-01 21:29:24 +01:00
}
entities[i].ax = 0;
}
if (entities[i].xp > 240)
{
entities[i].ax = 3;
entities[i].dir = 1;
}
if (entities[i].xp >= 310)
{
game.scmprogress++;
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
return disableentity(i);
2020-01-01 21:29:24 +01:00
}
}
break;
case 15: //Trophy
//wait for collision
if (entities[i].state == 1)
{
if (!script.running) trophytext+=2;
if (trophytext > 30) trophytext = 30;
trophytype = entities[i].para;
entities[i].state = 0;
}
break;
case 23:
//swn game!
switch(entities[i].behave)
{
case 0:
if (entities[i].state == 0) //Init
{
entities[i].vx = 7;
if (entities[i].xp > 320)
{
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
return disableentity(i);
}
}
2020-01-01 21:29:24 +01:00
break;
case 1:
if (entities[i].state == 0) //Init
2020-01-01 21:29:24 +01:00
{
entities[i].vx = -7;
if (entities[i].xp <-20)
{
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
return disableentity(i);
}
}
break;
}
break;
2020-01-01 21:29:24 +01:00
case 51: //Vertical warp line
if (entities[i].state == 2){
int j=getplayer();
if(INBOUNDS_VEC(j, entities) && entities[j].xp<=307){
customwarpmodevon=false;
entities[i].state = 0;
}
}else if (entities[i].state == 1)
{
entities[i].state = 2;
entities[i].statedelay = 2;
entities[i].onentity = 1;
customwarpmodevon=true;
}
break;
case 52: //Vertical warp line
if (entities[i].state == 2){
int j=getplayer();
if(INBOUNDS_VEC(j, entities) && entities[j].xp<=307){
customwarpmodevon=false;
entities[i].state = 0;
}
}else if (entities[i].state == 1)
{
entities[i].state = 2;
entities[i].statedelay = 2;
entities[i].onentity = 1;
customwarpmodevon=true;
}
break;
case 53: //Warp lines Horizonal
if (entities[i].state == 2){
customwarpmodehon=false;
entities[i].state = 0;
}else if (entities[i].state == 1)
{
entities[i].state = 2;
entities[i].statedelay = 2;
entities[i].onentity = 1;
customwarpmodehon=true;
}
break;
case 54: //Warp lines Horizonal
if (entities[i].state == 2){
customwarpmodehon=false;
entities[i].state = 0;
}else if (entities[i].state == 1)
{
entities[i].state = 2;
entities[i].statedelay = 2;
entities[i].onentity = 1;
customwarpmodehon=true;
}
break;
case 55: //Collectable crewmate
//wait for collision
if (entities[i].state == 0)
{
//Basic rules, don't change expression
int j = getplayer();
if (INBOUNDS_VEC(j, entities) && entities[j].xp > entities[i].xp + 5)
{
entities[i].dir = 1;
2020-01-01 21:29:24 +01:00
}
else if (INBOUNDS_VEC(j, entities) && entities[j].xp < entities[i].xp - 5)
{
entities[i].dir = 0;
}
}
else if (entities[i].state == 1)
{
if (INBOUNDS_ARR(entities[i].para, customcollect))
{
customcollect[(int) entities[i].para] = true;
}
if (game.intimetrial)
{
music.playef(27);
}
else
{
game.state = 1010;
//music.haltdasmusik();
if(music.currentsong!=-1) music.silencedasmusik();
music.playef(27);
}
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
return disableentity(i);
}
break;
case 100: //The teleporter
if (entities[i].state == 1)
{
//if inactive, activate!
if (entities[i].tile == 1)
2020-01-01 21:29:24 +01:00
{
music.playef(18);
entities[i].tile = 2;
entities[i].colour = 101;
if(!game.intimetrial && !game.nodeathmode)
{
game.state = 2000;
game.statedelay = 0;
}
2020-01-01 21:29:24 +01:00
game.activetele = true;
game.teleblock.x = entities[i].xp - 32;
game.teleblock.y = entities[i].yp - 32;
game.teleblock.w = 160;
game.teleblock.h = 160;
//Alright, let's set this as our savepoint too
//First, deactivate all other savepoints
for (size_t j = 0; j < entities.size(); j++)
{
if (entities[j].type == 8)
{
entities[j].colour = 4;
entities[j].onentity = 1;
}
}
game.savepoint = static_cast<int>(entities[i].para);
game.savex = entities[i].xp + 44;
game.savey = entities[i].yp + 44;
game.savegc = 0;
game.saverx = game.roomx;
game.savery = game.roomy;
int player = getplayer();
if (INBOUNDS_VEC(player, entities))
{
game.savedir = entities[player].dir;
}
2020-01-01 21:29:24 +01:00
}
entities[i].onentity = 0;
entities[i].state = 0;
2020-01-01 21:29:24 +01:00
}
else if (entities[i].state == 2)
2020-01-01 21:29:24 +01:00
{
//Initilise the teleporter without changing the game state or playing sound
entities[i].onentity = 0;
entities[i].tile = 6;
entities[i].colour = 102;
game.activetele = true;
game.teleblock.x = entities[i].xp - 32;
game.teleblock.y = entities[i].yp - 32;
game.teleblock.w = 160;
game.teleblock.h = 160;
entities[i].state = 0;
2020-01-01 21:29:24 +01:00
}
break;
}
}
else
{
entities[i].statedelay--;
if (entities[i].statedelay < 0)
{
entities[i].statedelay = 0;
2020-01-01 21:29:24 +01:00
}
}
return false;
2020-01-01 21:29:24 +01:00
}
void entityclass::animateentities( int _i )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(_i, entities))
{
puts("animateentities() out-of-bounds!");
return;
}
if(entities[_i].statedelay < 1)
2020-01-01 21:29:24 +01:00
{
switch(entities[_i].type)
2020-01-01 21:29:24 +01:00
{
case 0:
entities[_i].framedelay--;
if(entities[_i].dir==1)
2020-01-01 21:29:24 +01:00
{
entities[_i].drawframe=entities[_i].tile;
}
else
{
entities[_i].drawframe=entities[_i].tile+3;
}
2020-01-01 21:29:24 +01:00
if(entities[_i].visualonground>0 || entities[_i].visualonroof>0)
{
if(entities[_i].vx > 0.00f || entities[_i].vx < -0.00f)
2020-01-01 21:29:24 +01:00
{
//Walking
if(entities[_i].framedelay<=1)
2020-01-01 21:29:24 +01:00
{
entities[_i].framedelay=4;
entities[_i].walkingframe++;
2020-01-01 21:29:24 +01:00
}
if (entities[_i].walkingframe >=2) entities[_i].walkingframe=0;
entities[_i].drawframe += entities[_i].walkingframe + 1;
2020-01-01 21:29:24 +01:00
}
if (entities[_i].visualonroof > 0) entities[_i].drawframe += 6;
// Stuck in a wall? Then default to gravitycontrol
if (entities[_i].visualonground > 0 && entities[_i].visualonroof > 0
&& game.gravitycontrol == 0)
{
entities[_i].drawframe -= 6;
}
}
else
{
entities[_i].drawframe ++;
if (game.gravitycontrol == 1)
2020-01-01 21:29:24 +01:00
{
entities[_i].drawframe += 6;
2020-01-01 21:29:24 +01:00
}
}
2020-01-01 21:29:24 +01:00
if (game.deathseq > -1)
{
entities[_i].drawframe=13;
if (entities[_i].dir == 1) entities[_i].drawframe = 12;
if (game.gravitycontrol == 1) entities[_i].drawframe += 2;
}
break;
case 1:
case 23:
//Variable animation
switch(entities[_i].animate)
{
case 0:
//Simple oscilation
entities[_i].framedelay--;
if(entities[_i].framedelay<=0)
{
entities[_i].framedelay = 8;
if(entities[_i].actionframe==0)
2020-01-01 21:29:24 +01:00
{
entities[_i].walkingframe++;
if (entities[_i].walkingframe == 4)
2020-01-01 21:29:24 +01:00
{
entities[_i].walkingframe = 2;
entities[_i].actionframe = 1;
2020-01-01 21:29:24 +01:00
}
}
else
2020-01-01 21:29:24 +01:00
{
entities[_i].walkingframe--;
if (entities[_i].walkingframe == -1)
2020-01-01 21:29:24 +01:00
{
entities[_i].walkingframe = 1;
entities[_i].actionframe = 0;
2020-01-01 21:29:24 +01:00
}
}
}
2020-01-01 21:29:24 +01:00
entities[_i].drawframe = entities[_i].tile;
entities[_i].drawframe += entities[_i].walkingframe;
break;
case 1:
//Simple Loop
entities[_i].framedelay--;
if(entities[_i].framedelay<=0)
{
entities[_i].framedelay = 8;
entities[_i].walkingframe++;
if (entities[_i].walkingframe == 4)
2020-01-01 21:29:24 +01:00
{
entities[_i].walkingframe = 0;
2020-01-01 21:29:24 +01:00
}
}
2020-01-01 21:29:24 +01:00
entities[_i].drawframe = entities[_i].tile;
entities[_i].drawframe += entities[_i].walkingframe;
break;
case 2:
//Simpler Loop (just two frames)
entities[_i].framedelay--;
if(entities[_i].framedelay<=0)
{
entities[_i].framedelay = 2;
entities[_i].walkingframe++;
if (entities[_i].walkingframe == 2)
2020-01-01 21:29:24 +01:00
{
entities[_i].walkingframe = 0;
2020-01-01 21:29:24 +01:00
}
}
2020-01-01 21:29:24 +01:00
entities[_i].drawframe = entities[_i].tile;
entities[_i].drawframe += entities[_i].walkingframe;
break;
case 3:
//Simpler Loop (just two frames, but double sized)
entities[_i].framedelay--;
if(entities[_i].framedelay<=0)
{
entities[_i].framedelay = 2;
entities[_i].walkingframe++;
if (entities[_i].walkingframe == 2)
2020-01-01 21:29:24 +01:00
{
entities[_i].walkingframe = 0;
2020-01-01 21:29:24 +01:00
}
}
2020-01-01 21:29:24 +01:00
entities[_i].drawframe = entities[_i].tile;
entities[_i].drawframe += (entities[_i].walkingframe*2);
break;
case 4:
//Simpler Loop (just two frames, but double sized) (as above but slower)
entities[_i].framedelay--;
if(entities[_i].framedelay<=0)
{
entities[_i].framedelay = 6;
entities[_i].walkingframe++;
if (entities[_i].walkingframe == 2)
2020-01-01 21:29:24 +01:00
{
entities[_i].walkingframe = 0;
2020-01-01 21:29:24 +01:00
}
}
2020-01-01 21:29:24 +01:00
entities[_i].drawframe = entities[_i].tile;
entities[_i].drawframe += (entities[_i].walkingframe*2);
break;
case 5:
//Simpler Loop (just two frames) (slower)
entities[_i].framedelay--;
if(entities[_i].framedelay<=0)
2020-01-01 21:29:24 +01:00
{
entities[_i].framedelay = 6;
entities[_i].walkingframe++;
if (entities[_i].walkingframe == 2)
2020-01-01 21:29:24 +01:00
{
entities[_i].walkingframe = 0;
2020-01-01 21:29:24 +01:00
}
}
entities[_i].drawframe = entities[_i].tile;
entities[_i].drawframe += entities[_i].walkingframe;
2020-01-01 21:29:24 +01:00
break;
case 6:
//Normal Loop (four frames, double sized)
2020-01-01 21:29:24 +01:00
entities[_i].framedelay--;
if(entities[_i].framedelay<=0)
2020-01-01 21:29:24 +01:00
{
entities[_i].framedelay = 4;
entities[_i].walkingframe++;
if (entities[_i].walkingframe == 4)
{
entities[_i].walkingframe = 0;
}
2020-01-01 21:29:24 +01:00
}
entities[_i].drawframe = entities[_i].tile;
entities[_i].drawframe += (entities[_i].walkingframe*2);
break;
case 7:
//Simpler Loop (just two frames) (slower) (with directions!)
entities[_i].framedelay--;
if(entities[_i].framedelay<=0)
2020-01-01 21:29:24 +01:00
{
entities[_i].framedelay = 6;
entities[_i].walkingframe++;
if (entities[_i].walkingframe == 2)
2020-01-01 21:29:24 +01:00
{
entities[_i].walkingframe = 0;
2020-01-01 21:29:24 +01:00
}
}
entities[_i].drawframe = entities[_i].tile;
entities[_i].drawframe += entities[_i].walkingframe;
if (entities[_i].vx > 0.000f ) entities[_i].drawframe += 2;
break;
case 10:
//Threadmill left
entities[_i].framedelay--;
if(entities[_i].framedelay<=0)
2020-01-01 21:29:24 +01:00
{
entities[_i].framedelay = 3;//(6-entities[_i].para);
entities[_i].walkingframe--;
if (entities[_i].walkingframe == -1)
{
entities[_i].walkingframe = 3;
}
2020-01-01 21:29:24 +01:00
}
entities[_i].drawframe = entities[_i].tile;
entities[_i].drawframe += entities[_i].walkingframe;
break;
case 11:
//Threadmill right
entities[_i].framedelay--;
if(entities[_i].framedelay<=0)
2020-01-01 21:29:24 +01:00
{
entities[_i].framedelay = 3;//(6-entities[_i].para);
entities[_i].walkingframe++;
if (entities[_i].walkingframe == 4)
{
entities[_i].walkingframe = 0;
}
2020-01-01 21:29:24 +01:00
}
entities[_i].drawframe = entities[_i].tile;
entities[_i].drawframe += entities[_i].walkingframe;
break;
case 100:
//Simple case for no animation (platforms, etc)
entities[_i].drawframe = entities[_i].tile;
break;
default:
entities[_i].drawframe = entities[_i].tile;
2020-01-01 21:29:24 +01:00
break;
}
break;
Fix tile of disappearing platforms during final stretch During the final stretch, after Viridian turns off the Dimensional Stability Generator, the map goes all psychedelic and changes colors every 40 frames. Entities change their colors too, including conveyors, moving platforms, and disappearing platforms. But play around with the disappearing platforms for a bit and you'll notice they seem a bit glitchy. If you run on them at the right time, the tile they use while disappearing seems to abruptly change whenever the color of the room changes. If there's a color change while they're reappearing (when you die and respawn in the same room as them), they'll have the wrong tile and look like a conveyor. And even if you've never interacted with them at all, dying and respawning in the same room as them will change their tile to something wrong and also look like a conveyor. So, what's the problem? Well, first off, the tile of every untouched disappearing platform changing into a conveyor after you die and respawn in the same room is caused by a block of code in gamelogic() that gets run on each entity whenever you die. This block of code is the exact same block of code that gets ran on a disappearing platform if it's in the middle of disappearing. As a quick primer, every entity in the game has a state, which is just a number. You can view each entity's state in entityclass::updateentities(). State 0 of disappearing platforms is doing nothing, and they start with an onentity of 1, which means they turn to state 1 when they get touched. State 1 moves to state 2. State 2 does some decrementing, then moves to state 3 and sets the onentity to 4. State 3 also does nothing. After being touched, state 4 makes the platform reappear and move to state 5, but state 5 does the actual reappearing; state 5 then sets the state back to 0 and onentity back to 1. So, back to the copy-pasted block of code. The block of code was originally intended to fast-forward disappearing platforms if they were in the middle of disappearing, so the player respawn code would properly respawn the disappearing platform, instead of leaving it disappeared. What it does is keep updating the entity, while the state of the entity is 2, until it is no longer in state 2, then sets it to state 4. Crucially, the original block of code only ran if the disappearing platform was in state 2. But the other block of code, which was copy-pasted with slight modifications, runs on ALL disappearing platforms in final stretch, regardless of if they are in state 2 or not. Thus, all untouched platforms will be set to state 4, and state 4 will do the animation of the platform reappearing, which is invalid given that the platform never disappeared in the first place. So that's why dying and respawning in the same room as some disappearing platforms during final stretch will change their tiles to be conveyors. It seems to me that doing anything with death is wrong, here. The root cause is that map.changefinalcol() "resets" the tile of every disappearing platform, which is a function that gets called on every color change. The color change has nothing to do with dying, so why fiddle with the death code? Thus, I've deleted that entire block of code. What I've done to fix the issue is to make it so the tile of disappearing platforms aren't manually controlled. You see, unlike other entities in the game, the tile of disappearing platforms gets manually modified whenever it disappears or reappears. Other entities use the tile as a base and store their tile offset in the separate walkingframe attribute, which will be added to the tile attribute to produce the drawframe, which is the final thing that gets rendered - but for disappearing platforms, their tile gets directly incremented or decremented whenever they disappear or reappear, so when map.changefinalcol() gets ran to update the tile of every platform and conveyor, it basically discards the tile offset that was manually added in. Instead, what I've done is make it so disappearing platforms now use walkingframe, and thus their final drawframe will be their tile plus their walkingframe. Whenever map.changefinalcol() gets called, it is now free to modify the tile of disappearing platforms accordingly - after all, the tile offset is now stored in walkingframe, so no weird glitchiness can happen there.
2021-01-09 23:50:06 +01:00
case 2: //Disappearing platforms
entities[_i].drawframe = entities[_i].tile + entities[_i].walkingframe;
break;
case 11:
entities[_i].drawframe = entities[_i].tile;
if(entities[_i].animate==2)
{
//Simpler Loop (just two frames)
entities[_i].framedelay--;
if(entities[_i].framedelay<=0)
2020-01-01 21:29:24 +01:00
{
entities[_i].framedelay = 10;
entities[_i].walkingframe++;
if (entities[_i].walkingframe == 2)
{
entities[_i].walkingframe = 0;
}
2020-01-01 21:29:24 +01:00
}
entities[_i].drawframe = entities[_i].tile;
entities[_i].drawframe += entities[_i].walkingframe;
}
break;
case 12:
case 55:
case 14: //Crew member! Very similar to hero
entities[_i].framedelay--;
if(entities[_i].dir==1)
{
entities[_i].drawframe=entities[_i].tile;
}
else
{
entities[_i].drawframe=entities[_i].tile+3;
}
if(entities[_i].visualonground>0 || entities[_i].visualonroof>0)
{
if(entities[_i].vx > 0.0000f || entities[_i].vx < -0.000f)
{
//Walking
2020-01-01 21:29:24 +01:00
if(entities[_i].framedelay<=0)
{
entities[_i].framedelay=4;
entities[_i].walkingframe++;
2020-01-01 21:29:24 +01:00
}
if (entities[_i].walkingframe >=2) entities[_i].walkingframe=0;
entities[_i].drawframe += entities[_i].walkingframe + 1;
2020-01-01 21:29:24 +01:00
}
//if (entities[_i].visualonroof > 0) entities[_i].drawframe += 6;
}
else
{
entities[_i].drawframe ++;
//if (game.gravitycontrol == 1) {
// entities[_i].drawframe += 6;
//}
}
if (game.deathseq > -1)
{
entities[_i].drawframe=13;
if (entities[_i].dir == 1) entities[_i].drawframe = 12;
if (entities[_i].rule == 7) entities[_i].drawframe += 2;
//if (game.gravitycontrol == 1) entities[_i].drawframe += 2;
}
break;
case 100: //the teleporter!
if (entities[_i].tile == 1)
{
//it's inactive
entities[_i].drawframe = entities[_i].tile;
}
else if (entities[_i].tile == 2)
{
entities[_i].drawframe = entities[_i].tile;
entities[_i].framedelay--;
if(entities[_i].framedelay<=0)
{
entities[_i].framedelay = 1;
entities[_i].walkingframe = int(fRandom() * 6);
if (entities[_i].walkingframe >= 4)
2020-01-01 21:29:24 +01:00
{
entities[_i].walkingframe = -1;
entities[_i].framedelay = 4;
2020-01-01 21:29:24 +01:00
}
}
entities[_i].drawframe = entities[_i].tile;
entities[_i].drawframe += entities[_i].walkingframe;
}
else if (entities[_i].tile == 6)
{
//faster!
entities[_i].drawframe = entities[_i].tile;
2020-01-01 21:29:24 +01:00
entities[_i].framedelay--;
if(entities[_i].framedelay<=0)
{
entities[_i].framedelay = 2;
entities[_i].walkingframe = int(fRandom() * 6);
if (entities[_i].walkingframe >= 4)
{
entities[_i].walkingframe = -5;
entities[_i].framedelay = 4;
}
2020-01-01 21:29:24 +01:00
}
2020-01-01 21:29:24 +01:00
entities[_i].drawframe = entities[_i].tile;
entities[_i].drawframe += entities[_i].walkingframe;
2020-01-01 21:29:24 +01:00
}
break;
default:
entities[_i].drawframe = entities[_i].tile;
break;
2020-01-01 21:29:24 +01:00
}
}
else
{
//entities[_i].statedelay--;
if (entities[_i].statedelay < 0) entities[_i].statedelay = 0;
2020-01-01 21:29:24 +01:00
}
}
Fix regression: quick stopping changing drawframe This fixes a regression that desyncs my Nova TAS after re-removing the 1-frame input delay. Quick stopping is simply holding left/right but for less than 5 frames. Viridian doesn't decelerate when you let go and they immediately stop in place. (The code calls this tapping, but "quick stopping" is a better name because you can immediately counter-strafe to stop yourself from decelrating in the first place, and that works because of this same code.) So, the sequence of events in 2.2 and previous looks like this: - gameinput() - If quick stopping, set vx to 0 - gamerender() - Change drawframe depending on vx - gamelogic() - Use drawframe for collision (whyyyyyyyyyyyyyyyyyyyyyyyyyyy) And now (ignoring the intermediate period where the whole loop order was wrong), the sequence of events in 2.3 looks like this: - gamerenderfixed() - Change drawframe depending on vx - gamerender() - gameinput() - If quick stopping, set vx to 0 - gamelogic() - Use drawframe for collision (my mind has become numb to pain) So, this means that all the player movement stuff is completely the same. Except their drawframe is going to be different. Unfortunately, I had overlooked that gameinput() sets vx and that animateentities() (in gamerenderfixed()) checks vx. Although, to be fair, it's a pretty dumb decision to make collision detection be based on the actual sprites' pixels themselves, instead of a hitbox, in the first place, so you'd expect THAT to be the end of the dumb parade. Or maybe you shouldn't, I don't know. So, what's the solution? What I've done here is added duplicates of framedelay, drawframe, and walkingframe, for collision use only. They get updated in gamelogic(), after gameinput(), which is after when vx could be set to 0. I've kept the original framedelay, drawframe, and walkingframe around, to keep the same visuals as closely as possible. However, due to the removal of the input delay, whenever you quick stop, your sprite will be wrong for just 1 frame - because when you let go of the direction key, the game will set your vx to 0 and the logical drawframe will update to reflect that, but the previous frame cannot know in advance that you'll release the key on the next frame, and so the visual drawframe will assume that you keep holding the key. Whereas in 2.2 and below, when you release a direction key, the player's position will only update to reflect that on the next frame, but the current frame can immediately recognize that and update the drawframe now, instead of retconning it later. Basically the visual drawframe assumes that you keep holding the key, and if you don't, then it takes on the value of the collision drawframe anyway, so it's okay. And it's only visual, anyway - the collision drawframe of the next frame (when you release the key) will be the same as the drawframe of the frame you release the key in 2.2 and below. But I really don't care to try and fix this for if you re-enable the input delay because it's minor and it'd be more complicated.
2021-06-16 04:18:58 +02:00
void entityclass::animatehumanoidcollision(const int i)
{
/* For some awful reason, drawframe is used for actual collision.
* And removing the input delay changes collision drawframe
* because vx is checked in animateentities().
* So we need to separate the collision drawframe from the visual drawframe
* and update it separately in gamelogic.
* Hence this function.
*/
entclass* entity;
if (!INBOUNDS_VEC(i, entities))
{
puts("animatehumanoidcollision() out-of-bounds!");
return;
}
entity = &entities[i];
if (!entity->ishumanoid())
{
return;
}
if (entity->statedelay > 0)
{
return;
}
--entity->collisionframedelay;
if (entity->dir == 1)
{
entity->collisiondrawframe = entity->tile;
}
else
{
entity->collisiondrawframe = entity->tile + 3;
}
if (entity->visualonground > 0 || entity->visualonroof > 0)
{
if (entity->vx > 0.0f || entity->vx < -0.0f)
{
/* Walking */
if (entity->collisionframedelay <= 1)
{
entity->collisionframedelay = 4;
++entity->collisionwalkingframe;
}
if (entity->collisionwalkingframe >= 2)
{
entity->collisionwalkingframe = 0;
}
entity->collisiondrawframe += entity->collisionwalkingframe + 1;
}
if (entity->visualonroof > 0)
{
entity->collisiondrawframe += 6;
}
}
else
{
++entity->collisiondrawframe;
if (entity->type == 0 && game.gravitycontrol == 1)
{
entity->collisiondrawframe += 6;
}
}
/* deathseq shouldn't matter, but handling it anyway just in case */
if (game.deathseq > -1)
{
entity->collisiondrawframe = 13;
if (entity->dir == 1)
{
entity->collisiondrawframe = 12;
}
if ((entity->type == 0 && game.gravitycontrol == 1)
|| (entity->type != 0 && entity->rule == 7))
{
entity->collisiondrawframe += 2;
}
}
entity->framedelay = entity->collisionframedelay;
entity->drawframe = entity->collisiondrawframe;
entity->walkingframe = entity->collisionwalkingframe;
}
int entityclass::getcompanion(void)
2020-01-01 21:29:24 +01:00
{
//Returns the index of the companion with rule t
for (size_t i = 0; i < entities.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(entities[i].rule==6 || entities[i].rule==7)
{
return i;
}
}
return -1;
}
int entityclass::getplayer(void)
2020-01-01 21:29:24 +01:00
{
//Returns the index of the first player entity
for (size_t i = 0; i < entities.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(entities[i].type==0)
{
return i;
}
}
return -1;
}
int entityclass::getscm(void)
2020-01-01 21:29:24 +01:00
{
//Returns the supercrewmate
for (size_t i = 0; i < entities.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(entities[i].type==14)
{
return i;
}
}
return 0;
}
int entityclass::getlineat( int t )
{
//Get the entity which is a horizontal line at height t (for SWN game)
for (size_t i = 0; i < entities.size(); i++)
2020-01-01 21:29:24 +01:00
{
if (entities[i].size == 5)
{
if (entities[i].yp == t)
{
return i;
}
}
}
return 0;
}
int entityclass::getcrewman( int t )
{
//Returns the index of the crewman with colour index given by t
if (t == 0) t = 0;
if (t == 1) t = 20;
if (t == 2) t = 14;
if (t == 3) t = 15;
if (t == 4) t = 13;
if (t == 5) t = 16;
for (size_t i = 0; i < entities.size(); i++)
2020-01-01 21:29:24 +01:00
{
if ((entities[i].type == 12 || entities[i].type == 14)
&& (entities[i].rule == 6 || entities[i].rule == 7))
2020-01-01 21:29:24 +01:00
{
if(entities[i].colour==t)
{
return i;
}
}
}
return 0;
}
int entityclass::getcustomcrewman( int t )
{
//Returns the index of the crewman with colour index given by t
if (t == 0) t = 0;
if (t == 1) t = 20;
if (t == 2) t = 14;
if (t == 3) t = 15;
if (t == 4) t = 13;
if (t == 5) t = 16;
for (size_t i = 0; i < entities.size(); i++)
2020-01-01 21:29:24 +01:00
{
if (entities[i].type == 55)
{
if(entities[i].colour==t)
{
return i;
}
}
}
return 0;
}
int entityclass::getteleporter(void)
2020-01-01 21:29:24 +01:00
{
for (size_t i = 0; i < entities.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(entities[i].type==100)
2020-01-01 21:29:24 +01:00
{
return i;
}
}
return -1;
}
bool entityclass::entitycollide( int a, int b )
{
if (!INBOUNDS_VEC(a, entities) || !INBOUNDS_VEC(b, entities))
{
puts("entitycollide() out-of-bounds!");
return false;
}
2020-01-01 21:29:24 +01:00
//Do entities a and b collide?
SDL_Rect temprect;
temprect.x = entities[a].xp + entities[a].cx;
temprect.y = entities[a].yp + entities[a].cy;
temprect.w = entities[a].w;
temprect.h = entities[a].h;
SDL_Rect temprect2;
temprect2.x = entities[b].xp + entities[b].cx;
temprect2.y = entities[b].yp + entities[b].cy;
temprect2.w = entities[b].w;
temprect2.h = entities[b].h;
2020-01-01 21:29:24 +01:00
if (help.intersects(temprect, temprect2)) return true;
2020-01-01 21:29:24 +01:00
return false;
}
bool entityclass::checkdamage(bool scm /*= false*/)
2020-01-01 21:29:24 +01:00
{
//Returns true if player (or supercrewmate) collides with a damagepoint
for(size_t i=0; i < entities.size(); i++)
2020-01-01 21:29:24 +01:00
{
if((scm && entities[i].type == 14) || (!scm && entities[i].rule == 0))
2020-01-01 21:29:24 +01:00
{
SDL_Rect temprect;
temprect.x = entities[i].xp + entities[i].cx;
temprect.y = entities[i].yp + entities[i].cy;
temprect.w = entities[i].w;
temprect.h = entities[i].h;
2020-01-01 21:29:24 +01:00
for (size_t j=0; j<blocks.size(); j++)
2020-01-01 21:29:24 +01:00
{
if (blocks[j].type == DAMAGE && help.intersects(blocks[j].rect, temprect))
2020-01-01 21:29:24 +01:00
{
return true;
2020-01-01 21:29:24 +01:00
}
}
}
}
return false;
}
int entityclass::checktrigger(int* block_idx)
2020-01-01 21:29:24 +01:00
{
//Returns an int player entity (rule 0) collides with a trigger
//Also returns the index of the block
*block_idx = -1;
for(size_t i=0; i < entities.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(entities[i].rule==0)
{
SDL_Rect temprect;
temprect.x = entities[i].xp + entities[i].cx;
temprect.y = entities[i].yp + entities[i].cy;
temprect.w = entities[i].w;
temprect.h = entities[i].h;
2020-01-01 21:29:24 +01:00
for (size_t j=0; j<blocks.size(); j++)
2020-01-01 21:29:24 +01:00
{
if (blocks[j].type == TRIGGER && help.intersects(blocks[j].rect, temprect))
2020-01-01 21:29:24 +01:00
{
*block_idx = j;
return blocks[j].trigger;
2020-01-01 21:29:24 +01:00
}
}
}
}
return -1;
}
int entityclass::checkactivity(void)
2020-01-01 21:29:24 +01:00
{
//Returns an int player entity (rule 0) collides with an activity
for(size_t i=0; i < entities.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(entities[i].rule==0)
{
SDL_Rect temprect;
temprect.x = entities[i].xp + entities[i].cx;
temprect.y = entities[i].yp + entities[i].cy;
temprect.w = entities[i].w;
temprect.h = entities[i].h;
2020-01-01 21:29:24 +01:00
for (size_t j=0; j<blocks.size(); j++)
2020-01-01 21:29:24 +01:00
{
if (blocks[j].type == ACTIVITY && help.intersects(blocks[j].rect, temprect))
2020-01-01 21:29:24 +01:00
{
return j;
2020-01-01 21:29:24 +01:00
}
}
}
}
return -1;
}
int entityclass::getgridpoint( int t )
{
t = (t - (t % 8)) / 8;
return t;
}
bool entityclass::checkplatform(const SDL_Rect& temprect, int* px, int* py)
2020-01-01 21:29:24 +01:00
{
//Return true if rectset intersects a moving platform, setups px & py to the platform x & y
for (size_t i = 0; i < blocks.size(); i++)
2020-01-01 21:29:24 +01:00
{
if (blocks[i].type == BLOCK && help.intersects(blocks[i].rect, temprect))
2020-01-01 21:29:24 +01:00
{
*px = blocks[i].xp;
*py = blocks[i].yp;
return true;
2020-01-01 21:29:24 +01:00
}
}
return false;
}
bool entityclass::checkblocks(const SDL_Rect& temprect, const float dx, const float dy, const float dr, const bool skipdirblocks)
2020-01-01 21:29:24 +01:00
{
for (size_t i = 0; i < blocks.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(!skipdirblocks && blocks[i].type == DIRECTIONAL)
2020-01-01 21:29:24 +01:00
{
if (dy > 0 && blocks[i].trigger == 0) if (help.intersects(blocks[i].rect, temprect)) return true;
if (dy <= 0 && blocks[i].trigger == 1) if (help.intersects(blocks[i].rect, temprect)) return true;
if (dx > 0 && blocks[i].trigger == 2) if (help.intersects(blocks[i].rect, temprect)) return true;
if (dx <= 0 && blocks[i].trigger == 3) if (help.intersects(blocks[i].rect, temprect)) return true;
}
if (blocks[i].type == BLOCK && help.intersects(blocks[i].rect, temprect))
{
return true;
}
if (blocks[i].type == SAFE && (dr)==1 && help.intersects(blocks[i].rect, temprect))
{
return true;
2020-01-01 21:29:24 +01:00
}
}
return false;
}
bool entityclass::checkwall(const SDL_Rect& temprect, const float dx, const float dy, const float dr, const bool skipblocks, const bool skipdirblocks)
2020-01-01 21:29:24 +01:00
{
//Returns true if entity setup in temprect collides with a wall
if(skipblocks)
{
if (checkblocks(temprect, dx, dy, dr, skipdirblocks)) return true;
2020-01-01 21:29:24 +01:00
}
int tempx = getgridpoint(temprect.x);
int tempy = getgridpoint(temprect.y);
int tempw = getgridpoint(temprect.x + temprect.w - 1);
int temph = getgridpoint(temprect.y + temprect.h - 1);
2020-01-01 21:29:24 +01:00
if (map.collide(tempx, tempy)) return true;
if (map.collide(tempw, tempy)) return true;
if (map.collide(tempx, temph)) return true;
if (map.collide(tempw, temph)) return true;
if (temprect.h >= 12)
{
int tpy1 = getgridpoint(temprect.y + 6);
2020-01-01 21:29:24 +01:00
if (map.collide(tempx, tpy1)) return true;
if (map.collide(tempw, tpy1)) return true;
if (temprect.h >= 18)
{
tpy1 = getgridpoint(temprect.y + 12);
if (map.collide(tempx, tpy1)) return true;
if (map.collide(tempw, tpy1)) return true;
if (temprect.h >= 24)
{
tpy1 = getgridpoint(temprect.y + 18);
if (map.collide(tempx, tpy1)) return true;
if (map.collide(tempw, tpy1)) return true;
}
}
}
if (temprect.w >= 12)
{
int tpx1 = getgridpoint(temprect.x + 6);
2020-01-01 21:29:24 +01:00
if (map.collide(tpx1, tempy)) return true;
if (map.collide(tpx1, temph)) return true;
}
return false;
}
bool entityclass::checkwall(const SDL_Rect& temprect)
{
// Same as above but use default arguments for blocks
return checkwall(temprect, 0, 0, 0, true, false);
}
float entityclass::hplatformat(const int px, const int py)
2020-01-01 21:29:24 +01:00
{
//Returns first entity of horizontal platform at (px, py), -1000 otherwise.
for (size_t i = 0; i < entities.size(); i++)
2020-01-01 21:29:24 +01:00
{
if (entities[i].rule == 2 && entities[i].behave >= 2
&& entities[i].xp == px && entities[i].yp == py)
2020-01-01 21:29:24 +01:00
{
if (entities[i].behave == 8) //threadmill!
2020-01-01 21:29:24 +01:00
{
return entities[i].para;
}
else if(entities[i].behave == 9) //threadmill!
{
return -entities[i].para;
}
else
{
return entities[i].vx;
2020-01-01 21:29:24 +01:00
}
}
}
return -1000;
}
int entityclass::yline( int a, int b )
{
if (a < b) return -1;
return 1;
}
bool entityclass::entityhlinecollide( int t, int l )
{
if (!INBOUNDS_VEC(t, entities) || !INBOUNDS_VEC(l, entities))
{
puts("entityhlinecollide() out-of-bounds!");
return false;
}
2020-01-01 21:29:24 +01:00
//Returns true is entity t collided with the horizontal line l.
if(entities[t].xp + entities[t].cx+entities[t].w>=entities[l].xp)
{
if(entities[t].xp + entities[t].cx<=entities[l].xp+entities[l].w)
{
int linetemp = 0;
2020-01-01 21:29:24 +01:00
linetemp += yline(entities[t].yp, entities[l].yp);
linetemp += yline(entities[t].yp + entities[t].h, entities[l].yp);
linetemp += yline(entities[t].oldyp, entities[l].yp);
linetemp += yline(entities[t].oldyp + entities[t].h, entities[l].yp);
if (linetemp > -4 && linetemp < 4) return true;
return false;
}
}
return false;
}
bool entityclass::entityvlinecollide( int t, int l )
{
if (!INBOUNDS_VEC(t, entities) || !INBOUNDS_VEC(l, entities))
{
puts("entityvlinecollide() out-of-bounds!");
return false;
}
2020-01-01 21:29:24 +01:00
//Returns true is entity t collided with the vertical line l.
if(entities[t].yp + entities[t].cy+entities[t].h>=entities[l].yp
&& entities[t].yp + entities[t].cy<=entities[l].yp+entities[l].h)
2020-01-01 21:29:24 +01:00
{
int linetemp = 0;
2020-01-01 21:29:24 +01:00
linetemp += yline(entities[t].xp + entities[t].cx+1, entities[l].xp);
linetemp += yline(entities[t].xp + entities[t].cx+1 + entities[t].w, entities[l].xp);
linetemp += yline(entities[t].oldxp + entities[t].cx+1, entities[l].xp);
linetemp += yline(entities[t].oldxp + entities[t].cx+1 + entities[t].w, entities[l].xp);
2020-01-01 21:29:24 +01:00
if (linetemp > -4 && linetemp < 4) return true;
return false;
2020-01-01 21:29:24 +01:00
}
return false;
}
bool entityclass::entitywarphlinecollide(int t, int l) {
if (!INBOUNDS_VEC(t, entities) || !INBOUNDS_VEC(l, entities))
{
puts("entitywarphlinecollide() out-of-bounds!");
return false;
}
//Returns true is entity t collided with the horizontal line l.
if(entities[t].xp + entities[t].cx+entities[t].w>=entities[l].xp
&&entities[t].xp + entities[t].cx<=entities[l].xp+entities[l].w){
int linetemp = 0;
if (entities[l].yp < 120) {
//Top line
if (entities[t].vy < 0) {
if (entities[t].yp < entities[l].yp + 10) linetemp++;
if (entities[t].yp + entities[t].h < entities[l].yp + 10) linetemp++;
if (entities[t].oldyp < entities[l].yp + 10) linetemp++;
if (entities[t].oldyp + entities[t].h < entities[l].yp + 10) linetemp++;
}
if (linetemp > 0) return true;
return false;
}else {
//Bottom line
if (entities[t].vy > 0) {
if (entities[t].yp > entities[l].yp - 10) linetemp++;
if (entities[t].yp + entities[t].h > entities[l].yp - 10) linetemp++;
if (entities[t].oldyp > entities[l].yp - 10) linetemp++;
if (entities[t].oldyp + entities[t].h > entities[l].yp - 10) linetemp++;
}
if (linetemp > 0) return true;
return false;
}
}
return false;
2020-01-01 21:29:24 +01:00
}
2020-01-01 21:29:24 +01:00
bool entityclass::entitywarpvlinecollide(int t, int l) {
if (!INBOUNDS_VEC(t, entities) || !INBOUNDS_VEC(l, entities))
{
puts("entitywarpvlinecollide() out-of-bounds!");
return false;
}
//Returns true is entity t collided with the vertical warp line l.
if(entities[t].yp + entities[t].cy+entities[t].h>=entities[l].yp
&& entities[t].yp + entities[t].cy <= entities[l].yp + entities[l].h) {
int linetemp = 0;
if (entities[l].xp < 160) {
//Left hand line
if (entities[t].xp + entities[t].cx + 1 < entities[l].xp + 10) linetemp++;
if (entities[t].xp + entities[t].cx+1 + entities[t].w < entities[l].xp + 10) linetemp++;
if (entities[t].oldxp + entities[t].cx + 1 < entities[l].xp + 10) linetemp++;
if (entities[t].oldxp + entities[t].cx + 1 + entities[t].w < entities[l].xp + 10) linetemp++;
if (linetemp > 0) return true;
return false;
}else {
//Right hand line
if (entities[t].xp + entities[t].cx + 1 > entities[l].xp - 10) linetemp++;
if (entities[t].xp + entities[t].cx+1 + entities[t].w > entities[l].xp - 10) linetemp++;
if (entities[t].oldxp + entities[t].cx + 1 > entities[l].xp - 10) linetemp++;
if (entities[t].oldxp + entities[t].cx + 1 + entities[t].w > entities[l].xp - 10) linetemp++;
if (linetemp > 0) return true;
return false;
}
}
return false;
2020-01-01 21:29:24 +01:00
}
float entityclass::entitycollideplatformroof( int t )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(t, entities))
{
puts("entitycollideplatformroof() out-of-bounds!");
return -1000;
}
SDL_Rect temprect;
temprect.x = entities[t].xp + entities[t].cx;
temprect.y = entities[t].yp + entities[t].cy -1;
temprect.w = entities[t].w;
temprect.h = entities[t].h;
2020-01-01 21:29:24 +01:00
int px = 0, py = 0;
if (checkplatform(temprect, &px, &py))
2020-01-01 21:29:24 +01:00
{
//px and py now contain an x y coordinate for a platform, find it
return hplatformat(px, py);
2020-01-01 21:29:24 +01:00
}
return -1000;
}
float entityclass::entitycollideplatformfloor( int t )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(t, entities))
{
puts("entitycollideplatformfloor() out-of-bounds!");
return -1000;
}
SDL_Rect temprect;
temprect.x = entities[t].xp + entities[t].cx;
temprect.y = entities[t].yp + entities[t].cy + 1;
temprect.w = entities[t].w;
temprect.h = entities[t].h;
2020-01-01 21:29:24 +01:00
int px = 0, py = 0;
if (checkplatform(temprect, &px, &py))
2020-01-01 21:29:24 +01:00
{
//px and py now contain an x y coordinate for a platform, find it
return hplatformat(px, py);
2020-01-01 21:29:24 +01:00
}
return -1000;
}
bool entityclass::entitycollidefloor( int t )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(t, entities))
{
puts("entitycollidefloor() out-of-bounds!");
return false;
}
SDL_Rect temprect;
temprect.x = entities[t].xp + entities[t].cx;
temprect.y = entities[t].yp + entities[t].cy + 1;
temprect.w = entities[t].w;
temprect.h = entities[t].h;
2020-01-01 21:29:24 +01:00
if (checkwall(temprect)) return true;
2020-01-01 21:29:24 +01:00
return false;
}
bool entityclass::entitycollideroof( int t )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(t, entities))
{
puts("entitycollideroof() out-of-bounds!");
return false;
}
SDL_Rect temprect;
temprect.x = entities[t].xp + entities[t].cx;
temprect.y = entities[t].yp + entities[t].cy - 1;
temprect.w = entities[t].w;
temprect.h = entities[t].h;
2020-01-01 21:29:24 +01:00
if (checkwall(temprect)) return true;
2020-01-01 21:29:24 +01:00
return false;
}
bool entityclass::testwallsx( int t, int tx, int ty, const bool skipdirblocks )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(t, entities))
{
puts("testwallsx() out-of-bounds!");
return false;
}
SDL_Rect temprect;
temprect.x = tx + entities[t].cx;
temprect.y = ty + entities[t].cy;
temprect.w = entities[t].w;
temprect.h = entities[t].h;
2020-01-01 21:29:24 +01:00
bool skipblocks = entities[t].rule < 2 || entities[t].type == 14;
float dx = 0;
float dy = 0;
2020-01-01 21:29:24 +01:00
if (entities[t].rule == 0) dx = entities[t].vx;
float dr = entities[t].rule;
2020-01-01 21:29:24 +01:00
//Ok, now we check walls
if (checkwall(temprect, dx, dy, dr, skipblocks, skipdirblocks))
2020-01-01 21:29:24 +01:00
{
if (entities[t].vx > 1.0f)
{
entities[t].vx--;
entities[t].newxp = entities[t].xp + entities[t].vx;
return testwallsx(t, entities[t].newxp, entities[t].yp, skipdirblocks);
2020-01-01 21:29:24 +01:00
}
else if (entities[t].vx < -1.0f)
{
entities[t].vx++;
entities[t].newxp = entities[t].xp + entities[t].vx;
return testwallsx(t, entities[t].newxp, entities[t].yp, skipdirblocks);
2020-01-01 21:29:24 +01:00
}
else
{
entities[t].vx=0;
return false;
}
}
return true;
}
bool entityclass::testwallsy( int t, float tx, float ty )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(t, entities))
{
puts("testwallsy() out-of-bounds!");
return false;
}
SDL_Rect temprect;
temprect.x = static_cast<int>(tx) + entities[t].cx;
temprect.y = static_cast<int>(ty) + entities[t].cy;
temprect.w = entities[t].w;
temprect.h = entities[t].h;
2020-01-01 21:29:24 +01:00
bool skipblocks = entities[t].rule < 2 || entities[t].type == 14;
2020-01-01 21:29:24 +01:00
float dx = 0;
float dy = 0;
2020-01-01 21:29:24 +01:00
if (entities[t].rule == 0) dy = entities[t].vy;
float dr = entities[t].rule;
2020-01-01 21:29:24 +01:00
//Ok, now we check walls
if (checkwall(temprect, dx, dy, dr, skipblocks, false))
2020-01-01 21:29:24 +01:00
{
if (entities[t].vy > 1)
{
entities[t].vy--;
entities[t].newyp = int(entities[t].yp + entities[t].vy);
return testwallsy(t, entities[t].xp, entities[t].newyp);
2020-01-01 21:29:24 +01:00
}
else if (entities[t].vy < -1)
{
entities[t].vy++;
entities[t].newyp = int(entities[t].yp + entities[t].vy);
return testwallsy(t, entities[t].xp, entities[t].newyp);
2020-01-01 21:29:24 +01:00
}
else
{
entities[t].vy=0;
return false;
}
}
return true;
}
void entityclass::applyfriction( int t, float xrate, float yrate )
{
if (!INBOUNDS_VEC(t, entities))
{
puts("applyfriction() out-of-bounds!");
return;
}
2020-01-01 21:29:24 +01:00
if (entities[t].vx > 0.00f) entities[t].vx -= xrate;
if (entities[t].vx < 0.00f) entities[t].vx += xrate;
if (entities[t].vy > 0.00f) entities[t].vy -= yrate;
if (entities[t].vy < 0.00f) entities[t].vy += yrate;
if (entities[t].vy > 10.00f) entities[t].vy = 10.0f;
if (entities[t].vy < -10.00f) entities[t].vy = -10.0f;
if (entities[t].vx > 6.00f) entities[t].vx = 6.0f;
if (entities[t].vx < -6.00f) entities[t].vx = -6.0f;
if (SDL_fabsf(entities[t].vx) < xrate) entities[t].vx = 0.0f;
if (SDL_fabsf(entities[t].vy) < yrate) entities[t].vy = 0.0f;
2020-01-01 21:29:24 +01:00
}
void entityclass::updateentitylogic( int t )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(t, entities))
{
puts("updateentitylogic() out-of-bounds!");
return;
}
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
entities[t].oldxp = entities[t].xp;
entities[t].oldyp = entities[t].yp;
2020-01-01 21:29:24 +01:00
entities[t].vx = entities[t].vx + entities[t].ax;
entities[t].vy = entities[t].vy + entities[t].ay;
entities[t].ax = 0;
if (entities[t].gravity)
{
if (entities[t].rule == 0)
{
if(game.gravitycontrol==0)
{
entities[t].ay = 3;
}
else
{
entities[t].ay = -3;
}
}
else if (entities[t].rule == 7)
{
entities[t].ay = -3;
}
else
{
entities[t].ay = 3;
}
applyfriction(t, game.inertia, 0.25f);
}
entities[t].newxp = entities[t].xp + entities[t].vx;
entities[t].newyp = entities[t].yp + entities[t].vy;
}
void entityclass::entitymapcollision( int t )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(t, entities))
{
puts("entitymapcollision() out-of-bounds!");
return;
}
if (testwallsx(t, entities[t].newxp, entities[t].yp, false))
2020-01-01 21:29:24 +01:00
{
entities[t].xp = entities[t].newxp;
}
else
{
if (entities[t].onwall > 0) entities[t].state = entities[t].onwall;
if (entities[t].onxwall > 0) entities[t].state = entities[t].onxwall;
}
if (testwallsy(t, entities[t].xp, entities[t].newyp))
2020-01-01 21:29:24 +01:00
{
entities[t].yp = entities[t].newyp;
}
else
{
if (entities[t].onwall > 0) entities[t].state = entities[t].onwall;
if (entities[t].onywall > 0) entities[t].state = entities[t].onywall;
}
}
void entityclass::movingplatformfix( int t, int j )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(t, entities) || !INBOUNDS_VEC(j, entities))
{
puts("movingplatformfix() out-of-bounds!");
return;
}
//If this intersects the entity, then we move them along it
if (entitycollide(t, j))
2020-01-01 21:29:24 +01:00
{
//ok, bollox, let's make sure
entities[j].yp = entities[j].yp + int(entities[j].vy);
if (entitycollide(t, j))
{
entities[j].yp = entities[j].yp - int(entities[j].vy);
entities[j].vy = entities[t].vy;
entities[j].newyp = entities[j].yp + int(entities[j].vy);
if (testwallsy(j, entities[j].xp, entities[j].newyp))
2020-01-01 21:29:24 +01:00
{
if (entities[t].vy > 0)
2020-01-01 21:29:24 +01:00
{
entities[j].yp = entities[t].yp + entities[t].h;
entities[j].vy = 0;
entities[j].onroof = 2;
entities[j].visualonroof = 1;
2020-01-01 21:29:24 +01:00
}
else
{
entities[j].yp = entities[t].yp - entities[j].h-entities[j].cy;
entities[j].vy = 0;
entities[j].onground = 2;
entities[j].visualonground = 1;
2020-01-01 21:29:24 +01:00
}
}
else
{
entities[t].state = entities[t].onwall;
}
}
}
}
void entityclass::customwarplinecheck(int i) {
if (!INBOUNDS_VEC(i, entities))
{
puts("customwarplinecheck() out-of-bounds!");
return;
}
//Turns on obj.customwarpmodevon and obj.customwarpmodehon if player collides
//with warp lines
//We test entity to entity
for (int j = 0; j < (int) entities.size(); j++) {
if (i != j) {
if (entities[i].rule == 0 && entities[j].rule == 5 //Player vs vertical line!
&& (entities[j].type == 51 || entities[j].type == 52)
&& entitywarpvlinecollide(i, j)) {
customwarpmodevon = true;
}
if (entities[i].rule == 0 && entities[j].rule == 7 //Player vs horizontal WARP line
&& (entities[j].type == 53 || entities[j].type == 54)
&& entitywarphlinecollide(i, j)) {
customwarpmodehon = true;
}
}
}
2020-01-01 21:29:24 +01:00
}
void entityclass::entitycollisioncheck(void)
2020-01-01 21:29:24 +01:00
{
for (size_t i = 0; i < entities.size(); i++)
2020-01-01 21:29:24 +01:00
{
bool player = entities[i].rule == 0;
bool scm = game.supercrewmate && entities[i].type == 14;
if (!player && !scm)
{
continue;
}
//We test entity to entity
for (size_t j = 0; j < entities.size(); j++)
2020-01-01 21:29:24 +01:00
{
if (i == j)
2020-01-01 21:29:24 +01:00
{
continue;
2020-01-01 21:29:24 +01:00
}
collisioncheck(i, j, scm);
2020-01-01 21:29:24 +01:00
}
}
//can't have the player being stuck...
stuckprevention(getplayer());
2020-01-01 21:29:24 +01:00
//Can't have the supercrewmate getting stuck either!
if (game.supercrewmate)
{
stuckprevention(getscm());
2020-01-01 21:29:24 +01:00
}
//Is the player colliding with any damageblocks?
if (checkdamage() && !map.invincibility)
{
//usual player dead stuff
game.deathseq = 30;
}
//how about the supercrewmate?
if (game.supercrewmate)
{
if (checkdamage(true) && !map.invincibility)
2020-01-01 21:29:24 +01:00
{
//usual player dead stuff
game.scmhurt = true;
game.deathseq = 30;
}
}
// WARNING: If updating this code, don't forget to update Map.cpp mapclass::twoframedelayfix()
int block_idx = -1;
int activetrigger = checktrigger(&block_idx);
if (activetrigger > -1 && INBOUNDS_VEC(block_idx, blocks))
2020-01-01 21:29:24 +01:00
{
// Load the block's script if its gamestate is out of range
if (blocks[block_idx].script != "" && (activetrigger < 300 || activetrigger > 336))
{
game.startscript = true;
game.newscript = blocks[block_idx].script;
removetrigger(activetrigger);
game.state = 0;
}
else
{
game.state = activetrigger;
}
2020-01-01 21:29:24 +01:00
game.statedelay = 0;
}
}
void entityclass::collisioncheck(int i, int j, bool scm /*= false*/)
{
if (!INBOUNDS_VEC(i, entities) || !INBOUNDS_VEC(j, entities))
{
puts("collisioncheck() out-of-bounds!");
return;
}
switch (entities[j].rule)
{
case 1:
if (!entities[j].harmful)
{
break;
}
//person i hits enemy or enemy bullet j
if (entitycollide(i, j) && !map.invincibility)
{
if (entities[i].size == 0 && (entities[j].size == 0 || entities[j].size == 12))
{
//They're both sprites, so do a per pixel collision
point colpoint1;
colpoint1.x = entities[i].xp;
colpoint1.y = entities[i].yp;
point colpoint2;
colpoint2.x = entities[j].xp;
colpoint2.y = entities[j].yp;
Fix regression: quick stopping changing drawframe This fixes a regression that desyncs my Nova TAS after re-removing the 1-frame input delay. Quick stopping is simply holding left/right but for less than 5 frames. Viridian doesn't decelerate when you let go and they immediately stop in place. (The code calls this tapping, but "quick stopping" is a better name because you can immediately counter-strafe to stop yourself from decelrating in the first place, and that works because of this same code.) So, the sequence of events in 2.2 and previous looks like this: - gameinput() - If quick stopping, set vx to 0 - gamerender() - Change drawframe depending on vx - gamelogic() - Use drawframe for collision (whyyyyyyyyyyyyyyyyyyyyyyyyyyy) And now (ignoring the intermediate period where the whole loop order was wrong), the sequence of events in 2.3 looks like this: - gamerenderfixed() - Change drawframe depending on vx - gamerender() - gameinput() - If quick stopping, set vx to 0 - gamelogic() - Use drawframe for collision (my mind has become numb to pain) So, this means that all the player movement stuff is completely the same. Except their drawframe is going to be different. Unfortunately, I had overlooked that gameinput() sets vx and that animateentities() (in gamerenderfixed()) checks vx. Although, to be fair, it's a pretty dumb decision to make collision detection be based on the actual sprites' pixels themselves, instead of a hitbox, in the first place, so you'd expect THAT to be the end of the dumb parade. Or maybe you shouldn't, I don't know. So, what's the solution? What I've done here is added duplicates of framedelay, drawframe, and walkingframe, for collision use only. They get updated in gamelogic(), after gameinput(), which is after when vx could be set to 0. I've kept the original framedelay, drawframe, and walkingframe around, to keep the same visuals as closely as possible. However, due to the removal of the input delay, whenever you quick stop, your sprite will be wrong for just 1 frame - because when you let go of the direction key, the game will set your vx to 0 and the logical drawframe will update to reflect that, but the previous frame cannot know in advance that you'll release the key on the next frame, and so the visual drawframe will assume that you keep holding the key. Whereas in 2.2 and below, when you release a direction key, the player's position will only update to reflect that on the next frame, but the current frame can immediately recognize that and update the drawframe now, instead of retconning it later. Basically the visual drawframe assumes that you keep holding the key, and if you don't, then it takes on the value of the collision drawframe anyway, so it's okay. And it's only visual, anyway - the collision drawframe of the next frame (when you release the key) will be the same as the drawframe of the frame you release the key in 2.2 and below. But I really don't care to try and fix this for if you re-enable the input delay because it's minor and it'd be more complicated.
2021-06-16 04:18:58 +02:00
int drawframe1 = entities[i].collisiondrawframe;
int drawframe2 = entities[j].drawframe;
std::vector<SDL_Surface*>& spritesvec = graphics.flipmode ? graphics.flipsprites : graphics.sprites;
if (INBOUNDS_VEC(drawframe1, spritesvec) && INBOUNDS_VEC(drawframe2, spritesvec)
&& graphics.Hitest(spritesvec[drawframe1],
colpoint1, spritesvec[drawframe2], colpoint2))
{
//Do the collision stuff
game.deathseq = 30;
game.scmhurt = scm;
}
}
else
{
//Ok, then we just assume a normal bounding box collision
game.deathseq = 30;
game.scmhurt = scm;
}
}
break;
case 2: //Moving platforms
if (entities[j].behave >= 8 && entities[j].behave < 10)
{
//We don't want conveyors, moving platforms only
break;
}
Restore platform evaluation order to 2.2 This commit restores the evaluation order of moving platforms and conveyors to be what it was in 2.2. The evaluation order changed in 2.3 after the patchset to improve the handling of the `obj.entities` and `obj.blocks` vectors (#191). By evaluation order, I'm talking about the order in which platforms and conveyors will be evaluated (and thus will take priority) if Viridian stands on both a conveyor or platform at once, and they either have different speeds or are pointing in different directions. Nowhere in the main game is there a place where you can stand on two different conveyors/platforms at once, so this is solely within the territory of custom levels, which is my specialty. So what caused this evaluation order to change? Well, every moving platform and conveyor in the game is actually made up of two objects: an entity, and a block. The entity is the part that moves around, and the block is the part that actually has the collision. But if the entity is the part that moves around, and entities and blocks are in entirely separate vectors, how is the block part going to move along with it? Well, maybe you'd guess some sort of unique ID system, but spend some time digging around the code and you won't find any trace of any (there's no attribute on an entity to store such an ID, for starters). Instead, what the game does is actually remove all blocks that coincide with the exact top-left corner of the entity, and then create a new one. Destroying and creating blocks like this all the time is hugely wasteful, but hey, it worked. So why did the evaluation order change in 2.3? Well, to understand that, you'll need to understand 2.2's `active` system. Instead of having an object be real simply by virtue of it existing, 2.2 had this system where the object was only real if it had its `active` attribute set to true. In other words, you would be looking at a fake object that didn't actually exist if its `active` attribute was false. On the surface, this doesn't seem that bad. But this can lead to "holes" in a given vector of objects. A hole is simply an inactive object neighbored by active objects (or the inactive object could be the first one in the vector, but then have an active object immediately following it). If you have a vector of 3 objects, all of them active, then removing the second one will result in the vector containing an active object, followed by an inactive object, followed by an active one. However, since the switch to more properly use vectors instead of relying on this `active` system, there's no longer any way for holes to exist in a vector. Properly removing an object from a vector will just shift the rest of the objects down, so if we remove the second object after the vector fix, then this will simply move the third object into the slot of where the second object used to be. So, what happens if you destroy a block and then create a new one in the `active` system? Let's say that your `obj.blocks` looks like this, and here I'm denoting each block by writing out its coordinates: [30,60] [70,90] [80,100] and that you want to update the position of the second one, because the entity that that blocks belongs to has been updated. Okay, so, you delete that block, which then makes things look like this: [30,60] [-] [80,100] and then afterwards, you create a new block with the updated position, resulting in this: [30,60] [74,90] [80,100] Since `entityclass::createblock()` will find the first block slot that has a false `active` attribute, it puts the new object in the same slot as the old one. What has been essentially done here is that the slot of the block has basically been reserved for the new block with the new position. Here, the evaluation order of each block will stay the same. But then 2.3 comes along and changes things up. So we start with an `obj.blocks` like this again: [30,60] [70,90] [80,100] and we want to update the second block, like before. So we remove the second block, resulting in this: [30,60] [80,100] It should be obvious that unlike before, where the third block stayed in the third slot, the third block has now been moved to the second slot. But continuing on; we are now going to create the new block with its updated position, resulting in this: [30,60] [80,100] [70,90] At this point, we can see that the evaluation order of these blocks has been changed due to the fact that the third block has now been moved to the slot that was previously the slot of the second block. So what can we do about this? Well, we can basically emulate what VVVVVV did in 2.2, which is disable a block without actually removing it - except I'm not going to reintroduce an `active` attribute or anything. I'll disable the collision of all blocks at a certain position by setting their widths and heights to 0, and then re-enable them later by finding the first block at that same position, updating its position, and re-assigning its width and height again. The former is what `entityclass::nocollisionat()` does; the latter is what `entityclass::moveblockto()` does. The former mimicks turning off the `active` attribute of all blocks sharing a certain top-left corner; the latter mimicks creating a new block - and it will only do this for one block, because `entityclass::createblock()` in 2.2 only looked for the first block with a false `active` attribute. Now, some quirks relied on the previous behavior of destroying and creating blocks, but all of these quirks have been preserved with the way I implemented this fix. The first quirk is that platforms passing through 0,0 will destroy all spike hitboxes, script boxes, activity zones, and one-way hitboxes in the room. The hitboxes of moving platforms, disappearing platforms, 1x1 quicksand, and conveyors will not be affected. This is a consequence of the fact that the former group uses the `x` and `y` of their `rect`, while the latter group uses the `xp` and `yp` attributes. So the `xp` and `yp` of the former are both 0. Meaning, a platform passing through 0,0 destroys them all. Having these separate coordinates seems like an artifact from the Flash days. (And furthermore, there's an unused `x` and `y` attribute on all blocks, making for technically three separate sets of coordinates! This should probably be cleaned up, except for what I'm about to say...) But actually, if you merge both sets of coordinates into one, this lets moving platforms destroy script boxes and activity zones if it passes through the top-left corner of them, which is probably far worse than the destruction being localized to a specific coordinate that would never likely be reached normally. This quirk is preserved just fine without any special-casing, because instead of destroying all blocks at 0,0, they just get disabled, which does the same job. This quirk seems trivial to fix if I made it so that the position of a platform's block was updated instantaneously instead of having one step to disable it and another step to re-enable it, but I aim to preserve as much quirks as possible. The second quirk is that a moving platform passing through the top-left corner of a disappearing platform or 1x1 quicksand will destroy the block of that disappearing platform. This is because, again, when a moving platform updates, it destroys all blocks at its previous position, not just only one block. This is automatically preserved because this commit just disables the block of the disappearing platform instead of removing it. Just like the last one, this quirk seems extremely trivial to fix, and this time by simply making it so `entityclass::nocollisionat()` would have a `break` statement, i.e. only disabling the first block it finds instead of all blocks it finds, but I want to keep all quirks that are possible to keep. The last quirk is that, apparently, in order to prevent pushing the player vertically out of a moving platform if they get inside of one, the game destroys the block of the moving platform. If I had missed this edge case, then the block would've been destroyed, leaving the moving platform with no collision. But I caught it in my testing, so the block gets disabled instead of destroyed. Also, it seems obtuse for those who don't understand why a platform's block gets destroyed or disabled whenever the player collides with it in `entityclass::collisioncheck()`, so I've put up a comment documenting it as well. The different platform evaluation order desyncs my Nova TAS, but after applying this patchset and #504, my TAS syncs fine (save for the different walkingframe from starting immediately on the ground instead of in the air (#502), but that's minor and can be easily fixed). I've attached a test level to the pull request for this commit (#503) to demonstrate that this patchset not only fixes platform evaluation order, but preserves some bugs and quirks with the existing block system. The first room demonstrates the fixed platform evaluation order, by stepping on the conveyors that both point into each other. In 2.2, Viridian will move to the right of the background pillar, but in 2.3, Viridian will move to the left of the pillar. However, after applying this patch, Viridian will now move to the right of the pillar once again. The second room demonstrates that the platform-passing-through-0,0 trick still works (as explained above). The last room demonstrates that platforms passing through the top-left corners of disappearing platforms or 1x1 quicksand will remove the blocks of those entities, causing Viridian to immediately pass through them. This trick is still preserved after my patchset is applied.
2020-10-10 02:09:11 +02:00
if (entitycollide(i, j))
{
//Disable collision temporarily so we don't push the person out!
//Collision will be restored at end of platform update loop in gamelogic
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
disableblockat(entities[j].xp, entities[j].yp);
Restore platform evaluation order to 2.2 This commit restores the evaluation order of moving platforms and conveyors to be what it was in 2.2. The evaluation order changed in 2.3 after the patchset to improve the handling of the `obj.entities` and `obj.blocks` vectors (#191). By evaluation order, I'm talking about the order in which platforms and conveyors will be evaluated (and thus will take priority) if Viridian stands on both a conveyor or platform at once, and they either have different speeds or are pointing in different directions. Nowhere in the main game is there a place where you can stand on two different conveyors/platforms at once, so this is solely within the territory of custom levels, which is my specialty. So what caused this evaluation order to change? Well, every moving platform and conveyor in the game is actually made up of two objects: an entity, and a block. The entity is the part that moves around, and the block is the part that actually has the collision. But if the entity is the part that moves around, and entities and blocks are in entirely separate vectors, how is the block part going to move along with it? Well, maybe you'd guess some sort of unique ID system, but spend some time digging around the code and you won't find any trace of any (there's no attribute on an entity to store such an ID, for starters). Instead, what the game does is actually remove all blocks that coincide with the exact top-left corner of the entity, and then create a new one. Destroying and creating blocks like this all the time is hugely wasteful, but hey, it worked. So why did the evaluation order change in 2.3? Well, to understand that, you'll need to understand 2.2's `active` system. Instead of having an object be real simply by virtue of it existing, 2.2 had this system where the object was only real if it had its `active` attribute set to true. In other words, you would be looking at a fake object that didn't actually exist if its `active` attribute was false. On the surface, this doesn't seem that bad. But this can lead to "holes" in a given vector of objects. A hole is simply an inactive object neighbored by active objects (or the inactive object could be the first one in the vector, but then have an active object immediately following it). If you have a vector of 3 objects, all of them active, then removing the second one will result in the vector containing an active object, followed by an inactive object, followed by an active one. However, since the switch to more properly use vectors instead of relying on this `active` system, there's no longer any way for holes to exist in a vector. Properly removing an object from a vector will just shift the rest of the objects down, so if we remove the second object after the vector fix, then this will simply move the third object into the slot of where the second object used to be. So, what happens if you destroy a block and then create a new one in the `active` system? Let's say that your `obj.blocks` looks like this, and here I'm denoting each block by writing out its coordinates: [30,60] [70,90] [80,100] and that you want to update the position of the second one, because the entity that that blocks belongs to has been updated. Okay, so, you delete that block, which then makes things look like this: [30,60] [-] [80,100] and then afterwards, you create a new block with the updated position, resulting in this: [30,60] [74,90] [80,100] Since `entityclass::createblock()` will find the first block slot that has a false `active` attribute, it puts the new object in the same slot as the old one. What has been essentially done here is that the slot of the block has basically been reserved for the new block with the new position. Here, the evaluation order of each block will stay the same. But then 2.3 comes along and changes things up. So we start with an `obj.blocks` like this again: [30,60] [70,90] [80,100] and we want to update the second block, like before. So we remove the second block, resulting in this: [30,60] [80,100] It should be obvious that unlike before, where the third block stayed in the third slot, the third block has now been moved to the second slot. But continuing on; we are now going to create the new block with its updated position, resulting in this: [30,60] [80,100] [70,90] At this point, we can see that the evaluation order of these blocks has been changed due to the fact that the third block has now been moved to the slot that was previously the slot of the second block. So what can we do about this? Well, we can basically emulate what VVVVVV did in 2.2, which is disable a block without actually removing it - except I'm not going to reintroduce an `active` attribute or anything. I'll disable the collision of all blocks at a certain position by setting their widths and heights to 0, and then re-enable them later by finding the first block at that same position, updating its position, and re-assigning its width and height again. The former is what `entityclass::nocollisionat()` does; the latter is what `entityclass::moveblockto()` does. The former mimicks turning off the `active` attribute of all blocks sharing a certain top-left corner; the latter mimicks creating a new block - and it will only do this for one block, because `entityclass::createblock()` in 2.2 only looked for the first block with a false `active` attribute. Now, some quirks relied on the previous behavior of destroying and creating blocks, but all of these quirks have been preserved with the way I implemented this fix. The first quirk is that platforms passing through 0,0 will destroy all spike hitboxes, script boxes, activity zones, and one-way hitboxes in the room. The hitboxes of moving platforms, disappearing platforms, 1x1 quicksand, and conveyors will not be affected. This is a consequence of the fact that the former group uses the `x` and `y` of their `rect`, while the latter group uses the `xp` and `yp` attributes. So the `xp` and `yp` of the former are both 0. Meaning, a platform passing through 0,0 destroys them all. Having these separate coordinates seems like an artifact from the Flash days. (And furthermore, there's an unused `x` and `y` attribute on all blocks, making for technically three separate sets of coordinates! This should probably be cleaned up, except for what I'm about to say...) But actually, if you merge both sets of coordinates into one, this lets moving platforms destroy script boxes and activity zones if it passes through the top-left corner of them, which is probably far worse than the destruction being localized to a specific coordinate that would never likely be reached normally. This quirk is preserved just fine without any special-casing, because instead of destroying all blocks at 0,0, they just get disabled, which does the same job. This quirk seems trivial to fix if I made it so that the position of a platform's block was updated instantaneously instead of having one step to disable it and another step to re-enable it, but I aim to preserve as much quirks as possible. The second quirk is that a moving platform passing through the top-left corner of a disappearing platform or 1x1 quicksand will destroy the block of that disappearing platform. This is because, again, when a moving platform updates, it destroys all blocks at its previous position, not just only one block. This is automatically preserved because this commit just disables the block of the disappearing platform instead of removing it. Just like the last one, this quirk seems extremely trivial to fix, and this time by simply making it so `entityclass::nocollisionat()` would have a `break` statement, i.e. only disabling the first block it finds instead of all blocks it finds, but I want to keep all quirks that are possible to keep. The last quirk is that, apparently, in order to prevent pushing the player vertically out of a moving platform if they get inside of one, the game destroys the block of the moving platform. If I had missed this edge case, then the block would've been destroyed, leaving the moving platform with no collision. But I caught it in my testing, so the block gets disabled instead of destroyed. Also, it seems obtuse for those who don't understand why a platform's block gets destroyed or disabled whenever the player collides with it in `entityclass::collisioncheck()`, so I've put up a comment documenting it as well. The different platform evaluation order desyncs my Nova TAS, but after applying this patchset and #504, my TAS syncs fine (save for the different walkingframe from starting immediately on the ground instead of in the air (#502), but that's minor and can be easily fixed). I've attached a test level to the pull request for this commit (#503) to demonstrate that this patchset not only fixes platform evaluation order, but preserves some bugs and quirks with the existing block system. The first room demonstrates the fixed platform evaluation order, by stepping on the conveyors that both point into each other. In 2.2, Viridian will move to the right of the background pillar, but in 2.3, Viridian will move to the left of the pillar. However, after applying this patch, Viridian will now move to the right of the pillar once again. The second room demonstrates that the platform-passing-through-0,0 trick still works (as explained above). The last room demonstrates that platforms passing through the top-left corners of disappearing platforms or 1x1 quicksand will remove the blocks of those entities, causing Viridian to immediately pass through them. This trick is still preserved after my patchset is applied.
2020-10-10 02:09:11 +02:00
}
break;
case 3: //Entity to entity
if(entities[j].onentity>0)
{
if (entitycollide(i, j)) entities[j].state = entities[j].onentity;
}
break;
case 4: //Person vs horizontal line!
if(game.deathseq==-1)
{
//Here we compare the person's old position versus his new one versus the line.
//All points either be above or below it. Otherwise, there was a collision this frame.
if (entities[j].onentity > 0)
{
if (entityhlinecollide(i, j))
{
music.playef(8);
game.gravitycontrol = (game.gravitycontrol + 1) % 2;
game.totalflips++;
if (game.gravitycontrol == 0)
{
if (entities[i].vy < 1) entities[i].vy = 1;
}
else
{
if (entities[i].vy > -1) entities[i].vy = -1;
}
entities[j].state = entities[j].onentity;
entities[j].life = 6;
}
}
}
break;
case 5: //Person vs vertical gravity/warp line!
if(game.deathseq==-1)
{
if(entities[j].onentity>0)
{
if (entityvlinecollide(i, j))
{
entities[j].state = entities[j].onentity;
entities[j].life = 4;
}
}
}
break;
case 6: //Person versus crumbly blocks! Special case
if (entities[j].onentity > 0)
{
//ok; only check the actual collision if they're in a close proximity
int temp = entities[i].yp - entities[j].yp;
if (temp > -30 && temp < 30)
{
temp = entities[i].xp - entities[j].xp;
if (temp > -30 && temp < 30)
{
if (entitycollide(i, j)) entities[j].state = entities[j].onentity;
}
}
}
break;
case 7: // Person versus horizontal warp line, pre-2.1
if (game.glitchrunnermode
&& game.deathseq == -1
&& entities[j].onentity > 0
&& entityhlinecollide(i, j))
{
entities[j].state = entities[j].onentity;
}
break;
}
}
void entityclass::stuckprevention(int t)
{
if (!INBOUNDS_VEC(t, entities))
{
puts("stuckprevention() out-of-bounds!");
return;
}
// Can't have this entity (player or supercrewmate) being stuck...
if (!testwallsx(t, entities[t].xp, entities[t].yp, true))
{
// Let's try to get out...
if (game.gravitycontrol == 0)
{
entities[t].yp -= 3;
}
else
{
entities[t].yp += 3;
}
}
}