1
0
Fork 0
mirror of https://github.com/TerryCavanagh/VVVVVV.git synced 2024-11-04 18:29:41 +01:00
VVVVVV/desktop_version/src/Script.cpp

3864 lines
87 KiB
C++
Raw Normal View History

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