2020-09-28 04:15:06 +02:00
|
|
|
#define GAME_DEFINITION
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "Game.h"
|
|
|
|
|
2020-07-19 21:43:29 +02:00
|
|
|
#include <sstream>
|
2020-01-01 21:29:24 +01:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2020-07-19 21:03:16 +02:00
|
|
|
#include <tinyxml2.h>
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "editor.h"
|
|
|
|
#include "Entity.h"
|
|
|
|
#include "Enums.h"
|
|
|
|
#include "FileSystemUtils.h"
|
|
|
|
#include "Graphics.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "MakeAndPlay.h"
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "Map.h"
|
|
|
|
#include "Music.h"
|
|
|
|
#include "Network.h"
|
|
|
|
#include "Script.h"
|
|
|
|
#include "UtilityClass.h"
|
2020-09-25 18:09:21 +02:00
|
|
|
#include "XMLUtils.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
// lol, Win32 -flibit
|
|
|
|
#ifdef _WIN32
|
|
|
|
#define strcasecmp stricmp
|
|
|
|
#endif
|
|
|
|
|
|
|
|
bool GetButtonFromString(const char *pText, SDL_GameControllerButton *button)
|
|
|
|
{
|
2020-04-02 22:01:55 +02:00
|
|
|
if (*pText == '0' ||
|
|
|
|
*pText == 'a' ||
|
|
|
|
*pText == 'A')
|
|
|
|
{
|
|
|
|
*button = SDL_CONTROLLER_BUTTON_A;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (strcmp(pText, "1") == 0 ||
|
|
|
|
*pText == 'b' ||
|
|
|
|
*pText == 'B')
|
|
|
|
{
|
|
|
|
*button = SDL_CONTROLLER_BUTTON_B;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (*pText == '2' ||
|
|
|
|
*pText == 'x' ||
|
|
|
|
*pText == 'X')
|
|
|
|
{
|
|
|
|
*button = SDL_CONTROLLER_BUTTON_X;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (*pText == '3' ||
|
|
|
|
*pText == 'y' ||
|
|
|
|
*pText == 'Y')
|
|
|
|
{
|
|
|
|
*button = SDL_CONTROLLER_BUTTON_Y;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (*pText == '4' ||
|
|
|
|
strcasecmp(pText, "BACK") == 0)
|
|
|
|
{
|
|
|
|
*button = SDL_CONTROLLER_BUTTON_BACK;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (*pText == '5' ||
|
|
|
|
strcasecmp(pText, "GUIDE") == 0)
|
|
|
|
{
|
|
|
|
*button = SDL_CONTROLLER_BUTTON_GUIDE;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (*pText == '6' ||
|
|
|
|
strcasecmp(pText, "START") == 0)
|
|
|
|
{
|
|
|
|
*button = SDL_CONTROLLER_BUTTON_START;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (*pText == '7' ||
|
|
|
|
strcasecmp(pText, "LS") == 0)
|
|
|
|
{
|
|
|
|
*button = SDL_CONTROLLER_BUTTON_LEFTSTICK;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (*pText == '8' ||
|
|
|
|
strcasecmp(pText, "RS") == 0)
|
|
|
|
{
|
|
|
|
*button = SDL_CONTROLLER_BUTTON_RIGHTSTICK;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (*pText == '9' ||
|
|
|
|
strcasecmp(pText, "LB") == 0)
|
|
|
|
{
|
|
|
|
*button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (strcmp(pText, "10") == 0 ||
|
|
|
|
strcasecmp(pText, "RB") == 0)
|
|
|
|
{
|
|
|
|
*button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
Allow using help/graphics/music/game/key/map/obj everywhere
This commit makes `help`, `graphics`, `music`, `game`, `key`, `map`, and
`obj` essentially static global objects that can be used everywhere.
This is useful in case we ever need to add a new function in the future,
so we don't have to bother with passing a new argument in which means we
have to pass a new argument in to the function that calls that function
which means having to pass a new argument into the function that calls
THAT function, etc. which is a real headache when working on fan mods of
the source code.
Note that this changes NONE of the existing function signatures, it
merely just makes those variables accessible everywhere in the same way
`script` and `ed` are.
Also note that some classes had to be initialized after the filesystem
was initialized, but C++ would keep initializing them before the
filesystem got initialized, because I *had* to put them at the top of
`main.cpp`, or else they wouldn't be global variables.
The only way to work around this was to use entityclass's initialization
style (which I'm pretty sure entityclass of all things doesn't need to
be initialized this way), where you actually initialize the class in an
`init()` function, and so then you do `graphics.init()` after the
filesystem initialization, AFTER doing `Graphics graphics` up at the
top.
I've had to do this for `graphics` (but only because its child
GraphicsResources `grphx` needs to be initialized this way), `music`,
and `game`. I don't think this will affect anything. Other than that,
`help`, `key`, and `map` are still using the C++-intended method of
having ClassName::ClassName() functions.
2020-01-29 08:35:03 +01:00
|
|
|
void Game::init(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-25 23:49:54 +02:00
|
|
|
roomx = 0;
|
|
|
|
roomy = 0;
|
|
|
|
prevroomx = 0;
|
|
|
|
prevroomy = 0;
|
|
|
|
saverx = 0;
|
|
|
|
savery = 0;
|
|
|
|
|
Allow using help/graphics/music/game/key/map/obj everywhere
This commit makes `help`, `graphics`, `music`, `game`, `key`, `map`, and
`obj` essentially static global objects that can be used everywhere.
This is useful in case we ever need to add a new function in the future,
so we don't have to bother with passing a new argument in which means we
have to pass a new argument in to the function that calls that function
which means having to pass a new argument into the function that calls
THAT function, etc. which is a real headache when working on fan mods of
the source code.
Note that this changes NONE of the existing function signatures, it
merely just makes those variables accessible everywhere in the same way
`script` and `ed` are.
Also note that some classes had to be initialized after the filesystem
was initialized, but C++ would keep initializing them before the
filesystem got initialized, because I *had* to put them at the top of
`main.cpp`, or else they wouldn't be global variables.
The only way to work around this was to use entityclass's initialization
style (which I'm pretty sure entityclass of all things doesn't need to
be initialized this way), where you actually initialize the class in an
`init()` function, and so then you do `graphics.init()` after the
filesystem initialization, AFTER doing `Graphics graphics` up at the
top.
I've had to do this for `graphics` (but only because its child
GraphicsResources `grphx` needs to be initialized this way), `music`,
and `game`. I don't think this will affect anything. Other than that,
`help`, `key`, and `map` are still using the C++-intended method of
having ClassName::ClassName() functions.
2020-01-29 08:35:03 +01:00
|
|
|
mutebutton = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
muted = false;
|
2020-04-19 21:40:59 +02:00
|
|
|
musicmuted = false;
|
|
|
|
musicmutebutton = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-02 22:01:55 +02:00
|
|
|
glitchrunkludge = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
hascontrol = true;
|
|
|
|
jumpheld = false;
|
|
|
|
advancetext = false;
|
|
|
|
jumppressed = 0;
|
|
|
|
gravitycontrol = 0;
|
|
|
|
teleport = false;
|
|
|
|
edteleportent = 0; //Added in the port!
|
|
|
|
companion = 0;
|
|
|
|
roomchange = false;
|
|
|
|
|
|
|
|
|
|
|
|
savemystats = false;
|
|
|
|
quickrestartkludge = false;
|
|
|
|
|
|
|
|
tapleft = 0;
|
|
|
|
tapright = 0;
|
|
|
|
|
|
|
|
press_right = 0;
|
|
|
|
press_left = 0;
|
|
|
|
|
|
|
|
|
|
|
|
pausescript = false;
|
|
|
|
completestop = false;
|
|
|
|
activeactivity = -1;
|
|
|
|
act_fade = 0;
|
2020-04-29 06:58:19 +02:00
|
|
|
prev_act_fade = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
backgroundtext = false;
|
|
|
|
startscript = false;
|
|
|
|
inintermission = false;
|
|
|
|
|
|
|
|
alarmon = false;
|
|
|
|
alarmdelay = 0;
|
|
|
|
blackout = false;
|
|
|
|
creditposx = 0;
|
|
|
|
creditposy = 0;
|
|
|
|
creditposdelay = 0;
|
2020-05-02 03:23:52 +02:00
|
|
|
oldcreditposx = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
useteleporter = false;
|
|
|
|
teleport_to_teleporter = 0;
|
|
|
|
|
|
|
|
activetele = false;
|
|
|
|
readytotele = 0;
|
2020-04-30 02:04:25 +02:00
|
|
|
oldreadytotele = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
activity_r = 0;
|
|
|
|
activity_g = 0;
|
|
|
|
activity_b = 0;
|
|
|
|
creditposition = 0;
|
2020-04-30 19:56:27 +02:00
|
|
|
oldcreditposition = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
bestgamedeaths = -1;
|
|
|
|
|
|
|
|
//Accessibility Options
|
|
|
|
colourblindmode = false;
|
|
|
|
noflashingmode = false;
|
|
|
|
slowdown = 30;
|
|
|
|
|
2020-04-02 22:01:55 +02:00
|
|
|
// 0..5
|
|
|
|
controllerSensitivity = 2;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
nodeathmode = false;
|
|
|
|
nocutscenes = false;
|
|
|
|
|
|
|
|
customcol=0;
|
|
|
|
|
2020-07-03 03:10:52 +02:00
|
|
|
SDL_memset(crewstats, false, sizeof(crewstats));
|
|
|
|
SDL_memset(tele_crewstats, false, sizeof(tele_crewstats));
|
|
|
|
SDL_memset(quick_crewstats, false, sizeof(quick_crewstats));
|
2020-07-03 02:09:30 +02:00
|
|
|
SDL_memset(besttimes, -1, sizeof(besttimes));
|
2020-07-01 05:02:18 +02:00
|
|
|
SDL_memset(bestframes, -1, sizeof(bestframes));
|
2020-07-03 02:09:30 +02:00
|
|
|
SDL_memset(besttrinkets, -1, sizeof(besttrinkets));
|
|
|
|
SDL_memset(bestlives, -1, sizeof(bestlives));
|
|
|
|
SDL_memset(bestrank, -1, sizeof(bestrank));
|
2020-01-12 12:28:34 +01:00
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
crewstats[0] = true;
|
|
|
|
lastsaved = 0;
|
|
|
|
|
|
|
|
tele_gametime = "00:00";
|
|
|
|
tele_trinkets = 0;
|
|
|
|
tele_currentarea = "Error! Error!";
|
|
|
|
quick_gametime = "00:00";
|
|
|
|
quick_trinkets = 0;
|
|
|
|
quick_currentarea = "Error! Error!";
|
|
|
|
|
|
|
|
//Menu stuff initiliased here:
|
2020-07-03 01:45:22 +02:00
|
|
|
SDL_memset(unlock, false, sizeof(unlock));
|
|
|
|
SDL_memset(unlocknotify, false, sizeof(unlock));
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
currentmenuoption = 0;
|
2020-02-12 05:45:58 +01:00
|
|
|
current_credits_list_index = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
menuxoff = 0;
|
|
|
|
menuyoff = 0;
|
|
|
|
menucountdown = 0;
|
|
|
|
levelpage=0;
|
|
|
|
playcustomlevel=0;
|
2020-04-16 06:53:36 +02:00
|
|
|
createmenu(Menu::mainmenu);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
deathcounts = 0;
|
|
|
|
gameoverdelay = 0;
|
|
|
|
frames = 0;
|
|
|
|
seconds = 0;
|
|
|
|
minutes = 0;
|
|
|
|
hours = 0;
|
|
|
|
gamesaved = false;
|
2020-11-04 03:45:33 +01:00
|
|
|
gamesavefailed = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
savetime = "00:00";
|
|
|
|
savearea = "nowhere";
|
|
|
|
savetrinkets = 0;
|
|
|
|
|
|
|
|
intimetrial = false;
|
|
|
|
timetrialcountdown = 0;
|
|
|
|
timetrialshinytarget = 0;
|
|
|
|
timetrialparlost = false;
|
|
|
|
timetrialpar = 0;
|
|
|
|
timetrialresulttime = 0;
|
2020-06-30 00:53:19 +02:00
|
|
|
timetrialresultframes = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
totalflips = 0;
|
|
|
|
hardestroom = "Welcome Aboard";
|
|
|
|
hardestroomdeaths = 0;
|
|
|
|
currentroomdeaths=0;
|
|
|
|
|
|
|
|
inertia = 1.1f;
|
|
|
|
swnmode = false;
|
|
|
|
swntimer = 0;
|
|
|
|
swngame = 0;//Not playing sine wave ninja!
|
|
|
|
swnstate = 0;
|
|
|
|
swnstate2 = 0;
|
|
|
|
swnstate3 = 0;
|
|
|
|
swnstate4 = 0;
|
|
|
|
swndelay = 0;
|
|
|
|
swndeaths = 0;
|
|
|
|
supercrewmate = false;
|
|
|
|
scmhurt = false;
|
|
|
|
scmprogress = 0;
|
|
|
|
scmmoveme = false;
|
|
|
|
swncolstate = 0;
|
|
|
|
swncoldelay = 0;
|
|
|
|
swnrecord = 0;
|
|
|
|
swnbestrank = 0;
|
|
|
|
swnrank = 0;
|
|
|
|
swnmessage = 0;
|
|
|
|
|
|
|
|
clearcustomlevelstats();
|
|
|
|
|
|
|
|
saveFilePath = FILESYSTEM_getUserSaveDirectory();
|
|
|
|
|
2020-06-04 04:29:44 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
if (!FILESYSTEM_loadTiXml2Document("saves/qsave.vvv", doc))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
quicksummary = "";
|
|
|
|
printf("Quick Save Not Found\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-06-04 04:29:44 +02:00
|
|
|
tinyxml2::XMLHandle hDoc(&doc);
|
|
|
|
tinyxml2::XMLElement* pElem;
|
|
|
|
tinyxml2::XMLHandle hRoot(NULL);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-04 04:29:44 +02:00
|
|
|
pElem=hDoc.FirstChildElement().ToElement();
|
2020-01-01 21:29:24 +01:00
|
|
|
if (!pElem)
|
|
|
|
{
|
|
|
|
printf("Quick Save Appears Corrupted: No XML Root\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// save this for later
|
2020-06-04 04:29:44 +02:00
|
|
|
hRoot=tinyxml2::XMLHandle(pElem);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-04 04:29:44 +02:00
|
|
|
for( pElem = hRoot.FirstChildElement( "Data" ).FirstChild().ToElement(); pElem; pElem=pElem->NextSiblingElement())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
std::string pKey(pElem->Value());
|
|
|
|
const char* pText = pElem->GetText() ;
|
|
|
|
|
|
|
|
if (pKey == "summary")
|
|
|
|
{
|
|
|
|
quicksummary = pText;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-04 04:29:44 +02:00
|
|
|
tinyxml2::XMLDocument docTele;
|
|
|
|
if (!FILESYSTEM_loadTiXml2Document("saves/tsave.vvv", docTele))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
telesummary = "";
|
|
|
|
printf("Teleporter Save Not Found\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-06-04 04:29:44 +02:00
|
|
|
tinyxml2::XMLHandle hDoc(&docTele);
|
|
|
|
tinyxml2::XMLElement* pElem;
|
|
|
|
tinyxml2::XMLHandle hRoot(NULL);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
{
|
2020-06-04 04:29:44 +02:00
|
|
|
pElem=hDoc.FirstChildElement().ToElement();
|
2020-01-01 21:29:24 +01:00
|
|
|
// should always have a valid root but handle gracefully if it does
|
|
|
|
if (!pElem)
|
|
|
|
{
|
|
|
|
printf("Teleporter Save Appears Corrupted: No XML Root\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// save this for later
|
2020-06-04 04:29:44 +02:00
|
|
|
hRoot=tinyxml2::XMLHandle(pElem);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-06-04 04:29:44 +02:00
|
|
|
for( pElem = hRoot.FirstChildElement( "Data" ).FirstChild().ToElement(); pElem; pElem=pElem->NextSiblingElement())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
std::string pKey(pElem->Value());
|
|
|
|
const char* pText = pElem->GetText() ;
|
|
|
|
|
|
|
|
if (pKey == "summary")
|
|
|
|
{
|
|
|
|
telesummary = pText;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
screenshake = flashlight = 0 ;
|
|
|
|
|
|
|
|
stat_trinkets = 0;
|
|
|
|
|
|
|
|
state = 1;
|
|
|
|
statedelay = 0;
|
2020-03-31 02:16:02 +02:00
|
|
|
//updatestate();
|
2020-01-13 02:45:44 +01:00
|
|
|
|
|
|
|
skipfakeload = false;
|
2020-01-17 02:14:56 +01:00
|
|
|
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
ghostsenabled = false;
|
2020-06-13 00:23:16 +02:00
|
|
|
gametimer = 0;
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
|
2020-04-09 21:03:24 +02:00
|
|
|
cliplaytest = false;
|
|
|
|
playx = 0;
|
|
|
|
playy = 0;
|
|
|
|
playrx = 0;
|
|
|
|
playry = 0;
|
|
|
|
playgc = 0;
|
|
|
|
|
2020-05-08 00:23:55 +02:00
|
|
|
fadetomenu = false;
|
|
|
|
fadetomenudelay = 0;
|
2020-05-08 00:30:26 +02:00
|
|
|
fadetolab = false;
|
|
|
|
fadetolabdelay = 0;
|
2020-05-08 00:23:55 +02:00
|
|
|
|
2020-05-09 21:52:58 +02:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
|
|
|
shouldreturntoeditor = false;
|
|
|
|
#endif
|
|
|
|
|
2020-05-04 21:52:57 +02:00
|
|
|
over30mode = false;
|
2020-06-25 23:31:37 +02:00
|
|
|
glitchrunnermode = false;
|
2020-06-23 01:51:16 +02:00
|
|
|
|
|
|
|
ingame_titlemode = false;
|
2020-06-23 02:23:56 +02:00
|
|
|
kludge_ingametemp = Menu::mainmenu;
|
2020-05-04 21:52:57 +02:00
|
|
|
|
2020-06-30 04:49:14 +02:00
|
|
|
disablepause = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::lifesequence()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (lifeseq > 0)
|
|
|
|
{
|
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].invis = false;
|
|
|
|
if (lifeseq == 2) obj.entities[i].invis = true;
|
|
|
|
if (lifeseq == 6) obj.entities[i].invis = true;
|
|
|
|
if (lifeseq >= 8) obj.entities[i].invis = true;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
if (lifeseq > 5) gravitycontrol = savegc;
|
|
|
|
|
|
|
|
lifeseq--;
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities) && lifeseq <= 0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
obj.entities[i].invis = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Game::clearcustomlevelstats()
|
|
|
|
{
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
//just clearing the array
|
|
|
|
customlevelstats.clear();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
customlevelstatsloaded=false; //To ensure we don't load it where it isn't needed
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Game::updatecustomlevelstats(std::string clevel, int cscore)
|
|
|
|
{
|
|
|
|
if (clevel.find("levels/") != std::string::npos)
|
|
|
|
{
|
|
|
|
clevel = clevel.substr(7);
|
|
|
|
}
|
|
|
|
int tvar=-1;
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
for(size_t j=0; j<customlevelstats.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
if(clevel==customlevelstats[j].name)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
tvar=j;
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
break;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
2020-09-13 06:55:26 +02:00
|
|
|
if(tvar>=0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-09-13 06:55:26 +02:00
|
|
|
// We have an existing entry
|
|
|
|
// Don't update it unless it's a higher score
|
|
|
|
if (cscore > customlevelstats[tvar].score)
|
|
|
|
{
|
|
|
|
customlevelstats[tvar].score=cscore;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//add a new entry
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
CustomLevelStat levelstat = {clevel, cscore};
|
|
|
|
customlevelstats.push_back(levelstat);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
savecustomlevelstats();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Game::loadcustomlevelstats()
|
|
|
|
{
|
|
|
|
//testing
|
2020-06-30 03:44:54 +02:00
|
|
|
if(customlevelstatsloaded)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-30 03:44:54 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-30 03:47:45 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
if (!FILESYSTEM_loadTiXml2Document("saves/levelstats.vvv", doc))
|
|
|
|
{
|
|
|
|
//No levelstats file exists; start new
|
|
|
|
customlevelstats.clear();
|
|
|
|
savecustomlevelstats();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Old system
|
|
|
|
std::vector<std::string> customlevelnames;
|
|
|
|
std::vector<int> customlevelscores;
|
|
|
|
|
|
|
|
tinyxml2::XMLHandle hDoc(&doc);
|
|
|
|
tinyxml2::XMLElement* pElem;
|
|
|
|
tinyxml2::XMLHandle hRoot(NULL);
|
|
|
|
|
|
|
|
{
|
|
|
|
pElem=hDoc.FirstChildElement().ToElement();
|
|
|
|
// should always have a valid root but handle gracefully if it does
|
|
|
|
if (!pElem)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-30 03:47:45 +02:00
|
|
|
printf("Error: Levelstats file corrupted\n");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-06-30 03:44:54 +02:00
|
|
|
|
2020-06-30 03:47:45 +02:00
|
|
|
// save this for later
|
|
|
|
hRoot=tinyxml2::XMLHandle(pElem);
|
|
|
|
}
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
|
2020-06-30 03:47:45 +02:00
|
|
|
// First pass, look for the new system of storing stats
|
|
|
|
// If they don't exist, then fall back to the old system
|
|
|
|
for (pElem = hRoot.FirstChildElement("Data").FirstChild().ToElement(); pElem; pElem = pElem->NextSiblingElement())
|
|
|
|
{
|
|
|
|
std::string pKey(pElem->Value());
|
|
|
|
const char* pText = pElem->GetText();
|
|
|
|
if (pText == NULL)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-30 03:47:45 +02:00
|
|
|
pText = "";
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-30 03:47:45 +02:00
|
|
|
if (pKey == "stats")
|
|
|
|
{
|
|
|
|
for (tinyxml2::XMLElement* stat_el = pElem->FirstChildElement(); stat_el; stat_el = stat_el->NextSiblingElement())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-30 03:47:45 +02:00
|
|
|
CustomLevelStat stat = {};
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-30 03:47:45 +02:00
|
|
|
if (stat_el->GetText() != NULL)
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
stat.score = help.Int(stat_el->GetText());
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-30 03:47:45 +02:00
|
|
|
if (stat_el->Attribute("name"))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-30 03:47:45 +02:00
|
|
|
stat.name = stat_el->Attribute("name");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-06-30 03:47:45 +02:00
|
|
|
customlevelstats.push_back(stat);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-06-30 03:47:45 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
2020-06-30 03:47:45 +02:00
|
|
|
// Since we're still here, we must be on the old system
|
|
|
|
for( pElem = hRoot.FirstChildElement( "Data" ).FirstChild().ToElement(); pElem; pElem=pElem->NextSiblingElement())
|
|
|
|
{
|
|
|
|
std::string pKey(pElem->Value());
|
|
|
|
const char* pText = pElem->GetText() ;
|
|
|
|
if(pText == NULL)
|
|
|
|
{
|
|
|
|
pText = "";
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-30 03:47:45 +02:00
|
|
|
if (pKey == "customlevelscore")
|
|
|
|
{
|
|
|
|
std::string TextString = (pText);
|
|
|
|
if(TextString.length())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-30 03:47:45 +02:00
|
|
|
std::vector<std::string> values = split(TextString,',');
|
|
|
|
for(size_t i = 0; i < values.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
customlevelscores.push_back(help.Int(values[i].c_str()));
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-06-30 03:47:45 +02:00
|
|
|
}
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-30 03:47:45 +02:00
|
|
|
if (pKey == "customlevelstats")
|
|
|
|
{
|
|
|
|
std::string TextString = (pText);
|
|
|
|
if(TextString.length())
|
|
|
|
{
|
|
|
|
std::vector<std::string> values = split(TextString,'|');
|
|
|
|
for(size_t i = 0; i < values.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-30 03:47:45 +02:00
|
|
|
customlevelnames.push_back(values[i]);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
|
2020-06-30 03:47:45 +02:00
|
|
|
// If the two arrays happen to differ in length, just go with the smallest one
|
|
|
|
for (size_t i = 0; i < std::min(customlevelnames.size(), customlevelscores.size()); i++)
|
|
|
|
{
|
|
|
|
CustomLevelStat stat = {customlevelnames[i], customlevelscores[i]};
|
|
|
|
customlevelstats.push_back(stat);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Game::savecustomlevelstats()
|
|
|
|
{
|
2020-06-04 04:32:18 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
2020-09-25 18:09:21 +02:00
|
|
|
bool already_exists = FILESYSTEM_loadTiXml2Document("saves/levelstats.vvv", doc);
|
|
|
|
if (!already_exists)
|
|
|
|
{
|
|
|
|
puts("No levelstats.vvv found. Creating new file");
|
|
|
|
}
|
|
|
|
|
|
|
|
xml::update_declaration(doc);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:09:21 +02:00
|
|
|
tinyxml2::XMLElement * root = xml::update_element(doc, "Levelstats");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:09:21 +02:00
|
|
|
xml::update_comment(root, " Levelstats Save file ");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:09:21 +02:00
|
|
|
tinyxml2::XMLElement * msgs = xml::update_element(root, "Data");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
int numcustomlevelstats = customlevelstats.size();
|
2020-01-01 21:29:24 +01:00
|
|
|
if(numcustomlevelstats>=200)numcustomlevelstats=199;
|
2020-09-25 18:09:21 +02:00
|
|
|
xml::update_tag(msgs, "numcustomlevelstats", numcustomlevelstats);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string customlevelscorestr;
|
|
|
|
for(int i = 0; i < numcustomlevelstats; i++ )
|
|
|
|
{
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
customlevelscorestr += help.String(customlevelstats[i].score) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:09:21 +02:00
|
|
|
xml::update_tag(msgs, "customlevelscore", customlevelscorestr.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string customlevelstatsstr;
|
|
|
|
for(int i = 0; i < numcustomlevelstats; i++ )
|
|
|
|
{
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
customlevelstatsstr += customlevelstats[i].name + "|";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:09:21 +02:00
|
|
|
xml::update_tag(msgs, "customlevelstats", customlevelstatsstr.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
// New system
|
2020-09-25 18:09:21 +02:00
|
|
|
tinyxml2::XMLElement* msg = xml::update_element_delete_contents(msgs, "stats");
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
tinyxml2::XMLElement* stat_el;
|
|
|
|
for (size_t i = 0; i < customlevelstats.size(); i++)
|
|
|
|
{
|
|
|
|
stat_el = doc.NewElement("stat");
|
|
|
|
CustomLevelStat& stat = customlevelstats[i];
|
|
|
|
|
|
|
|
stat_el->SetAttribute("name", stat.name.c_str());
|
|
|
|
stat_el->LinkEndChild(doc.NewText(help.String(stat.score).c_str()));
|
|
|
|
|
|
|
|
msg->LinkEndChild(stat_el);
|
|
|
|
}
|
|
|
|
|
2020-06-04 04:32:18 +02:00
|
|
|
if(FILESYSTEM_saveTiXml2Document("saves/levelstats.vvv", doc))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
printf("Level stats saved\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
printf("Could Not Save level stats!\n");
|
|
|
|
printf("Failed: %s%s\n", saveFilePath.c_str(), "levelstats.vvv");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::updatestate()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
statedelay--;
|
|
|
|
if(statedelay<=0){
|
2020-04-02 22:01:55 +02:00
|
|
|
statedelay=0;
|
|
|
|
glitchrunkludge=false;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
if (statedelay <= 0)
|
|
|
|
{
|
|
|
|
switch(state)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
//Do nothing here! Standard game state
|
2020-08-03 00:39:21 +02:00
|
|
|
|
|
|
|
//Prevent softlocks if there's no cutscene running right now
|
|
|
|
if (!script.running)
|
|
|
|
{
|
|
|
|
hascontrol = true;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
//Game initilisation
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
//Opening cutscene
|
|
|
|
advancetext = true;
|
|
|
|
hascontrol = false;
|
|
|
|
state = 3;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("To do: write quick", 50, 80, 164, 164, 255);
|
|
|
|
graphics.addline("intro to story!");
|
2020-01-01 21:29:24 +01:00
|
|
|
//Oh no! what happen to rest of crew etc crash into dimension
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
//End of opening cutscene for now
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press arrow keys or WASD to move ", -1, 195, 174, 174, 174);
|
|
|
|
graphics.textboxtimer(60);
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
//Demo over
|
|
|
|
advancetext = true;
|
|
|
|
hascontrol = false;
|
2020-03-31 02:16:02 +02:00
|
|
|
/*graphics.createtextbox(" Prototype Complete ", 50, 80, 164, 164, 255);
|
|
|
|
graphics.addline("Congrats! More Info Soon!");
|
|
|
|
graphics.textboxcenter();
|
2020-01-01 21:29:24 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
startscript = true;
|
|
|
|
newscript="returntohub";
|
|
|
|
obj.removetrigger(5);
|
|
|
|
state = 6;
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
//End of opening cutscene for now
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
//Enter dialogue
|
|
|
|
obj.removetrigger(8);
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[13])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[13] = true;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ENTER to view map ", -1, 155, 174, 174, 174);
|
|
|
|
graphics.addline(" and quicksave");
|
|
|
|
graphics.textboxtimer(60);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 9:
|
|
|
|
//Start SWN Minigame Mode B
|
|
|
|
obj.removetrigger(9);
|
|
|
|
|
|
|
|
swnmode = true;
|
|
|
|
swngame = 6;
|
|
|
|
swndelay = 150;
|
|
|
|
swntimer = 60 * 30;
|
|
|
|
|
|
|
|
//set the checkpoint in the middle of the screen
|
|
|
|
savepoint = 0;
|
|
|
|
savex = 148;
|
|
|
|
savey = 100;
|
|
|
|
savegc = 0;
|
|
|
|
saverx = roomx;
|
|
|
|
savery = roomy;
|
|
|
|
savedir = 0;
|
|
|
|
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 10:
|
|
|
|
//Start SWN Minigame Mode A
|
|
|
|
obj.removetrigger(10);
|
|
|
|
|
|
|
|
swnmode = true;
|
|
|
|
swngame = 4;
|
|
|
|
swndelay = 150;
|
|
|
|
swntimer = 60 * 30;
|
|
|
|
|
|
|
|
//set the checkpoint in the middle of the screen
|
|
|
|
savepoint = 0;
|
|
|
|
savex = 148;
|
|
|
|
savey = 100;
|
|
|
|
savegc = 0;
|
|
|
|
saverx = roomx;
|
|
|
|
savery = roomy;
|
|
|
|
savedir = 0;
|
|
|
|
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 11:
|
|
|
|
//Intermission 1 instructional textbox, depends on last saved
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremovefast();
|
|
|
|
graphics.createtextbox(" When you're NOT standing on ", -1, 3, 174, 174, 174);
|
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (lastsaved == 2)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" the ceiling, Vitellary will");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (lastsaved == 3)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" the ceiling, Vermilion will");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (lastsaved == 4)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" the ceiling, Verdigris will");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (lastsaved == 5)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" the ceiling, Victoria will");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (lastsaved == 2)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" the floor, Vitellary will");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (lastsaved == 3)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" the floor, Vermilion will");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (lastsaved == 4)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" the floor, Verdigris will");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (lastsaved == 5)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" the floor, Victoria will");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" stop and wait for you.");
|
|
|
|
graphics.textboxtimer(180);
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 12:
|
|
|
|
//Intermission 1 instructional textbox, depends on last saved
|
|
|
|
obj.removetrigger(12);
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[61])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[61] = true;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremovefast();
|
|
|
|
graphics.createtextbox(" You can't continue to the next ", -1, 8, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
if (lastsaved == 5)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" room until she is safely across. ");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" room until he is safely across. ");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxtimer(120);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 13:
|
|
|
|
//textbox removal
|
|
|
|
obj.removetrigger(13);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremovefast();
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 14:
|
|
|
|
//Intermission 1 instructional textbox, depends on last saved
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" When you're standing on the ceiling, ", -1, 3, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" When you're standing on the floor, ", -1, 3, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
if (lastsaved == 2)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" Vitellary will try to walk to you. ");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (lastsaved == 3)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" Vermilion will try to walk to you. ");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (lastsaved == 4)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" Verdigris will try to walk to you. ");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (lastsaved == 5)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" Victoria will try to walk to you. ");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxtimer(280);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 15:
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//leaving the naughty corner
|
2020-06-13 05:36:08 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[obj.getplayer()].tile = 0;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
break;
|
2020-06-13 05:36:08 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 16:
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//entering the naughty corner
|
2020-06-13 05:36:08 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if(INBOUNDS_VEC(i, obj.entities) && obj.entities[i].tile == 0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[i].tile = 144;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(2);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
state = 0;
|
|
|
|
break;
|
2020-06-13 05:36:08 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
case 17:
|
|
|
|
//Arrow key tutorial
|
|
|
|
obj.removetrigger(17);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" If you prefer, you can press UP or ", -1, 195, 174, 174, 174);
|
|
|
|
graphics.addline(" DOWN instead of ACTION to flip.");
|
|
|
|
graphics.textboxtimer(100);
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 20:
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[1])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[1] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
obj.removetrigger(20);
|
|
|
|
break;
|
|
|
|
case 21:
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[2])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[2] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
obj.removetrigger(21);
|
|
|
|
break;
|
|
|
|
case 22:
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[3])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremovefast();
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[3] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ACTION to flip ", -1, 25, 174, 174, 174);
|
|
|
|
graphics.textboxtimer(60);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
obj.removetrigger(22);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 30:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[4])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[4] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
newscript="firststeps";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(30);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 31:
|
|
|
|
//state = 55; statedelay = 50;
|
|
|
|
state = 0;
|
|
|
|
statedelay = 0;
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[6])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[6] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[5] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
newscript="communicationstation";
|
|
|
|
state = 0;
|
|
|
|
statedelay = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(31);
|
|
|
|
break;
|
|
|
|
case 32:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[7])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[7] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
newscript="teleporterback";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(32);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 33:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[9])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[9] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
newscript="rescueblue";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(33);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 34:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[10])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[10] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
newscript="rescueyellow";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(34);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 35:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[11])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[11] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
newscript="rescuegreen";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(35);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 36:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[8])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[8] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
newscript="rescuered";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(36);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 37:
|
|
|
|
//Generic "run script"
|
|
|
|
if (companion == 0)
|
|
|
|
{
|
|
|
|
startscript = true;
|
|
|
|
newscript="int2_yellow";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(37);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 38:
|
|
|
|
//Generic "run script"
|
|
|
|
if (companion == 0)
|
|
|
|
{
|
|
|
|
startscript = true;
|
|
|
|
newscript="int2_red";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(38);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 39:
|
|
|
|
//Generic "run script"
|
|
|
|
if (companion == 0)
|
|
|
|
{
|
|
|
|
startscript = true;
|
|
|
|
newscript="int2_green";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(39);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 40:
|
|
|
|
//Generic "run script"
|
|
|
|
if (companion == 0)
|
|
|
|
{
|
|
|
|
startscript = true;
|
|
|
|
newscript="int2_blue";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(40);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 41:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[60])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[60] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
if (lastsaved == 2)
|
|
|
|
{
|
|
|
|
newscript = "int1yellow_2";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 3)
|
|
|
|
{
|
|
|
|
newscript = "int1red_2";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 4)
|
|
|
|
{
|
|
|
|
newscript = "int1green_2";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 5)
|
|
|
|
{
|
|
|
|
newscript = "int1blue_2";
|
|
|
|
}
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(41);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 42:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[62])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[62] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
if (lastsaved == 2)
|
|
|
|
{
|
|
|
|
newscript = "int1yellow_3";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 3)
|
|
|
|
{
|
|
|
|
newscript = "int1red_3";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 4)
|
|
|
|
{
|
|
|
|
newscript = "int1green_3";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 5)
|
|
|
|
{
|
|
|
|
newscript = "int1blue_3";
|
|
|
|
}
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(42);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 43:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[63])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[63] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
if (lastsaved == 2)
|
|
|
|
{
|
|
|
|
newscript = "int1yellow_4";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 3)
|
|
|
|
{
|
|
|
|
newscript = "int1red_4";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 4)
|
|
|
|
{
|
|
|
|
newscript = "int1green_4";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 5)
|
|
|
|
{
|
|
|
|
newscript = "int1blue_4";
|
|
|
|
}
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(43);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 44:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[64])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[64] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
if (lastsaved == 2)
|
|
|
|
{
|
|
|
|
newscript = "int1yellow_5";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 3)
|
|
|
|
{
|
|
|
|
newscript = "int1red_5";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 4)
|
|
|
|
{
|
|
|
|
newscript = "int1green_5";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 5)
|
|
|
|
{
|
|
|
|
newscript = "int1blue_5";
|
|
|
|
}
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(44);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 45:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[65])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[65] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
if (lastsaved == 2)
|
|
|
|
{
|
|
|
|
newscript = "int1yellow_6";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 3)
|
|
|
|
{
|
|
|
|
newscript = "int1red_6";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 4)
|
|
|
|
{
|
|
|
|
newscript = "int1green_6";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 5)
|
|
|
|
{
|
|
|
|
newscript = "int1blue_6";
|
|
|
|
}
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(45);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 46:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[66])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[66] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
if (lastsaved == 2)
|
|
|
|
{
|
|
|
|
newscript = "int1yellow_7";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 3)
|
|
|
|
{
|
|
|
|
newscript = "int1red_7";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 4)
|
|
|
|
{
|
|
|
|
newscript = "int1green_7";
|
|
|
|
}
|
|
|
|
else if (lastsaved == 5)
|
|
|
|
{
|
|
|
|
newscript = "int1blue_7";
|
|
|
|
}
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(46);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 47:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[69])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[69] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
newscript="trenchwarfare";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(47);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 48:
|
|
|
|
//Generic "run script"
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[70])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[70] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
startscript = true;
|
|
|
|
newscript="trinketcollector";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(48);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 49:
|
|
|
|
//Start final level music
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[71])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[71] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
music.niceplay(15); //Final level remix
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
obj.removetrigger(49);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 50:
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(15);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Help! Can anyone hear", 35, 15, 255, 134, 255);
|
|
|
|
graphics.addline("this message?");
|
|
|
|
graphics.textboxtimer(60);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 100;
|
|
|
|
break;
|
|
|
|
case 51:
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(15);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Verdigris? Are you out", 30, 12, 255, 134, 255);
|
|
|
|
graphics.addline("there? Are you ok?");
|
|
|
|
graphics.textboxtimer(60);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 100;
|
|
|
|
break;
|
|
|
|
case 52:
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(15);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Please help us! We've crashed", 5, 22, 255, 134, 255);
|
|
|
|
graphics.addline("and need assistance!");
|
|
|
|
graphics.textboxtimer(60);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 100;
|
|
|
|
break;
|
|
|
|
case 53:
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(15);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Hello? Anyone out there?", 40, 15, 255, 134, 255);
|
|
|
|
graphics.textboxtimer(60);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 100;
|
|
|
|
break;
|
|
|
|
case 54:
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(15);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("This is Doctor Violet from the", 5, 8, 255, 134, 255);
|
|
|
|
graphics.addline("D.S.S. Souleye! Please respond!");
|
|
|
|
graphics.textboxtimer(60);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 100;
|
|
|
|
break;
|
|
|
|
case 55:
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(15);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Please... Anyone...", 45, 14, 255, 134, 255);
|
|
|
|
graphics.textboxtimer(60);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 100;
|
|
|
|
break;
|
|
|
|
case 56:
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(15);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Please be alright, everyone...", 25, 18, 255, 134, 255);
|
|
|
|
graphics.textboxtimer(60);
|
2020-01-01 21:29:24 +01:00
|
|
|
state=50;
|
|
|
|
statedelay = 100;
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 80:
|
|
|
|
//Used to return to menu from the game
|
2020-03-31 02:16:02 +02:00
|
|
|
if(graphics.fademode == 1) state++;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 81:
|
2020-05-07 23:38:19 +02:00
|
|
|
quittomenu();
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 82:
|
|
|
|
//Time Trial Complete!
|
|
|
|
obj.removetrigger(82);
|
|
|
|
hascontrol = false;
|
2020-06-18 07:58:44 +02:00
|
|
|
timetrialresulttime = seconds + (minutes * 60) + (hours * 60 * 60);
|
2020-06-30 00:53:19 +02:00
|
|
|
timetrialresultframes = frames;
|
2020-01-01 21:29:24 +01:00
|
|
|
timetrialrank = 0;
|
|
|
|
if (timetrialresulttime <= timetrialpar) timetrialrank++;
|
2020-04-07 08:46:27 +02:00
|
|
|
if (trinkets() >= timetrialshinytarget) timetrialrank++;
|
2020-01-01 21:29:24 +01:00
|
|
|
if (deathcounts == 0) timetrialrank++;
|
|
|
|
|
2020-07-01 03:59:16 +02:00
|
|
|
if (timetrialresulttime < besttimes[timetriallevel]
|
|
|
|
|| (timetrialresulttime == besttimes[timetriallevel] && timetrialresultframes < bestframes[timetriallevel])
|
|
|
|
|| besttimes[timetriallevel]==-1)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
besttimes[timetriallevel] = timetrialresulttime;
|
2020-07-01 03:59:16 +02:00
|
|
|
bestframes[timetriallevel] = timetrialresultframes;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-04-07 08:46:27 +02:00
|
|
|
if (trinkets() > besttrinkets[timetriallevel] || besttrinkets[timetriallevel]==-1)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-07 08:46:27 +02:00
|
|
|
besttrinkets[timetriallevel] = trinkets();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
if (deathcounts < bestlives[timetriallevel] || bestlives[timetriallevel]==-1)
|
|
|
|
{
|
|
|
|
bestlives[timetriallevel] = deathcounts;
|
|
|
|
}
|
|
|
|
if (timetrialrank > bestrank[timetriallevel] || bestrank[timetriallevel]==-1)
|
|
|
|
{
|
|
|
|
bestrank[timetriallevel] = timetrialrank;
|
2020-04-02 22:01:55 +02:00
|
|
|
if(timetrialrank>=3){
|
2020-08-01 21:49:07 +02:00
|
|
|
if(timetriallevel==0) unlockAchievement("vvvvvvtimetrial_station1_fixed");
|
|
|
|
if(timetriallevel==1) unlockAchievement("vvvvvvtimetrial_lab_fixed");
|
|
|
|
if(timetriallevel==2) unlockAchievement("vvvvvvtimetrial_tower_fixed");
|
|
|
|
if(timetriallevel==3) unlockAchievement("vvvvvvtimetrial_station2_fixed");
|
|
|
|
if(timetriallevel==4) unlockAchievement("vvvvvvtimetrial_warp_fixed");
|
|
|
|
if(timetriallevel==5) unlockAchievement("vvvvvvtimetrial_final_fixed");
|
2020-04-02 22:01:55 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
savestats();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.fademode = 2;
|
2020-01-01 21:29:24 +01:00
|
|
|
music.fadeout();
|
|
|
|
state++;
|
|
|
|
break;
|
|
|
|
case 83:
|
|
|
|
frames--;
|
2020-03-31 02:16:02 +02:00
|
|
|
if(graphics.fademode == 1) state++;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 84:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.flipmode = false;
|
2020-04-17 05:15:53 +02:00
|
|
|
gamestate = TITLEMODE;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.fademode = 4;
|
|
|
|
graphics.backgrounddrawn = true;
|
2020-11-03 00:23:53 +01:00
|
|
|
graphics.titlebg.tdrawback = true;
|
2020-04-16 06:53:36 +02:00
|
|
|
createmenu(Menu::timetrialcomplete);
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 85:
|
|
|
|
//Cutscene skip version of final level change
|
|
|
|
obj.removetrigger(85);
|
|
|
|
//Init final stretch
|
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
music.play(2);
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[72] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
screenshake = 10;
|
|
|
|
flashlight = 5;
|
|
|
|
map.finalstretch = true;
|
|
|
|
map.warpx = false;
|
|
|
|
map.warpy = false;
|
|
|
|
map.background = 6;
|
|
|
|
|
|
|
|
map.final_colormode = true;
|
|
|
|
map.final_colorframe = 1;
|
|
|
|
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
//From 90-100 are run scripts for the eurogamer expo only, remove later
|
|
|
|
case 90:
|
|
|
|
//Generic "run script"
|
|
|
|
startscript = true;
|
|
|
|
newscript="startexpolevel_station1";
|
|
|
|
obj.removetrigger(90);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 91:
|
|
|
|
//Generic "run script"
|
|
|
|
startscript = true;
|
|
|
|
newscript="startexpolevel_lab";
|
|
|
|
obj.removetrigger(91);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 92:
|
|
|
|
//Generic "run script"
|
|
|
|
startscript = true;
|
|
|
|
newscript="startexpolevel_warp";
|
|
|
|
obj.removetrigger(92);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 93:
|
|
|
|
//Generic "run script"
|
|
|
|
startscript = true;
|
|
|
|
newscript="startexpolevel_tower";
|
|
|
|
obj.removetrigger(93);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 94:
|
|
|
|
//Generic "run script"
|
|
|
|
startscript = true;
|
|
|
|
newscript="startexpolevel_station2";
|
|
|
|
obj.removetrigger(94);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
case 95:
|
|
|
|
//Generic "run script"
|
|
|
|
startscript = true;
|
|
|
|
newscript="startexpolevel_final";
|
|
|
|
obj.removetrigger(95);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 96:
|
|
|
|
//Used to return to gravitron to game
|
2020-03-31 02:16:02 +02:00
|
|
|
if(graphics.fademode == 1) state++;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 97:
|
2020-05-08 00:17:04 +02:00
|
|
|
returntolab();
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 100:
|
|
|
|
//
|
|
|
|
// Meeting crewmate in the warpzone
|
|
|
|
//
|
|
|
|
obj.removetrigger(100);
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[4])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[4] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 101:
|
|
|
|
{
|
|
|
|
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
hascontrol = false;
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities) && obj.entities[i].onroof > 0 && gravitycontrol == 1)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
gravitycontrol = 0;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(1);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities) && obj.entities[i].onground > 0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
state++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 102:
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
companion = 6;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getcompanion();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:43:19 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 0;
|
|
|
|
obj.entities[i].state = 1;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
advancetext = true;
|
|
|
|
hascontrol = false;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Captain! I've been so worried!", 60, 90, 164, 255, 164);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(12);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 104:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("I'm glad you're ok!", 135, 152, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(11);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxactive();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 106:
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("I've been trying to find a", 74, 70, 164, 255, 164);
|
|
|
|
graphics.addline("way out, but I keep going");
|
|
|
|
graphics.addline("around in circles...");
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(2);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxactive();
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getcompanion();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:43:19 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 54;
|
|
|
|
obj.entities[i].state = 0;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 108:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Don't worry! I have a", 125, 152, 164, 164, 255);
|
|
|
|
graphics.addline("teleporter key!");
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(11);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxactive();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 110:
|
|
|
|
{
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getcompanion();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:43:19 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 0;
|
|
|
|
obj.entities[i].state = 1;
|
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Follow me!", 185, 154, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(11);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxactive();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 112:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 115:
|
|
|
|
//
|
|
|
|
// Test script for space station, totally delete me!
|
|
|
|
//
|
|
|
|
hascontrol = false;
|
|
|
|
state++;
|
|
|
|
break;
|
|
|
|
case 116:
|
|
|
|
advancetext = true;
|
|
|
|
hascontrol = false;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Sorry Eurogamers! Teleporting around", 60 - 20, 200, 255, 64, 64);
|
|
|
|
graphics.addline("the map doesn't work in this version!");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
break;
|
|
|
|
case 118:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 120:
|
|
|
|
//
|
|
|
|
// Meeting crewmate in the space station
|
|
|
|
//
|
|
|
|
obj.removetrigger(120);
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[5])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:46:41 +02:00
|
|
|
obj.flags[5] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 121:
|
|
|
|
{
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
2020-01-01 21:29:24 +01:00
|
|
|
hascontrol = false;
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities) && obj.entities[i].onground > 0 && gravitycontrol == 0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
gravitycontrol = 1;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(1);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities) && obj.entities[i].onroof > 0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
state++;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 122:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
companion = 7;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getcompanion();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:43:19 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 6;
|
|
|
|
obj.entities[i].state = 1;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
advancetext = true;
|
|
|
|
hascontrol = false;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Captain! You're ok!", 60-10, 90-40, 255, 255, 134);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(14);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 124:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("I've found a teleporter, but", 60-20, 90 - 40, 255, 255, 134);
|
|
|
|
graphics.addline("I can't get it to go anywhere...");
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(2);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxactive();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
int i = obj.getcompanion(); if (INBOUNDS_VEC(i, obj.entities)) { /*obj.entities[i].tile = 66; obj.entities[i].state = 0;*/ }
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 126:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("I can help with that!", 125, 152-40, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(11);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxactive();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 128:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("I have the teleporter", 130, 152-35, 164, 164, 255);
|
|
|
|
graphics.addline("codex for our ship!");
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(11);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxactive();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 130:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Yey! Let's go home!", 60-30, 90-35, 255, 255, 134);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(14);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxactive();
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getcompanion();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:43:19 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 6;
|
|
|
|
obj.entities[i].state = 1;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 132:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 200:
|
|
|
|
//Init final stretch
|
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
//music.play(2);
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[72] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
screenshake = 10;
|
|
|
|
flashlight = 5;
|
|
|
|
map.finalstretch = true;
|
|
|
|
map.warpx = false;
|
|
|
|
map.warpy = false;
|
|
|
|
map.background = 6;
|
|
|
|
|
|
|
|
map.final_colormode = true;
|
|
|
|
map.final_colorframe = 1;
|
|
|
|
|
|
|
|
startscript = true;
|
|
|
|
newscript="finalterminal_finish";
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
Fix the two-frame-delay when entering a room with an "init" script
This patch is very kludge-y, but at least it fixes a semi-noticeable
visual issue in custom levels that use internal scripts to spawn
entities when loading a room.
Basically, the problem here is that when the game checks for script
boxes and sets newscript, newscript has already been processed for that
frame, and when the game does load a script, script.run() has already
been processed for that frame.
That issue can be fixed, but it turns out that due to my over-30-FPS
game loop changes, there's now ANOTHER visible frame of delay between
room load and entity creation, because the render function gets called
in between the script being loaded at the end of gamelogic() and the
script actually getting run.
So... I have to temporary move script.run() to the end of gamelogic()
(in map.twoframedelayfix()), and make sure it doesn't get run next
frame, because double-evaluations are bad. To do that, I have to
introduce the kludge variable script.dontrunnextframe, which does
exactly as it says.
And with all that work, the two-frame (now three-frame) delay is fixed.
2020-06-28 02:08:57 +02:00
|
|
|
// WARNING: If updating this code, make sure to update Map.cpp mapclass::twoframedelayfix()
|
2020-01-01 21:29:24 +01:00
|
|
|
case 300:
|
|
|
|
case 301:
|
|
|
|
case 302:
|
|
|
|
case 303:
|
|
|
|
case 304:
|
|
|
|
case 305:
|
|
|
|
case 306:
|
|
|
|
case 307:
|
|
|
|
case 308:
|
|
|
|
case 309:
|
|
|
|
case 310:
|
|
|
|
case 311:
|
|
|
|
case 312:
|
|
|
|
case 313:
|
|
|
|
case 314:
|
|
|
|
case 315:
|
|
|
|
case 316:
|
|
|
|
case 317:
|
|
|
|
case 318:
|
|
|
|
case 319:
|
|
|
|
case 320:
|
|
|
|
case 321:
|
|
|
|
case 322:
|
|
|
|
case 323:
|
|
|
|
case 324:
|
|
|
|
case 325:
|
|
|
|
case 326:
|
|
|
|
case 327:
|
|
|
|
case 328:
|
|
|
|
case 329:
|
|
|
|
case 330:
|
|
|
|
case 331:
|
|
|
|
case 332:
|
|
|
|
case 333:
|
|
|
|
case 334:
|
|
|
|
case 335:
|
|
|
|
case 336:
|
|
|
|
startscript = true;
|
2020-07-10 03:14:18 +02:00
|
|
|
newscript="custom_"+customscript[state - 300];
|
|
|
|
obj.removetrigger(state);
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1000:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.showcutscenebars = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
hascontrol = false;
|
|
|
|
completestop = true;
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
break;
|
|
|
|
case 1001:
|
|
|
|
//Found a trinket!
|
|
|
|
advancetext = true;
|
|
|
|
state++;
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Congratulations! ", 50, 105, 174, 174, 174);
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("You have found a shiny trinket!");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-09 06:56:47 +02:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
2020-01-01 21:29:24 +01:00
|
|
|
if(map.custommode)
|
|
|
|
{
|
2020-04-09 07:09:11 +02:00
|
|
|
graphics.createtextbox(" " + help.number(trinkets()) + " out of " + help.number(ed.numtrinkets())+ " ", 50, 65, 174, 174, 174);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
2020-04-09 06:56:47 +02:00
|
|
|
#endif
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-07 08:46:27 +02:00
|
|
|
graphics.createtextbox(" " + help.number(trinkets()) + " out of Twenty ", 50, 65, 174, 174, 174);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Congratulations! ", 50, 85, 174, 174, 174);
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("You have found a shiny trinket!");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-09 06:56:47 +02:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
2020-01-01 21:29:24 +01:00
|
|
|
if(map.custommode)
|
|
|
|
{
|
2020-04-09 07:09:11 +02:00
|
|
|
graphics.createtextbox(" " + help.number(trinkets()) + " out of " + help.number(ed.numtrinkets())+ " ", 50, 135, 174, 174, 174);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
2020-04-09 06:56:47 +02:00
|
|
|
#endif
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-07 08:46:27 +02:00
|
|
|
graphics.createtextbox(" " + help.number(trinkets()) + " out of Twenty ", 50, 135, 174, 174, 174);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2020-07-05 07:55:21 +02:00
|
|
|
case 1002:
|
|
|
|
if (!advancetext)
|
|
|
|
{
|
|
|
|
// Prevent softlocks if we somehow don't have advancetext
|
|
|
|
state++;
|
|
|
|
}
|
|
|
|
break;
|
2020-01-01 21:29:24 +01:00
|
|
|
case 1003:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
completestop = false;
|
|
|
|
state = 0;
|
|
|
|
//music.play(music.resumesong);
|
|
|
|
if(!muted && music.currentsong>-1) music.fadeMusicVolumeIn(3000);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.showcutscenebars = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 1010:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.showcutscenebars = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
hascontrol = false;
|
|
|
|
completestop = true;
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
break;
|
2020-04-09 07:01:32 +02:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
2020-01-01 21:29:24 +01:00
|
|
|
case 1011:
|
Fix crewmate-found text boxes overlapping in flip mode
The problem was that the code seemed to be wrongly copy-pasted from the
code for generating the trinket-found text boxes (to the point where
even the comment for the crewmate-found text boxes didn't get changed
from "//Found a trinket!").
For the trinket-found text boxes, they use y-positions 85 and 135 if not
in flip mode, and y-positions 105 and 65 if the game IS in flip mode.
These text boxes are positioned correctly in flip mode.
However, for the crewmate-found text boxes, they use y-positions 85 and
135 if not in flip mode, as usual, but they use y-positions 105 and 135
if the game IS in flip mode. Looks like someone forgot to change the
second y-position when copy-pasting code around.
Which is actually a bit funny, because I can conclude from this that it
seems like the code to position these text boxes in flip mode was
bolted-on AFTER the initial code of these text boxes was written.
I can also conclude (hot take incoming) that basically no one actually
ever tested this game in flip mode (but that was already evident, given
TerryCavanagh/VVVVVV#140, less strongly TerryCavanagh/VVVVVV#141, and
TerryCavanagh/VVVVVV#142 is another flip-mode-related bug which I guess
sorta kinda doesn't really count since text outline wasn't enabled until
2.3?).
So I fixed the second y-position to be 65, just like the y-position the
trinket text boxes use. I even took the opportunity to fix the comment
to say "//Found a crewmate!" instead of "//Found a trinket!".
2020-02-16 07:58:42 +01:00
|
|
|
//Found a crewmate!
|
2020-01-01 21:29:24 +01:00
|
|
|
advancetext = true;
|
|
|
|
state++;
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Congratulations! ", 50, 105, 174, 174, 174);
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("You have found a lost crewmate!");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-09 07:13:43 +02:00
|
|
|
if(ed.numcrewmates()-crewmates()==0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All crewmates rescued! ", 50, 65, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-04-09 07:13:43 +02:00
|
|
|
else if(ed.numcrewmates()-crewmates()==1)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 07:13:43 +02:00
|
|
|
graphics.createtextbox(" " + help.number(ed.numcrewmates()-crewmates())+ " remains ", 50, 65, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-04-09 07:13:43 +02:00
|
|
|
graphics.createtextbox(" " + help.number(ed.numcrewmates()-crewmates())+ " remain ", 50, 65, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Congratulations! ", 50, 85, 174, 174, 174);
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("You have found a lost crewmate!");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-09 07:13:43 +02:00
|
|
|
if(ed.numcrewmates()-crewmates()==0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All crewmates rescued! ", 50, 135, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-04-09 07:13:43 +02:00
|
|
|
else if(ed.numcrewmates()-crewmates()==1)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 07:13:43 +02:00
|
|
|
graphics.createtextbox(" " + help.number(ed.numcrewmates()-crewmates())+ " remains ", 50, 135, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-04-09 07:13:43 +02:00
|
|
|
graphics.createtextbox(" " + help.number(ed.numcrewmates()-crewmates())+ " remain ", 50, 135, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
2020-07-05 07:55:21 +02:00
|
|
|
case 1012:
|
|
|
|
if (!advancetext)
|
|
|
|
{
|
|
|
|
// Prevent softlocks if we somehow don't have advancetext
|
|
|
|
state++;
|
|
|
|
}
|
|
|
|
break;
|
2020-01-01 21:29:24 +01:00
|
|
|
case 1013:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
completestop = false;
|
|
|
|
state = 0;
|
|
|
|
|
2020-04-09 07:13:43 +02:00
|
|
|
if(ed.numcrewmates()-crewmates()==0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if(map.custommodeforreal)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.fademode = 2;
|
2020-01-01 21:29:24 +01:00
|
|
|
if(!muted && ed.levmusic>0) music.fadeMusicVolumeIn(3000);
|
|
|
|
if(ed.levmusic>0) music.fadeout();
|
|
|
|
state=1014;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-07-11 09:00:42 +02:00
|
|
|
shouldreturntoeditor = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
if(!muted && ed.levmusic>0) music.fadeMusicVolumeIn(3000);
|
|
|
|
if(ed.levmusic>0) music.fadeout();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(!muted && ed.levmusic>0) music.fadeMusicVolumeIn(3000);
|
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.showcutscenebars = false;
|
2020-05-03 05:38:06 +02:00
|
|
|
returntomenu(Menu::levellist);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-02-10 01:53:01 +01:00
|
|
|
#endif
|
2020-01-01 21:29:24 +01:00
|
|
|
case 1014:
|
|
|
|
frames--;
|
2020-03-31 02:16:02 +02:00
|
|
|
if(graphics.fademode == 1) state++;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 1015:
|
2020-04-09 07:01:32 +02:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
2020-01-01 21:29:24 +01:00
|
|
|
//Update level stats
|
2020-04-09 07:13:43 +02:00
|
|
|
if(ed.numcrewmates()-crewmates()==0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Finished level
|
2020-04-09 07:09:11 +02:00
|
|
|
if(ed.numtrinkets()-trinkets()==0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//and got all the trinkets!
|
|
|
|
updatecustomlevelstats(customlevelfilename, 3);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
updatecustomlevelstats(customlevelfilename, 1);
|
|
|
|
}
|
|
|
|
}
|
2020-04-09 07:01:32 +02:00
|
|
|
#endif
|
2020-08-01 22:01:58 +02:00
|
|
|
quittomenu();
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 2000:
|
|
|
|
//Game Saved!
|
2020-07-11 09:23:31 +02:00
|
|
|
if (inspecial() || map.custommode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-11-04 02:58:38 +01:00
|
|
|
if (savetele())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-11-04 02:58:38 +01:00
|
|
|
graphics.createtextbox(" Game Saved ", -1, graphics.flipmode ? 202 : 12, 174, 174, 174);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxtimer(25);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-11-04 02:58:38 +01:00
|
|
|
graphics.createtextbox(" ERROR: Could not save game! ", -1, graphics.flipmode ? 202 : 12, 255, 60, 60);
|
|
|
|
graphics.textboxtimer(50);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2500:
|
|
|
|
|
|
|
|
music.play(5);
|
|
|
|
//Activating a teleporter (appear)
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 90;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 2501:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 0;
|
|
|
|
//we're done here!
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(10);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 2502:
|
2020-06-13 04:31:08 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 5;
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 04:31:08 +02:00
|
|
|
{
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
obj.entities[i].invis = false;
|
|
|
|
|
|
|
|
int j = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(j, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp = obj.entities[j].xp+44;
|
|
|
|
obj.entities[i].yp = obj.entities[j].yp+44;
|
Restore previous oldxp/oldyp variables in favor of lerpoldxp/lerpoldyp
I was investigating a desync in my Nova TAS, and it turns out that
the gravity line collision functions check for the `oldxp` and `oldyp`
of the player, i.e. their position on the previous frame, along with
their position on the current frame. So, if the player either collided
with the gravity line last frame or this frame, then the player collided
with the gravity line this frame.
Except, that's not actually true. It turns out that `oldxp` and `oldyp`
don't necessarily always correspond to the `xp` and `yp` of the player
on the previous frame. It turns out that your `oldyp` will be updated if
you stand on a vertically moving platform, before the gravity line
collision function gets ran. So, if you were colliding with a gravity
line on the previous frame, but you got moved out of there by a
vertically moving platform, then you just don't collide with the gravity
line at all.
However, this behavior changed in 2.3 after my over-30-FPS patch got
merged (#220). That patch took advantage of the existing `oldxp` and
`oldyp` entity attributes, and uses them to interpolate their positions
during rendering to make everything look real smooth.
Previously, `oldxp` and `oldyp` would both be updated in
`entityclass::updateentitylogic()`. However, I moved it in that patch to
update right before `gameinput()` in `main.cpp`.
As a result, `oldyp` no longer gets updated whenever the player stands
on a vertically moving platform. This ends up desyncing my TAS.
As expected, updating `oldyp` in `entityclass::movingplatformfix()` (the
function responsible for moving the player whenever they stand on a
vertically moving platform) makes it so that my TAS syncs, but the
visuals are glitchy when standing on a vertically moving platform. And
as much as I'd like to get rid of gravity lines checking for whether
you've collided with them on the previous frame, doing that desyncs my
TAS, too.
In the end, it seems like I should just leave `oldxp` and `oldyp` alone,
and switch to using dedicated variables that are never used in the
physics of the game. So I'm introducing `lerpoldxp` and `lerpoldyp`, and
replacing all instances of using `oldxp` and `oldyp` that my over-30-FPS
patch added, with `lerpoldxp` and `lerpoldyp` instead.
After doing this, and applying #503 as well, my Nova TAS syncs after
some minor but acceptable fixes with Viridian's walkingframe.
2020-10-10 05:58:58 +02:00
|
|
|
obj.entities[i].lerpoldxp = obj.entities[i].xp;
|
|
|
|
obj.entities[i].lerpoldyp = obj.entities[i].yp;
|
2020-06-13 05:36:08 +02:00
|
|
|
}
|
|
|
|
obj.entities[i].ay = -6;
|
|
|
|
obj.entities[i].ax = 6;
|
|
|
|
obj.entities[i].vy = -6;
|
|
|
|
obj.entities[i].vx = 6;
|
2020-06-13 04:31:08 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
i = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 04:31:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 1;
|
|
|
|
obj.entities[i].colour = 101;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-06-13 04:31:08 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 2503:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 2504:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
//obj.entities[i].xp += 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 2505:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 8;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 2506:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 2507:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
//obj.entities[i].xp += 4;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 2508:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 2;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 2509:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 1;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 2510:
|
|
|
|
advancetext = true;
|
|
|
|
hascontrol = false;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Hello?", 125+24, 152-20, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(11);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxactive();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 2512:
|
|
|
|
advancetext = true;
|
|
|
|
hascontrol = false;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Is anyone there?", 125+8, 152-24, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(11);
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxactive();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 2514:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
|
|
|
|
state = 0;
|
|
|
|
music.play(3);
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 3000:
|
|
|
|
//Activating a teleporter (long version for level complete)
|
|
|
|
state++;
|
|
|
|
statedelay = 30;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 90;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3001:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3002:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3003:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3004:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 0;
|
|
|
|
//we're done here!
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(10);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3005:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 50;
|
|
|
|
//testing!
|
|
|
|
//state = 3006; //Warp Zone
|
|
|
|
//state = 3020; //Space Station
|
|
|
|
switch(companion)
|
|
|
|
{
|
|
|
|
case 6:
|
|
|
|
state = 3006;
|
|
|
|
break; //Warp Zone
|
|
|
|
case 7:
|
|
|
|
state = 3020;
|
|
|
|
break; //Space Station
|
|
|
|
case 8:
|
|
|
|
state = 3040;
|
|
|
|
break; //Lab
|
|
|
|
case 9:
|
|
|
|
state = 3060;
|
|
|
|
break; //Tower
|
|
|
|
case 10:
|
|
|
|
state = 3080;
|
|
|
|
break; //Intermission 2
|
|
|
|
case 11:
|
|
|
|
state = 3085;
|
|
|
|
break; //Intermission 1
|
|
|
|
}
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
obj.entities[i].invis = true;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-02 00:32:21 +02:00
|
|
|
i = obj.getcompanion();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if(INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-03 22:50:16 +02:00
|
|
|
obj.removeentity(i);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
i = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 04:31:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 1;
|
|
|
|
obj.entities[i].colour = 100;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
case 3006:
|
|
|
|
//Level complete! (warp zone)
|
2020-03-31 02:16:02 +02:00
|
|
|
unlocknum(4);
|
2020-01-01 21:29:24 +01:00
|
|
|
lastsaved = 4;
|
|
|
|
music.play(0);
|
|
|
|
state++;
|
|
|
|
statedelay = 75;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 180, 165, 165, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 12, 165, 165, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
//graphics.addline(" Level Complete! ");
|
|
|
|
graphics.addline(" ");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
/* advancetext = true;
|
|
|
|
hascontrol = false;
|
|
|
|
state = 3;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("To do: write quick", 50, 80, 164, 164, 255);
|
|
|
|
graphics.addline("intro to story!");*/
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3007:
|
|
|
|
state++;
|
|
|
|
statedelay = 45;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 104, 175,174,174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 64+8+16, 175,174,174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" You have rescued ");
|
|
|
|
graphics.addline(" a crew member! ");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3008:
|
2020-04-17 01:08:56 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 45;
|
|
|
|
|
2020-04-17 01:08:56 +02:00
|
|
|
int temp = 6 - crewrescued();
|
2020-01-01 21:29:24 +01:00
|
|
|
if (temp == 1)
|
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = " One remains ";
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (temp > 0)
|
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = " " + help.number(temp) + " remain ";
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All Crew Members Rescued! ", -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All Crew Members Rescued! ", -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-04-17 01:08:56 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 3009:
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ACTION to continue ", -1, 20, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ACTION to continue ", -1, 196, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3010:
|
|
|
|
if (jumppressed)
|
|
|
|
{
|
|
|
|
state++;
|
|
|
|
statedelay = 30;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3011:
|
|
|
|
state = 3070;
|
|
|
|
statedelay = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3020:
|
|
|
|
//Level complete! (Space Station 2)
|
2020-03-31 02:16:02 +02:00
|
|
|
unlocknum(3);
|
2020-01-01 21:29:24 +01:00
|
|
|
lastsaved = 2;
|
|
|
|
music.play(0);
|
|
|
|
state++;
|
|
|
|
statedelay = 75;
|
|
|
|
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 180, 165, 165, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 12, 165, 165, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
//graphics.addline(" Level Complete! ");
|
|
|
|
graphics.addline(" ");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
/* advancetext = true;
|
|
|
|
hascontrol = false;
|
|
|
|
state = 3;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("To do: write quick", 50, 80, 164, 164, 255);
|
|
|
|
graphics.addline("intro to story!");*/
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3021:
|
|
|
|
state++;
|
|
|
|
statedelay = 45;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 104, 174,175,174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 64+8+16, 174,175,174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" You have rescued ");
|
|
|
|
graphics.addline(" a crew member! ");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3022:
|
2020-04-17 01:08:56 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 45;
|
|
|
|
|
2020-04-17 01:08:56 +02:00
|
|
|
int temp = 6 - crewrescued();
|
2020-01-01 21:29:24 +01:00
|
|
|
if (temp == 1)
|
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = " One remains ";
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (temp > 0)
|
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = " " + help.number(temp) + " remain ";
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All Crew Members Rescued! ", -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All Crew Members Rescued! ", -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-04-17 01:08:56 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 3023:
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ACTION to continue ", -1, 20, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ACTION to continue ", -1, 196, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3024:
|
|
|
|
if (jumppressed)
|
|
|
|
{
|
|
|
|
state++;
|
|
|
|
statedelay = 30;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3025:
|
|
|
|
state = 3070;
|
|
|
|
statedelay = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3040:
|
|
|
|
//Level complete! (Lab)
|
2020-03-31 02:16:02 +02:00
|
|
|
unlocknum(1);
|
2020-01-01 21:29:24 +01:00
|
|
|
lastsaved = 5;
|
|
|
|
music.play(0);
|
|
|
|
state++;
|
|
|
|
statedelay = 75;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 180, 165, 165, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 12, 165, 165, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
//graphics.addline(" Level Complete! ");
|
|
|
|
graphics.addline(" ");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
/* advancetext = true;
|
|
|
|
hascontrol = false;
|
|
|
|
state = 3;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("To do: write quick", 50, 80, 164, 164, 255);
|
|
|
|
graphics.addline("intro to story!");*/
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3041:
|
|
|
|
state++;
|
|
|
|
statedelay = 45;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 104, 174,174,175);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 64+8+16, 174,174,175);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" You have rescued ");
|
|
|
|
graphics.addline(" a crew member! ");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3042:
|
2020-04-17 01:08:56 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 45;
|
|
|
|
|
2020-04-17 01:08:56 +02:00
|
|
|
int temp = 6 - crewrescued();
|
2020-01-01 21:29:24 +01:00
|
|
|
if (temp == 1)
|
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = " One remains ";
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (temp > 0)
|
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = " " + help.number(temp) + " remain ";
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All Crew Members Rescued! ", -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All Crew Members Rescued! ", -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-04-17 01:08:56 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 3043:
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ACTION to continue ", -1, 20, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ACTION to continue ", -1, 196, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3044:
|
|
|
|
if (jumppressed)
|
|
|
|
{
|
|
|
|
state++;
|
|
|
|
statedelay = 30;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3045:
|
|
|
|
state = 3070;
|
|
|
|
statedelay = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3050:
|
|
|
|
//Level complete! (Space Station 1)
|
2020-03-31 02:16:02 +02:00
|
|
|
unlocknum(0);
|
2020-01-01 21:29:24 +01:00
|
|
|
lastsaved = 1;
|
|
|
|
music.play(0);
|
|
|
|
state++;
|
|
|
|
statedelay = 75;
|
|
|
|
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 180, 165, 165, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 12, 165, 165, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
//graphics.addline(" Level Complete! ");
|
|
|
|
graphics.addline(" ");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
/* advancetext = true;
|
|
|
|
hascontrol = false;
|
|
|
|
state = 3;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("To do: write quick", 50, 80, 164, 164, 255);
|
|
|
|
graphics.addline("intro to story!");*/
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3051:
|
|
|
|
state++;
|
|
|
|
statedelay = 45;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 104, 175,175,174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 64+8+16, 175,175,174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" You have rescued ");
|
|
|
|
graphics.addline(" a crew member! ");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3052:
|
2020-04-17 01:08:56 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 45;
|
|
|
|
|
2020-04-17 01:08:56 +02:00
|
|
|
int temp = 6 - crewrescued();
|
2020-01-01 21:29:24 +01:00
|
|
|
if (temp == 1)
|
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = " One remains ";
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (temp > 0)
|
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = " " + help.number(temp) + " remain ";
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All Crew Members Rescued! ", -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All Crew Members Rescued! ", -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-04-17 01:08:56 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 3053:
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ACTION to continue ", -1, 20, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ACTION to continue ", -1, 196, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3054:
|
|
|
|
if (jumppressed)
|
|
|
|
{
|
|
|
|
state++;
|
|
|
|
statedelay = 30;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
teleportscript = "";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3055:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.fademode = 2;
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 10;
|
|
|
|
break;
|
|
|
|
case 3056:
|
2020-03-31 02:16:02 +02:00
|
|
|
if(graphics.fademode==1)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
startscript = true;
|
Fix unwinnable save from rescuing Violet out of order
You're intended to rescue Violet first, and not second, third, or
fourth, and especially not last.
If you rescue her second, third, or fourth, your crewmate progress will
be reset, but you won't be able to re-rescue them again. This is because
Vitellary, Verdigris, Victoria, and Vermilion will be temporarily marked
as rescued during the `bigopenworld` cutscene, so duplicate versions of
them don't spawn during the cutscene, and then will be marked as missing
later to undo it.
This first issue can be trivially fixed by simply toggling flags to
prevent duplicates of them from spawning during the cutscene instead of
fiddling with their rescue statuses.
However, there is still another issue. If you rescue Violet last, then
you won't be warped to the Final Level, meaning you can't properly
complete the game. This can be fixed by adding a `crewrescued() == 6`
check to the Space Station 1 Level Complete cutscene. There is
additionally a temporary unrescuing of Violet so she doesn't get
duplicated during the `bigopenworld` cutscene, and I've had to move that
to the start of the `bigopenworld` and `bigopenworldskip` scripts,
otherwise the `crewrescued() == 6` check won't work properly.
I haven't added hooks for Intermission 1 or 2 because you're not really
meant to play the intermissions with Violet (but you probably could
anyway, there'd just be no dialogue).
Oh, and the pre-Final Level cutscene expects the player to already be
hidden before it starts playing, but if you rescue Violet last the
player is still visible, so I've fixed that. But there still ends up
being two Violets, so I'll probably replace it with a special cutscene
later that's not so nonsensical.
2020-08-09 01:09:55 +02:00
|
|
|
if (crewrescued() == 6)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Fix unwinnable save from rescuing Violet out of order
You're intended to rescue Violet first, and not second, third, or
fourth, and especially not last.
If you rescue her second, third, or fourth, your crewmate progress will
be reset, but you won't be able to re-rescue them again. This is because
Vitellary, Verdigris, Victoria, and Vermilion will be temporarily marked
as rescued during the `bigopenworld` cutscene, so duplicate versions of
them don't spawn during the cutscene, and then will be marked as missing
later to undo it.
This first issue can be trivially fixed by simply toggling flags to
prevent duplicates of them from spawning during the cutscene instead of
fiddling with their rescue statuses.
However, there is still another issue. If you rescue Violet last, then
you won't be warped to the Final Level, meaning you can't properly
complete the game. This can be fixed by adding a `crewrescued() == 6`
check to the Space Station 1 Level Complete cutscene. There is
additionally a temporary unrescuing of Violet so she doesn't get
duplicated during the `bigopenworld` cutscene, and I've had to move that
to the start of the `bigopenworld` and `bigopenworldskip` scripts,
otherwise the `crewrescued() == 6` check won't work properly.
I haven't added hooks for Intermission 1 or 2 because you're not really
meant to play the intermissions with Violet (but you probably could
anyway, there'd just be no dialogue).
Oh, and the pre-Final Level cutscene expects the player to already be
hidden before it starts playing, but if you rescue Violet last the
player is still visible, so I've fixed that. But there still ends up
being two Violets, so I'll probably replace it with a special cutscene
later that's not so nonsensical.
2020-08-09 01:09:55 +02:00
|
|
|
newscript = "startlevel_final";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Fix unwinnable save from rescuing Violet out of order
You're intended to rescue Violet first, and not second, third, or
fourth, and especially not last.
If you rescue her second, third, or fourth, your crewmate progress will
be reset, but you won't be able to re-rescue them again. This is because
Vitellary, Verdigris, Victoria, and Vermilion will be temporarily marked
as rescued during the `bigopenworld` cutscene, so duplicate versions of
them don't spawn during the cutscene, and then will be marked as missing
later to undo it.
This first issue can be trivially fixed by simply toggling flags to
prevent duplicates of them from spawning during the cutscene instead of
fiddling with their rescue statuses.
However, there is still another issue. If you rescue Violet last, then
you won't be warped to the Final Level, meaning you can't properly
complete the game. This can be fixed by adding a `crewrescued() == 6`
check to the Space Station 1 Level Complete cutscene. There is
additionally a temporary unrescuing of Violet so she doesn't get
duplicated during the `bigopenworld` cutscene, and I've had to move that
to the start of the `bigopenworld` and `bigopenworldskip` scripts,
otherwise the `crewrescued() == 6` check won't work properly.
I haven't added hooks for Intermission 1 or 2 because you're not really
meant to play the intermissions with Violet (but you probably could
anyway, there'd just be no dialogue).
Oh, and the pre-Final Level cutscene expects the player to already be
hidden before it starts playing, but if you rescue Violet last the
player is still visible, so I've fixed that. But there still ends up
being two Violets, so I'll probably replace it with a special cutscene
later that's not so nonsensical.
2020-08-09 01:09:55 +02:00
|
|
|
if (nocutscenes)
|
|
|
|
{
|
|
|
|
newscript="bigopenworldskip";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
newscript = "bigopenworld";
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 3060:
|
|
|
|
//Level complete! (Tower)
|
2020-03-31 02:16:02 +02:00
|
|
|
unlocknum(2);
|
2020-01-01 21:29:24 +01:00
|
|
|
lastsaved = 3;
|
|
|
|
music.play(0);
|
|
|
|
state++;
|
|
|
|
statedelay = 75;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 180, 165, 165, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 12, 165, 165, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
//graphics.addline(" Level Complete! ");
|
|
|
|
graphics.addline(" ");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
/* advancetext = true;
|
|
|
|
hascontrol = false;
|
|
|
|
state = 3;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("To do: write quick", 50, 80, 164, 164, 255);
|
|
|
|
graphics.addline("intro to story!");*/
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3061:
|
|
|
|
state++;
|
|
|
|
statedelay = 45;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 104, 175,174,175);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 64+8+16, 175,174,175);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" You have rescued ");
|
|
|
|
graphics.addline(" a crew member! ");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3062:
|
2020-04-17 01:08:56 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 45;
|
|
|
|
|
2020-04-17 01:08:56 +02:00
|
|
|
int temp = 6 - crewrescued();
|
2020-01-01 21:29:24 +01:00
|
|
|
if (temp == 1)
|
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = " One remains ";
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (temp > 0)
|
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = " " + help.number(temp) + " remain ";
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All Crew Members Rescued! ", -1, 72, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All Crew Members Rescued! ", -1, 128+16, 174, 174, 174);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-04-17 01:08:56 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 3063:
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ACTION to continue ", -1, 20, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ACTION to continue ", -1, 196, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3064:
|
|
|
|
if (jumppressed)
|
|
|
|
{
|
|
|
|
state++;
|
|
|
|
statedelay = 30;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3065:
|
|
|
|
state = 3070;
|
|
|
|
statedelay = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 3070:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.fademode = 2;
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
break;
|
|
|
|
case 3071:
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.fademode == 1) state++;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3072:
|
|
|
|
//Ok, we need to adjust some flags based on who've we've rescued. Some of there conversation options
|
|
|
|
//change depending on when they get back to the ship.
|
|
|
|
if (lastsaved == 2)
|
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
if (crewstats[3]) obj.flags[25] = true;
|
|
|
|
if (crewstats[4]) obj.flags[26] = true;
|
|
|
|
if (crewstats[5]) obj.flags[24] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (lastsaved == 3)
|
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
if (crewstats[2]) obj.flags[50] = true;
|
|
|
|
if (crewstats[4]) obj.flags[49] = true;
|
|
|
|
if (crewstats[5]) obj.flags[48] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (lastsaved == 4)
|
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
if (crewstats[2]) obj.flags[54] = true;
|
|
|
|
if (crewstats[3]) obj.flags[55] = true;
|
|
|
|
if (crewstats[5]) obj.flags[56] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (lastsaved == 5)
|
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
if (crewstats[2]) obj.flags[37] = true;
|
|
|
|
if (crewstats[3]) obj.flags[38] = true;
|
|
|
|
if (crewstats[4]) obj.flags[39] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
//We're pitch black now, make a decision
|
|
|
|
companion = 0;
|
|
|
|
if (crewrescued() == 6)
|
|
|
|
{
|
|
|
|
startscript = true;
|
|
|
|
newscript="startlevel_final";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
else if (crewrescued() == 4)
|
|
|
|
{
|
|
|
|
companion = 11;
|
|
|
|
supercrewmate = true;
|
|
|
|
scmprogress = 0;
|
|
|
|
|
|
|
|
startscript = true;
|
|
|
|
newscript = "intermission_1";
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[19] = true;
|
|
|
|
if (lastsaved == 2) obj.flags[32] = true;
|
|
|
|
if (lastsaved == 3) obj.flags[35] = true;
|
|
|
|
if (lastsaved == 4) obj.flags[34] = true;
|
|
|
|
if (lastsaved == 5) obj.flags[33] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
else if (crewrescued() == 5)
|
|
|
|
{
|
|
|
|
startscript = true;
|
|
|
|
newscript = "intermission_2";
|
2020-04-09 08:34:26 +02:00
|
|
|
obj.flags[20] = true;
|
|
|
|
if (lastsaved == 2) obj.flags[32] = true;
|
|
|
|
if (lastsaved == 3) obj.flags[35] = true;
|
|
|
|
if (lastsaved == 4) obj.flags[34] = true;
|
|
|
|
if (lastsaved == 5) obj.flags[33] = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
startscript = true;
|
|
|
|
newscript="regularreturn";
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3080:
|
|
|
|
//returning from an intermission, very like 3070
|
|
|
|
if (inintermission)
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.fademode = 2;
|
2020-01-01 21:29:24 +01:00
|
|
|
companion = 0;
|
2020-04-17 06:25:30 +02:00
|
|
|
returnmenu();
|
2020-01-01 21:29:24 +01:00
|
|
|
state=3100;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
unlocknum(7);
|
|
|
|
graphics.fademode = 2;
|
2020-01-01 21:29:24 +01:00
|
|
|
companion = 0;
|
|
|
|
state++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3081:
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.fademode == 1) state++;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3082:
|
|
|
|
map.finalmode = false;
|
|
|
|
startscript = true;
|
|
|
|
newscript="regularreturn";
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3085:
|
|
|
|
//returning from an intermission, very like 3070
|
|
|
|
//return to menu from here
|
|
|
|
if (inintermission)
|
|
|
|
{
|
|
|
|
companion = 0;
|
|
|
|
supercrewmate = false;
|
|
|
|
state++;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.fademode = 2;
|
2020-01-01 21:29:24 +01:00
|
|
|
music.fadeout();
|
2020-04-17 06:25:30 +02:00
|
|
|
returnmenu();
|
2020-01-01 21:29:24 +01:00
|
|
|
state=3100;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
unlocknum(6);
|
|
|
|
graphics.fademode = 2;
|
2020-01-01 21:29:24 +01:00
|
|
|
companion = 0;
|
|
|
|
supercrewmate = false;
|
|
|
|
state++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3086:
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.fademode == 1) state++;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3087:
|
|
|
|
map.finalmode = false;
|
|
|
|
startscript = true;
|
|
|
|
newscript="regularreturn";
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3100:
|
2020-03-31 02:16:02 +02:00
|
|
|
if(graphics.fademode == 1) state++;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3101:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.flipmode = false;
|
2020-04-17 05:15:53 +02:00
|
|
|
gamestate = TITLEMODE;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.fademode = 4;
|
|
|
|
graphics.backgrounddrawn = true;
|
2020-11-03 00:23:53 +01:00
|
|
|
graphics.titlebg.tdrawback = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
music.play(6);
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
//startscript = true; newscript="returntohub";
|
|
|
|
//state = 0;
|
|
|
|
|
|
|
|
/*case 3025:
|
|
|
|
if (recording == 1) {
|
|
|
|
//if recording the input, output it to debug here
|
|
|
|
trace(recordstring);
|
|
|
|
help.toclipboard(recordstring);
|
|
|
|
}
|
|
|
|
test = true; teststring = recordstring;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Congratulations! ", 50, 80, 164, 164, 255);
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("Your play of this level has");
|
|
|
|
graphics.addline("been copied to the clipboard.");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("Please consider pasting and");
|
|
|
|
graphics.addline("sending it to me! Even if you");
|
|
|
|
graphics.addline("made a lot of mistakes - knowing");
|
|
|
|
graphics.addline("exactly where people are having");
|
|
|
|
graphics.addline("trouble is extremely useful!");
|
|
|
|
graphics.textboxcenter();
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
break;*/
|
|
|
|
|
|
|
|
case 3500:
|
|
|
|
music.fadeout();
|
|
|
|
state++;
|
|
|
|
statedelay = 120;
|
|
|
|
//state = 3511; //testing
|
|
|
|
break;
|
|
|
|
case 3501:
|
|
|
|
//Game complete!
|
2020-08-01 21:49:07 +02:00
|
|
|
unlockAchievement("vvvvvvgamecomplete");
|
2020-03-31 02:16:02 +02:00
|
|
|
unlocknum(5);
|
2020-01-01 21:29:24 +01:00
|
|
|
crewstats[0] = true;
|
|
|
|
state++;
|
|
|
|
statedelay = 75;
|
|
|
|
music.play(7);
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 180, 164, 165, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Remove allowspecial, replace with opaqueness check
When I added the over-30-FPS mode, I kept running into this problem
where the special images of text boxes would render during the
deltaframes of fade-in/fade-out animations, even though they shouldn't
be. So I simply added a flag to the text box that enables drawing these
special images.
However, this doesn't solve the problem fully, and there's still a small
chance that a special-image text box could draw another special image
during its deltaframes. It's really rare and you have to have your
deltaframe luck juuuuuust right (or you could use libTAS, probably), but
it helps to be in 40% slowmode and have a high refresh rate (which, if
it isn't a multiple of 30, you should disable VSync, too, in order to
not have a low framerate).
So instead, special images will only be drawn if the text box has fully
faded in completely. That solves the issue completely.
2020-08-05 05:42:43 +02:00
|
|
|
graphics.createtextbox("", -1, 12, 164, 165, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.addline(" ");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.addline("");
|
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3502:
|
|
|
|
state++;
|
|
|
|
statedelay = 45+15;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All Crew Members Rescued! ", -1, 175-24, 0, 0, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" All Crew Members Rescued! ", -1, 64, 0, 0, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
savetime = timestring();
|
2020-06-30 00:53:19 +02:00
|
|
|
savetime += "." + help.twodigits(frames*100 / 30);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3503:
|
2020-04-03 00:05:41 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 45;
|
|
|
|
|
2020-04-07 08:46:27 +02:00
|
|
|
std::string tempstring = help.number(trinkets());
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Trinkets Found:", 48, 155-24, 0,0,0);
|
|
|
|
graphics.createtextbox(tempstring, 180, 155-24, 0, 0, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Trinkets Found:", 48, 84, 0,0,0);
|
|
|
|
graphics.createtextbox(tempstring, 180, 84, 0, 0, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
2020-04-03 00:05:41 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 3504:
|
2020-04-03 00:05:41 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 45+15;
|
|
|
|
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = savetime;
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Game Time:", 64, 143-24, 0,0,0);
|
|
|
|
graphics.createtextbox(tempstring, 180, 143-24, 0, 0, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Game Time:", 64, 96, 0,0,0);
|
|
|
|
graphics.createtextbox(tempstring, 180, 96, 0, 0, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
2020-04-03 00:05:41 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 3505:
|
|
|
|
state++;
|
|
|
|
statedelay = 45;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Total Flips:", 64, 116-24, 0,0,0);
|
|
|
|
graphics.createtextbox(help.String(totalflips), 180, 116-24, 0, 0, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Total Flips:", 64, 123, 0,0,0);
|
|
|
|
graphics.createtextbox(help.String(totalflips), 180, 123, 0, 0, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3506:
|
|
|
|
state++;
|
|
|
|
statedelay = 45+15;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Total Deaths:", 64, 104-24, 0,0,0);
|
|
|
|
graphics.createtextbox(help.String(deathcounts), 180, 104-24, 0, 0, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox("Total Deaths:", 64, 135, 0,0,0);
|
|
|
|
graphics.createtextbox(help.String(deathcounts), 180, 135, 0, 0, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3507:
|
2020-04-03 00:05:41 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 45+15;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = "Hardest Room (with " + help.String(hardestroomdeaths) + " deaths)";
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 81-24, 0,0,0);
|
|
|
|
graphics.createtextbox(hardestroom, -1, 69-24, 0, 0, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = "Hardest Room (with " + help.String(hardestroomdeaths) + " deaths)";
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(tempstring, -1, 158, 0,0,0);
|
|
|
|
graphics.createtextbox(hardestroom, -1, 170, 0, 0, 0);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
2020-04-03 00:05:41 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 3508:
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.flipmode)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ACTION to continue ", -1, 20, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.createtextbox(" Press ACTION to continue ", -1, 196, 164, 164, 255);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxcenterx();
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3509:
|
|
|
|
if (jumppressed)
|
|
|
|
{
|
|
|
|
state++;
|
|
|
|
statedelay = 30;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.textboxremove();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3510:
|
|
|
|
//Save stats and stuff here
|
2020-04-09 08:34:26 +02:00
|
|
|
if (!obj.flags[73])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//flip mode complete
|
2020-08-01 21:49:07 +02:00
|
|
|
unlockAchievement("vvvvvvgamecompleteflip");
|
2020-07-03 04:56:26 +02:00
|
|
|
unlocknum(19);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (bestgamedeaths == -1)
|
|
|
|
{
|
|
|
|
bestgamedeaths = deathcounts;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (deathcounts < bestgamedeaths)
|
|
|
|
{
|
|
|
|
bestgamedeaths = deathcounts;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-02 22:01:55 +02:00
|
|
|
if (bestgamedeaths > -1) {
|
|
|
|
if (bestgamedeaths <= 500) {
|
2020-08-01 21:49:07 +02:00
|
|
|
unlockAchievement("vvvvvvcomplete500");
|
2020-04-02 22:01:55 +02:00
|
|
|
}
|
|
|
|
if (bestgamedeaths <= 250) {
|
2020-08-01 21:49:07 +02:00
|
|
|
unlockAchievement("vvvvvvcomplete250");
|
2020-04-02 22:01:55 +02:00
|
|
|
}
|
|
|
|
if (bestgamedeaths <= 100) {
|
2020-08-01 21:49:07 +02:00
|
|
|
unlockAchievement("vvvvvvcomplete100");
|
2020-04-02 22:01:55 +02:00
|
|
|
}
|
|
|
|
if (bestgamedeaths <= 50) {
|
2020-08-01 21:49:07 +02:00
|
|
|
unlockAchievement("vvvvvvcomplete50");
|
2020-04-02 22:01:55 +02:00
|
|
|
}
|
|
|
|
}
|
2020-04-02 21:41:33 +02:00
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
savestats();
|
2020-01-01 21:29:24 +01:00
|
|
|
if (nodeathmode)
|
|
|
|
{
|
2020-08-01 21:49:07 +02:00
|
|
|
unlockAchievement("vvvvvvmaster"); //bloody hell
|
2020-07-03 04:56:26 +02:00
|
|
|
unlocknum(20);
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 3520;
|
|
|
|
statedelay = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
statedelay = 120;
|
|
|
|
state++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3511:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter (long version for level complete)
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].colour = 102;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
state++;
|
|
|
|
statedelay = 30;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 90;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 3512:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3513:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3514:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3515:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 0;
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
obj.entities[i].invis = true;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//we're done here!
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(10);
|
2020-01-01 21:29:24 +01:00
|
|
|
statedelay = 60;
|
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 3516:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.fademode = 2;
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
break;
|
|
|
|
case 3517:
|
2020-03-31 02:16:02 +02:00
|
|
|
if (graphics.fademode == 1)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
state++;
|
|
|
|
statedelay = 30;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3518:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.fademode = 4;
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
statedelay = 30;
|
|
|
|
//music.play(5);
|
|
|
|
//music.play(10);
|
|
|
|
|
|
|
|
map.finalmode = false;
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 0;
|
|
|
|
map.finalstretch = false;
|
|
|
|
map.finalx = 100;
|
|
|
|
map.finaly = 100;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.cutscenebarspos = 320;
|
2020-04-29 02:16:24 +02:00
|
|
|
graphics.oldcutscenebarspos = 320;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
teleport_to_new_area = true;
|
|
|
|
teleportscript = "gamecomplete";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3520:
|
|
|
|
//NO DEATH MODE COMPLETE JESUS
|
|
|
|
hascontrol = false;
|
|
|
|
crewstats[0] = true;
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.fademode = 2;
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
break;
|
|
|
|
case 3521:
|
2020-03-31 02:16:02 +02:00
|
|
|
if(graphics.fademode == 1) state++;
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3522:
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.flipmode = false;
|
2020-04-17 05:15:53 +02:00
|
|
|
gamestate = TITLEMODE;
|
2020-03-31 02:16:02 +02:00
|
|
|
graphics.fademode = 4;
|
|
|
|
graphics.backgrounddrawn = true;
|
2020-11-03 00:23:53 +01:00
|
|
|
graphics.titlebg.tdrawback = true;
|
2020-04-16 06:53:36 +02:00
|
|
|
createmenu(Menu::nodeathmodecomplete);
|
2020-01-01 21:29:24 +01:00
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 4000:
|
|
|
|
//Activating a teleporter (short version)
|
|
|
|
state++;
|
|
|
|
statedelay = 10;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 10;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4001:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 0;
|
|
|
|
//we're done here!
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(10);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4002:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 10;
|
|
|
|
//testing!
|
|
|
|
//state = 3006; //Warp Zone
|
|
|
|
//state = 3020; //Space Station
|
|
|
|
//state = 3040; //Lab
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
obj.entities[i].invis = true;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
i = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if(INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
obj.entities[i].tile = 1;
|
|
|
|
obj.entities[i].colour = 100;
|
|
|
|
}
|
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4003:
|
|
|
|
state = 0;
|
|
|
|
statedelay = 0;
|
|
|
|
teleport_to_new_area = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 4010:
|
|
|
|
//Activating a teleporter (default appear)
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 90;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4011:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 0;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(10);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4012:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 5;
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
|
|
|
int j = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(j, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp = obj.entities[j].xp+44;
|
|
|
|
obj.entities[i].yp = obj.entities[j].yp+44;
|
Restore previous oldxp/oldyp variables in favor of lerpoldxp/lerpoldyp
I was investigating a desync in my Nova TAS, and it turns out that
the gravity line collision functions check for the `oldxp` and `oldyp`
of the player, i.e. their position on the previous frame, along with
their position on the current frame. So, if the player either collided
with the gravity line last frame or this frame, then the player collided
with the gravity line this frame.
Except, that's not actually true. It turns out that `oldxp` and `oldyp`
don't necessarily always correspond to the `xp` and `yp` of the player
on the previous frame. It turns out that your `oldyp` will be updated if
you stand on a vertically moving platform, before the gravity line
collision function gets ran. So, if you were colliding with a gravity
line on the previous frame, but you got moved out of there by a
vertically moving platform, then you just don't collide with the gravity
line at all.
However, this behavior changed in 2.3 after my over-30-FPS patch got
merged (#220). That patch took advantage of the existing `oldxp` and
`oldyp` entity attributes, and uses them to interpolate their positions
during rendering to make everything look real smooth.
Previously, `oldxp` and `oldyp` would both be updated in
`entityclass::updateentitylogic()`. However, I moved it in that patch to
update right before `gameinput()` in `main.cpp`.
As a result, `oldyp` no longer gets updated whenever the player stands
on a vertically moving platform. This ends up desyncing my TAS.
As expected, updating `oldyp` in `entityclass::movingplatformfix()` (the
function responsible for moving the player whenever they stand on a
vertically moving platform) makes it so that my TAS syncs, but the
visuals are glitchy when standing on a vertically moving platform. And
as much as I'd like to get rid of gravity lines checking for whether
you've collided with them on the previous frame, doing that desyncs my
TAS, too.
In the end, it seems like I should just leave `oldxp` and `oldyp` alone,
and switch to using dedicated variables that are never used in the
physics of the game. So I'm introducing `lerpoldxp` and `lerpoldyp`, and
replacing all instances of using `oldxp` and `oldyp` that my over-30-FPS
patch added, with `lerpoldxp` and `lerpoldyp` instead.
After doing this, and applying #503 as well, my Nova TAS syncs after
some minor but acceptable fixes with Viridian's walkingframe.
2020-10-10 05:58:58 +02:00
|
|
|
obj.entities[i].lerpoldxp = obj.entities[i].xp;
|
|
|
|
obj.entities[i].lerpoldyp = obj.entities[i].yp;
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[j].tile = 2;
|
|
|
|
obj.entities[j].colour = 101;
|
|
|
|
}
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
obj.entities[i].invis = false;
|
|
|
|
obj.entities[i].dir = 1;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[i].ay = -6;
|
|
|
|
obj.entities[i].ax = 6;
|
|
|
|
obj.entities[i].vy = -6;
|
|
|
|
obj.entities[i].vx = 6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4013:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4014:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4015:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 8;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4016:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4017:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 3;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4018:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 1;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4019:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
if (intimetrial || nodeathmode || inintermission)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 02:16:02 +02:00
|
|
|
savetele();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getteleporter();
|
2020-01-01 21:29:24 +01:00
|
|
|
activetele = true;
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 04:31:08 +02:00
|
|
|
{
|
|
|
|
teleblock.x = obj.entities[i].xp - 32;
|
|
|
|
teleblock.y = obj.entities[i].yp - 32;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
teleblock.w = 160;
|
|
|
|
teleblock.h = 160;
|
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
state = 0;
|
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
case 4020:
|
|
|
|
//Activating a teleporter (default appear)
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 90;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4021:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 0;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(10);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4022:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 5;
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
|
|
|
int j = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(j, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp = obj.entities[j].xp+44;
|
|
|
|
obj.entities[i].yp = obj.entities[j].yp+44;
|
Restore previous oldxp/oldyp variables in favor of lerpoldxp/lerpoldyp
I was investigating a desync in my Nova TAS, and it turns out that
the gravity line collision functions check for the `oldxp` and `oldyp`
of the player, i.e. their position on the previous frame, along with
their position on the current frame. So, if the player either collided
with the gravity line last frame or this frame, then the player collided
with the gravity line this frame.
Except, that's not actually true. It turns out that `oldxp` and `oldyp`
don't necessarily always correspond to the `xp` and `yp` of the player
on the previous frame. It turns out that your `oldyp` will be updated if
you stand on a vertically moving platform, before the gravity line
collision function gets ran. So, if you were colliding with a gravity
line on the previous frame, but you got moved out of there by a
vertically moving platform, then you just don't collide with the gravity
line at all.
However, this behavior changed in 2.3 after my over-30-FPS patch got
merged (#220). That patch took advantage of the existing `oldxp` and
`oldyp` entity attributes, and uses them to interpolate their positions
during rendering to make everything look real smooth.
Previously, `oldxp` and `oldyp` would both be updated in
`entityclass::updateentitylogic()`. However, I moved it in that patch to
update right before `gameinput()` in `main.cpp`.
As a result, `oldyp` no longer gets updated whenever the player stands
on a vertically moving platform. This ends up desyncing my TAS.
As expected, updating `oldyp` in `entityclass::movingplatformfix()` (the
function responsible for moving the player whenever they stand on a
vertically moving platform) makes it so that my TAS syncs, but the
visuals are glitchy when standing on a vertically moving platform. And
as much as I'd like to get rid of gravity lines checking for whether
you've collided with them on the previous frame, doing that desyncs my
TAS, too.
In the end, it seems like I should just leave `oldxp` and `oldyp` alone,
and switch to using dedicated variables that are never used in the
physics of the game. So I'm introducing `lerpoldxp` and `lerpoldyp`, and
replacing all instances of using `oldxp` and `oldyp` that my over-30-FPS
patch added, with `lerpoldxp` and `lerpoldyp` instead.
After doing this, and applying #503 as well, my Nova TAS syncs after
some minor but acceptable fixes with Viridian's walkingframe.
2020-10-10 05:58:58 +02:00
|
|
|
obj.entities[i].lerpoldxp = obj.entities[i].xp;
|
|
|
|
obj.entities[i].lerpoldyp = obj.entities[i].yp;
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[j].tile = 2;
|
|
|
|
obj.entities[j].colour = 101;
|
|
|
|
}
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
obj.entities[i].invis = false;
|
|
|
|
obj.entities[i].dir = 1;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[i].ay = -6;
|
|
|
|
obj.entities[i].ax = 6;
|
|
|
|
obj.entities[i].vy = -6;
|
|
|
|
obj.entities[i].vx = 6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4023:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 12;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4024:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 12;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4025:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4026:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 8;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4027:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 5;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4028:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 2;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4029:
|
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 4030:
|
|
|
|
//Activating a teleporter (default appear)
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 90;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4031:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 0;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(10);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4032:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 5;
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
|
|
|
int j = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(j, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp = obj.entities[j].xp+44;
|
|
|
|
obj.entities[i].yp = obj.entities[j].yp+44;
|
Restore previous oldxp/oldyp variables in favor of lerpoldxp/lerpoldyp
I was investigating a desync in my Nova TAS, and it turns out that
the gravity line collision functions check for the `oldxp` and `oldyp`
of the player, i.e. their position on the previous frame, along with
their position on the current frame. So, if the player either collided
with the gravity line last frame or this frame, then the player collided
with the gravity line this frame.
Except, that's not actually true. It turns out that `oldxp` and `oldyp`
don't necessarily always correspond to the `xp` and `yp` of the player
on the previous frame. It turns out that your `oldyp` will be updated if
you stand on a vertically moving platform, before the gravity line
collision function gets ran. So, if you were colliding with a gravity
line on the previous frame, but you got moved out of there by a
vertically moving platform, then you just don't collide with the gravity
line at all.
However, this behavior changed in 2.3 after my over-30-FPS patch got
merged (#220). That patch took advantage of the existing `oldxp` and
`oldyp` entity attributes, and uses them to interpolate their positions
during rendering to make everything look real smooth.
Previously, `oldxp` and `oldyp` would both be updated in
`entityclass::updateentitylogic()`. However, I moved it in that patch to
update right before `gameinput()` in `main.cpp`.
As a result, `oldyp` no longer gets updated whenever the player stands
on a vertically moving platform. This ends up desyncing my TAS.
As expected, updating `oldyp` in `entityclass::movingplatformfix()` (the
function responsible for moving the player whenever they stand on a
vertically moving platform) makes it so that my TAS syncs, but the
visuals are glitchy when standing on a vertically moving platform. And
as much as I'd like to get rid of gravity lines checking for whether
you've collided with them on the previous frame, doing that desyncs my
TAS, too.
In the end, it seems like I should just leave `oldxp` and `oldyp` alone,
and switch to using dedicated variables that are never used in the
physics of the game. So I'm introducing `lerpoldxp` and `lerpoldyp`, and
replacing all instances of using `oldxp` and `oldyp` that my over-30-FPS
patch added, with `lerpoldxp` and `lerpoldyp` instead.
After doing this, and applying #503 as well, my Nova TAS syncs after
some minor but acceptable fixes with Viridian's walkingframe.
2020-10-10 05:58:58 +02:00
|
|
|
obj.entities[i].lerpoldxp = obj.entities[i].xp;
|
|
|
|
obj.entities[i].lerpoldyp = obj.entities[i].yp;
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[j].tile = 2;
|
|
|
|
obj.entities[j].colour = 101;
|
|
|
|
}
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
obj.entities[i].invis = false;
|
|
|
|
obj.entities[i].dir = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[i].ay = -6;
|
|
|
|
obj.entities[i].ax = -6;
|
|
|
|
obj.entities[i].vy = -6;
|
|
|
|
obj.entities[i].vx = -6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4033:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp -= 12;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4034:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp -= 12;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4035:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp -= 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4036:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp -= 8;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4037:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp -= 5;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4038:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp -= 2;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4039:
|
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 4040:
|
|
|
|
//Activating a teleporter (default appear)
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 90;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4041:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 0;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(10);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4042:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 5;
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
|
|
|
int j = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(j, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp = obj.entities[j].xp+44;
|
|
|
|
obj.entities[i].yp = obj.entities[j].yp+44;
|
Restore previous oldxp/oldyp variables in favor of lerpoldxp/lerpoldyp
I was investigating a desync in my Nova TAS, and it turns out that
the gravity line collision functions check for the `oldxp` and `oldyp`
of the player, i.e. their position on the previous frame, along with
their position on the current frame. So, if the player either collided
with the gravity line last frame or this frame, then the player collided
with the gravity line this frame.
Except, that's not actually true. It turns out that `oldxp` and `oldyp`
don't necessarily always correspond to the `xp` and `yp` of the player
on the previous frame. It turns out that your `oldyp` will be updated if
you stand on a vertically moving platform, before the gravity line
collision function gets ran. So, if you were colliding with a gravity
line on the previous frame, but you got moved out of there by a
vertically moving platform, then you just don't collide with the gravity
line at all.
However, this behavior changed in 2.3 after my over-30-FPS patch got
merged (#220). That patch took advantage of the existing `oldxp` and
`oldyp` entity attributes, and uses them to interpolate their positions
during rendering to make everything look real smooth.
Previously, `oldxp` and `oldyp` would both be updated in
`entityclass::updateentitylogic()`. However, I moved it in that patch to
update right before `gameinput()` in `main.cpp`.
As a result, `oldyp` no longer gets updated whenever the player stands
on a vertically moving platform. This ends up desyncing my TAS.
As expected, updating `oldyp` in `entityclass::movingplatformfix()` (the
function responsible for moving the player whenever they stand on a
vertically moving platform) makes it so that my TAS syncs, but the
visuals are glitchy when standing on a vertically moving platform. And
as much as I'd like to get rid of gravity lines checking for whether
you've collided with them on the previous frame, doing that desyncs my
TAS, too.
In the end, it seems like I should just leave `oldxp` and `oldyp` alone,
and switch to using dedicated variables that are never used in the
physics of the game. So I'm introducing `lerpoldxp` and `lerpoldyp`, and
replacing all instances of using `oldxp` and `oldyp` that my over-30-FPS
patch added, with `lerpoldxp` and `lerpoldyp` instead.
After doing this, and applying #503 as well, my Nova TAS syncs after
some minor but acceptable fixes with Viridian's walkingframe.
2020-10-10 05:58:58 +02:00
|
|
|
obj.entities[i].lerpoldxp = obj.entities[i].xp;
|
|
|
|
obj.entities[i].lerpoldyp = obj.entities[i].yp;
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[j].tile = 2;
|
|
|
|
obj.entities[j].colour = 101;
|
|
|
|
}
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
obj.entities[i].invis = false;
|
|
|
|
obj.entities[i].dir = 1;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[i].ay = -6;
|
|
|
|
obj.entities[i].ax = 6;
|
|
|
|
obj.entities[i].vy = -6;
|
|
|
|
obj.entities[i].vx = 6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4043:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 12;
|
|
|
|
obj.entities[i].yp -= 15;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4044:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 12;
|
|
|
|
obj.entities[i].yp -= 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4045:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 12;
|
|
|
|
obj.entities[i].yp -= 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4046:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 8;
|
|
|
|
obj.entities[i].yp -= 8;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4047:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 6;
|
|
|
|
obj.entities[i].yp -= 8;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4048:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 3;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4049:
|
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 4050:
|
|
|
|
//Activating a teleporter (default appear)
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 90;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4051:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 0;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(10);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4052:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 5;
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
|
|
|
int j = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(j, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp = obj.entities[j].xp+44;
|
|
|
|
obj.entities[i].yp = obj.entities[j].yp+44;
|
Restore previous oldxp/oldyp variables in favor of lerpoldxp/lerpoldyp
I was investigating a desync in my Nova TAS, and it turns out that
the gravity line collision functions check for the `oldxp` and `oldyp`
of the player, i.e. their position on the previous frame, along with
their position on the current frame. So, if the player either collided
with the gravity line last frame or this frame, then the player collided
with the gravity line this frame.
Except, that's not actually true. It turns out that `oldxp` and `oldyp`
don't necessarily always correspond to the `xp` and `yp` of the player
on the previous frame. It turns out that your `oldyp` will be updated if
you stand on a vertically moving platform, before the gravity line
collision function gets ran. So, if you were colliding with a gravity
line on the previous frame, but you got moved out of there by a
vertically moving platform, then you just don't collide with the gravity
line at all.
However, this behavior changed in 2.3 after my over-30-FPS patch got
merged (#220). That patch took advantage of the existing `oldxp` and
`oldyp` entity attributes, and uses them to interpolate their positions
during rendering to make everything look real smooth.
Previously, `oldxp` and `oldyp` would both be updated in
`entityclass::updateentitylogic()`. However, I moved it in that patch to
update right before `gameinput()` in `main.cpp`.
As a result, `oldyp` no longer gets updated whenever the player stands
on a vertically moving platform. This ends up desyncing my TAS.
As expected, updating `oldyp` in `entityclass::movingplatformfix()` (the
function responsible for moving the player whenever they stand on a
vertically moving platform) makes it so that my TAS syncs, but the
visuals are glitchy when standing on a vertically moving platform. And
as much as I'd like to get rid of gravity lines checking for whether
you've collided with them on the previous frame, doing that desyncs my
TAS, too.
In the end, it seems like I should just leave `oldxp` and `oldyp` alone,
and switch to using dedicated variables that are never used in the
physics of the game. So I'm introducing `lerpoldxp` and `lerpoldyp`, and
replacing all instances of using `oldxp` and `oldyp` that my over-30-FPS
patch added, with `lerpoldxp` and `lerpoldyp` instead.
After doing this, and applying #503 as well, my Nova TAS syncs after
some minor but acceptable fixes with Viridian's walkingframe.
2020-10-10 05:58:58 +02:00
|
|
|
obj.entities[i].lerpoldxp = obj.entities[i].xp;
|
|
|
|
obj.entities[i].lerpoldyp = obj.entities[i].yp;
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[j].tile = 2;
|
|
|
|
obj.entities[j].colour = 101;
|
|
|
|
}
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
obj.entities[i].invis = false;
|
|
|
|
obj.entities[i].dir = 1;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[i].ay = -6;
|
|
|
|
obj.entities[i].ax = 6;
|
|
|
|
obj.entities[i].vy = -6;
|
|
|
|
obj.entities[i].vx = 6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4053:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 4;
|
|
|
|
obj.entities[i].yp -= 15;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4054:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 4;
|
|
|
|
obj.entities[i].yp -= 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4055:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 4;
|
|
|
|
obj.entities[i].yp -= 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4056:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 4;
|
|
|
|
obj.entities[i].yp -= 8;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4057:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 2;
|
|
|
|
obj.entities[i].yp -= 8;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4058:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 1;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4059:
|
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 4060:
|
|
|
|
//Activating a teleporter (default appear)
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 90;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4061:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 0;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(10);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4062:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 5;
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
|
|
|
int j = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(j, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp = obj.entities[j].xp+44;
|
|
|
|
obj.entities[i].yp = obj.entities[j].yp+44;
|
Restore previous oldxp/oldyp variables in favor of lerpoldxp/lerpoldyp
I was investigating a desync in my Nova TAS, and it turns out that
the gravity line collision functions check for the `oldxp` and `oldyp`
of the player, i.e. their position on the previous frame, along with
their position on the current frame. So, if the player either collided
with the gravity line last frame or this frame, then the player collided
with the gravity line this frame.
Except, that's not actually true. It turns out that `oldxp` and `oldyp`
don't necessarily always correspond to the `xp` and `yp` of the player
on the previous frame. It turns out that your `oldyp` will be updated if
you stand on a vertically moving platform, before the gravity line
collision function gets ran. So, if you were colliding with a gravity
line on the previous frame, but you got moved out of there by a
vertically moving platform, then you just don't collide with the gravity
line at all.
However, this behavior changed in 2.3 after my over-30-FPS patch got
merged (#220). That patch took advantage of the existing `oldxp` and
`oldyp` entity attributes, and uses them to interpolate their positions
during rendering to make everything look real smooth.
Previously, `oldxp` and `oldyp` would both be updated in
`entityclass::updateentitylogic()`. However, I moved it in that patch to
update right before `gameinput()` in `main.cpp`.
As a result, `oldyp` no longer gets updated whenever the player stands
on a vertically moving platform. This ends up desyncing my TAS.
As expected, updating `oldyp` in `entityclass::movingplatformfix()` (the
function responsible for moving the player whenever they stand on a
vertically moving platform) makes it so that my TAS syncs, but the
visuals are glitchy when standing on a vertically moving platform. And
as much as I'd like to get rid of gravity lines checking for whether
you've collided with them on the previous frame, doing that desyncs my
TAS, too.
In the end, it seems like I should just leave `oldxp` and `oldyp` alone,
and switch to using dedicated variables that are never used in the
physics of the game. So I'm introducing `lerpoldxp` and `lerpoldyp`, and
replacing all instances of using `oldxp` and `oldyp` that my over-30-FPS
patch added, with `lerpoldxp` and `lerpoldyp` instead.
After doing this, and applying #503 as well, my Nova TAS syncs after
some minor but acceptable fixes with Viridian's walkingframe.
2020-10-10 05:58:58 +02:00
|
|
|
obj.entities[i].lerpoldxp = obj.entities[i].xp;
|
|
|
|
obj.entities[i].lerpoldyp = obj.entities[i].yp;
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[j].tile = 2;
|
|
|
|
obj.entities[j].colour = 101;
|
|
|
|
}
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
obj.entities[i].invis = false;
|
|
|
|
obj.entities[i].dir = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[i].ay = -6;
|
|
|
|
obj.entities[i].ax = -6;
|
|
|
|
obj.entities[i].vy = -6;
|
|
|
|
obj.entities[i].vx = -6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4063:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp -= 28;
|
|
|
|
obj.entities[i].yp -= 8;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4064:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp -= 28;
|
|
|
|
obj.entities[i].yp -= 8;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4065:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp -= 25;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4066:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp -= 25;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4067:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp -= 20;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4068:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp -= 16;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4069:
|
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 4070:
|
|
|
|
//Activating a teleporter (special for final script, player has colour changed to match rescued crewmate)
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 90;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4071:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 0;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(10);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4072:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 5;
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
|
|
|
int j = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(j, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp = obj.entities[j].xp+44;
|
|
|
|
obj.entities[i].yp = obj.entities[j].yp+44;
|
Restore previous oldxp/oldyp variables in favor of lerpoldxp/lerpoldyp
I was investigating a desync in my Nova TAS, and it turns out that
the gravity line collision functions check for the `oldxp` and `oldyp`
of the player, i.e. their position on the previous frame, along with
their position on the current frame. So, if the player either collided
with the gravity line last frame or this frame, then the player collided
with the gravity line this frame.
Except, that's not actually true. It turns out that `oldxp` and `oldyp`
don't necessarily always correspond to the `xp` and `yp` of the player
on the previous frame. It turns out that your `oldyp` will be updated if
you stand on a vertically moving platform, before the gravity line
collision function gets ran. So, if you were colliding with a gravity
line on the previous frame, but you got moved out of there by a
vertically moving platform, then you just don't collide with the gravity
line at all.
However, this behavior changed in 2.3 after my over-30-FPS patch got
merged (#220). That patch took advantage of the existing `oldxp` and
`oldyp` entity attributes, and uses them to interpolate their positions
during rendering to make everything look real smooth.
Previously, `oldxp` and `oldyp` would both be updated in
`entityclass::updateentitylogic()`. However, I moved it in that patch to
update right before `gameinput()` in `main.cpp`.
As a result, `oldyp` no longer gets updated whenever the player stands
on a vertically moving platform. This ends up desyncing my TAS.
As expected, updating `oldyp` in `entityclass::movingplatformfix()` (the
function responsible for moving the player whenever they stand on a
vertically moving platform) makes it so that my TAS syncs, but the
visuals are glitchy when standing on a vertically moving platform. And
as much as I'd like to get rid of gravity lines checking for whether
you've collided with them on the previous frame, doing that desyncs my
TAS, too.
In the end, it seems like I should just leave `oldxp` and `oldyp` alone,
and switch to using dedicated variables that are never used in the
physics of the game. So I'm introducing `lerpoldxp` and `lerpoldyp`, and
replacing all instances of using `oldxp` and `oldyp` that my over-30-FPS
patch added, with `lerpoldxp` and `lerpoldyp` instead.
After doing this, and applying #503 as well, my Nova TAS syncs after
some minor but acceptable fixes with Viridian's walkingframe.
2020-10-10 05:58:58 +02:00
|
|
|
obj.entities[i].lerpoldxp = obj.entities[i].xp;
|
|
|
|
obj.entities[i].lerpoldyp = obj.entities[i].yp;
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[j].tile = 2;
|
|
|
|
obj.entities[j].colour = 101;
|
|
|
|
}
|
|
|
|
obj.entities[i].invis = false;
|
|
|
|
obj.entities[i].dir = 1;
|
|
|
|
obj.entities[i].colour = obj.crewcolour(lastsaved);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[i].ay = -6;
|
|
|
|
obj.entities[i].ax = 6;
|
|
|
|
obj.entities[i].vy = -6;
|
|
|
|
obj.entities[i].vx = 6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4073:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4074:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4075:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 8;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4076:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4077:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 3;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4078:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 1;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4079:
|
|
|
|
state = 0;
|
|
|
|
startscript = true;
|
|
|
|
newscript = "finallevel_teleporter";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 4080:
|
|
|
|
//Activating a teleporter (default appear)
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 90;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4081:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 0;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(10);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4082:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 5;
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
|
|
|
int j = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(j, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp = obj.entities[j].xp+44;
|
|
|
|
obj.entities[i].yp = obj.entities[j].yp+44;
|
Restore previous oldxp/oldyp variables in favor of lerpoldxp/lerpoldyp
I was investigating a desync in my Nova TAS, and it turns out that
the gravity line collision functions check for the `oldxp` and `oldyp`
of the player, i.e. their position on the previous frame, along with
their position on the current frame. So, if the player either collided
with the gravity line last frame or this frame, then the player collided
with the gravity line this frame.
Except, that's not actually true. It turns out that `oldxp` and `oldyp`
don't necessarily always correspond to the `xp` and `yp` of the player
on the previous frame. It turns out that your `oldyp` will be updated if
you stand on a vertically moving platform, before the gravity line
collision function gets ran. So, if you were colliding with a gravity
line on the previous frame, but you got moved out of there by a
vertically moving platform, then you just don't collide with the gravity
line at all.
However, this behavior changed in 2.3 after my over-30-FPS patch got
merged (#220). That patch took advantage of the existing `oldxp` and
`oldyp` entity attributes, and uses them to interpolate their positions
during rendering to make everything look real smooth.
Previously, `oldxp` and `oldyp` would both be updated in
`entityclass::updateentitylogic()`. However, I moved it in that patch to
update right before `gameinput()` in `main.cpp`.
As a result, `oldyp` no longer gets updated whenever the player stands
on a vertically moving platform. This ends up desyncing my TAS.
As expected, updating `oldyp` in `entityclass::movingplatformfix()` (the
function responsible for moving the player whenever they stand on a
vertically moving platform) makes it so that my TAS syncs, but the
visuals are glitchy when standing on a vertically moving platform. And
as much as I'd like to get rid of gravity lines checking for whether
you've collided with them on the previous frame, doing that desyncs my
TAS, too.
In the end, it seems like I should just leave `oldxp` and `oldyp` alone,
and switch to using dedicated variables that are never used in the
physics of the game. So I'm introducing `lerpoldxp` and `lerpoldyp`, and
replacing all instances of using `oldxp` and `oldyp` that my over-30-FPS
patch added, with `lerpoldxp` and `lerpoldyp` instead.
After doing this, and applying #503 as well, my Nova TAS syncs after
some minor but acceptable fixes with Viridian's walkingframe.
2020-10-10 05:58:58 +02:00
|
|
|
obj.entities[i].lerpoldxp = obj.entities[i].xp;
|
|
|
|
obj.entities[i].lerpoldyp = obj.entities[i].yp;
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[j].tile = 2;
|
|
|
|
obj.entities[j].colour = 101;
|
|
|
|
}
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
obj.entities[i].invis = false;
|
|
|
|
obj.entities[i].dir = 1;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[i].ay = -6;
|
|
|
|
obj.entities[i].ax = 6;
|
|
|
|
obj.entities[i].vy = -6;
|
|
|
|
obj.entities[i].vx = 6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4083:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4084:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4085:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 8;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4086:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4087:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 3;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4088:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 1;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4089:
|
|
|
|
startscript = true;
|
|
|
|
newscript = "gamecomplete_ending";
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 4090:
|
|
|
|
//Activating a teleporter (default appear)
|
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 90;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(9);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4091:
|
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 0;
|
|
|
|
flashlight = 5;
|
|
|
|
screenshake = 0;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(10);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4092:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Activating a teleporter 2
|
|
|
|
state++;
|
|
|
|
statedelay = 5;
|
|
|
|
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
|
|
|
int j = obj.getteleporter();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(j, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp = obj.entities[j].xp+44;
|
|
|
|
obj.entities[i].yp = obj.entities[j].yp+44;
|
Restore previous oldxp/oldyp variables in favor of lerpoldxp/lerpoldyp
I was investigating a desync in my Nova TAS, and it turns out that
the gravity line collision functions check for the `oldxp` and `oldyp`
of the player, i.e. their position on the previous frame, along with
their position on the current frame. So, if the player either collided
with the gravity line last frame or this frame, then the player collided
with the gravity line this frame.
Except, that's not actually true. It turns out that `oldxp` and `oldyp`
don't necessarily always correspond to the `xp` and `yp` of the player
on the previous frame. It turns out that your `oldyp` will be updated if
you stand on a vertically moving platform, before the gravity line
collision function gets ran. So, if you were colliding with a gravity
line on the previous frame, but you got moved out of there by a
vertically moving platform, then you just don't collide with the gravity
line at all.
However, this behavior changed in 2.3 after my over-30-FPS patch got
merged (#220). That patch took advantage of the existing `oldxp` and
`oldyp` entity attributes, and uses them to interpolate their positions
during rendering to make everything look real smooth.
Previously, `oldxp` and `oldyp` would both be updated in
`entityclass::updateentitylogic()`. However, I moved it in that patch to
update right before `gameinput()` in `main.cpp`.
As a result, `oldyp` no longer gets updated whenever the player stands
on a vertically moving platform. This ends up desyncing my TAS.
As expected, updating `oldyp` in `entityclass::movingplatformfix()` (the
function responsible for moving the player whenever they stand on a
vertically moving platform) makes it so that my TAS syncs, but the
visuals are glitchy when standing on a vertically moving platform. And
as much as I'd like to get rid of gravity lines checking for whether
you've collided with them on the previous frame, doing that desyncs my
TAS, too.
In the end, it seems like I should just leave `oldxp` and `oldyp` alone,
and switch to using dedicated variables that are never used in the
physics of the game. So I'm introducing `lerpoldxp` and `lerpoldyp`, and
replacing all instances of using `oldxp` and `oldyp` that my over-30-FPS
patch added, with `lerpoldxp` and `lerpoldyp` instead.
After doing this, and applying #503 as well, my Nova TAS syncs after
some minor but acceptable fixes with Viridian's walkingframe.
2020-10-10 05:58:58 +02:00
|
|
|
obj.entities[i].lerpoldxp = obj.entities[i].xp;
|
|
|
|
obj.entities[i].lerpoldyp = obj.entities[i].yp;
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[j].tile = 2;
|
|
|
|
obj.entities[j].colour = 101;
|
|
|
|
}
|
|
|
|
obj.entities[i].colour = 0;
|
|
|
|
obj.entities[i].invis = false;
|
|
|
|
obj.entities[i].dir = 1;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[i].ay = -6;
|
|
|
|
obj.entities[i].ax = 6;
|
|
|
|
obj.entities[i].vy = -6;
|
|
|
|
obj.entities[i].vx = 6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4093:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4094:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 10;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4095:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 8;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4096:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4097:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 3;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4098:
|
2020-09-08 09:56:00 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
state++;
|
|
|
|
statedelay = 15;
|
2020-09-08 09:56:00 +02:00
|
|
|
int i = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].xp += 1;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
2020-09-08 09:56:00 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 4099:
|
|
|
|
if (nocutscenes)
|
|
|
|
{
|
|
|
|
startscript = true;
|
|
|
|
newscript = "levelonecompleteskip";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
startscript = true;
|
|
|
|
newscript = "levelonecomplete_ending";
|
|
|
|
}
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::gethardestroom()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (currentroomdeaths > hardestroomdeaths)
|
|
|
|
{
|
|
|
|
hardestroomdeaths = currentroomdeaths;
|
|
|
|
hardestroom = map.roomname;
|
|
|
|
if (map.roomname == "glitch")
|
|
|
|
{
|
|
|
|
if (roomx == 42 && roomy == 51)
|
|
|
|
{
|
|
|
|
hardestroom = "Rear Vindow";
|
|
|
|
}
|
|
|
|
else if (roomx == 48 && roomy == 51)
|
|
|
|
{
|
|
|
|
hardestroom = "On the Vaterfront";
|
|
|
|
}
|
|
|
|
else if (roomx == 49 && roomy == 51)
|
|
|
|
{
|
|
|
|
hardestroom = "The Untouchavles";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (map.roomname == "change")
|
|
|
|
{
|
|
|
|
if (roomx == 45 && roomy == 51) hardestroom =map.specialnames[3];
|
|
|
|
if (roomx == 46 && roomy == 51) hardestroom =map.specialnames[4];
|
|
|
|
if (roomx == 47 && roomy == 51) hardestroom =map.specialnames[5];
|
|
|
|
if (roomx == 50 && roomy == 53) hardestroom =map.specialnames[6];
|
|
|
|
if (roomx == 50 && roomy == 54) hardestroom = map.specialnames[7];
|
|
|
|
}
|
2020-02-04 04:27:49 +01:00
|
|
|
else if (map.roomname == "")
|
|
|
|
{
|
|
|
|
hardestroom = "Dimension VVVVVV";
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::deletestats()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-26 22:38:24 +02:00
|
|
|
if (!FILESYSTEM_delete("saves/unlock.vvv"))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-26 22:38:24 +02:00
|
|
|
puts("Error deleting saves/unlock.vvv");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-04-26 22:38:24 +02:00
|
|
|
else
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-03 01:45:22 +02:00
|
|
|
for (int i = 0; i < numunlock; i++)
|
2020-04-26 22:38:24 +02:00
|
|
|
{
|
|
|
|
unlock[i] = false;
|
|
|
|
unlocknotify[i] = false;
|
|
|
|
}
|
2020-07-03 02:09:30 +02:00
|
|
|
for (int i = 0; i < numtrials; i++)
|
2020-04-26 22:38:24 +02:00
|
|
|
{
|
|
|
|
besttimes[i] = -1;
|
2020-07-01 03:59:16 +02:00
|
|
|
bestframes[i] = -1;
|
2020-04-26 22:38:24 +02:00
|
|
|
besttrinkets[i] = -1;
|
|
|
|
bestlives[i] = -1;
|
|
|
|
bestrank[i] = -1;
|
|
|
|
}
|
2020-11-04 06:56:18 +01:00
|
|
|
#ifndef MAKEANDPLAY
|
2020-04-26 22:38:24 +02:00
|
|
|
graphics.setflipmode = false;
|
2020-11-04 06:56:18 +01:00
|
|
|
#endif
|
2020-04-26 22:38:24 +02:00
|
|
|
stat_trinkets = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Move all settings to settings.vvv
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.
The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.
The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).
Closes #373 fully.
2020-11-04 08:11:21 +01:00
|
|
|
void Game::deletesettings()
|
|
|
|
{
|
|
|
|
if (!FILESYSTEM_delete("saves/settings.vvv"))
|
|
|
|
{
|
|
|
|
puts("Error deleting saves/settings.vvv");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::unlocknum( int t )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-08-01 21:49:07 +02:00
|
|
|
#if !defined(MAKEANDPLAY)
|
2020-03-15 16:17:12 +01:00
|
|
|
if (map.custommode)
|
|
|
|
{
|
|
|
|
//Don't let custom levels unlock things!
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
unlock[t] = true;
|
2020-03-31 02:16:02 +02:00
|
|
|
savestats();
|
2020-08-01 21:49:07 +02:00
|
|
|
#endif
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-07-03 03:10:52 +02:00
|
|
|
#define LOAD_ARRAY_RENAME(ARRAY_NAME, DEST) \
|
2020-07-03 01:58:58 +02:00
|
|
|
if (pKey == #ARRAY_NAME) \
|
|
|
|
{ \
|
|
|
|
std::string TextString = pText; \
|
|
|
|
if (TextString.length()) \
|
|
|
|
{ \
|
|
|
|
std::vector<std::string> values = split(TextString, ','); \
|
2020-07-03 03:10:52 +02:00
|
|
|
for (size_t i = 0; i < SDL_min(SDL_arraysize(DEST), values.size()); i++) \
|
2020-07-03 01:58:58 +02:00
|
|
|
{ \
|
2020-08-07 06:31:29 +02:00
|
|
|
DEST[i] = help.Int(values[i].c_str()); \
|
2020-07-03 01:58:58 +02:00
|
|
|
} \
|
|
|
|
} \
|
|
|
|
}
|
|
|
|
|
2020-07-03 03:10:52 +02:00
|
|
|
#define LOAD_ARRAY(ARRAY_NAME) LOAD_ARRAY_RENAME(ARRAY_NAME, ARRAY_NAME)
|
|
|
|
|
2020-11-13 01:45:51 +01:00
|
|
|
void Game::loadstats(ScreenSettings* screen_settings)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-03 19:11:37 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
if (!FILESYSTEM_loadTiXml2Document("saves/unlock.vvv", doc))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Move all settings to settings.vvv
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.
The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.
The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).
Closes #373 fully.
2020-11-04 08:11:21 +01:00
|
|
|
// Save unlock.vvv only. Maybe we have a settings.vvv laying around too,
|
|
|
|
// and we don't want to overwrite that!
|
|
|
|
savestats(true);
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
printf("No Stats found. Assuming a new player\n");
|
|
|
|
}
|
|
|
|
|
2020-06-03 19:11:37 +02:00
|
|
|
tinyxml2::XMLHandle hDoc(&doc);
|
|
|
|
tinyxml2::XMLElement* pElem;
|
|
|
|
tinyxml2::XMLHandle hRoot(NULL);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
{
|
2020-06-03 19:11:37 +02:00
|
|
|
pElem=hDoc.FirstChildElement().ToElement();
|
2020-01-01 21:29:24 +01:00
|
|
|
// should always have a valid root but handle gracefully if it does
|
|
|
|
if (!pElem)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
// save this for later
|
2020-06-03 19:11:37 +02:00
|
|
|
hRoot=tinyxml2::XMLHandle(pElem);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Move all settings to settings.vvv
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.
The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.
The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).
Closes #373 fully.
2020-11-04 08:11:21 +01:00
|
|
|
tinyxml2::XMLElement* dataNode = hRoot.FirstChildElement("Data").FirstChild().ToElement();
|
|
|
|
|
|
|
|
for( pElem = dataNode; pElem; pElem=pElem->NextSiblingElement())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
std::string pKey(pElem->Value());
|
|
|
|
const char* pText = pElem->GetText() ;
|
|
|
|
|
2020-07-03 01:58:58 +02:00
|
|
|
LOAD_ARRAY(unlock)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 01:58:58 +02:00
|
|
|
LOAD_ARRAY(unlocknotify)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 02:09:30 +02:00
|
|
|
LOAD_ARRAY(besttimes)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 01:58:58 +02:00
|
|
|
LOAD_ARRAY(bestframes)
|
2020-07-01 03:59:16 +02:00
|
|
|
|
2020-07-03 02:09:30 +02:00
|
|
|
LOAD_ARRAY(besttrinkets)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
2020-07-03 02:09:30 +02:00
|
|
|
LOAD_ARRAY(bestlives)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
2020-07-03 02:09:30 +02:00
|
|
|
LOAD_ARRAY(bestrank)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (pKey == "bestgamedeaths")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
bestgamedeaths = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pKey == "stat_trinkets")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
stat_trinkets = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Move all settings to settings.vvv
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.
The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.
The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).
Closes #373 fully.
2020-11-04 08:11:21 +01:00
|
|
|
if (pKey == "swnbestrank")
|
|
|
|
{
|
|
|
|
swnbestrank = help.Int(pText);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pKey == "swnrecord")
|
|
|
|
{
|
|
|
|
swnrecord = help.Int(pText);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-13 01:45:51 +01:00
|
|
|
deserializesettings(dataNode, screen_settings);
|
Move all settings to settings.vvv
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.
The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.
The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).
Closes #373 fully.
2020-11-04 08:11:21 +01:00
|
|
|
}
|
|
|
|
|
2020-11-13 01:45:51 +01:00
|
|
|
void Game::deserializesettings(tinyxml2::XMLElement* dataNode, ScreenSettings* screen_settings)
|
Move all settings to settings.vvv
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.
The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.
The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).
Closes #373 fully.
2020-11-04 08:11:21 +01:00
|
|
|
{
|
|
|
|
// Don't duplicate controller buttons!
|
|
|
|
controllerButton_flip.clear();
|
|
|
|
controllerButton_map.clear();
|
|
|
|
controllerButton_esc.clear();
|
|
|
|
controllerButton_restart.clear();
|
|
|
|
|
|
|
|
for (tinyxml2::XMLElement* pElem = dataNode;
|
|
|
|
pElem != NULL;
|
|
|
|
pElem = pElem->NextSiblingElement())
|
|
|
|
{
|
|
|
|
std::string pKey(pElem->Value());
|
|
|
|
const char* pText = pElem->GetText();
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
if (pKey == "fullscreen")
|
|
|
|
{
|
2020-11-13 01:45:51 +01:00
|
|
|
screen_settings->fullscreen = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-04-02 22:01:55 +02:00
|
|
|
if (pKey == "stretch")
|
|
|
|
{
|
2020-11-13 01:45:51 +01:00
|
|
|
screen_settings->stretch = help.Int(pText);
|
2020-04-02 22:01:55 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-02 22:01:55 +02:00
|
|
|
if (pKey == "useLinearFilter")
|
|
|
|
{
|
2020-11-13 01:45:51 +01:00
|
|
|
screen_settings->linearFilter = help.Int(pText);
|
2020-04-02 22:01:55 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-02 22:01:55 +02:00
|
|
|
if (pKey == "window_width")
|
|
|
|
{
|
2020-11-13 01:45:51 +01:00
|
|
|
screen_settings->windowWidth = help.Int(pText);
|
2020-04-02 22:01:55 +02:00
|
|
|
}
|
|
|
|
if (pKey == "window_height")
|
|
|
|
{
|
2020-11-13 01:45:51 +01:00
|
|
|
screen_settings->windowHeight = help.Int(pText);
|
2020-04-02 22:01:55 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
if (pKey == "noflashingmode")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
noflashingmode = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pKey == "colourblindmode")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
colourblindmode = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pKey == "setflipmode")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
graphics.setflipmode = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pKey == "invincibility")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.invincibility = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pKey == "slowdown")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
slowdown = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pKey == "advanced_smoothing")
|
|
|
|
{
|
2020-11-13 01:45:51 +01:00
|
|
|
screen_settings->badSignal = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-04-02 22:01:55 +02:00
|
|
|
if (pKey == "usingmmmmmm")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-11-13 01:48:39 +01:00
|
|
|
music.usingmmmmmm = (bool) help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
if (pKey == "ghostsenabled")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
ghostsenabled = help.Int(pText);
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
}
|
|
|
|
|
2020-01-13 02:45:44 +01:00
|
|
|
if (pKey == "skipfakeload")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
skipfakeload = help.Int(pText);
|
2020-01-13 02:45:44 +01:00
|
|
|
}
|
|
|
|
|
2020-06-30 04:49:14 +02:00
|
|
|
if (pKey == "disablepause")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
disablepause = help.Int(pText);
|
2020-06-30 04:49:14 +02:00
|
|
|
}
|
|
|
|
|
2020-05-04 21:52:57 +02:00
|
|
|
if (pKey == "over30mode")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
over30mode = help.Int(pText);
|
2020-05-04 21:52:57 +02:00
|
|
|
}
|
|
|
|
|
2020-06-25 23:31:37 +02:00
|
|
|
if (pKey == "glitchrunnermode")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
glitchrunnermode = help.Int(pText);
|
2020-06-25 23:31:37 +02:00
|
|
|
}
|
|
|
|
|
2020-05-04 22:19:47 +02:00
|
|
|
if (pKey == "vsync")
|
|
|
|
{
|
2020-11-13 01:45:51 +01:00
|
|
|
screen_settings->useVsync = help.Int(pText);
|
2020-05-04 22:19:47 +02:00
|
|
|
}
|
|
|
|
|
2020-01-17 18:37:53 +01:00
|
|
|
if (pKey == "notextoutline")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
graphics.notextoutline = help.Int(pText);
|
2020-01-17 18:37:53 +01:00
|
|
|
}
|
|
|
|
|
2020-01-25 05:43:04 +01:00
|
|
|
if (pKey == "translucentroomname")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
graphics.translucentroomname = help.Int(pText);
|
2020-01-25 05:43:04 +01:00
|
|
|
}
|
|
|
|
|
2020-01-29 08:17:13 +01:00
|
|
|
if (pKey == "showmousecursor")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
graphics.showmousecursor = help.Int(pText);
|
2020-01-29 08:17:13 +01:00
|
|
|
}
|
|
|
|
|
2020-04-02 22:01:55 +02:00
|
|
|
if (pKey == "flipButton")
|
|
|
|
{
|
|
|
|
SDL_GameControllerButton newButton;
|
|
|
|
if (GetButtonFromString(pText, &newButton))
|
|
|
|
{
|
|
|
|
controllerButton_flip.push_back(newButton);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pKey == "enterButton")
|
|
|
|
{
|
|
|
|
SDL_GameControllerButton newButton;
|
|
|
|
if (GetButtonFromString(pText, &newButton))
|
|
|
|
{
|
|
|
|
controllerButton_map.push_back(newButton);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pKey == "escButton")
|
|
|
|
{
|
|
|
|
SDL_GameControllerButton newButton;
|
|
|
|
if (GetButtonFromString(pText, &newButton))
|
|
|
|
{
|
|
|
|
controllerButton_esc.push_back(newButton);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-09 00:41:59 +02:00
|
|
|
if (pKey == "restartButton")
|
|
|
|
{
|
|
|
|
SDL_GameControllerButton newButton;
|
|
|
|
if (GetButtonFromString(pText, &newButton))
|
|
|
|
{
|
|
|
|
controllerButton_restart.push_back(newButton);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-02 22:01:55 +02:00
|
|
|
if (pKey == "controllerSensitivity")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
controllerSensitivity = help.Int(pText);
|
2020-04-02 22:01:55 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-11-04 07:12:58 +01:00
|
|
|
if (graphics.showmousecursor)
|
2020-01-29 08:17:13 +01:00
|
|
|
{
|
|
|
|
SDL_ShowCursor(SDL_ENABLE);
|
|
|
|
}
|
2020-11-04 07:12:58 +01:00
|
|
|
else
|
|
|
|
{
|
2020-01-29 08:17:13 +01:00
|
|
|
SDL_ShowCursor(SDL_DISABLE);
|
|
|
|
}
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
if (controllerButton_flip.size() < 1)
|
|
|
|
{
|
|
|
|
controllerButton_flip.push_back(SDL_CONTROLLER_BUTTON_A);
|
|
|
|
}
|
|
|
|
if (controllerButton_map.size() < 1)
|
|
|
|
{
|
|
|
|
controllerButton_map.push_back(SDL_CONTROLLER_BUTTON_Y);
|
|
|
|
}
|
|
|
|
if (controllerButton_esc.size() < 1)
|
|
|
|
{
|
|
|
|
controllerButton_esc.push_back(SDL_CONTROLLER_BUTTON_B);
|
|
|
|
}
|
2020-08-09 00:41:59 +02:00
|
|
|
if (controllerButton_restart.size() < 1)
|
|
|
|
{
|
|
|
|
controllerButton_restart.push_back(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Move all settings to settings.vvv
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.
The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.
The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).
Closes #373 fully.
2020-11-04 08:11:21 +01:00
|
|
|
void Game::savestats(const bool stats_only /*= true*/)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-03 19:59:49 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
2020-09-25 18:10:10 +02:00
|
|
|
bool already_exists = FILESYSTEM_loadTiXml2Document("saves/unlock.vvv", doc);
|
|
|
|
if (!already_exists)
|
|
|
|
{
|
|
|
|
puts("No unlock.vvv found. Creating new file");
|
|
|
|
}
|
|
|
|
|
|
|
|
xml::update_declaration(doc);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
tinyxml2::XMLElement * root = xml::update_element(doc, "Save");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_comment(root, " Save file " );
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
tinyxml2::XMLElement * dataNode = xml::update_element(root, "Data");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string s_unlock;
|
2020-07-03 01:45:22 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(unlock); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-02 21:30:37 +02:00
|
|
|
s_unlock += help.String(unlock[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "unlock", s_unlock.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string s_unlocknotify;
|
2020-07-03 01:45:22 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(unlocknotify); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-02 21:30:37 +02:00
|
|
|
s_unlocknotify += help.String(unlocknotify[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "unlocknotify", s_unlocknotify.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string s_besttimes;
|
2020-07-03 02:09:30 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(besttimes); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-02 21:30:37 +02:00
|
|
|
s_besttimes += help.String(besttimes[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "besttimes", s_besttimes.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-01 03:59:16 +02:00
|
|
|
std::string s_bestframes;
|
2020-07-03 02:09:30 +02:00
|
|
|
for (size_t i = 0; i < SDL_arraysize(bestframes); i++)
|
2020-07-01 03:59:16 +02:00
|
|
|
{
|
|
|
|
s_bestframes += help.String(bestframes[i]) + ",";
|
|
|
|
}
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "bestframes", s_bestframes.c_str());
|
2020-07-01 03:59:16 +02:00
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
std::string s_besttrinkets;
|
2020-07-03 02:09:30 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(besttrinkets); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-02 21:30:37 +02:00
|
|
|
s_besttrinkets += help.String(besttrinkets[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "besttrinkets", s_besttrinkets.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string s_bestlives;
|
2020-07-03 02:09:30 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(bestlives); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-02 21:30:37 +02:00
|
|
|
s_bestlives += help.String(bestlives[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "bestlives", s_bestlives.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string s_bestrank;
|
2020-07-03 02:09:30 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(bestrank); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-02 21:30:37 +02:00
|
|
|
s_bestrank += help.String(bestrank[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "bestrank", s_bestrank.c_str());
|
|
|
|
|
|
|
|
xml::update_tag(dataNode, "bestgamedeaths", bestgamedeaths);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "stat_trinkets", stat_trinkets);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Move all settings to settings.vvv
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.
The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.
The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).
Closes #373 fully.
2020-11-04 08:11:21 +01:00
|
|
|
xml::update_tag(dataNode, "swnbestrank", swnbestrank);
|
|
|
|
|
|
|
|
xml::update_tag(dataNode, "swnrecord", swnrecord);
|
|
|
|
|
|
|
|
serializesettings(dataNode);
|
|
|
|
|
|
|
|
FILESYSTEM_saveTiXml2Document("saves/unlock.vvv", doc);
|
|
|
|
|
|
|
|
if (!stats_only)
|
|
|
|
{
|
|
|
|
savesettings();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Game::serializesettings(tinyxml2::XMLElement* dataNode)
|
|
|
|
{
|
|
|
|
tinyxml2::XMLDocument& doc = xml::get_document(dataNode);
|
|
|
|
|
2020-11-13 01:45:51 +01:00
|
|
|
xml::update_tag(dataNode, "fullscreen", !graphics.screenbuffer->isWindowed);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-11-13 01:45:51 +01:00
|
|
|
xml::update_tag(dataNode, "stretch", graphics.screenbuffer->stretchMode);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-11-13 01:45:51 +01:00
|
|
|
xml::update_tag(dataNode, "useLinearFilter", graphics.screenbuffer->isFiltered);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
int width, height;
|
2020-07-13 22:09:44 +02:00
|
|
|
if (graphics.screenbuffer != NULL)
|
|
|
|
{
|
|
|
|
graphics.screenbuffer->GetWindowSize(&width, &height);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
width = 320;
|
|
|
|
height = 240;
|
|
|
|
}
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "window_width", width);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "window_height", height);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "noflashingmode", noflashingmode);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "colourblindmode", colourblindmode);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "setflipmode", graphics.setflipmode);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "invincibility", map.invincibility);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "slowdown", slowdown);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
2020-11-13 01:45:51 +01:00
|
|
|
xml::update_tag(dataNode, "advanced_smoothing", graphics.screenbuffer->badSignalEffect);
|
2020-04-02 21:41:33 +02:00
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-11-13 01:48:39 +01:00
|
|
|
xml::update_tag(dataNode, "usingmmmmmm", music.usingmmmmmm);
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "ghostsenabled", (int) ghostsenabled);
|
2020-01-13 02:45:44 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "skipfakeload", (int) skipfakeload);
|
2020-06-30 04:49:14 +02:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "disablepause", (int) disablepause);
|
2020-01-17 18:37:53 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "notextoutline", (int) graphics.notextoutline);
|
2020-01-25 05:43:04 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "translucentroomname", (int) graphics.translucentroomname);
|
2020-01-29 08:17:13 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "showmousecursor", (int) graphics.showmousecursor);
|
2020-05-04 21:52:57 +02:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "over30mode", (int) over30mode);
|
|
|
|
|
|
|
|
xml::update_tag(dataNode, "glitchrunnermode", (int) glitchrunnermode);
|
2020-06-25 23:31:37 +02:00
|
|
|
|
2020-07-13 22:09:44 +02:00
|
|
|
int vsyncOption;
|
|
|
|
if (graphics.screenbuffer != NULL)
|
|
|
|
{
|
|
|
|
vsyncOption = (int) graphics.screenbuffer->vsync;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
vsyncOption = 0;
|
|
|
|
}
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "vsync", vsyncOption);
|
|
|
|
|
|
|
|
// Delete all controller buttons we had previously.
|
|
|
|
// dataNode->FirstChildElement() shouldn't be NULL at this point...
|
|
|
|
// we've already added a bunch of elements
|
|
|
|
for (tinyxml2::XMLElement* element = dataNode->FirstChildElement();
|
|
|
|
element != NULL;
|
|
|
|
/* Increment code handled separately */)
|
|
|
|
{
|
|
|
|
const char* name = element->Name();
|
|
|
|
|
|
|
|
if (SDL_strcmp(name, "flipButton") == 0
|
|
|
|
|| SDL_strcmp(name, "enterButton") == 0
|
|
|
|
|| SDL_strcmp(name, "escButton") == 0
|
|
|
|
|| SDL_strcmp(name, "restartButton") == 0)
|
|
|
|
{
|
|
|
|
// Can't just doc.DeleteNode(element) and then go to next,
|
|
|
|
// element->NextSiblingElement() will be NULL.
|
|
|
|
// Instead, store pointer of element we want to delete. Then
|
|
|
|
// increment `element`. And THEN delete the element.
|
|
|
|
tinyxml2::XMLElement* delete_this = element;
|
|
|
|
|
|
|
|
element = element->NextSiblingElement();
|
|
|
|
|
|
|
|
doc.DeleteNode(delete_this);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
element = element->NextSiblingElement();
|
|
|
|
}
|
2020-05-04 22:19:47 +02:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
// Now add them
|
2020-01-01 21:29:24 +01:00
|
|
|
for (size_t i = 0; i < controllerButton_flip.size(); i += 1)
|
|
|
|
{
|
2020-09-25 18:10:10 +02:00
|
|
|
tinyxml2::XMLElement* msg = doc.NewElement("flipButton");
|
2020-06-03 19:59:49 +02:00
|
|
|
msg->LinkEndChild(doc.NewText(help.String((int) controllerButton_flip[i]).c_str()));
|
2020-01-01 21:29:24 +01:00
|
|
|
dataNode->LinkEndChild(msg);
|
|
|
|
}
|
|
|
|
for (size_t i = 0; i < controllerButton_map.size(); i += 1)
|
|
|
|
{
|
2020-09-25 18:10:10 +02:00
|
|
|
tinyxml2::XMLElement* msg = doc.NewElement("enterButton");
|
2020-06-03 19:59:49 +02:00
|
|
|
msg->LinkEndChild(doc.NewText(help.String((int) controllerButton_map[i]).c_str()));
|
2020-01-01 21:29:24 +01:00
|
|
|
dataNode->LinkEndChild(msg);
|
|
|
|
}
|
|
|
|
for (size_t i = 0; i < controllerButton_esc.size(); i += 1)
|
|
|
|
{
|
2020-09-25 18:10:10 +02:00
|
|
|
tinyxml2::XMLElement* msg = doc.NewElement("escButton");
|
2020-06-03 19:59:49 +02:00
|
|
|
msg->LinkEndChild(doc.NewText(help.String((int) controllerButton_esc[i]).c_str()));
|
2020-01-01 21:29:24 +01:00
|
|
|
dataNode->LinkEndChild(msg);
|
|
|
|
}
|
2020-08-09 00:41:59 +02:00
|
|
|
for (size_t i = 0; i < controllerButton_restart.size(); i += 1)
|
|
|
|
{
|
2020-09-25 18:10:10 +02:00
|
|
|
tinyxml2::XMLElement* msg = doc.NewElement("restartButton");
|
2020-08-09 00:41:59 +02:00
|
|
|
msg->LinkEndChild(doc.NewText(help.String((int) controllerButton_restart[i]).c_str()));
|
|
|
|
dataNode->LinkEndChild(msg);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:10 +02:00
|
|
|
xml::update_tag(dataNode, "controllerSensitivity", controllerSensitivity);
|
Move all settings to settings.vvv
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.
The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.
The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).
Closes #373 fully.
2020-11-04 08:11:21 +01:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-11-13 01:45:51 +01:00
|
|
|
void Game::loadsettings(ScreenSettings* screen_settings)
|
Move all settings to settings.vvv
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.
The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.
The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).
Closes #373 fully.
2020-11-04 08:11:21 +01:00
|
|
|
{
|
|
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
if (!FILESYSTEM_loadTiXml2Document("saves/settings.vvv", doc))
|
|
|
|
{
|
|
|
|
savesettings();
|
|
|
|
puts("No settings.vvv found");
|
|
|
|
}
|
|
|
|
|
|
|
|
tinyxml2::XMLHandle hDoc(&doc);
|
|
|
|
tinyxml2::XMLElement* pElem;
|
|
|
|
tinyxml2::XMLHandle hRoot(NULL);
|
|
|
|
|
|
|
|
{
|
|
|
|
pElem = hDoc.FirstChildElement().ToElement();
|
|
|
|
// should always have a valid root but handle gracefully if it doesn't
|
|
|
|
if (!pElem)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
// save this for later
|
|
|
|
hRoot = tinyxml2::XMLHandle(pElem);
|
|
|
|
}
|
|
|
|
|
|
|
|
tinyxml2::XMLElement* dataNode = hRoot.FirstChildElement("Data").FirstChild().ToElement();
|
|
|
|
|
2020-11-13 01:45:51 +01:00
|
|
|
deserializesettings(dataNode, screen_settings);
|
Move all settings to settings.vvv
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.
The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.
The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).
Closes #373 fully.
2020-11-04 08:11:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Game::savesettings()
|
|
|
|
{
|
|
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
bool already_exists = FILESYSTEM_loadTiXml2Document("saves/settings.vvv", doc);
|
|
|
|
if (!already_exists)
|
|
|
|
{
|
|
|
|
puts("No settings.vvv found. Creating new file");
|
|
|
|
}
|
|
|
|
|
|
|
|
xml::update_declaration(doc);
|
|
|
|
|
|
|
|
tinyxml2::XMLElement* root = xml::update_element(doc, "Settings");
|
|
|
|
|
|
|
|
xml::update_comment(root, " Settings (duplicated from unlock.vvv) ");
|
|
|
|
|
|
|
|
tinyxml2::XMLElement* dataNode = xml::update_element(root, "Data");
|
|
|
|
|
|
|
|
serializesettings(dataNode);
|
|
|
|
|
|
|
|
FILESYSTEM_saveTiXml2Document("saves/settings.vvv", doc);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::customstart()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
jumpheld = true;
|
|
|
|
|
|
|
|
savex = edsavex;
|
|
|
|
savey = edsavey;
|
|
|
|
saverx = edsaverx;
|
|
|
|
savery = edsavery;
|
|
|
|
|
|
|
|
savegc = edsavegc;
|
|
|
|
savedir = edsavedir; //Worldmap Start
|
|
|
|
//savex = 6 * 8; savey = 15 * 8; saverx = 46; savery = 54; savegc = 0; savedir = 1; //Final Level Current
|
|
|
|
savepoint = 0;
|
|
|
|
gravitycontrol = savegc;
|
|
|
|
|
|
|
|
//state = 2; deathseq = -1; lifeseq = 10; //Not dead, in game initilisation state
|
|
|
|
state = 0;
|
|
|
|
deathseq = -1;
|
|
|
|
lifeseq = 0;
|
|
|
|
|
|
|
|
//let's teleport in!
|
|
|
|
//state = 2500;
|
|
|
|
//if (!nocutscenes) music.play(5);
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::start()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
jumpheld = true;
|
|
|
|
|
|
|
|
savex = 232;
|
|
|
|
savey = 113;
|
|
|
|
saverx = 104;
|
|
|
|
savery = 110;
|
|
|
|
savegc = 0;
|
|
|
|
savedir = 1; //Worldmap Start
|
|
|
|
//savex = 6 * 8; savey = 15 * 8; saverx = 46; savery = 54; savegc = 0; savedir = 1; //Final Level Current
|
|
|
|
savepoint = 0;
|
|
|
|
gravitycontrol = savegc;
|
|
|
|
|
|
|
|
//state = 2; deathseq = -1; lifeseq = 10; //Not dead, in game initilisation state
|
|
|
|
state = 0;
|
|
|
|
deathseq = -1;
|
|
|
|
lifeseq = 0;
|
|
|
|
|
|
|
|
//let's teleport in!
|
|
|
|
//state = 2500;
|
|
|
|
if (!nocutscenes) music.play(5);
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::deathsequence()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
if (supercrewmate && scmhurt)
|
|
|
|
{
|
|
|
|
i = obj.getscm();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
i = obj.getplayer();
|
|
|
|
}
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].colour = 1;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-13 05:36:08 +02:00
|
|
|
obj.entities[i].invis = false;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
if (deathseq == 30)
|
|
|
|
{
|
|
|
|
if (nodeathmode)
|
|
|
|
{
|
|
|
|
music.fadeout();
|
|
|
|
gameoverdelay = 60;
|
|
|
|
}
|
|
|
|
deathcounts++;
|
2020-04-02 01:36:35 +02:00
|
|
|
music.playef(2);
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
obj.entities[i].invis = true;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
if (map.finalmode)
|
|
|
|
{
|
2020-05-20 02:20:46 +02:00
|
|
|
if (roomx - 41 >= 0 && roomx - 41 < 20 && roomy - 48 >= 0 && roomy - 48 < 20)
|
|
|
|
{
|
|
|
|
map.roomdeathsfinal[roomx - 41 + (20 * (roomy - 48))]++;
|
|
|
|
currentroomdeaths = map.roomdeathsfinal[roomx - 41 + (20 * (roomy - 48))];
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-05-20 02:20:46 +02:00
|
|
|
if (roomx - 100 >= 0 && roomx - 100 < 20 && roomy - 100 >= 0 && roomy - 100 < 20)
|
|
|
|
{
|
|
|
|
map.roomdeaths[roomx - 100 + (20*(roomy - 100))]++;
|
|
|
|
currentroomdeaths = map.roomdeaths[roomx - 100 + (20 * (roomy - 100))];
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities))
|
2020-06-13 05:36:08 +02:00
|
|
|
{
|
|
|
|
if (deathseq == 25) obj.entities[i].invis = true;
|
|
|
|
if (deathseq == 20) obj.entities[i].invis = true;
|
|
|
|
if (deathseq == 16) obj.entities[i].invis = true;
|
|
|
|
if (deathseq == 14) obj.entities[i].invis = true;
|
|
|
|
if (deathseq == 12) obj.entities[i].invis = true;
|
|
|
|
if (deathseq < 10) obj.entities[i].invis = true;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
if (!nodeathmode)
|
|
|
|
{
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(i, obj.entities) && deathseq <= 1) obj.entities[i].invis = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gameoverdelay--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::startspecial( int t )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
jumpheld = true;
|
|
|
|
|
|
|
|
switch(t)
|
|
|
|
{
|
|
|
|
case 0: //Secret Lab
|
|
|
|
savex = 104;
|
|
|
|
savey = 169;
|
|
|
|
saverx = 118;
|
|
|
|
savery = 106;
|
|
|
|
savegc = 0;
|
|
|
|
savedir = 1;
|
|
|
|
break;
|
|
|
|
case 1: //Intermission 1 (any)
|
|
|
|
savex = 80;
|
|
|
|
savey = 57;
|
|
|
|
saverx = 41;
|
|
|
|
savery = 56;
|
|
|
|
savegc = 0;
|
|
|
|
savedir = 0;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
savex = 232;
|
|
|
|
savey = 113;
|
|
|
|
saverx = 104;
|
|
|
|
savery = 110;
|
|
|
|
savegc = 0;
|
|
|
|
savedir = 1; //Worldmap Start
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
savepoint = 0;
|
|
|
|
gravitycontrol = savegc;
|
|
|
|
state = 0;
|
|
|
|
deathseq = -1;
|
|
|
|
lifeseq = 0;
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::starttrial( int t )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
jumpheld = true;
|
|
|
|
|
|
|
|
switch(t)
|
|
|
|
{
|
|
|
|
case 0: //Space Station 1
|
|
|
|
savex = 200;
|
|
|
|
savey = 161;
|
|
|
|
saverx = 113;
|
|
|
|
savery = 105;
|
|
|
|
savegc = 0;
|
|
|
|
savedir = 1;
|
|
|
|
break;
|
|
|
|
case 1: //Lab
|
|
|
|
savex = 191;
|
|
|
|
savey = 33;
|
|
|
|
saverx = 102;
|
|
|
|
savery = 116;
|
|
|
|
savegc = 0;
|
|
|
|
savedir = 1;
|
|
|
|
break;
|
|
|
|
case 2: //Tower
|
|
|
|
savex = 84;
|
|
|
|
savey = 193, saverx = 108;
|
|
|
|
savery = 109;
|
|
|
|
savegc = 0;
|
|
|
|
savedir = 1;
|
|
|
|
break;
|
|
|
|
case 3: //Space Station 2
|
|
|
|
savex = 148;
|
|
|
|
savey = 38;
|
|
|
|
saverx = 112;
|
|
|
|
savery = 114;
|
|
|
|
savegc = 1;
|
|
|
|
savedir = 0;
|
|
|
|
break;
|
|
|
|
case 4: //Warp
|
|
|
|
savex = 52;
|
|
|
|
savey = 73;
|
|
|
|
saverx = 114;
|
|
|
|
savery = 101;
|
|
|
|
savegc = 0;
|
|
|
|
savedir = 1;
|
|
|
|
break;
|
|
|
|
case 5: //Final
|
|
|
|
savex = 101;
|
|
|
|
savey = 113;
|
|
|
|
saverx = 46;
|
|
|
|
savery = 54;
|
|
|
|
savegc = 0;
|
|
|
|
savedir = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
savex = 232;
|
|
|
|
savey = 113;
|
|
|
|
saverx = 104;
|
|
|
|
savery = 110;
|
|
|
|
savegc = 0;
|
|
|
|
savedir = 1; //Worldmap Start
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
savepoint = 0;
|
|
|
|
gravitycontrol = savegc;
|
|
|
|
|
|
|
|
//state = 2; deathseq = -1; lifeseq = 10; //Not dead, in game initilisation state
|
|
|
|
state = 0;
|
|
|
|
deathseq = -1;
|
|
|
|
lifeseq = 0;
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::loadquick()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-04 02:24:31 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
if (!FILESYSTEM_loadTiXml2Document("saves/qsave.vvv", doc)) return;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-14 01:31:02 +02:00
|
|
|
readmaingamesave(doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Game::readmaingamesave(tinyxml2::XMLDocument& doc)
|
|
|
|
{
|
2020-06-04 02:24:31 +02:00
|
|
|
tinyxml2::XMLHandle hDoc(&doc);
|
|
|
|
tinyxml2::XMLElement* pElem;
|
|
|
|
tinyxml2::XMLHandle hRoot(NULL);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
{
|
2020-06-04 02:24:31 +02:00
|
|
|
pElem=hDoc.FirstChildElement().ToElement();
|
2020-01-01 21:29:24 +01:00
|
|
|
// should always have a valid root but handle gracefully if it does
|
|
|
|
if (!pElem)
|
|
|
|
{
|
|
|
|
printf("Save Not Found\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// save this for later
|
2020-06-04 02:24:31 +02:00
|
|
|
hRoot=tinyxml2::XMLHandle(pElem);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-06-04 02:24:31 +02:00
|
|
|
for( pElem = hRoot.FirstChildElement( "Data" ).FirstChild().ToElement(); pElem; pElem=pElem->NextSiblingElement())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
std::string pKey(pElem->Value());
|
|
|
|
const char* pText = pElem->GetText() ;
|
|
|
|
if(pText == NULL)
|
|
|
|
{
|
|
|
|
pText = "";
|
|
|
|
}
|
|
|
|
|
2020-07-03 06:01:09 +02:00
|
|
|
LOAD_ARRAY_RENAME(worldmap, map.explored)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 03:22:19 +02:00
|
|
|
LOAD_ARRAY_RENAME(flags, obj.flags)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 03:10:52 +02:00
|
|
|
LOAD_ARRAY(crewstats)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 04:17:32 +02:00
|
|
|
LOAD_ARRAY_RENAME(collect, obj.collect)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
if (pKey == "finalmode")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.finalmode = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
if (pKey == "finalstretch")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.finalstretch = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pKey == "finalx")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.finalx = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "finaly")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.finaly = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savex")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
savex = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savey")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
savey = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "saverx")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
saverx = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savery")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
savery = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savegc")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
savegc = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savedir")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
savedir= help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savepoint")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
savepoint = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "companion")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
companion = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "lastsaved")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
lastsaved = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "teleportscript")
|
|
|
|
{
|
|
|
|
teleportscript = pText;
|
|
|
|
}
|
|
|
|
else if (pKey == "supercrewmate")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
supercrewmate = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "scmprogress")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
scmprogress = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "scmmoveme")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
scmmoveme = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "frames")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
frames = help.Int(pText);
|
2020-04-02 22:01:55 +02:00
|
|
|
frames = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "seconds")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
seconds = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "minutes")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
minutes = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "hours")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
hours = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "deathcounts")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
deathcounts = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "totalflips")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
totalflips = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "hardestroom")
|
|
|
|
{
|
2020-05-18 23:16:23 +02:00
|
|
|
hardestroom = pText;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "hardestroomdeaths")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
hardestroomdeaths = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "currentsong")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
int song = help.Int(pText);
|
Fix silent music saves playing MMMMMM track 15 if...
This commit fixes a bug that's existed since MMMMMM was added (so, 2.2),
where if you quicksaved in a custom level while no music was playing,
then quit and loaded that quicksave, and you were using PPPPPP while
having MMMMMM available, it would play MMMMMM track 15, even though the
game intends for the music to simply be silent.
This is due to the same bug that lets you play MMMMMM tracks if you're
on PPPPPP - musicclass::play() does a modulo, but C++ modulo is not
guaranteed to be positive given negative inputs, so the 16-track offset
is added to a negative number, resulting in targeting the MMMMMM
soundtrack instead of PPPPPP.
That exploit doesn't harm anyone and shouldn't be fixed, EXCEPT it
causes a problem in this specific case. But this bug can be fixed
without removing that exploit.
Note that I made the check do not-equal to -1 instead of greater-than
-1, so levels that intend on using track -2, -3, -4, etc. upon loading a
quicksave will still work as their creator intended. It's just that
specifically -1 is patched out, just to fix this issue.
2020-08-07 03:36:35 +02:00
|
|
|
if (song != -1)
|
|
|
|
{
|
|
|
|
music.play(song);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (map.finalmode)
|
|
|
|
{
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 0;
|
|
|
|
}
|
|
|
|
if (map.finalstretch)
|
|
|
|
{
|
|
|
|
map.finalstretch = true;
|
|
|
|
map.final_colormode = true;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
map.showteleporters = true;
|
2020-04-09 08:34:26 +02:00
|
|
|
if(obj.flags[12]) map.showtargets = true;
|
|
|
|
if (obj.flags[42]) map.showtrinkets = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::customloadquick(std::string savfile)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 21:03:24 +02:00
|
|
|
if (cliplaytest) {
|
|
|
|
savex = playx;
|
2020-05-04 20:41:38 +02:00
|
|
|
savey = playy;
|
2020-04-09 21:03:24 +02:00
|
|
|
saverx = playrx;
|
|
|
|
savery = playry;
|
|
|
|
savegc = playgc;
|
2020-07-11 20:55:26 +02:00
|
|
|
music.play(playmusic);
|
2020-04-09 21:03:24 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
std::string levelfile = savfile.substr(7);
|
2020-06-04 02:34:05 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
if (!FILESYSTEM_loadTiXml2Document(("saves/"+levelfile+".vvv").c_str(), doc)) return;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-04 02:34:05 +02:00
|
|
|
tinyxml2::XMLHandle hDoc(&doc);
|
|
|
|
tinyxml2::XMLElement* pElem;
|
|
|
|
tinyxml2::XMLHandle hRoot(NULL);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
{
|
2020-06-04 02:34:05 +02:00
|
|
|
pElem=hDoc.FirstChildElement().ToElement();
|
2020-01-01 21:29:24 +01:00
|
|
|
// should always have a valid root but handle gracefully if it does
|
|
|
|
if (!pElem)
|
|
|
|
{
|
|
|
|
printf("Save Not Found\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// save this for later
|
2020-06-04 02:34:05 +02:00
|
|
|
hRoot=tinyxml2::XMLHandle(pElem);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-06-04 02:34:05 +02:00
|
|
|
for( pElem = hRoot.FirstChildElement( "Data" ).FirstChild().ToElement(); pElem; pElem=pElem->NextSiblingElement())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
std::string pKey(pElem->Value());
|
|
|
|
const char* pText = pElem->GetText() ;
|
|
|
|
if(pText == NULL)
|
|
|
|
{
|
|
|
|
pText = "";
|
|
|
|
}
|
|
|
|
|
2020-07-03 06:01:09 +02:00
|
|
|
LOAD_ARRAY_RENAME(worldmap, map.explored)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 03:22:19 +02:00
|
|
|
LOAD_ARRAY_RENAME(flags, obj.flags)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 03:10:52 +02:00
|
|
|
LOAD_ARRAY_RENAME(moods, obj.customcrewmoods)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 03:10:52 +02:00
|
|
|
LOAD_ARRAY(crewstats)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 04:17:32 +02:00
|
|
|
LOAD_ARRAY_RENAME(collect, obj.collect)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 04:17:32 +02:00
|
|
|
LOAD_ARRAY_RENAME(customcollect, obj.customcollect)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
if (pKey == "finalmode")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.finalmode = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
if (pKey == "finalstretch")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.finalstretch = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (map.finalmode)
|
|
|
|
{
|
|
|
|
map.final_colormode = false;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 0;
|
|
|
|
}
|
|
|
|
if (map.finalstretch)
|
|
|
|
{
|
|
|
|
map.finalstretch = true;
|
|
|
|
map.final_colormode = true;
|
|
|
|
map.final_mapcol = 0;
|
|
|
|
map.final_colorframe = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (pKey == "finalx")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.finalx = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "finaly")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.finaly = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savex")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
savex = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savey")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
savey = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "saverx")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
saverx = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savery")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
savery = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savegc")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
savegc = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savedir")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
savedir= help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savepoint")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
savepoint = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "companion")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
companion = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "lastsaved")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
lastsaved = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "teleportscript")
|
|
|
|
{
|
|
|
|
teleportscript = pText;
|
|
|
|
}
|
|
|
|
else if (pKey == "supercrewmate")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
supercrewmate = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "scmprogress")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
scmprogress = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "scmmoveme")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
scmmoveme = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "frames")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
frames = help.Int(pText);
|
2020-04-02 22:01:55 +02:00
|
|
|
frames = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "seconds")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
seconds = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "minutes")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
minutes = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "hours")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
hours = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "deathcounts")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
deathcounts = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "totalflips")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
totalflips = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "hardestroom")
|
|
|
|
{
|
2020-05-18 23:16:23 +02:00
|
|
|
hardestroom = pText;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "hardestroomdeaths")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
hardestroomdeaths = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "currentsong")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
int song = help.Int(pText);
|
Fix silent music saves playing MMMMMM track 15 if...
This commit fixes a bug that's existed since MMMMMM was added (so, 2.2),
where if you quicksaved in a custom level while no music was playing,
then quit and loaded that quicksave, and you were using PPPPPP while
having MMMMMM available, it would play MMMMMM track 15, even though the
game intends for the music to simply be silent.
This is due to the same bug that lets you play MMMMMM tracks if you're
on PPPPPP - musicclass::play() does a modulo, but C++ modulo is not
guaranteed to be positive given negative inputs, so the 16-track offset
is added to a negative number, resulting in targeting the MMMMMM
soundtrack instead of PPPPPP.
That exploit doesn't harm anyone and shouldn't be fixed, EXCEPT it
causes a problem in this specific case. But this bug can be fixed
without removing that exploit.
Note that I made the check do not-equal to -1 instead of greater-than
-1, so levels that intend on using track -2, -3, -4, etc. upon loading a
quicksave will still work as their creator intended. It's just that
specifically -1 is patched out, just to fix this issue.
2020-08-07 03:36:35 +02:00
|
|
|
if (song != -1)
|
|
|
|
{
|
|
|
|
music.play(song);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "showminimap")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.customshowmm = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
map.showteleporters = true;
|
2020-04-09 08:34:26 +02:00
|
|
|
if(obj.flags[12]) map.showtargets = true;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::loadsummary()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-04 04:26:54 +02:00
|
|
|
tinyxml2::XMLDocument docTele;
|
|
|
|
if (!FILESYSTEM_loadTiXml2Document("saves/tsave.vvv", docTele))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
telesummary = "";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-06-04 04:26:54 +02:00
|
|
|
tinyxml2::XMLHandle hDoc(&docTele);
|
|
|
|
tinyxml2::XMLElement* pElem;
|
|
|
|
tinyxml2::XMLHandle hRoot(NULL);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
{
|
2020-06-04 04:26:54 +02:00
|
|
|
pElem=hDoc.FirstChildElement().ToElement();
|
2020-01-01 21:29:24 +01:00
|
|
|
// should always have a valid root but handle gracefully if it does
|
|
|
|
if (!pElem)
|
|
|
|
{
|
|
|
|
printf("Save Not Found\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// save this for later
|
2020-06-04 04:26:54 +02:00
|
|
|
hRoot=tinyxml2::XMLHandle(pElem);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
int l_minute, l_second, l_hours;
|
|
|
|
l_minute = l_second= l_hours = 0;
|
|
|
|
int l_saveX = 0;
|
|
|
|
int l_saveY = 0;
|
2020-06-04 04:26:54 +02:00
|
|
|
for( pElem = hRoot.FirstChildElement( "Data" ).FirstChild().ToElement(); pElem; pElem=pElem->NextSiblingElement())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
std::string pKey(pElem->Value());
|
|
|
|
const char* pText = pElem->GetText() ;
|
|
|
|
|
|
|
|
if (pKey == "summary")
|
|
|
|
{
|
|
|
|
telesummary = pText;
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (pKey == "seconds")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
l_second = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "minutes")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
l_minute = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "hours")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
l_hours = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savery")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
l_saveY = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "saverx")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
l_saveX = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "trinkets")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
tele_trinkets = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "finalmode")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.finalmode = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "finalstretch")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.finalstretch = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-07-03 03:10:52 +02:00
|
|
|
LOAD_ARRAY_RENAME(crewstats, tele_crewstats)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
}
|
2020-03-31 02:16:02 +02:00
|
|
|
tele_gametime = giventimestring(l_hours,l_minute, l_second);
|
2020-01-01 21:29:24 +01:00
|
|
|
tele_currentarea = map.currentarea(map.area(l_saveX, l_saveY));
|
|
|
|
}
|
|
|
|
|
2020-06-04 04:26:54 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
if (!FILESYSTEM_loadTiXml2Document("saves/qsave.vvv", doc))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
quicksummary = "";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-06-04 04:26:54 +02:00
|
|
|
tinyxml2::XMLHandle hDoc(&doc);
|
|
|
|
tinyxml2::XMLElement* pElem;
|
|
|
|
tinyxml2::XMLHandle hRoot(NULL);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
{
|
2020-06-04 04:26:54 +02:00
|
|
|
pElem=hDoc.FirstChildElement().ToElement();
|
2020-01-01 21:29:24 +01:00
|
|
|
// should always have a valid root but handle gracefully if it does
|
|
|
|
if (!pElem)
|
|
|
|
{
|
|
|
|
printf("Save Not Found\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// save this for later
|
2020-06-04 04:26:54 +02:00
|
|
|
hRoot=tinyxml2::XMLHandle(pElem);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
int l_minute, l_second, l_hours;
|
|
|
|
l_minute = l_second= l_hours = 0;
|
|
|
|
int l_saveX = 0;
|
|
|
|
int l_saveY = 0;
|
2020-06-04 04:26:54 +02:00
|
|
|
for( pElem = hRoot.FirstChildElement( "Data" ).FirstChild().ToElement(); pElem; pElem=pElem->NextSiblingElement())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
std::string pKey(pElem->Value());
|
|
|
|
const char* pText = pElem->GetText() ;
|
|
|
|
|
|
|
|
if (pKey == "summary")
|
|
|
|
{
|
|
|
|
quicksummary = pText;
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (pKey == "seconds")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
l_second = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "minutes")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
l_minute = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "hours")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
l_hours = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "savery")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
l_saveY = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "saverx")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
l_saveX = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "trinkets")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
quick_trinkets = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "finalmode")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.finalmode = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else if (pKey == "finalstretch")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
map.finalstretch = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-07-03 03:10:52 +02:00
|
|
|
LOAD_ARRAY_RENAME(crewstats, quick_crewstats)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
quick_gametime = giventimestring(l_hours,l_minute, l_second);
|
2020-01-01 21:29:24 +01:00
|
|
|
quick_currentarea = map.currentarea(map.area(l_saveX, l_saveY));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::initteleportermode()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Set the teleporter variable to the right position!
|
|
|
|
teleport_to_teleporter = 0;
|
|
|
|
|
2020-04-15 04:32:30 +02:00
|
|
|
for (size_t i = 0; i < map.teleporters.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
if (roomx == map.teleporters[i].x + 100 && roomy == map.teleporters[i].y + 100)
|
|
|
|
{
|
|
|
|
teleport_to_teleporter = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-04 02:58:38 +01:00
|
|
|
bool Game::savetele()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-14 05:08:39 +02:00
|
|
|
if (map.custommode || inspecial())
|
2020-03-15 16:17:12 +01:00
|
|
|
{
|
|
|
|
//Don't trash save data!
|
2020-11-04 02:58:38 +01:00
|
|
|
return false;
|
2020-03-15 16:17:12 +01:00
|
|
|
}
|
|
|
|
|
2020-06-03 20:39:33 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
2020-09-25 18:10:48 +02:00
|
|
|
bool already_exists = FILESYSTEM_loadTiXml2Document("saves/tsave.vvv", doc);
|
|
|
|
if (!already_exists)
|
|
|
|
{
|
|
|
|
puts("No tsave.vvv found. Creating new file");
|
|
|
|
}
|
2020-09-14 01:31:02 +02:00
|
|
|
telesummary = writemaingamesave(doc);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-11-04 02:58:38 +01:00
|
|
|
if(!FILESYSTEM_saveTiXml2Document("saves/tsave.vvv", doc))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-09-14 01:31:02 +02:00
|
|
|
printf("Could Not Save game!\n");
|
|
|
|
printf("Failed: %s%s\n", saveFilePath.c_str(), "tsave.vvv");
|
2020-11-04 02:58:38 +01:00
|
|
|
return false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-11-04 02:58:38 +01:00
|
|
|
printf("Game saved\n");
|
|
|
|
return true;
|
2020-09-14 01:31:02 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
2020-11-04 03:45:33 +01:00
|
|
|
bool Game::savequick()
|
2020-09-14 01:31:02 +02:00
|
|
|
{
|
|
|
|
if (map.custommode || inspecial())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-09-14 01:31:02 +02:00
|
|
|
//Don't trash save data!
|
2020-11-04 03:45:33 +01:00
|
|
|
return false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-09-14 01:31:02 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
2020-09-25 18:10:48 +02:00
|
|
|
bool already_exists = FILESYSTEM_loadTiXml2Document("saves/qsave.vvv", doc);
|
|
|
|
if (!already_exists)
|
|
|
|
{
|
|
|
|
puts("No qsave.vvv found. Creating new file");
|
|
|
|
}
|
2020-09-14 01:31:02 +02:00
|
|
|
quicksummary = writemaingamesave(doc);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-11-04 03:45:33 +01:00
|
|
|
if(!FILESYSTEM_saveTiXml2Document("saves/qsave.vvv", doc))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
printf("Could Not Save game!\n");
|
2020-09-14 01:31:02 +02:00
|
|
|
printf("Failed: %s%s\n", saveFilePath.c_str(), "qsave.vvv");
|
2020-11-04 03:45:33 +01:00
|
|
|
return false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-11-04 03:45:33 +01:00
|
|
|
printf("Game saved\n");
|
|
|
|
return true;
|
2020-09-14 01:31:02 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-14 01:31:02 +02:00
|
|
|
// Returns summary of save
|
|
|
|
std::string Game::writemaingamesave(tinyxml2::XMLDocument& doc)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-09-14 01:31:02 +02:00
|
|
|
//TODO make this code a bit cleaner.
|
|
|
|
|
2020-06-14 05:08:39 +02:00
|
|
|
if (map.custommode || inspecial())
|
2020-03-15 16:17:12 +01:00
|
|
|
{
|
|
|
|
//Don't trash save data!
|
2020-09-14 01:31:02 +02:00
|
|
|
return "";
|
2020-03-15 16:17:12 +01:00
|
|
|
}
|
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_declaration(doc);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
tinyxml2::XMLElement * root = xml::update_element(doc, "Save");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_comment(root, " Save file " );
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
tinyxml2::XMLElement * msgs = xml::update_element(root, "Data");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
//Flags, map and stats
|
|
|
|
|
|
|
|
std::string mapExplored;
|
2020-07-03 06:01:09 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(map.explored); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-02 21:30:37 +02:00
|
|
|
mapExplored += help.String(map.explored[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "worldmap", mapExplored.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string flags;
|
2020-07-03 03:22:19 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(obj.flags); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
flags += help.String((int) obj.flags[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "flags", flags.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string crewstatsString;
|
2020-07-03 03:10:52 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(crewstats); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-02 21:30:37 +02:00
|
|
|
crewstatsString += help.String(crewstats[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "crewstats", crewstatsString.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string collect;
|
2020-07-03 04:17:32 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(obj.collect); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:56:36 +02:00
|
|
|
collect += help.String((int) obj.collect[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "collect", collect.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//Position
|
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "finalx", map.finalx);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "finaly", map.finaly);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "savex", savex);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "savey", savey);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "saverx", saverx);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "savery", savery);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "savegc", savegc);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "savedir", savedir);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "savepoint", savepoint);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "trinkets", trinkets());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
//Special stats
|
2020-04-03 00:11:45 +02:00
|
|
|
|
2020-11-06 09:20:58 +01:00
|
|
|
if (music.nicefade)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "currentsong", music.nicechange);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "currentsong", music.currentsong);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "teleportscript", teleportscript.c_str());
|
|
|
|
xml::update_tag(msgs, "companion", companion);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "lastsaved", lastsaved);
|
2020-09-25 18:11:52 +02:00
|
|
|
xml::update_tag(msgs, "supercrewmate", (int) supercrewmate);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "scmprogress", scmprogress);
|
2020-09-25 18:11:52 +02:00
|
|
|
xml::update_tag(msgs, "scmmoveme", (int) scmmoveme);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "frames", frames);
|
|
|
|
xml::update_tag(msgs, "seconds", seconds);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "minutes", minutes);
|
|
|
|
xml::update_tag(msgs, "hours", hours);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "deathcounts", deathcounts);
|
|
|
|
xml::update_tag(msgs, "totalflips", totalflips);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "hardestroom", hardestroom.c_str());
|
|
|
|
xml::update_tag(msgs, "hardestroomdeaths", hardestroomdeaths);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:52 +02:00
|
|
|
xml::update_tag(msgs, "finalmode", (int) map.finalmode);
|
|
|
|
xml::update_tag(msgs, "finalstretch", (int) map.finalstretch);
|
2020-09-14 01:31:02 +02:00
|
|
|
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
std::string summary = savearea + ", " + timestring();
|
2020-09-25 18:10:48 +02:00
|
|
|
xml::update_tag(msgs, "summary", summary.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-14 01:31:02 +02:00
|
|
|
return summary;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-09-14 01:31:02 +02:00
|
|
|
|
2020-11-04 03:45:33 +01:00
|
|
|
bool Game::customsavequick(std::string savfile)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-09-25 18:11:03 +02:00
|
|
|
const std::string levelfile = savfile.substr(7);
|
|
|
|
|
2020-06-04 02:36:27 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
2020-09-25 18:11:03 +02:00
|
|
|
bool already_exists = FILESYSTEM_loadTiXml2Document(("saves/" + levelfile + ".vvv").c_str(), doc);
|
|
|
|
if (!already_exists)
|
|
|
|
{
|
|
|
|
printf("No %s.vvv found. Creating new file\n", levelfile.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
xml::update_declaration(doc);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
tinyxml2::XMLElement * root = xml::update_element(doc, "Save");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_comment(root, " Save file ");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
tinyxml2::XMLElement * msgs = xml::update_element(root, "Data");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
//Flags, map and stats
|
|
|
|
|
|
|
|
std::string mapExplored;
|
2020-07-03 06:01:09 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(map.explored); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-02 21:30:37 +02:00
|
|
|
mapExplored += help.String(map.explored[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "worldmap", mapExplored.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string flags;
|
2020-07-03 03:22:19 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(obj.flags); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:34:26 +02:00
|
|
|
flags += help.String((int) obj.flags[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "flags", flags.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string moods;
|
2020-07-03 03:10:52 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(obj.customcrewmoods); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-02 21:30:37 +02:00
|
|
|
moods += help.String(obj.customcrewmoods[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "moods", moods.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string crewstatsString;
|
2020-07-03 03:10:52 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(crewstats); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-02 21:30:37 +02:00
|
|
|
crewstatsString += help.String(crewstats[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "crewstats", crewstatsString.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string collect;
|
2020-07-03 04:17:32 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(obj.collect); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:56:36 +02:00
|
|
|
collect += help.String((int) obj.collect[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "collect", collect.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string customcollect;
|
2020-07-03 04:17:32 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(obj.customcollect); i++ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-09 08:58:03 +02:00
|
|
|
customcollect += help.String((int) obj.customcollect[i]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "customcollect", customcollect.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//Position
|
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "finalx", map.finalx);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "finaly", map.finaly);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "savex", savex);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "savey", savey);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "saverx", saverx);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "savery", savery);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "savegc", savegc);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "savedir", savedir);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "savepoint", savepoint);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "trinkets", trinkets());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "crewmates", crewmates());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
//Special stats
|
2020-04-03 00:11:45 +02:00
|
|
|
|
2020-11-06 09:20:58 +01:00
|
|
|
if (music.nicefade)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "currentsong", music.nicechange );
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "currentsong", music.currentsong);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "teleportscript", teleportscript.c_str());
|
|
|
|
xml::update_tag(msgs, "companion", companion);
|
|
|
|
|
|
|
|
xml::update_tag(msgs, "lastsaved", lastsaved);
|
2020-09-25 18:11:52 +02:00
|
|
|
xml::update_tag(msgs, "supercrewmate", (int) supercrewmate);
|
2020-09-25 18:11:03 +02:00
|
|
|
|
|
|
|
xml::update_tag(msgs, "scmprogress", scmprogress);
|
2020-09-25 18:11:52 +02:00
|
|
|
xml::update_tag(msgs, "scmmoveme", (int) scmmoveme);
|
2020-09-25 18:11:03 +02:00
|
|
|
|
|
|
|
|
|
|
|
xml::update_tag(msgs, "frames", frames);
|
|
|
|
xml::update_tag(msgs, "seconds", seconds);
|
|
|
|
|
|
|
|
xml::update_tag(msgs, "minutes", minutes);
|
|
|
|
xml::update_tag(msgs, "hours", hours);
|
|
|
|
|
|
|
|
xml::update_tag(msgs, "deathcounts", deathcounts);
|
|
|
|
xml::update_tag(msgs, "totalflips", totalflips);
|
|
|
|
|
|
|
|
xml::update_tag(msgs, "hardestroom", hardestroom.c_str());
|
|
|
|
xml::update_tag(msgs, "hardestroomdeaths", hardestroomdeaths);
|
|
|
|
|
2020-09-25 18:11:52 +02:00
|
|
|
xml::update_tag(msgs, "showminimap", (int) map.customshowmm);
|
2020-09-25 18:11:03 +02:00
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
std::string summary = savearea + ", " + timestring();
|
2020-09-25 18:11:03 +02:00
|
|
|
xml::update_tag(msgs, "summary", summary.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
customquicksummary = summary;
|
|
|
|
|
2020-11-04 03:45:33 +01:00
|
|
|
if(!FILESYSTEM_saveTiXml2Document(("saves/"+levelfile+".vvv").c_str(), doc))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
printf("Could Not Save game!\n");
|
2020-07-11 23:12:17 +02:00
|
|
|
printf("Failed: %s%s%s\n", saveFilePath.c_str(), levelfile.c_str(), ".vvv");
|
2020-11-04 03:45:33 +01:00
|
|
|
return false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-11-04 03:45:33 +01:00
|
|
|
printf("Game saved\n");
|
|
|
|
return true;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
void Game::loadtele()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-03 20:04:36 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
if (!FILESYSTEM_loadTiXml2Document("saves/tsave.vvv", doc)) return;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-14 01:31:02 +02:00
|
|
|
readmaingamesave(doc);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string Game::unrescued()
|
|
|
|
{
|
|
|
|
//Randomly return the name of an unrescued crewmate
|
|
|
|
if (fRandom() * 100 > 50)
|
|
|
|
{
|
|
|
|
if (!crewstats[5]) return "Victoria";
|
|
|
|
if (!crewstats[2]) return "Vitellary";
|
|
|
|
if (!crewstats[4]) return "Verdigris";
|
|
|
|
if (!crewstats[3]) return "Vermilion";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (fRandom() * 100 > 50)
|
|
|
|
{
|
|
|
|
if (!crewstats[2]) return "Vitellary";
|
|
|
|
if (!crewstats[4]) return "Verdigris";
|
|
|
|
if (!crewstats[3]) return "Vermilion";
|
|
|
|
if (!crewstats[5]) return "Victoria";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!crewstats[4]) return "Verdigris";
|
|
|
|
if (!crewstats[3]) return "Vermilion";
|
|
|
|
if (!crewstats[5]) return "Victoria";
|
|
|
|
if (!crewstats[2]) return "Vitellary";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "you";
|
|
|
|
}
|
|
|
|
|
|
|
|
void Game::gameclock()
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
test = true;
|
|
|
|
std::ostringstream os;
|
2020-04-02 22:01:55 +02:00
|
|
|
os << hours << ":" << minutes << ":" << seconds << ", " << frames;
|
2020-01-01 21:29:24 +01:00
|
|
|
teststring = os.str();
|
|
|
|
*/
|
2020-04-02 22:01:55 +02:00
|
|
|
frames++;
|
|
|
|
if (frames >= 30)
|
|
|
|
{
|
|
|
|
frames -= 30;
|
|
|
|
seconds++;
|
|
|
|
if (seconds >= 60)
|
|
|
|
{
|
|
|
|
seconds -= 60;
|
|
|
|
minutes++;
|
|
|
|
if (minutes >= 60)
|
|
|
|
{
|
|
|
|
minutes -= 60;
|
|
|
|
hours++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
std::string Game::giventimestring( int hrs, int min, int sec )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = "";
|
2020-01-01 21:29:24 +01:00
|
|
|
if (hrs > 0)
|
|
|
|
{
|
|
|
|
tempstring += help.String(hrs) + ":";
|
|
|
|
}
|
|
|
|
tempstring += help.twodigits(min) + ":" + help.twodigits(sec);
|
|
|
|
return tempstring;
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
std::string Game::timestring()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = "";
|
2020-01-01 21:29:24 +01:00
|
|
|
if (hours > 0)
|
|
|
|
{
|
|
|
|
tempstring += help.String(hours) + ":";
|
|
|
|
}
|
|
|
|
tempstring += help.twodigits(minutes) + ":" + help.twodigits(seconds);
|
|
|
|
return tempstring;
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
std::string Game::partimestring()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//given par time in seconds:
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = "";
|
2020-01-01 21:29:24 +01:00
|
|
|
if (timetrialpar >= 60)
|
|
|
|
{
|
|
|
|
tempstring = help.twodigits(int((timetrialpar - (timetrialpar % 60)) / 60)) + ":" + help.twodigits(timetrialpar % 60);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tempstring = "00:" + help.twodigits(timetrialpar);
|
|
|
|
}
|
|
|
|
return tempstring;
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
std::string Game::resulttimestring()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//given result time in seconds:
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = "";
|
2020-06-15 02:51:39 +02:00
|
|
|
if (timetrialresulttime >= 60)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
tempstring = help.twodigits(int((timetrialresulttime - (timetrialresulttime % 60)) / 60)) + ":"
|
|
|
|
+ help.twodigits(timetrialresulttime % 60);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tempstring = "00:" + help.twodigits(timetrialresulttime);
|
|
|
|
}
|
2020-06-30 00:53:19 +02:00
|
|
|
tempstring += "." + help.twodigits(timetrialresultframes*100 / 30);
|
2020-01-01 21:29:24 +01:00
|
|
|
return tempstring;
|
|
|
|
}
|
|
|
|
|
2020-03-31 02:16:02 +02:00
|
|
|
std::string Game::timetstring( int t )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//given par time in seconds:
|
2020-04-03 00:05:41 +02:00
|
|
|
std::string tempstring = "";
|
2020-01-01 21:29:24 +01:00
|
|
|
if (t >= 60)
|
|
|
|
{
|
|
|
|
tempstring = help.twodigits(int((t - (t % 60)) / 60)) + ":" + help.twodigits(t % 60);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tempstring = "00:" + help.twodigits(t);
|
|
|
|
}
|
|
|
|
return tempstring;
|
|
|
|
}
|
|
|
|
|
2020-04-17 04:16:40 +02:00
|
|
|
void Game::returnmenu()
|
|
|
|
{
|
|
|
|
if (menustack.empty())
|
|
|
|
{
|
|
|
|
puts("Error: returning to previous menu frame on empty stack!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
MenuStackFrame& frame = menustack[menustack.size()-1];
|
|
|
|
|
|
|
|
//Store this in case createmenu() removes the stack frame
|
|
|
|
int previousoption = frame.option;
|
|
|
|
|
|
|
|
createmenu(frame.name, true);
|
|
|
|
currentmenuoption = previousoption;
|
|
|
|
|
|
|
|
//Remove the stackframe now, but createmenu() might have already gotten to it
|
|
|
|
//if we were returning to the main menu
|
|
|
|
if (!menustack.empty())
|
|
|
|
{
|
|
|
|
menustack.pop_back();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-26 02:36:36 +02:00
|
|
|
void Game::returntomenu(enum Menu::MenuName t)
|
|
|
|
{
|
|
|
|
if (currentmenuname == t)
|
|
|
|
{
|
2020-05-19 00:19:56 +02:00
|
|
|
//Re-create the menu
|
|
|
|
int keep_menu_option = currentmenuoption;
|
|
|
|
createmenu(t, true);
|
|
|
|
if (keep_menu_option < (int) menuoptions.size())
|
|
|
|
{
|
|
|
|
currentmenuoption = keep_menu_option;
|
|
|
|
}
|
2020-04-26 02:36:36 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Unwind the menu stack until we reach our desired menu
|
|
|
|
int i = menustack.size() - 1;
|
|
|
|
while (i >= 0)
|
|
|
|
{
|
|
|
|
//If we pop it off we can't reference it anymore, so check for it now
|
|
|
|
bool is_the_menu_we_want = menustack[i].name == t;
|
|
|
|
|
|
|
|
returnmenu();
|
|
|
|
|
|
|
|
if (is_the_menu_we_want)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-17 04:04:36 +02:00
|
|
|
void Game::createmenu( enum Menu::MenuName t, bool samemenu/*= false*/ )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-17 04:04:36 +02:00
|
|
|
if (t == Menu::mainmenu)
|
|
|
|
{
|
|
|
|
//Either we've just booted up the game or returned from gamemode
|
|
|
|
//Whichever it is, we shouldn't have a stack,
|
|
|
|
//and most likely don't have a current stackframe
|
|
|
|
menustack.clear();
|
|
|
|
}
|
|
|
|
else if (!samemenu)
|
|
|
|
{
|
|
|
|
MenuStackFrame frame;
|
|
|
|
frame.option = currentmenuoption;
|
|
|
|
frame.name = currentmenuname;
|
|
|
|
menustack.push_back(frame);
|
|
|
|
}
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
currentmenuoption = 0;
|
|
|
|
currentmenuname = t;
|
|
|
|
menuyoff = 0;
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
int maxspacing = 30; // maximum value for menuspacing, can only become lower.
|
2020-01-01 21:29:24 +01:00
|
|
|
menucountdown = 0;
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
menuoptions.clear();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-17 00:19:17 +02:00
|
|
|
switch (t)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-17 00:19:17 +02:00
|
|
|
case Menu::mainmenu:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
#if !defined(MAKEANDPLAY)
|
|
|
|
option("start game");
|
|
|
|
#endif
|
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
|
|
|
option("player levels");
|
|
|
|
#endif
|
|
|
|
option("graphic options");
|
|
|
|
option("game options");
|
|
|
|
#if !defined(MAKEANDPLAY)
|
|
|
|
option("view credits");
|
|
|
|
#endif
|
|
|
|
option("quit game");
|
2020-04-02 22:01:55 +02:00
|
|
|
menuyoff = -10;
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
maxspacing = 15;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
2020-02-10 03:21:19 +01:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
2020-04-17 00:19:17 +02:00
|
|
|
case Menu::playerworlds:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("play a level");
|
2020-04-02 22:01:55 +02:00
|
|
|
#if !defined(NO_EDITOR)
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("level editor");
|
|
|
|
#endif
|
2020-04-18 03:50:10 +02:00
|
|
|
option("open level folder", FILESYSTEM_openDirectoryEnabled());
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("back to menu");
|
2020-02-10 03:21:19 +01:00
|
|
|
menuyoff = -40;
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
maxspacing = 15;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::levellist:
|
2020-01-01 21:29:24 +01:00
|
|
|
if(ed.ListOfMetaData.size()==0)
|
|
|
|
{
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("ok");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = -20;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for(int i=0; i<(int) ed.ListOfMetaData.size(); i++) // FIXME: int/size_t! -flibit
|
|
|
|
{
|
|
|
|
if(i>=levelpage*8 && i< (levelpage*8)+8)
|
|
|
|
{
|
|
|
|
//This is, er, suboptimal. Whatever, life optimisation and all that
|
|
|
|
int tvar=-1;
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
for(size_t j=0; j<customlevelstats.size(); j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
if(ed.ListOfMetaData[i].filename.substr(7) == customlevelstats[j].name)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
tvar=j;
|
Refactor how custom level stats are stored, read, and written
There were a few problems with the old way of doing things:
(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.
To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.
For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
2020-06-30 03:39:22 +02:00
|
|
|
break;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
2020-07-04 06:30:31 +02:00
|
|
|
const char* prefix;
|
2020-01-01 21:29:24 +01:00
|
|
|
if(tvar>=0)
|
|
|
|
{
|
2020-07-04 06:30:31 +02:00
|
|
|
switch (customlevelstats[tvar].score)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-04 06:30:31 +02:00
|
|
|
case 0:
|
|
|
|
{
|
|
|
|
static const char tmp[] = " ";
|
|
|
|
prefix = tmp;
|
|
|
|
break;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-07-04 06:30:31 +02:00
|
|
|
case 1:
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-04 06:30:31 +02:00
|
|
|
static const char tmp[] = " * ";
|
|
|
|
prefix = tmp;
|
|
|
|
break;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-07-04 06:30:31 +02:00
|
|
|
case 3:
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-04 06:30:31 +02:00
|
|
|
static const char tmp[] = "** ";
|
|
|
|
prefix = tmp;
|
|
|
|
break;
|
|
|
|
}
|
2020-07-15 18:11:23 +02:00
|
|
|
default:
|
|
|
|
SDL_assert(0 && "Unhandled menu text prefix!");
|
|
|
|
prefix = "";
|
|
|
|
break;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-07-04 06:30:31 +02:00
|
|
|
static const char tmp[] = " ";
|
|
|
|
prefix = tmp;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-07-04 06:30:31 +02:00
|
|
|
char text[menutextbytes];
|
|
|
|
SDL_snprintf(text, sizeof(text), "%s%s", prefix, ed.ListOfMetaData[i].title.c_str());
|
|
|
|
for (size_t ii = 0; ii < SDL_arraysize(text); ii++)
|
2020-07-03 23:54:23 +02:00
|
|
|
{
|
|
|
|
text[ii] = SDL_tolower(text[ii]);
|
|
|
|
}
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option(text);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if((size_t) ((levelpage*8)+8) <ed.ListOfMetaData.size())
|
|
|
|
{
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("next page");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("first page");
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-04-17 08:05:49 +02:00
|
|
|
if (levelpage == 0)
|
|
|
|
{
|
|
|
|
option("last page");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
option("previous page");
|
|
|
|
}
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return to menu");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
menuxoff = 20;
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
menuyoff = 70-(menuoptions.size()*10);
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
menuspacing = 5;
|
|
|
|
return; // skip automatic centering, will turn out bad with levels list
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
2020-02-10 01:53:01 +01:00
|
|
|
#endif
|
2020-04-17 00:19:17 +02:00
|
|
|
case Menu::quickloadlevel:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("continue from save");
|
|
|
|
option("start from beginning");
|
|
|
|
option("back to levels");
|
2020-02-10 01:53:01 +01:00
|
|
|
menuyoff = -30;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::youwannaquit:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("yes, quit");
|
|
|
|
option("no, return");
|
2020-02-10 01:53:01 +01:00
|
|
|
menuyoff = -20;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::errornostart:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("ok");
|
2020-02-10 01:53:01 +01:00
|
|
|
menuyoff = -20;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::graphicoptions:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("toggle fullscreen");
|
2020-06-30 22:36:57 +02:00
|
|
|
option("scaling mode");
|
2020-06-30 22:30:59 +02:00
|
|
|
option("resize to nearest", graphics.screenbuffer->isWindowed);
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("toggle filter");
|
|
|
|
option("toggle analogue");
|
2020-05-04 21:52:57 +02:00
|
|
|
option("toggle fps");
|
2020-05-04 22:19:47 +02:00
|
|
|
option("toggle vsync");
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return");
|
Add "resize to nearest" graphics option
If you want your game window to simply be exactly 320x240, or 640x480,
or 960x720 etc. then it's really annoying that there's no easy way to do
this (to clarify, this is different from integer mode, which controls
the size of the game INSIDE the window). The easiest way would be having
to close the game, go into unlock.vvv, and edit the window size
manually. VCE has a 1x/2x/3x/4x graphics option to solve this, although
it does not account for actual monitor size (those 1x/2x/3x/4x modes are
all you get, whether or not you have a monitor too small for some of
them or too big for any of them to be what you want).
I discussed this with flibit, and he said that VCE's approach (if it
accounted for monitor size) wouldn't work on high-retina displays or
high DPIs, because getting the actual multiplier to account for those
monitors is kind of a pain. So the next best thing would be to add an
option that resizes to the nearest perfect multiple of 320x240. That way
you could simply resize the window and let the game correct any
imperfect dimensions automatically.
2020-06-30 07:02:21 +02:00
|
|
|
menuyoff = -10;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::ed_settings:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("change description");
|
|
|
|
option("edit scripts");
|
|
|
|
option("change music");
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
option("editor ghosts");
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("load level");
|
|
|
|
option("save level");
|
|
|
|
option("quit to main menu");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
menuyoff = -20;
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
maxspacing = 15;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::ed_desc:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("change name");
|
|
|
|
option("change author");
|
|
|
|
option("change description");
|
|
|
|
option("change website");
|
|
|
|
option("back to settings");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
menuyoff = 6;
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
maxspacing = 15;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::ed_music:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("next song");
|
|
|
|
option("back");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 16;
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
maxspacing = 15;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::ed_quit:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("yes, save and quit");
|
|
|
|
option("no, quit without saving");
|
|
|
|
option("return to editor");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 8;
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
maxspacing = 15;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::options:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("accessibility options");
|
2020-06-30 22:06:19 +02:00
|
|
|
option("advanced options");
|
2020-06-30 05:45:57 +02:00
|
|
|
#if !defined(MAKEANDPLAY)
|
|
|
|
if (ingame_titlemode && unlock[18])
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
option("flip mode");
|
|
|
|
}
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
#if !defined(MAKEANDPLAY)
|
|
|
|
option("unlock play modes");
|
|
|
|
#endif
|
|
|
|
option("game pad options");
|
|
|
|
option("clear data");
|
|
|
|
//Add extra menu for mmmmmm mod
|
|
|
|
if(music.mmmmmm){
|
|
|
|
option("soundtrack");
|
|
|
|
}
|
2020-04-02 22:01:55 +02:00
|
|
|
|
2020-06-30 22:06:19 +02:00
|
|
|
option("return");
|
|
|
|
menuyoff = 0;
|
|
|
|
break;
|
|
|
|
case Menu::advancedoptions:
|
|
|
|
option("toggle mouse");
|
|
|
|
option("unfocus pause");
|
|
|
|
option("fake load screen");
|
|
|
|
option("room name background");
|
|
|
|
option("glitchrunner mode");
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return");
|
2020-04-02 22:01:55 +02:00
|
|
|
menuyoff = 0;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::accessibility:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("animated backgrounds");
|
|
|
|
option("screen effects");
|
|
|
|
option("text outline");
|
2020-09-28 04:15:06 +02:00
|
|
|
option("invincibility", !ingame_titlemode || (!insecretlab && !intimetrial && !nodeathmode));
|
|
|
|
option("slowdown", !ingame_titlemode || (!insecretlab && !intimetrial && !nodeathmode));
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return");
|
2020-06-30 22:06:19 +02:00
|
|
|
menuyoff = 0;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::controller:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("analog stick sensitivity");
|
|
|
|
option("bind flip");
|
|
|
|
option("bind enter");
|
|
|
|
option("bind menu");
|
2020-08-09 01:06:20 +02:00
|
|
|
option("bind restart");
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return");
|
2020-04-02 22:01:55 +02:00
|
|
|
menuyoff = 10;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::cleardatamenu:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("no! don't delete");
|
|
|
|
option("yes, delete everything");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 64;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::setinvincibility:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("no, return to options");
|
|
|
|
option("yes, enable");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 64;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
2020-04-17 01:02:01 +02:00
|
|
|
case Menu::setslowdown:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("normal speed");
|
|
|
|
option("80% speed");
|
|
|
|
option("60% speed");
|
|
|
|
option("40% speed");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 16;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::unlockmenu:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("unlock time trials");
|
|
|
|
option("unlock intermissions", !unlock[16]);
|
|
|
|
option("unlock no death mode", !unlock[17]);
|
|
|
|
option("unlock flip mode", !unlock[18]);
|
|
|
|
option("unlock ship jukebox", (stat_trinkets<20));
|
|
|
|
option("unlock secret lab", !unlock[8]);
|
|
|
|
option("return");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = -20;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::credits:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("next page");
|
2020-04-17 08:37:49 +02:00
|
|
|
option("last page");
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 64;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::credits2:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("next page");
|
2020-04-17 08:37:49 +02:00
|
|
|
option("previous page");
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 64;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::credits25:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("next page");
|
2020-04-17 08:37:49 +02:00
|
|
|
option("previous page");
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 64;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::credits3:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("next page");
|
2020-04-17 08:37:49 +02:00
|
|
|
option("previous page");
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 64;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::credits4:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("next page");
|
2020-04-17 08:37:49 +02:00
|
|
|
option("previous page");
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 64;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::credits5:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("next page");
|
2020-04-17 08:37:49 +02:00
|
|
|
option("previous page");
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 64;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::credits6:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("first page");
|
2020-04-17 08:37:49 +02:00
|
|
|
option("previous page");
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 64;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::play:
|
2020-04-17 01:08:56 +02:00
|
|
|
{
|
2020-01-01 21:29:24 +01:00
|
|
|
//Ok, here's where the unlock stuff comes into it:
|
|
|
|
//First up, time trials:
|
2020-04-17 01:08:56 +02:00
|
|
|
int temp = 0;
|
2020-01-01 21:29:24 +01:00
|
|
|
if (unlock[0] && stat_trinkets >= 3 && !unlocknotify[9]) temp++;
|
|
|
|
if (unlock[1] && stat_trinkets >= 6 && !unlocknotify[10]) temp++;
|
|
|
|
if (unlock[2] && stat_trinkets >= 9 && !unlocknotify[11]) temp++;
|
|
|
|
if (unlock[3] && stat_trinkets >= 12 && !unlocknotify[12]) temp++;
|
|
|
|
if (unlock[4] && stat_trinkets >= 15 && !unlocknotify[13]) temp++;
|
|
|
|
if (unlock[5] && stat_trinkets >= 18 && !unlocknotify[14]) temp++;
|
|
|
|
if (temp > 0)
|
|
|
|
{
|
|
|
|
//you've unlocked a time trial!
|
|
|
|
if (unlock[0] && stat_trinkets >= 3)
|
|
|
|
{
|
|
|
|
unlocknotify[9] = true;
|
|
|
|
unlock[9] = true;
|
|
|
|
}
|
|
|
|
if (unlock[1] && stat_trinkets >= 6)
|
|
|
|
{
|
|
|
|
unlocknotify[10] = true;
|
|
|
|
unlock[10] = true;
|
|
|
|
}
|
|
|
|
if (unlock[2] && stat_trinkets >= 9)
|
|
|
|
{
|
|
|
|
unlocknotify[11] = true;
|
|
|
|
unlock[11] = true;
|
|
|
|
}
|
|
|
|
if (unlock[3] && stat_trinkets >= 12)
|
|
|
|
{
|
|
|
|
unlocknotify[12] = true;
|
|
|
|
unlock[12] = true;
|
|
|
|
}
|
|
|
|
if (unlock[4] && stat_trinkets >= 15)
|
|
|
|
{
|
|
|
|
unlocknotify[13] = true;
|
|
|
|
unlock[13] = true;
|
|
|
|
}
|
|
|
|
if (unlock[5] && stat_trinkets >= 18)
|
|
|
|
{
|
|
|
|
unlocknotify[14] = true;
|
|
|
|
unlock[14] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (temp == 1)
|
|
|
|
{
|
2020-08-03 06:37:52 +02:00
|
|
|
createmenu(Menu::unlocktimetrial, true);
|
2020-01-01 21:29:24 +01:00
|
|
|
savemystats = true;
|
|
|
|
}
|
|
|
|
else if (temp > 1)
|
|
|
|
{
|
2020-08-03 06:37:52 +02:00
|
|
|
createmenu(Menu::unlocktimetrials, true);
|
2020-01-01 21:29:24 +01:00
|
|
|
savemystats = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//Alright, we haven't unlocked any time trials. How about no death mode?
|
|
|
|
temp = 0;
|
|
|
|
if (bestrank[0] >= 2) temp++;
|
|
|
|
if (bestrank[1] >= 2) temp++;
|
|
|
|
if (bestrank[2] >= 2) temp++;
|
|
|
|
if (bestrank[3] >= 2) temp++;
|
|
|
|
if (bestrank[4] >= 2) temp++;
|
|
|
|
if (bestrank[5] >= 2) temp++;
|
|
|
|
if (temp >= 4 && !unlocknotify[17])
|
|
|
|
{
|
|
|
|
//Unlock No Death Mode
|
|
|
|
unlocknotify[17] = true;
|
|
|
|
unlock[17] = true;
|
2020-08-03 06:37:52 +02:00
|
|
|
createmenu(Menu::unlocknodeathmode, true);
|
2020-01-01 21:29:24 +01:00
|
|
|
savemystats = true;
|
|
|
|
}
|
2020-04-15 21:11:33 +02:00
|
|
|
//Alright then! Flip mode?
|
|
|
|
else if (unlock[5] && !unlocknotify[18])
|
|
|
|
{
|
|
|
|
unlock[18] = true;
|
|
|
|
unlocknotify[18] = true;
|
2020-08-03 06:37:52 +02:00
|
|
|
createmenu(Menu::unlockflipmode, true);
|
2020-04-15 21:11:33 +02:00
|
|
|
savemystats = true;
|
|
|
|
}
|
|
|
|
//What about the intermission levels?
|
|
|
|
else if (unlock[7] && !unlocknotify[16])
|
|
|
|
{
|
|
|
|
unlock[16] = true;
|
|
|
|
unlocknotify[16] = true;
|
2020-08-03 06:37:52 +02:00
|
|
|
createmenu(Menu::unlockintermission, true);
|
2020-04-15 21:11:33 +02:00
|
|
|
savemystats = true;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
else
|
|
|
|
{
|
2020-04-26 22:41:35 +02:00
|
|
|
if (save_exists())
|
2020-04-26 22:09:56 +02:00
|
|
|
{
|
|
|
|
option("continue");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-05-06 21:37:50 +02:00
|
|
|
option("new game");
|
2020-04-26 22:09:56 +02:00
|
|
|
}
|
2020-04-16 04:38:42 +02:00
|
|
|
//ok, secret lab! no notification, but test:
|
|
|
|
if (unlock[8])
|
|
|
|
{
|
2020-04-26 22:14:52 +02:00
|
|
|
option("secret lab", !map.invincibility && slowdown == 30);
|
2020-04-16 04:38:42 +02:00
|
|
|
}
|
2020-04-15 21:11:33 +02:00
|
|
|
option("play modes");
|
2020-04-26 22:41:35 +02:00
|
|
|
if (save_exists())
|
2020-04-26 22:09:56 +02:00
|
|
|
{
|
|
|
|
option("new game");
|
|
|
|
}
|
2020-04-15 21:11:33 +02:00
|
|
|
option("return");
|
2020-04-16 04:38:42 +02:00
|
|
|
if (unlock[8])
|
|
|
|
{
|
|
|
|
menuyoff = -30;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
menuyoff = -40;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
2020-04-17 01:08:56 +02:00
|
|
|
}
|
2020-04-17 00:19:17 +02:00
|
|
|
case Menu::unlocktimetrial:
|
|
|
|
case Menu::unlocktimetrials:
|
|
|
|
case Menu::unlocknodeathmode:
|
|
|
|
case Menu::unlockintermission:
|
|
|
|
case Menu::unlockflipmode:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("continue");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 70;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::newgamewarning:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("start new game");
|
|
|
|
option("return to menu");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 64;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::playmodes:
|
2020-04-26 22:14:52 +02:00
|
|
|
option("time trials", !map.invincibility && slowdown == 30);
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("intermissions", unlock[16]);
|
2020-04-26 22:14:52 +02:00
|
|
|
option("no death mode", unlock[17] && !map.invincibility && slowdown == 30);
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("flip mode", unlock[18]);
|
|
|
|
option("return to play menu");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 8;
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
maxspacing = 20;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::intermissionmenu:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("play intermission 1");
|
|
|
|
option("play intermission 2");
|
|
|
|
option("return to play menu");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = -35;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::playint1:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("Vitellary");
|
|
|
|
option("Vermilion");
|
|
|
|
option("Verdigris");
|
|
|
|
option("Victoria");
|
|
|
|
option("return");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 10;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::playint2:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("Vitellary");
|
|
|
|
option("Vermilion");
|
|
|
|
option("Verdigris");
|
|
|
|
option("Victoria");
|
|
|
|
option("return");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 10;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::continuemenu:
|
2020-04-17 04:52:39 +02:00
|
|
|
map.settowercolour(3);
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("continue from teleporter");
|
|
|
|
option("continue from quicksave");
|
|
|
|
option("return to play menu");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 20;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::startnodeathmode:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("disable cutscenes");
|
|
|
|
option("enable cutscenes");
|
|
|
|
option("return to play menu");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 40;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::gameover:
|
2020-01-01 21:29:24 +01:00
|
|
|
menucountdown = 120;
|
2020-04-16 06:53:36 +02:00
|
|
|
menudest=Menu::gameover2;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::gameover2:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return to play menu");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 80;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::unlockmenutrials:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("space station 1", !unlock[9]);
|
|
|
|
option("the laboratory", !unlock[10]);
|
|
|
|
option("the tower", !unlock[11]);
|
|
|
|
option("space station 2", !unlock[12]);
|
|
|
|
option("the warp zone", !unlock[13]);
|
|
|
|
option("the final level", !unlock[14]);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return to unlock menu");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 0;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::timetrials:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option(unlock[9] ? "space station 1" : "???", unlock[9]);
|
|
|
|
option(unlock[10] ? "the laboratory" : "???", unlock[10]);
|
|
|
|
option(unlock[11] ? "the tower" : "???", unlock[11]);
|
|
|
|
option(unlock[12] ? "space station 2" : "???", unlock[12]);
|
|
|
|
option(unlock[13] ? "the warp zone" : "???", unlock[13]);
|
|
|
|
option(unlock[14] ? "the final level" : "???", unlock[14]);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return to play menu");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 0;
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
maxspacing = 15;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::nodeathmodecomplete:
|
2020-01-01 21:29:24 +01:00
|
|
|
menucountdown = 90;
|
2020-04-16 06:53:36 +02:00
|
|
|
menudest = Menu::nodeathmodecomplete2;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::nodeathmodecomplete2:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return to play menu");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 70;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::timetrialcomplete:
|
2020-01-01 21:29:24 +01:00
|
|
|
menucountdown = 90;
|
2020-04-16 06:53:36 +02:00
|
|
|
menudest=Menu::timetrialcomplete2;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::timetrialcomplete2:
|
2020-01-01 21:29:24 +01:00
|
|
|
menucountdown = 60;
|
2020-04-16 06:53:36 +02:00
|
|
|
menudest=Menu::timetrialcomplete3;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::timetrialcomplete3:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return to play menu");
|
|
|
|
option("try again");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 70;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::gamecompletecontinue:
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
option("return to play menu");
|
2020-01-01 21:29:24 +01:00
|
|
|
menuyoff = 70;
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
|
|
|
|
// Automatically center the menu. We must check the width of the menu with the initial horizontal spacing.
|
2020-06-29 23:18:33 +02:00
|
|
|
// If it's too wide, reduce the horizontal spacing by 5 and retry.
|
|
|
|
// Try to limit the menu width to 272 pixels: 320 minus 16*2 for square brackets, minus 8*2 padding.
|
|
|
|
// The square brackets fall outside the menu width (i.e. selected menu options are printed 16 pixels to the left)
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
bool done_once = false;
|
|
|
|
int menuwidth = 0;
|
2020-06-29 02:58:38 +02:00
|
|
|
for (; !done_once || (menuwidth > 272 && menuspacing > 0); maxspacing -= 5)
|
Make menus automatically centered and narrowed
All menus had a hardcoded X position (offset to an arbitrary starting
point of 110) and a hardcoded horizontal spacing for the "staircasing"
(mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or
something else). Not all menus were centered, and seem to have been
manually made narrower (with lower horizontal spacing) whenever text
ran offscreen during development.
This system may already be hard to work with in an English-only menu
system, since you may need to adjust horizontal spacing or positioning
when adding an option. The main reason I made this change is that it's
even less optimal when menu options have to be translated, since
maximum string lengths are hard to determine, and it's easy to have
menu options running offscreen, especially when not all menus are
checked for all languages and when options could be added in the middle
of a menu after translations of that menu are already checked.
Now, menus are automatically centered based on their options, and they
are automatically made narrower if they won't fit with the default
horizontal spacing of 30 pixels (with some padding). The game.menuxoff
variable for the menu X position is now also offset to 0 instead of 110
The _default_ horizontal spacing can be changed on a per-menu basis,
and most menus (not all) which already had a narrower spacing set,
retain that as a maximum spacing, simply because they looked odd with
30 pixels of spacing (especially the main menu). They will be made even
narrower automatically if needed. In the most extreme case, the spacing
can go down to 0 and options will be displayed right below each other.
This isn't in the usual style of the game, but at least we did the best
we could to prevent options running offscreen.
The only exception to automatic menu centering and narrowing is the
list of player levels, because it's a special case and existing
behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
|
|
|
{
|
|
|
|
done_once = true;
|
|
|
|
menuspacing = maxspacing;
|
|
|
|
menuwidth = 0;
|
|
|
|
for (size_t i = 0; i < menuoptions.size(); i++)
|
|
|
|
{
|
|
|
|
int width = i*menuspacing + graphics.len(menuoptions[i].text);
|
|
|
|
if (width > menuwidth)
|
|
|
|
menuwidth = width;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
menuxoff = (320-menuwidth)/2;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Game::deletequick()
|
|
|
|
{
|
2020-04-26 22:22:26 +02:00
|
|
|
if( !FILESYSTEM_delete( "saves/qsave.vvv" ) )
|
|
|
|
puts("Error deleting saves/qsave.vvv");
|
2020-04-26 22:15:38 +02:00
|
|
|
else
|
|
|
|
quicksummary = "";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Game::deletetele()
|
|
|
|
{
|
2020-04-26 22:22:26 +02:00
|
|
|
if( !FILESYSTEM_delete( "saves/tsave.vvv" ) )
|
|
|
|
puts("Error deleting saves/tsave.vvv");
|
2020-04-26 22:15:38 +02:00
|
|
|
else
|
|
|
|
telesummary = "";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Game::swnpenalty()
|
|
|
|
{
|
|
|
|
//set the SWN clock back to the closest 5 second interval
|
|
|
|
if (swntimer <= 150)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 150) swntimer = 150;
|
|
|
|
}
|
|
|
|
else if (swntimer <= 300)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 300) swntimer = 300;
|
|
|
|
}
|
|
|
|
else if (swntimer <= 450)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 450) swntimer = 450;
|
|
|
|
}
|
|
|
|
else if (swntimer <= 600)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 600) swntimer = 600;
|
|
|
|
}
|
|
|
|
else if (swntimer <= 750)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 750) swntimer = 750;
|
|
|
|
}
|
|
|
|
else if (swntimer <= 900)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 900) swntimer = 900;
|
|
|
|
}
|
|
|
|
else if (swntimer <= 1050)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 1050) swntimer = 1050;
|
|
|
|
}
|
|
|
|
else if (swntimer <= 1200)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 1200) swntimer = 1200;
|
|
|
|
}
|
|
|
|
else if (swntimer <= 1350)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 1350) swntimer = 1350;
|
|
|
|
}
|
|
|
|
else if (swntimer <= 1500)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 1500) swntimer = 1500;
|
|
|
|
}
|
|
|
|
else if (swntimer <= 1650)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 1650) swntimer = 1650;
|
|
|
|
}
|
|
|
|
else if (swntimer <= 1800)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 1800) swntimer = 1800;
|
|
|
|
}
|
|
|
|
else if (swntimer <= 2100)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 2100) swntimer = 2100;
|
|
|
|
}
|
|
|
|
else if (swntimer <= 2400)
|
|
|
|
{
|
|
|
|
swntimer += 8;
|
|
|
|
if (swntimer > 2400) swntimer = 2400;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int Game::crewrescued()
|
|
|
|
{
|
2020-04-10 01:01:53 +02:00
|
|
|
int temp = 0;
|
2020-07-03 03:10:52 +02:00
|
|
|
for (size_t i = 0; i < SDL_arraysize(crewstats); i++)
|
2020-04-10 01:01:53 +02:00
|
|
|
{
|
|
|
|
if (crewstats[i])
|
|
|
|
{
|
|
|
|
temp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return temp;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Game::resetgameclock()
|
|
|
|
{
|
|
|
|
frames = 0;
|
|
|
|
seconds = 0;
|
|
|
|
minutes = 0;
|
|
|
|
hours = 0;
|
|
|
|
}
|
2020-04-07 08:46:27 +02:00
|
|
|
|
|
|
|
int Game::trinkets()
|
|
|
|
{
|
2020-04-10 01:17:11 +02:00
|
|
|
int temp = 0;
|
2020-07-03 04:17:32 +02:00
|
|
|
for (size_t i = 0; i < SDL_arraysize(obj.collect); i++)
|
2020-04-10 01:17:11 +02:00
|
|
|
{
|
|
|
|
if (obj.collect[i])
|
|
|
|
{
|
|
|
|
temp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return temp;
|
2020-04-07 08:46:27 +02:00
|
|
|
}
|
2020-04-07 08:53:32 +02:00
|
|
|
|
|
|
|
int Game::crewmates()
|
|
|
|
{
|
2020-04-10 01:17:52 +02:00
|
|
|
int temp = 0;
|
2020-07-03 04:17:32 +02:00
|
|
|
for (size_t i = 0; i < SDL_arraysize(obj.customcollect); i++)
|
2020-04-10 01:17:52 +02:00
|
|
|
{
|
|
|
|
if (obj.customcollect[i])
|
|
|
|
{
|
|
|
|
temp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return temp;
|
2020-04-07 08:53:32 +02:00
|
|
|
}
|
2020-04-26 21:43:30 +02:00
|
|
|
|
|
|
|
bool Game::anything_unlocked()
|
|
|
|
{
|
2020-07-03 01:45:22 +02:00
|
|
|
for (size_t i = 0; i < SDL_arraysize(unlock); i++)
|
2020-04-26 21:43:30 +02:00
|
|
|
{
|
|
|
|
if (unlock[i] &&
|
|
|
|
(i == 8 // Secret Lab
|
2020-06-28 11:12:56 +02:00
|
|
|
|| (i >= 9 && i <= 14) // any Time Trial
|
2020-04-26 21:43:30 +02:00
|
|
|
|| i == 16 // Intermission replays
|
|
|
|
|| i == 17 // No Death Mode
|
|
|
|
|| i == 18)) // Flip Mode
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2020-04-26 22:41:35 +02:00
|
|
|
|
|
|
|
bool Game::save_exists()
|
|
|
|
{
|
|
|
|
return telesummary != "" || quicksummary != "";
|
|
|
|
}
|
2020-05-07 23:38:19 +02:00
|
|
|
|
|
|
|
void Game::quittomenu()
|
|
|
|
{
|
|
|
|
gamestate = TITLEMODE;
|
|
|
|
graphics.fademode = 4;
|
2020-06-17 09:56:07 +02:00
|
|
|
FILESYSTEM_unmountassets(); // should be before music.play(6)
|
2020-05-07 23:38:19 +02:00
|
|
|
music.play(6);
|
|
|
|
graphics.backgrounddrawn = false;
|
2020-11-03 00:23:53 +01:00
|
|
|
graphics.titlebg.tdrawback = true;
|
2020-05-07 23:38:19 +02:00
|
|
|
graphics.flipmode = false;
|
|
|
|
//Don't be stuck on the summary screen,
|
|
|
|
//or "who do you want to play the level with?"
|
|
|
|
//or "do you want cutscenes?"
|
|
|
|
//or the confirm-load-quicksave menu
|
2020-05-07 23:54:39 +02:00
|
|
|
if (intimetrial)
|
2020-05-07 23:38:19 +02:00
|
|
|
{
|
|
|
|
returntomenu(Menu::timetrials);
|
|
|
|
}
|
2020-05-07 23:54:39 +02:00
|
|
|
else if (inintermission)
|
2020-05-07 23:38:19 +02:00
|
|
|
{
|
|
|
|
returntomenu(Menu::intermissionmenu);
|
|
|
|
}
|
2020-05-07 23:54:39 +02:00
|
|
|
else if (nodeathmode)
|
2020-05-07 23:38:19 +02:00
|
|
|
{
|
|
|
|
returntomenu(Menu::playmodes);
|
|
|
|
}
|
2020-05-07 23:54:39 +02:00
|
|
|
else if (map.custommode)
|
2020-05-07 23:38:19 +02:00
|
|
|
{
|
|
|
|
returntomenu(Menu::levellist);
|
|
|
|
}
|
|
|
|
else if (save_exists() || anything_unlocked())
|
|
|
|
{
|
|
|
|
returntomenu(Menu::play);
|
2020-05-19 02:49:35 +02:00
|
|
|
if (!insecretlab)
|
|
|
|
{
|
|
|
|
//Select "continue"
|
|
|
|
currentmenuoption = 0;
|
|
|
|
}
|
2020-05-07 23:38:19 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
createmenu(Menu::mainmenu);
|
|
|
|
}
|
2020-05-07 23:54:39 +02:00
|
|
|
script.hardreset();
|
2020-05-07 23:38:19 +02:00
|
|
|
}
|
2020-05-08 00:17:04 +02:00
|
|
|
|
|
|
|
void Game::returntolab()
|
|
|
|
{
|
|
|
|
gamestate = GAMEMODE;
|
|
|
|
graphics.fademode = 4;
|
2020-05-08 00:36:38 +02:00
|
|
|
map.gotoroom(119, 107);
|
|
|
|
int player = obj.getplayer();
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(player, obj.entities))
|
2020-05-08 00:36:38 +02:00
|
|
|
{
|
|
|
|
obj.entities[player].xp = 132;
|
|
|
|
obj.entities[player].yp = 137;
|
|
|
|
}
|
|
|
|
gravitycontrol = 0;
|
|
|
|
|
|
|
|
savepoint = 0;
|
|
|
|
saverx = 119;
|
|
|
|
savery = 107;
|
|
|
|
savex = 132;
|
|
|
|
savey = 137;
|
|
|
|
savegc = 0;
|
Use explicit INBOUNDS_VEC() instead of checking sentinel -1
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
2020-09-09 13:15:14 +02:00
|
|
|
if (INBOUNDS_VEC(player, obj.entities))
|
2020-05-08 00:36:38 +02:00
|
|
|
{
|
|
|
|
savedir = obj.entities[player].dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
music.play(11);
|
2020-05-08 00:17:04 +02:00
|
|
|
}
|
2020-05-09 21:35:17 +02:00
|
|
|
|
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
|
|
|
void Game::returntoeditor()
|
|
|
|
{
|
2020-09-28 04:15:06 +02:00
|
|
|
gamestate = EDITORMODE;
|
2020-05-09 21:35:17 +02:00
|
|
|
|
2020-07-11 09:10:06 +02:00
|
|
|
graphics.textbox.clear();
|
2020-09-28 04:15:06 +02:00
|
|
|
hascontrol = true;
|
|
|
|
advancetext = false;
|
|
|
|
completestop = false;
|
|
|
|
state = 0;
|
2020-05-09 21:35:17 +02:00
|
|
|
graphics.showcutscenebars = false;
|
|
|
|
graphics.fademode = 0;
|
|
|
|
|
2020-07-11 09:04:54 +02:00
|
|
|
ed.keydelay = 6;
|
2020-07-11 09:09:02 +02:00
|
|
|
ed.settingskey = true;
|
2020-07-11 09:27:00 +02:00
|
|
|
ed.oldnotedelay = 0;
|
|
|
|
ed.notedelay = 0;
|
2020-07-11 09:39:09 +02:00
|
|
|
ed.roomnamehide = 0;
|
2020-07-11 09:04:54 +02:00
|
|
|
|
2020-05-09 21:35:17 +02:00
|
|
|
graphics.backgrounddrawn=false;
|
|
|
|
music.fadeout();
|
|
|
|
//If warpdir() is used during playtesting, we need to set it back after!
|
|
|
|
for (int j = 0; j < ed.maxheight; j++)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < ed.maxwidth; i++)
|
|
|
|
{
|
|
|
|
ed.level[i+(j*ed.maxwidth)].warpdir=ed.kludgewarpdir[i+(j*ed.maxwidth)];
|
|
|
|
}
|
|
|
|
}
|
2020-11-03 00:23:53 +01:00
|
|
|
graphics.titlebg.scrolldir = 0;
|
2020-05-09 21:35:17 +02:00
|
|
|
}
|
|
|
|
#endif
|
2020-06-23 02:26:45 +02:00
|
|
|
|
|
|
|
void Game::returntopausemenu()
|
|
|
|
{
|
|
|
|
ingame_titlemode = false;
|
|
|
|
returntomenu(kludge_ingametemp);
|
|
|
|
gamestate = MAPMODE;
|
2020-09-28 04:15:06 +02:00
|
|
|
mapheld = true;
|
2020-06-23 06:41:21 +02:00
|
|
|
graphics.flipmode = graphics.setflipmode;
|
Fix being able to circumvent not-in-Flip-Mode detection
So you get a trophy and achievement for completing the game in Flip
Mode. Which begs the question, how does the game know that you've played
through the game in Flip Mode the entire way, and haven't switched it
off at any point? It looks like if you play normally all the way up
until the checkpoint in V, and then turn on Flip Mode, the game won't
give you the trophy. What gives?
Well, actually, what happens is that every time you press Enter on a
teleporter, the game will set flag 73 to true if you're NOT in Flip
Mode. Then when Game Complete runs, the game will check if flag 73 is
off, and then give you the achievement and trophy accordingly.
However, what this means is that you could just save your game before
pressing Enter on a teleporter, then quit and go into options, turn on
Flip Mode, use the teleporter, then save your game (it's automatically
saved since you just used a teleporter), quit and go into options, and
turn it off. Then you'd get the Flip Mode trophy even though you haven't
actually played the entire game in Flip Mode.
Furthermore, in 2.3 you can bring up the pause menu to toggle Flip Mode,
so you don't even have to quit to circumvent this detection.
To fix both of these exploits, I moved the turning on of flag 73 to
starting a new game, loading a quicksave, and loading a telesave (cases
0, 1, and 2 respectively in scriptclass::startgamemode()). I also added
a Flip Mode check to the routine that runs whenever you exit an options
menu back to the pause menu, so you can't circumvent the detection that
way, either.
2020-07-11 01:30:28 +02:00
|
|
|
if (!map.custommode && !graphics.flipmode)
|
|
|
|
{
|
|
|
|
obj.flags[73] = true;
|
|
|
|
}
|
2020-06-23 02:26:45 +02:00
|
|
|
}
|
2020-08-01 21:49:07 +02:00
|
|
|
|
|
|
|
void Game::unlockAchievement(const char *name) {
|
|
|
|
#if !defined(MAKEANDPLAY)
|
|
|
|
if (!map.custommode) NETWORK_unlockAchievement(name);
|
|
|
|
#endif
|
2020-08-01 22:04:37 +02:00
|
|
|
}
|